Starting STM8 Microcontrollers

STM8S003K3 Discovery

Independent Watchdog (IWDG)

The IWDG is just the ordinary watchdog timer we usually find in any modern micro. The purpose of this timer is to recover a micro from an unanticipated event that may result in unresponsive or erratic behaviour. As the name suggests, this timer does not share anything with any other internal hardware peripheral and is clocked by LSI (128kHz) only. Thus, it is invulnerable to main clock (HSE or HSI) failure.

Block Diagram

Clock Diagram

The IWDG works by decrementing a counter, counting time in the process. When the counter hits zero, a reset is issued. Usually we would want that this reset never occurs and so the counter is periodically updated in the application firmware. If for some reason, the counter is not refreshed, a reset will occur, recovering the MCU from a disastrous situation.

Configuring the IWDG is very easy with SPL. There are certain steps to follow but SPL manages them well internally. All we’ll need is to configure the IWDG and reload it periodically before time runs out.

The formula required to calculate timeout is given below:

Formula

Typical values of timeout are as shown below:

Typical Values

 

Hardware Connection

CubeMX

Code Example

#include "STM8S.h"
 
 
void clock_setup(void);
void GPIO_setup(void);
void IWDG_setup(void);
 
 
void main(void)
{
    unsigned int t = 0;
                
    clock_setup();
    GPIO_setup();
                                
    GPIO_WriteLow(GPIOD, GPIO_PIN_0);
    for(t = 0; t < 60000; t++);
                
    IWDG_setup();
 
    while(TRUE)
    {
        GPIO_WriteReverse(GPIOD, GPIO_PIN_0);
        for(t = 0; t < 1000; t++)
        {
             if(GPIO_ReadInputPin(GPIOB, GPIO_PIN_7) == FALSE)
             {
                  IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
                  IWDG_ReloadCounter();
                  IWDG_WriteAccessCmd(IWDG_WriteAccess_Disable);
             }
        }
     };
}
 
 
void clock_setup(void)
{
     CLK_DeInit();
                
     CLK_HSECmd(DISABLE);
     CLK_LSICmd(ENABLE);
     while(CLK_GetFlagStatus(CLK_FLAG_LSIRDY) == FALSE);
     CLK_HSICmd(ENABLE);
     while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);
                
     CLK_ClockSwitchCmd(ENABLE);
     CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
     CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV4);
                
     CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
     DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);
                
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}
 
 
void GPIO_setup(void)
{
     GPIO_DeInit(GPIOB);
     GPIO_DeInit(GPIOD);
 
     GPIO_Init(GPIOB, GPIO_PIN_7, GPIO_MODE_IN_PU_NO_IT);
     GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_PP_LOW_FAST);
}
 
 
void IWDG_setup(void)
{
     IWDG_Enable();
     IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
     IWDG_SetPrescaler(IWDG_Prescaler_128);
     IWDG_SetReload(0x99);
     IWDG_WriteAccessCmd(IWDG_WriteAccess_Disable);
}

 

Explanation

In this example, we need not to look at peripheral and CPU clock as IWDG is not dependent on them. Still we can see that the CPU is running at 500 kHz speed while the peripherals at 2 MHz speed.

CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV4);

To setup the IWDG, we need to enable it first and then apply Write Access Protection key (0x55). We just need to set the prescaler and the counter value. The down counter will start from this value and count down to zero unless refreshed. In this example, the prescaler is set to 128 and reload value is set to 153 (0x99). Thus, with these we get a timeout of approximately 300ms. After entering these values we must prevent accidental changes in the firmware and so to do so the write access must be disabled.

void IWDG_setup(void)
{
    IWDG_Enable();
    IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
    IWDG_SetPrescaler(IWDG_Prescaler_128);
    IWDG_SetReload(0x99);
    IWDG_WriteAccessCmd(IWDG_WriteAccess_Disable);
}

Disco board’s user button and LED are used for the demo. At the very beginning, the LED is lit for some time before the IWDG is configured, indicating the start of the firmware. In the main loop, the LED is toggled with some delay arranged by a for loop. Inside the loop, the button’s state is polled. If the button is kept pressed it will always be in logic low state, reloading the IWDG counter. If its state changes to logic high and 300ms passes out, a reset is triggered.

GPIO_WriteReverse(GPIOD, GPIO_PIN_0);
for(t = 0; t < 1000; t++)
{
     if(GPIO_ReadInputPin(GPIOB, GPIO_PIN_7) == FALSE)
     {
          IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
          IWDG_ReloadCounter();
          IWDG_WriteAccessCmd(IWDG_WriteAccess_Disable);
     }
}

Note it is possible to calibrate LSI. It is however rarely needed.

 

Demo

Video link: https://www.youtube.com/watch?v=05XKoy0ieHo

 

Window Watchdog (WWDG)

The WWDG is a bit more advanced watchdog timer. Unlike the IWDG, it will trigger a reset condition if its counter is reloaded earlier or later than a predefined time window. This kind of timer is usually found in high-end microcontrollers like ARMs, ATXMegas and recently released micros. Cool features like this and others make me feel that indeed STM8s are high-end affordable 8-bit alternatives when compared to traditional 8-bit MCUs.

Block Diagram

The WWDG works by comparing a down counter against a window register. The counter can only be refreshed when its value is greater than 0x3F and less than window register value. If the counter is refreshed before the value set on window register or when the counter is less than or equal to 0x3F. If the counter hits the value 0x3F, reset automatically triggers. It is programmer’s responsibility to refresh the counter at proper time. Note unlike IWDG, WWDG is not independent of main clock.

Timing

Formula


Hardware Connection

CubeMX

 

Code Example

The code example here demonstrates WWDG action. Simply Disco board’s user LED and button are used. When the code starts executing, the LED starts blinking slowly, indicating the start of the code. When the code executes the main loop, the LED blinks rapidly to indicate main loop execution. If the button is pressed randomly the micro is reset because the counter is refreshed before the allowed time. Sometimes the micro may not reset because the counter may be in the allowed window – hence the name Window Watchdog.

#include "STM8S.h"
 
 
void clock_setup(void);
void GPIO_setup(void);
void WWDG_setup(void);
 
 
void main(void)
{
    unsigned char i = 0x00;
                
    clock_setup();
    GPIO_setup();
                
    for(i = 0x00; i < 0x04; i++)
    {
         GPIO_WriteReverse(GPIOD, GPIO_PIN_0);
         delay_ms(40);
    }
                
    WWDG_setup();       
                
    while(TRUE)
    {
          if((GPIO_ReadInputPin(GPIOB, GPIO_PIN_7) == FALSE) || 
             ((WWDG_GetCounter() > 0x60) &&    (WWDG_GetCounter() < 0x7F)))
          {
               WWDG_SetCounter(0x7F);
          }
          GPIO_WriteReverse(GPIOD, GPIO_PIN_0);
          delay_ms(20);
     };
}
 
 
void clock_setup(void)
{
     CLK_DeInit();
                
     CLK_HSECmd(DISABLE);
     CLK_LSICmd(DISABLE);
     CLK_HSICmd(ENABLE);
     while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);
                
     CLK_ClockSwitchCmd(ENABLE);
     CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
     CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV64);
                
     CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
     DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);
                
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);          
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}
 
 
void GPIO_setup(void)
{                               
     GPIO_DeInit(GPIOB);
     GPIO_Init(GPIOB, GPIO_PIN_7, GPIO_MODE_IN_PU_NO_IT);
                
     GPIO_DeInit(GPIOD);
     GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_OD_HIZ_FAST);
}
 
 
void WWDG_setup(void)
{
     WWDG_Init(0x7F, 0x60);
}

 

Explanation

There’s no way to enable watchdogs manually in software as they are always enabled. However, there are configuration bits to select if the IWDG and the WWDG are enabled in software or hardware. They only come in effect when configured. This is cool.

WDG Configuration Bits

For WWDG, we just need to set the value of the down counter and the window register value only.

void WWDG_setup(void)
{
     WWDG_Init(0x7F, 0x60);
}

We need to monitor the WWDG in order to reload it when it is the right time window.

while(TRUE)
{
      if((GPIO_ReadInputPin(GPIOB, GPIO_PIN_7) == FALSE) || 
      ((WWDG_GetCounter() > 0x60) &&    (WWDG_GetCounter() < 0x7F)))
      {
           WWDG_SetCounter(0x7F);
      }
      GPIO_WriteReverse(GPIOD, GPIO_PIN_0);
      delay_ms(20);
};

Remember too early or too late will reset the micro.

 

Demo

Video link: https://www.youtube.com/watch?v=a_JWHJCh_-o

 

Timer Overview

Timers are perhaps the most versatile piece of hardware in any micro. As their name tells, timers are useful for measurement of timed events like frequency, time, phase sequence, etc. and generate time-based events like PWM, waveform, etc. Timers are also needed for touch sensing applications.

In any STM8 micro, there are three categories of timers. These are:

  • Advanced Control Timer (TIM1)
  • General Purpose Timers (TIM2, TIM3 & TIM5)
  • Basic Timers (TIM4 & TIM6)

The basic working principle of all timers are same with some minor differences. Advanced timers are mainly intended for applications requiring specialized motor control, SMPSs, inverters, waveform generation, pulse width measurements, etc. Then there are general purpose timers that share almost all the features of advanced timer without the advanced features like brake, dead-time control, etc. Basic timers are all same as general purpose timers but lack PWM output/capture input pins and are intended mainly for time base generations. Here’s the summary of all timers of STM8 micros:

Timer Comparision

Unlike the timers of other micros, STM8 timers have the more functionality that are otherwise only available in some special micros only. Timer cover a significant part of the reference manual. They are so elaborate that it is not possible to describe all of them in just one post. Therefore, here we’ll be exploring the basics for now.

Time Base Generation (TIM2)

Time base generation is the most basic property of any timer and is also the most needed requirement in embedded systems. This mode can be used with or without interrupt. We’ll first check the method firstly without interrupt and then with interrupt.

With time base generation, we can accurately time stuffs and events that are more precise than using delays, loops or other methods. Time base generation utilizes hardware timers and so work independently from other processes. It has many uses. For instance, with it we can avoid software delays, generate time slots of a Real-Time Operating System (RTOS) and many other tasks.

Time-Base Unit

The time base unit for all timers of STM8 is all same. There are a few differences. For example, Timer 1 (TIM1) has a repetition counter. It is like a counter within another counter. Other timers lack this part. All timers can count up while advance timers can count down too.

The basic theory of time base generation is you have a peripheral clock which you would like to scale according to your need. Thus, you prescale it and use the new clock to run a counter. The counter will tick, incrementing count as time flies. It is just like counting from 0 to 100 and repeating from 0 again after reaching 100. Shown below is the generalized formula for finding an important event called timer reload:

Formula for TIM base

This is the amount of time that will pass before timer overflow event occurs and the timer restarts from its initial count.

In my example, the peripheral or master clock is set to 2MHz. Thus, to make timer 2 (TIM2) reload after roughly 2 seconds, I have to prescale it by a factor of 2048 and load it with 1952 counts. Note that TIM2 doesn’t have a repetition counter and so it is set to 1.

Hardware Connection

CubeMX

 

Code Example

In this example, Disco board’s user LED is turned-on and off without using any software delay. TIM2 is used to create time delay as such that the code is not stuck in a time-wasting loop.

#include "STM8S.h"
 
 
void clock_setup(void);
void GPIO_setup(void);
void TIM2_setup(void);
 
 
void main(void)
{
    clock_setup();
    GPIO_setup();
    TIM2_setup();
                
    while(TRUE)
    {
          if(TIM2_GetCounter() > 976)
          {
               GPIO_WriteHigh(GPIOD, GPIO_PIN_0);
          }
          else
          {
               GPIO_WriteLow(GPIOD, GPIO_PIN_0);
          }
    };
}
 
 
void clock_setup(void)
{
    CLK_DeInit();
                
    CLK_HSECmd(DISABLE);
    CLK_LSICmd(DISABLE);
    CLK_HSICmd(ENABLE);
    while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);
                
    CLK_ClockSwitchCmd(ENABLE);
    CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
    CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
                
    CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
    DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);
                
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, ENABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}
 
 
void GPIO_setup(void)
{
    GPIO_DeInit(GPIOD);
    GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_OD_HIZ_SLOW);
}
 
 
void TIM2_setup(void)
{
    IM2_DeInit();
    TIM2_TimeBaseInit(TIM2_PRESCALER_2048, 1952);
    TIM2_Cmd(ENABLE);
}

 

Explanation

Firstly, the CPU and the peripheral clock are both set at 2 MHz.

CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
….
….
CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, ENABLE);

As explained earlier, to get 2 second timer reload interval we need to prescale the timer by 2048 and load its counter with 1952. This is what should be the setup for TIM2:

void TIM2_setup(void)
{
     TIM2_DeInit();
     TIM2_TimeBaseInit(TIM2_PRESCALER_2048, 1952);
     TIM2_Cmd(ENABLE);
}

Our goal is to keep the LED on for 1 second and off for 1 second. It takes 1952 TIM2 counts for 2 second interval and so one second passes when this count is 976. Thus, in the main loop we are checking the value of TIM2’s counter. From 0 to 976 counts, the LED is on and from 977 to 1952 counts, the LED is off. Note that the LED’s positive end is connected to VDD and so it will turn on only PD0 is low.

if(TIM2_GetCounter() > 976)
{
     GPIO_WriteHigh(GPIOD, GPIO_PIN_0);
}
else
{
     GPIO_WriteLow(GPIOD, GPIO_PIN_0);
}

 

Demo

Video link: https://youtu.be/ZstHDHAAHOM

 

Timer Interrupt (TIM4)

In this example uses the same concepts of the previous example but it is based on timer interrupt – TIM4 interrupt. Timer interrupts are very important interrupts apart from other interrupts in a micro. To me they are highly valuable and useful.

This example demonstrates how to scan and project information on multiple seven segment displays with timer interrupt while the main loop can process the information to be displayed.

Hardware Connection

TIM4 Interrupt

 

Code Example

main.c

#include "STM8S.h"
 
 
unsigned int value = 0x00;
 
unsigned char n = 0x00;
unsigned char seg = 0x01;
const unsigned char num[0x0A] = 
{0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90};
 
 
void GPIO_setup(void);
void clock_setup(void);
void TIM4_setup(void);
 
 
void main(void)
{
     GPIO_setup();
     clock_setup();
     TIM4_setup();
                
     while (TRUE)
     {
           value++;
           delay_ms(999);
     };
}
 
 
void GPIO_setup(void)
{
     GPIO_DeInit(GPIOC);
     GPIO_Init(GPIOC, ((GPIO_Pin_TypeDef)(GPIO_PIN_4 | GPIO_PIN_5 
               | GPIO_PIN_6 | GPIO_PIN_7)), GPIO_MODE_OUT_PP_HIGH_FAST);
                
     GPIO_DeInit(GPIOD);
     GPIO_Init(GPIOD, GPIO_PIN_ALL, GPIO_MODE_OUT_PP_HIGH_FAST);
}
 
 
void clock_setup(void)
{
     CLK_DeInit();
                
     CLK_HSECmd(DISABLE);
     CLK_LSICmd(DISABLE);
     CLK_HSICmd(ENABLE);
     while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);
                
     CLK_ClockSwitchCmd(ENABLE);
     CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
     CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
                
     CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
     DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);
                
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, ENABLE);
}
 
 
void TIM4_setup(void)
{               
     TIM4_DeInit();
     TIM4_TimeBaseInit(TIM4_PRESCALER_32, 128);      
     TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);
     TIM4_Cmd(ENABLE);
     
     enableInterrupts();
}

 

stm8s_it.h (top part only)

#ifndef __STM8S_IT_H
#define __STM8S_IT_H
 
@far @interrupt void TIM4_UPD_IRQHandler(void);


/* Includes ------------------------------------------------------------------*/
#include "stm8s.h"

 

stm8s_it.c (top part only)

#include "stm8s.h"
#include "stm8s_it.h"
 
 
extern unsigned int value;
 
extern unsigned char n;
extern unsigned char seg;
extern const unsigned char num[10];
 
 
void TIM4_UPD_IRQHandler(void) 
{
     switch(seg)
     {
           case 1:
           {
               n = (value / 1000);
               GPIO_Write(GPIOD, num[n]);
               GPIO_Write(GPIOC, 0xE0);
               break;
           }
                                
           case 2:
           {
               n = ((value / 100) % 10);
               GPIO_Write(GPIOD, num[n]);
               GPIO_Write(GPIOC, 0xD0);
               break;
           }
                                
           case 3:
           {
               n = ((value / 10) % 10);
               GPIO_Write(GPIOD, num[n]);
               GPIO_Write(GPIOC, 0xB0);
               break;
           }
                                
           case 4:
           {
               n = (value % 10);
               GPIO_Write(GPIOD, num[n]);
               GPIO_Write(GPIOC, 0x70);
               break;
           }
       }
                
       seg++;
       if(seg > 4)
       {
            seg = 1;
       }
       
       TIM4_ClearFlag(TIM4_FLAG_UPDATE);
}

 

stm8_interrupt_vector.c

#include "stm8s_it.h"
 
typedef void @far (*interrupt_handler_t)(void);
 
struct interrupt_vector {
                unsigned char interrupt_instruction;
                interrupt_handler_t interrupt_handler;
};
 
//@far @interrupt void NonHandledInterrupt (void)
//{
                /* in order to detect unexpected events during development, 
                   it is recommended to set a breakpoint on the following instruction
                */
                //return;
//}
 
extern void _stext();     /* startup routine */
 
 
struct interrupt_vector const _vectab[] = {
                {0x82, (interrupt_handler_t)_stext}, /* reset */
                {0x82, NonHandledInterrupt}, /* trap  */
                {0x82, NonHandledInterrupt}, /* irq0  */
                {0x82, NonHandledInterrupt}, /* irq1  */
                {0x82, NonHandledInterrupt}, /* irq2  */
                {0x82, NonHandledInterrupt}, /* irq3  */
                {0x82, NonHandledInterrupt}, /* irq4  */
                {0x82, NonHandledInterrupt}, /* irq5  */
                {0x82, NonHandledInterrupt}, /* irq6  */
                {0x82, NonHandledInterrupt}, /* irq7  */
                {0x82, NonHandledInterrupt}, /* irq8  */
                {0x82, NonHandledInterrupt}, /* irq9  */
                {0x82, NonHandledInterrupt}, /* irq10 */
                {0x82, NonHandledInterrupt}, /* irq11 */
                {0x82, NonHandledInterrupt}, /* irq12 */
                {0x82, NonHandledInterrupt}, /* irq13 */
                {0x82, NonHandledInterrupt}, /* irq14 */
                {0x82, NonHandledInterrupt}, /* irq15 */
                {0x82, NonHandledInterrupt}, /* irq16 */
                {0x82, NonHandledInterrupt}, /* irq17 */
                {0x82, NonHandledInterrupt}, /* irq18 */
                {0x82, NonHandledInterrupt}, /* irq19 */
                {0x82, NonHandledInterrupt}, /* irq20 */
                {0x82, NonHandledInterrupt}, /* irq21 */
                {0x82, NonHandledInterrupt}, /* irq22 */
                {0x82, (interrupt_handler_t)TIM4_UPD_IRQHandler}, /* irq23 */
                {0x82, NonHandledInterrupt}, /* irq24 */
                {0x82, NonHandledInterrupt}, /* irq25 */
                {0x82, NonHandledInterrupt}, /* irq26 */
                {0x82, NonHandledInterrupt}, /* irq27 */
                {0x82, NonHandledInterrupt}, /* irq28 */
                {0x82, NonHandledInterrupt}, /* irq29 */
};

 

Explanation

Both the peripheral and CPU clocks are running at 2MHz.

CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
....
….
CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, ENABLE);

TIM4 is a basic timer and so it is better to use it for such tasks. We initialize it by setting its prescaler to 32 and loading its counter with 128. These values will make TIM4 overflow and interrupt every 2ms – enough time to project info in one seven segment. There are 4 seven segment displays and so within 8ms all four are updated and your eyes see it as if all projecting info at the same time – a trick of vision. Lastly, we need to enable what kind of interrupt we are expecting from the timer and finally enable the global interrupt.

 void TIM4_setup(void)
{               
    TIM4_DeInit();
    TIM4_TimeBaseInit(TIM4_PRESCALER_32, 128);      
    TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);
    TIM4_Cmd(ENABLE);
    
    enableInterrupts();
}

Remember the first interrupt example? We have to let the compiler know which interrupt we are using. If you look at the datasheet, you’ll see that TIM4 update/overflow is located in IRQ23. We need this and so we should make the following change in the stm8_interrupt_vector.c file:

{0x82, (interrupt_handler_t)TIM4_UPD_IRQHandler}, /* irq23 */

Remember to add the interrupt header and source files as we are going to use interrupt here. Inside the ISR, we do the scanning of each seven segment. Every time an overflow interrupt occurs, a seven segment is changed. At the end of the ISR a counter is incremented to select the next display when new overflow event occurs. Inside the Switch-Case, we turn on the seven segment and decide the value that seven segment should show. Finally, the timer overflow/update flag is cleared.

void TIM4_UPD_IRQHandler(void) 
{
     switch(seg)
     {
           case 1:
           {
               n = (value / 1000);
               GPIO_Write(GPIOD, num[n]);
               GPIO_Write(GPIOC, 0xE0);
               break;
           }
                                
           case 2:
           {
               n = ((value / 100) % 10);
               GPIO_Write(GPIOD, num[n]);
               GPIO_Write(GPIOC, 0xD0);
               break;
           }
                                
           case 3:
           {
               n = ((value / 10) % 10);
               GPIO_Write(GPIOD, num[n]);
               GPIO_Write(GPIOC, 0xB0);
               break;
           }
                                
           case 4:
           {
               n = (value % 10);
               GPIO_Write(GPIOD, num[n]);
               GPIO_Write(GPIOC, 0x70);
               break;
           }
       }
                
       seg++;
       if(seg > 4)
       {
            seg = 1;
       }
       
       TIM4_ClearFlag(TIM4_FLAG_UPDATE);
}

 

Demo

TIM4

Video link: https://youtu.be/Sa20Hf2N4gE

 

General Purpose Pulse Width Modulation (TIM2 PWM)

Pulse Width Modulation (PWM) is a must-have feature of any microcontroller. PWM has many uses like motor control, SMPSs, lighting control, sound generation, waveform generation, etc. Unlike other micros which have limited PWM channels, STM8 has several PWM channels. For instance, STM8S003K has seven independent PWM channels, three of which belong to TIM2 – a general purpose (GP) timer.

PWMs generated by GP timers are basic PWMs. They can be used for simple tasks like LED brightness control, servo motor control, etc. that don’t require advanced features like dead-time, brake or complementary waveform generation. In this section, we will see how to use TIM2 to generate simple PWMs.

Please note that in more advanced STM8 micros, timer I/Os are dependent on alternate function configuration bits. Check those bits before uploading codes. In some STM8 micros, the I/Os are also remappable, meaning that the I/Os can be swapped in different GPIOs. Take the help of STM8CubeMx if needed.

Hardware Connection

GP PWM CubeMX

 

Code Example

This is a pretty simple example. Here all three channels of TIM2 are used to smoothly fade and glow three LEDs connected to the timer channels.

#include "STM8S.h"
 
 
void clock_setup(void);
void GPIO_setup(void);
void TIM2_setup(void);
 
 
void main(void)
{
     signed int pwm_duty = 0x0000;
                
     clock_setup();
     GPIO_setup();
     TIM2_setup();
                
     while(TRUE)
     {
          for(pwm_duty = 0; pwm_duty < 1000; pwm_duty += 10)
          {
               TIM2_SetCompare1(pwm_duty);
               TIM2_SetCompare2(pwm_duty);
               TIM2_SetCompare3(pwm_duty);
               delay_ms(10);
          }
          for(pwm_duty = 1000; pwm_duty > 0; pwm_duty -= 10)
          {
               TIM2_SetCompare1(pwm_duty);
               TIM2_SetCompare2(pwm_duty);
               TIM2_SetCompare3(pwm_duty);
               delay_ms(10);
          }
      };
}
 
 
void clock_setup(void)
{
      CLK_DeInit();
                
      CLK_HSECmd(DISABLE);
      CLK_LSICmd(DISABLE);
      CLK_HSICmd(ENABLE);
      while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);
                
      CLK_ClockSwitchCmd(ENABLE);
      CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
      CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
                
      CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
      DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);
                
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, ENABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}
 
 
void GPIO_setup(void)
{
      GPIO_DeInit(GPIOA);
      GPIO_Init(GPIOA, GPIO_PIN_3, GPIO_MODE_OUT_PP_HIGH_FAST);
                
      GPIO_DeInit(GPIOD);
      GPIO_Init(GPIOD, ((GPIO_Pin_TypeDef)GPIO_PIN_3 | GPIO_PIN_4), 
                GPIO_MODE_OUT_PP_HIGH_FAST);
}
 
 
void TIM2_setup(void)
{
      TIM2_DeInit();
      TIM2_TimeBaseInit(TIM2_PRESCALER_32, 1000);
      TIM2_OC1Init(TIM2_OCMODE_PWM1, TIM2_OUTPUTSTATE_ENABLE, 1000, 
                   TIM2_OCPOLARITY_HIGH);
      TIM2_OC2Init(TIM2_OCMODE_PWM1, TIM2_OUTPUTSTATE_ENABLE, 1000, 
                   TIM2_OCPOLARITY_LOW);
      TIM2_OC3Init(TIM2_OCMODE_PWM1, TIM2_OUTPUTSTATE_ENABLE, 1000, 
                   TIM2_OCPOLARITY_HIGH);
      TIM2_Cmd(ENABLE);
}

 

Explanation

Again, the CPU and the peripheral clock are set at 2 MHz.

CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
….
….
CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, ENABLE);

Next, we need to configure the PWM GPIOs as outputs.

void GPIO_setup(void)
{
      GPIO_DeInit(GPIOA);
      GPIO_Init(GPIOA, GPIO_PIN_3, GPIO_MODE_OUT_PP_HIGH_FAST);
                
      GPIO_DeInit(GPIOD);
      GPIO_Init(GPIOD, ((GPIO_Pin_TypeDef)GPIO_PIN_3 | GPIO_PIN_4), 
                GPIO_MODE_OUT_PP_HIGH_FAST);
}

Just like other microcontrollers, PWM generation involves a timer. Here as said TIM2 is that timer. We need to set time base first before actually configuring the PWM channels.

void TIM2_setup(void)
{
      TIM2_DeInit();
      TIM2_TimeBaseInit(TIM2_PRESCALER_32, 1000);
      TIM2_OC1Init(TIM2_OCMODE_PWM1, TIM2_OUTPUTSTATE_ENABLE, 1000, 
                   TIM2_OCPOLARITY_HIGH);
      TIM2_OC2Init(TIM2_OCMODE_PWM1, TIM2_OUTPUTSTATE_ENABLE, 1000, 
                   TIM2_OCPOLARITY_LOW);
      TIM2_OC3Init(TIM2_OCMODE_PWM1, TIM2_OUTPUTSTATE_ENABLE, 1000, 
                   TIM2_OCPOLARITY_HIGH);
      TIM2_Cmd(ENABLE);
}

In the codes above, TIM2 has a time base of 16ms or 62.5kHz. This time base is further divided by the Output Compare (OC) unit. Thus, here the 62.5kHz base is further divided by 1000 to get 62.5Hz PWM frequency. The maximum duty cycle is therefore 1000. Additionally, we can set PWM polarity and command the channel if or if not should it behave in an inverted manner.

To change PWM duty, we need to call the following function:

TIM2_SetCompareX(pwm_duty);                 where X represents channel ID (1, 2 or 3)

Note that in STM8 micros, there is a trade-off between duty cycle and PWM frequency. If the PWM resolution, i.e. duty cycle is big then PWM frequency is small and vice-versa. This is true for all timers.

 

Demo

TIM2 PWM

Video link: https://www.youtube.com/watch?v=BPS5unUHDz4

 

Advanced Pulse Width Modulation (TIM1 PWM)

Timer 1 (TIM1) is an advance timer and so the PWMs generated by it have several additional features that are not available with other timers. For example, it is possible to generate complementary PWMs with TIM1. Up to three sets of complimentary PWMs can be generated. Such PWMs are useful in designing three phase inverters, rectifiers and other power-related tasks. TIM1 PWMs are also very useful for motor control applications. It is also possible to add dead-time and brake. Apart from these TIM1 can also generate PWMs just like GP timers. In this mode however, complimentary PWM outputs are unavailable and up to four independent PWM channels can be made available.

In this example, I have demonstrated how to create complementary PWMs in TIM1 PWM channel 1.

Hardware Connection

TIM1 PWM

 

Code Example

#include "STM8S.h"
 
 
void clock_setup(void);
void GPIO_setup(void);
void TIM1_setup(void);
 
 
void main(void)
{
     signed int i = 0;
                
     clock_setup();
     GPIO_setup();
     TIM1_setup();
                
     while(TRUE)
     {
          for(i = 0; i < 1000; i += 1)
          {
              TIM1_SetCompare1(i);
              delay_ms(1);
          }
          for(i = 1000; i > 0; i -= 1)
          {
              TIM1_SetCompare1(i);
              delay_ms(1);
          }
     };
}
 
 
void clock_setup(void)
{
     CLK_DeInit();
                
     CLK_HSECmd(DISABLE);
     CLK_LSICmd(DISABLE);
     CLK_HSICmd(ENABLE);
     while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);
                
     CLK_ClockSwitchCmd(ENABLE);
     CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
     CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
                
     CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
     DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);
                
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);          
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, ENABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}
 
 
void GPIO_setup(void)
{                               
     GPIO_DeInit(GPIOB);
     GPIO_Init(GPIOB, GPIO_PIN_0, GPIO_MODE_OUT_PP_HIGH_FAST);
                
     GPIO_DeInit(GPIOC);
     GPIO_Init(GPIOC, GPIO_PIN_1, GPIO_MODE_OUT_PP_HIGH_FAST);
}
 
 
void TIM1_setup(void)
{
     TIM1_DeInit();
                
     TIM1_TimeBaseInit(16, TIM1_COUNTERMODE_UP, 1000, 1);
                
     TIM1_OC1Init(TIM1_OCMODE_PWM1, 
                  TIM1_OUTPUTSTATE_ENABLE, 
                  TIM1_OUTPUTNSTATE_ENABLE, 
                  1000, 
                  TIM1_OCPOLARITY_LOW, 
                  TIM1_OCNPOLARITY_LOW, 
                  TIM1_OCIDLESTATE_RESET, 
                  TIM1_OCNIDLESTATE_RESET);
                
    TIM1_CtrlPWMOutputs(ENABLE);
    TIM1_Cmd(ENABLE);
}

 

Explanation

This time we used the full 16MHz speed of HSI both for peripheral and CPU clocks:

CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
….
….
CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, ENABLE);

Like as with the previous example PWM output GPIOs are set as outputs:

void GPIO_setup(void)
{                               
     GPIO_DeInit(GPIOB);
     GPIO_Init(GPIOB, GPIO_PIN_0, GPIO_MODE_OUT_PP_HIGH_FAST);
                
     GPIO_DeInit(GPIOC);
     GPIO_Init(GPIOC, GPIO_PIN_1, GPIO_MODE_OUT_PP_HIGH_FAST);
}

TIM1 and OC channel initialization is just like the previous example with some minor differences. The time base generation part seems to have some additional arguments. These are because:

  • Unlike other timers, TIM1 prescaler value is not a fixed set of multiples of 2.
  • The counting mode is not just up mode counting. Counting mode can also be down counting.
  • TIM1 has additional repetition counter.
  • Except the basic timers all timers in STM8 are 16-bit timer.

If you open the header file for TIM1, you’ll see many functions. Many of these functions are not available with other timers, expressing the power of an advance timer.

Likewise, there are some additional info we must feed when configuring the OC channels. We need to set info about complementary channels even if we don’t need them. We can additionally set the default idle states of PWMs apart from polarities.

void TIM1_setup(void)
{
     TIM1_DeInit();
                
     TIM1_TimeBaseInit(16, TIM1_COUNTERMODE_UP, 1000, 1);
                
     TIM1_OC1Init(TIM1_OCMODE_PWM1, 
                  TIM1_OUTPUTSTATE_ENABLE, 
                  TIM1_OUTPUTNSTATE_ENABLE, 
                  1000, 
                  TIM1_OCPOLARITY_LOW, 
                  TIM1_OCNPOLARITY_LOW, 
                  TIM1_OCIDLESTATE_RESET, 
                  TIM1_OCNIDLESTATE_RESET);
                
    TIM1_CtrlPWMOutputs(ENABLE);
    TIM1_Cmd(ENABLE);
}

To change the duty cycle of a channel, we need to call this function:

TIM1_SetCompareX(duty_cycle);       //   where X represents channel ID (1, 2, 3 or 4)

Complementary outputs occur in pairs and so they are interdependent. That’s why there is no separate function for outputs labelled N.

 

Demo

TIM1 PWM cpwm

Video link: https://youtu.be/uclCXH1ZPWU

 

Timer Input Capture (TIM1 & TIM2)

Input capture is needed for measurements of pulses, pulse widths, frequencies, phase detection and similar stuffs. With external interrupts these measurements can be done with some limitations. However, using timer capture has some serious benefits. First of all, accuracy of measurements and secondly timer capture simplifies many tasks as timers themselves time stuffs properly. Dedicated hardware make stuffs like PWM measurement less complex and resource-friendly too.

STM8 timers have several capture channels just like output compare channels (PWM). The number of input capture channels is same as the number of PWM channels. Except basic timers all timers have input capture option.

Hardware Connection

TIM CAP

 

Code Example

In this demo, TIM2 is configured to generate PWM on its CH1 output. TIM1 is configured to capture every rising edge of incoming waveform at its input capture channel CH1. When a capture event occurs, the current time count of TIM1 is saved. By deducting the recent capture count from the previous capture count, we can measure time period of the incoming PWM signal and hence its frequency. If the frequency is too high, TIM1 may overflow and so we need to take care of it too. We, thus, need to check TIM1 overflow event too.

main.c

#include "STM8S.h"
#include "lcd.h"
 
 
unsigned int overflow_count = 0;
unsigned long pulse_ticks = 0;
unsigned long start_time = 0;
unsigned long end_time = 0;
 
 
void clock_setup(void);
void GPIO_setup(void);
void TIM1_setup(void);
void TIM2_setup(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned long value);
 
 
void main()
{               
    unsigned long time_period = 0;
                
    clock_setup();
    GPIO_setup();
    TIM1_setup();
    TIM2_setup();
    LCD_init();  
                
    LCD_clear_home(); 
    LCD_goto(0, 0);
    LCD_putstr("T/ms:");
    delay_ms(10);
                
    while(TRUE)
    {
          time_period = pulse_ticks;
          lcd_print(0, 1, time_period);
          delay_ms(400);
    };
}
 
 
void clock_setup(void)
{
    CLK_DeInit();
                
    CLK_HSECmd(DISABLE);
    CLK_LSICmd(DISABLE);
    CLK_HSICmd(ENABLE);
    while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);
                
    CLK_ClockSwitchCmd(ENABLE);
    CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
    CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV2);
                
    CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
    DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);
                
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, ENABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, ENABLE);
    CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}
 
 
void GPIO_setup(void)
{               
     GPIO_DeInit(GPIOC);
     GPIO_Init(GPIOC, GPIO_PIN_1, GPIO_MODE_IN_FL_NO_IT);
                
     GPIO_DeInit(GPIOD);
     GPIO_Init(GPIOD, GPIO_PIN_4, GPIO_MODE_OUT_PP_HIGH_FAST);
}
 
 
void TIM1_setup(void)
{
     TIM1_DeInit();
     TIM1_TimeBaseInit(2000, TIM1_COUNTERMODE_UP, 55535, 1);
     TIM1_ICInit(TIM1_CHANNEL_1, TIM1_ICPOLARITY_RISING, 
                 TIM1_ICSELECTION_DIRECTTI, 1, 1);
     TIM1_ITConfig(TIM1_IT_UPDATE, ENABLE);
     TIM1_ITConfig(TIM1_IT_CC1, ENABLE);
     TIM1_Cmd(ENABLE);
     
     enableInterrupts();
}
 
 
void TIM2_setup(void)
{
     TIM2_DeInit();
     TIM2_TimeBaseInit(TIM2_PRESCALER_32, 1250);
     TIM2_OC1Init(TIM2_OCMODE_PWM1, TIM2_OUTPUTSTATE_ENABLE, 1000, 
                  TIM2_OCPOLARITY_LOW);
     TIM2_SetCompare1(625);
     TIM2_Cmd(ENABLE);
}
 
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned long value)
{
     char tmp[6] = {0x20, 0x20, 0x20, 0x20, 0x20, 0x20} ;
 
     tmp[0] = (((value / 100000) % 10) + 0x30);
     tmp[1] = (((value / 10000) % 10) + 0x30);
     tmp[2] = (((value / 1000) % 10) + 0x30);
     tmp[3] = (((value / 100) % 10) + 0x30);
     tmp[4] = (((value / 10) % 10) + 0x30);
     tmp[5] = ((value % 10) + 0x30);
                
     LCD_goto(x_pos, y_pos);
     LCD_putstr(tmp);   
}

 

stm8_interrupt_vector.c (Interrupt vector address part only)

….
{0x82, (interrupt_handler_t)TIM1_UPD_IRQHandler}, /* irq11 */
{0x82, (interrupt_handler_t)TIM1_CH1_CCP_IRQHandler}, /* irq12 */
….

 

stm8s_it.h (Top part only)

#ifndef __STM8S_IT_H
#define __STM8S_IT_H
 
 
@far @interrupt void TIM1_UPD_IRQHandler(void);
@far @interrupt void TIM1_CH1_CCP_IRQHandler(void);
 
/* Includes ------------------------------------------------------------------*/
#include "stm8s.h"
….

 

stm8s_it.c (Top part only)

#include "stm8s.h"
#include "stm8s_it.h"
 
 
extern unsigned int overflow_count;
extern unsigned long pulse_ticks;
extern unsigned long start_time;
extern unsigned long end_time;
 
 
void TIM1_UPD_IRQHandler(void) 
{
     overflow_count++;
     TIM1_ClearITPendingBit(TIM1_IT_UPDATE);
     TIM1_ClearFlag(TIM1_FLAG_UPDATE);
}
 
 
void TIM1_CH1_CCP_IRQHandler(void) 
{
     end_time = TIM1_GetCapture1();
     pulse_ticks = ((overflow_count << 16) - start_time + end_time);
     start_time = end_time;
     overflow_count = 0;
     TIM1_ClearITPendingBit(TIM1_IT_CC1);
     TIM1_ClearFlag(TIM1_FLAG_CC1);
}

 

Explanation

The clocks and peripherals are set first. We are using 2 MHz peripheral clock and the CPU is running at 500 kHz.

CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV2);
….
….            
CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, ENABLE);
CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, ENABLE);

GPIOs must be set too. Since TIM2 is to output PWM, its CH1 must be set output. Likewise, TIM1’s CH1 GPIO must be set as an input.

GPIO_DeInit(GPIOC);
GPIO_Init(GPIOC, GPIO_PIN_1, GPIO_MODE_IN_FL_NO_IT);
                
GPIO_DeInit(GPIOD);
GPIO_Init(GPIOD, GPIO_PIN_4, GPIO_MODE_OUT_PP_HIGH_FAST);

TIM1 need to be configured for input capture. We need to set time base for TIM1 first. It is set as such that TIM1 will overflow every second. Then we set input capture channel by specifying the edge sensitivity, channel, mode and scalars. Since we will be using interrupts, we must enable relevant interrupts.

void TIM1_setup(void)
{
     TIM1_DeInit();
     TIM1_TimeBaseInit(2000, TIM1_COUNTERMODE_UP, 55535, 1);
     TIM1_ICInit(TIM1_CHANNEL_1, TIM1_ICPOLARITY_RISING, 
                 TIM1_ICSELECTION_DIRECTTI, 1, 1);
     TIM1_ITConfig(TIM1_IT_UPDATE, ENABLE);
     TIM1_ITConfig(TIM1_IT_CC1, ENABLE);
     TIM1_Cmd(ENABLE);
     
     enableInterrupts();
}

TIM2 is set for PWM generation on its CH1. The generated PWM will have a frequency of 50Hz and 50% duty cycle. The setup of TIM2 should be by now self-explanatory:

void TIM2_setup(void)
{
     TIM2_DeInit();
     TIM2_TimeBaseInit(TIM2_PRESCALER_32, 1250);
     TIM2_OC1Init(TIM2_OCMODE_PWM1, TIM2_OUTPUTSTATE_ENABLE, 1000, 
                  TIM2_OCPOLARITY_LOW);
     TIM2_SetCompare1(625);
     TIM2_Cmd(ENABLE);
}

In the vector table of stm8_interrupt_vector.c file, we need to specify the interrupts we will be using:

{0x82, (interrupt_handler_t)TIM1_UPD_IRQHandler}, /* irq11 */
{0x82, (interrupt_handler_t)TIM1_CH1_CCP_IRQHandler}, /* irq12 */

We have to specify the interrupt subroutine (ISR) prototype functions in the stm8s_it.h file. These functions are the places where the code will jump when respective interrupt occurs:

@far @interrupt void TIM1_UPD_IRQHandler(void);
@far @interrupt void TIM1_CH1_CCP_IRQHandler(void);

The ISR functions are coded in the stm8s_it.c file:

void TIM1_UPD_IRQHandler(void) 
{
     overflow_count++;
     TIM1_ClearITPendingBit(TIM1_IT_UPDATE);
     TIM1_ClearFlag(TIM1_FLAG_UPDATE);
}

The first part is dealing with TIM1 overflow. If a capture occurs when TIM1 count is near to reset value we need to take this account. This part does so and an overflow counter is incremented.

void TIM1_CH1_CCP_IRQHandler(void) 
{
     end_time = TIM1_GetCapture1();
     pulse_ticks = ((overflow_count << 16) - start_time + end_time);
     start_time = end_time;
     overflow_count = 0;
     TIM1_ClearITPendingBit(TIM1_IT_CC1);
     TIM1_ClearFlag(TIM1_FLAG_CC1);
}

The second part is where TIM1 capture is recorded. Once a rising edge is captured, an interrupt is issued. In the interrupt, we must first save the current TIM1 counter count in the variable named end_time. The formula for pulse tick is then computed. Note how the TIM1 overflow is addressed in the formula. The new start time should be the previous capture time because we need to deduct old capture count from new capture count. Lastly, overflow counter, capture flag and pending interrupts are cleared.

In the main loop, we are just displaying the time period of capture in a LCD while everything is being processed in the background by interrupts:

while(TRUE)
{
     time_period = pulse_ticks;
     lcd_print(0, 1, time_period);
     delay_ms(400);
};

 

Demo

Capture capture

Video link: https://youtu.be/bzLUDwuFQTw

 

Communication Overview

STM8 microcontrollers are packed with several communication interfaces. These interfaces are needed to communicate with external devices like sensors, actuators, drives, etc. The most commonly used ones are Serial Communication (UART), Serial Peripheral Interface (SPI) and Inter-Integrated Circuit (I2C). There are also other additional more robust communication interfaces like Controller Area Network (CAN), Local Interconnect Network (LIN), Infrared Data Association (IrDA) and RS-485. The latter communications will not be discussed here in this article and are kept for future issues. These are methods are, however, not frequently used and are special forms of communications. For example, CAN and LIN are mostly used in automotive industries. Each method communication has its own advantages and disadvantages. Here we’ll see the individual basics of various methods of communications.

Comms Comparision

In STM8 microcontrollers, LIN, IrDA, RS-485 and UART all share the UART hardware peripheral. For other communications, there are dedicated separate hardware. We will now be exploring the basic ones here.

Serial Communication (UART)

Serial communication is perhaps the mostly-used classic communication method for interfacing a PC or other machines with a micro. With just two wire, we can achieve a full-duplex point-to-point communication. Owing to its simplicity and wide usage, it is the communication interface backbone that is used with GSM modems, RF modules, Bluetooth devices like RN-52, Wi-Fi devices like the popular ESP8266, etc. It is also widely used in industries. Other communications rely on it, for example, RS-485, LIN, etc.

 PPT

Most STM8s have at least one UART module. Some have more than one. Different UARTs have different features as shown:

UARTs

To learn more about UART visit the following link:

https://learn.mikroe.com/uart-serial-communication/

The UARTs of STM8 micros are so robust and packed with so many features that it is quite impossible to explain them all in this one article. Here we will explore the basic serial communication only.  LIN and IRDA will hopefully be covered in future articles.

Hardware Connection

Cubemx

 

Code Example

#include "STM8S.h"
 
 
void clock_setup(void);
void GPIO_setup(void);
void UART1_setup(void);
 
 
void main(void)
{
     unsigned char i = 0;   
     char ch = 0;
                
     clock_setup();
     GPIO_setup();
     UART1_setup();
     LCD_init();
     LCD_clear_home();
                
     LCD_goto(0, 0);
     LCD_putstr("TX:");
     LCD_goto(0, 1);
     LCD_putstr("RX:");
                
     while(TRUE)
     {
          if(UART1_GetFlagStatus(UART1_FLAG_RXNE) == TRUE)
          {
             ch = UART1_ReceiveData8();
             LCD_goto(7, 1);
             LCD_putchar(ch);
             UART1_ClearFlag(UART1_FLAG_RXNE);
             UART1_SendData8(i + 0x30);
          }
          
          if(UART1_GetFlagStatus(UART1_FLAG_TXE) == FALSE)
          {
             LCD_goto(7, 0);
             LCD_putchar(i + 0x30);
             i++;
          }
      };
}
 
 
void clock_setup(void)
{
      CLK_DeInit();
                
      CLK_HSECmd(DISABLE);
      CLK_LSICmd(DISABLE);
      CLK_HSICmd(ENABLE);
      while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);
                
      CLK_ClockSwitchCmd(ENABLE);
      CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
      CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
                
      CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
      DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);
                
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, ENABLE);           
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}
 
 
void GPIO_setup(void)
{                               
     GPIO_DeInit(GPIOD);
                
     GPIO_Init(GPIOD, GPIO_PIN_5, GPIO_MODE_OUT_PP_HIGH_FAST);
     GPIO_Init(GPIOD, GPIO_PIN_6, GPIO_MODE_IN_PU_NO_IT);
}
 
 
void UART1_setup(void)
{
     UART1_DeInit();
                
     UART1_Init(9600, 
                UART1_WORDLENGTH_8D, 
                UART1_STOPBITS_1, 
                UART1_PARITY_NO, 
                UART1_SYNCMODE_CLOCK_DISABLE, 
                UART1_MODE_TXRX_ENABLE);
                
     UART1_Cmd(ENABLE);
}

 

Explanation

The peripheral and CPU clocks are set at 2MHz:

CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
….
….
CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, ENABLE);

The TX-RX GPIO pins are set as output and input respectively:

GPIO_DeInit(GPIOD);
                
GPIO_Init(GPIOD, GPIO_PIN_5, GPIO_MODE_OUT_PP_HIGH_FAST);
GPIO_Init(GPIOD, GPIO_PIN_6, GPIO_MODE_IN_PU_NO_IT);

UART setup is straightforward. We just need to set baud rate, no. of data bits, no. of stop bit, parity and type of communication (synchronous or asynchronous).

void UART1_setup(void)
{
     UART1_DeInit();
                
     UART1_Init(9600, 
                UART1_WORDLENGTH_8D, 
                UART1_STOPBITS_1, 
                UART1_PARITY_NO, 
                UART1_SYNCMODE_CLOCK_DISABLE, 
                UART1_MODE_TXRX_ENABLE);
                
     UART1_Cmd(ENABLE);
}

In the main code, we are checking both transmission complete and reception complete flags. With these flags, we will know if new data arrived and if it is possible to send a new data.

The first part checks if any new data received. That’s why the IF condition is checking if the RX buffer is empty or not. If it is not empty then new data must have arrived. The new data (a character here) is fetched and displayed on a LCD. Then we clear the RX buffer not empty flag to enable reception of new data. After that we are sending some data to the host PC over the UART.

if(UART1_GetFlagStatus(UART1_FLAG_RXNE) == TRUE)
{
   ch = UART1_ReceiveData8();
   LCD_goto(7, 1);
   LCD_putchar(ch);
   UART1_ClearFlag(UART1_FLAG_RXNE);
   UART1_SendData8(i + 0x30);
}

In the second part, we are checking if the last data was sent from our STM8 micro. The data sent is then displayed on LCD. 

if(UART1_GetFlagStatus(UART1_FLAG_TXE) == FALSE)
{
   LCD_goto(7, 0);
   LCD_putchar(i + 0x30);
   i++;
}

Please note that both of these flags are very important.

 

Demo

UART

Untitled

Video link: https://youtu.be/uo2tYDUnMmE

 

Serial Peripheral Interface (SPI)

SPI communication is an onboard synchronous communication method and is used by a number of devices including sensors, TFT displays, GPIO expanders, PWM controller ICs, memory chips, addon support devices, etc.

There’s always one master device in a SPI communication bus which generates clock and select slave(s). Master sends commands to slave(s). Slave(s) responds to commands sent by the master. The number of slaves in a SPI bus is virtually unlimited. Except the chip selection pin, all SPI devices in a bus can share the same clock and data pins.

Typical full-duplex SPI bus requires four basic I/O pins:

  • Master-Out-Slave-In (MOSI) connected to Slave-Data-In (SDI).
  • Master-In-Slave-Out (MIS0) connected to Slave-Data-Out (SDO).
  • Serial Clock (SCLK) connected to Slave Clock (SCK).
  • Slave Select (SS) connected to Chip Select (CS).

spi

In general, if you wish to know more about SPI bus here are some cool links:

STM8s have SPI hardware that are more capable than the SPI hardware found in other micros. An additional feature of STM8’s SPI is the hardware CRC. This feature ensures reliable data communication between devices.

 

Hardware Connection

spi_cube

Code Example

main.c

#include "STM8S.h"
#include "MAX72XX.h"
 
 
void clock_setup(void);
void GPIO_setup(void);
void SPI_setup(void);
 
 
void main()
{
    unsigned char i = 0x00;
    unsigned char j = 0x00;
 
    unsigned char temp[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    
    const unsigned char text[96] =
    {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   
        0x00, 0x7E, 0x04, 0x08, 0x08, 0x04, 0x7E, 0x00,        //M
        0x00, 0x42, 0x42, 0x7E, 0x7E, 0x42, 0x42, 0x00,        //I
        0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x24, 0x00,        //C
        0x00, 0x7E, 0x1A, 0x1A, 0x1A, 0x2A, 0x44, 0x00,        //R
        0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00,        //O
        0x00, 0x7C, 0x12, 0x12, 0x12, 0x12, 0x7C, 0x00,        //A
        0x00, 0x7E, 0x1A, 0x1A, 0x1A, 0x2A, 0x44, 0x00,        //R
        0x00, 0x7E, 0x7E, 0x4A, 0x4A, 0x4A, 0x42, 0x00,        //E
        0x00, 0x7E, 0x04, 0x08, 0x10, 0x20, 0x7E, 0x00,        //N
        0x00, 0x7C, 0x12, 0x12, 0x12, 0x12, 0x7C, 0x00,        //A
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00   
    };
    
                
    clock_setup();
    GPIO_setup();
    SPI_setup();
    MAX72xx_init();
                
    while(TRUE)
    {
          for(i = 0; i < sizeof(temp); i++)
          {
              temp[i] = 0x00;
          }
                                
          for(i = 0; i < sizeof(text); i++)
          {
              for(j = 0; j < sizeof(temp); j++)
              {
                  temp[j] = text[(i + j)];
                  MAX72xx_write((1 + j), temp[j]);
                  delay_ms(9);
              }
           }
    };
}
 
 
void clock_setup(void)
{
     CLK_DeInit();
                
     CLK_HSECmd(DISABLE);
     CLK_LSICmd(DISABLE);
     CLK_HSICmd(ENABLE);
     while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);
                
     CLK_ClockSwitchCmd(ENABLE);
     CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
     CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
                
     CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
     DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);
                
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, ENABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}
 
 
void GPIO_setup(void)
{
     GPIO_DeInit(GPIOC);
     GPIO_Init(GPIOC, ((GPIO_Pin_TypeDef)GPIO_PIN_5 | GPIO_PIN_6), 
               GPIO_MODE_OUT_PP_HIGH_FAST);
}
 
 
void SPI_setup(void)
{
     SPI_DeInit();
     SPI_Init(SPI_FIRSTBIT_MSB, 
              SPI_BAUDRATEPRESCALER_2, 
              SPI_MODE_MASTER, 
              SPI_CLOCKPOLARITY_HIGH, 
              SPI_CLOCKPHASE_1EDGE, 
              SPI_DATADIRECTION_1LINE_TX, 
              SPI_NSS_SOFT, 
              0x00);
     SPI_Cmd(ENABLE);
}

 

MAX72xx.h

#include "STM8S.h"
 
 
#define CS_pin                                                GPIO_PIN_4
#define CS_port                                               GPIOC
 
#define NOP                                                   0x00
#define DIG0                                                  0x01
#define DIG1                                                  0x02
#define DIG2                                                  0x03
#define DIG3                                                  0x04
#define DIG4                                                  0x05
#define DIG5                                                  0x06
#define DIG6                                                  0x07
#define DIG7                                                  0x08
 
#define decode_mode_reg                                       0x09
#define intensity_reg                                         0x0A
#define scan_limit_reg                                        0x0B
#define shutdown_reg                                          0x0C
#define display_test_reg                                      0x0F
 
#define shutdown_cmd                                          0x00
#define run_cmd                                               0x01
 
#define no_test_cmd                                           0x00
#define test_cmd                                              0x01
                             
#define digit_0_only                                          0x00
#define digit_0_to_1                                          0x01
#define digit_0_to_2                                          0x02
#define digit_0_to_3                                          0x03
#define digit_0_to_4                                          0x04
#define digit_0_to_5                                          0x05
#define digit_0_to_6                                          0x06
#define digit_0_to_7                                          0x07
                                                  
#define No_decode_for_all                                     0x00
#define Code_B_decode_digit_0                                 0x01
#define Code_B_decode_digit_0_to_3                            0x0F
#define Code_B_decode_for_all                                 0xFF
 
 
void MAX72xx_init(void);
void MAX72xx_write(unsigned char address, unsigned char value);

 

MAX72xx.c

#include "MAX72xx.h"
 
 
void MAX72xx_init(void)
{
    GPIO_Init(CS_port, CS_pin, GPIO_MODE_OUT_PP_HIGH_FAST);
 
    MAX72xx_write(shutdown_reg, run_cmd);                 
    MAX72xx_write(decode_mode_reg, 0x00);
    MAX72xx_write(scan_limit_reg, 0x07);
    MAX72xx_write(intensity_reg, 0x04);
    MAX72xx_write(display_test_reg, test_cmd);
    delay_ms(10);     
    MAX72xx_write(display_test_reg, no_test_cmd);  
}
 
 
void MAX72xx_write(unsigned char address, unsigned char value)
{
    while(SPI_GetFlagStatus(SPI_FLAG_BSY));
    GPIO_WriteLow(CS_port, CS_pin);
                
    SPI_SendData(address);
    while(!SPI_GetFlagStatus(SPI_FLAG_TXE));
                
    SPI_SendData(value);
    while(!SPI_GetFlagStatus(SPI_FLAG_TXE));
                
    GPIO_WriteHigh(CS_port, CS_pin);
}

 

Explanation

This time we are again using the max peripheral and CPU clock:

CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
….
….
CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, ENABLE);

We also need to set the GPIOs:

#define CS_pin                         GPIO_PIN_4
#define CS_port                       GPIOC
….
….
GPIO_DeInit(GPIOC);
GPIO_Init(CS_port, CS_pin, GPIO_MODE_OUT_PP_HIGH_FAST);
GPIO_Init(GPIOC, ((GPIO_Pin_TypeDef)GPIO_PIN_5 | GPIO_PIN_6), 
          GPIO_MODE_OUT_PP_HIGH_FAST);

Note we can use definitions to make things meaningful. The GPIOs should be configured as fast I/Os because SPI communication is faster than simple GPIO operations.

Now for the SPI configuration part. Assuming you know how to interpret timing diagrams and understand device datasheets, SPI configuration should not be a problem. Here in the case of MAX7219, we have configured the SPI port as to send MSB first, we have also selected a fast peripheral clock, we have made the STM8 SPI act like a master with proper SPI mode and we have set the sort of duplex. The last two parameters are not important as we are not using hardware slave select option and CRC feature.

void SPI_setup(void)
{
     SPI_DeInit();
     SPI_Init(SPI_FIRSTBIT_MSB, 
              SPI_BAUDRATEPRESCALER_2, 
              SPI_MODE_MASTER, 
              SPI_CLOCKPOLARITY_HIGH, 
              SPI_CLOCKPHASE_1EDGE, 
              SPI_DATADIRECTION_1LINE_TX, 
              SPI_NSS_SOFT, 
              0x00);
     SPI_Cmd(ENABLE);
}

The timing diagram of MAX7219 suggests that CS should be low in order for MAX7219 to receive data.

Timing Diagram

Then it suggests that when idle, clock must be high, data transfer is done on every rising edge of the clock. All these are what required for setting up the SPI hardware.

void MAX72xx_write(unsigned char address, unsigned char value)
{
    while(SPI_GetFlagStatus(SPI_FLAG_BSY));
    GPIO_WriteLow(CS_port, CS_pin);
                
    SPI_SendData(address);
    while(!SPI_GetFlagStatus(SPI_FLAG_TXE));
                
    SPI_SendData(value);
    while(!SPI_GetFlagStatus(SPI_FLAG_TXE));
                
    GPIO_WriteHigh(CS_port, CS_pin);
}

Before sending data to MAX7219, we must check if the SPI hardware is busy for some reason. We set CS low by setting STM8’s slave select pin (PC4) low. Then we send address and data. Every time we send something we must wait until it has completely been sent out. Finally, we set CS high to latch sent data. This function is what we will need to set MAX7219 things up and also to update displays.

The demo here is that of a MAX7219- based scrolling dot-matrix display. Letters of MICROARENA – the name of my Facebook page is scrolled.

Demo

SPI

MAX7219

Video link: https://youtu.be/O7mre-bzsGE

 

Inter-Integrated Circuit (I2C)

I2C is another popular form of on board synchronous serial communication developed by NXP. It just uses two wires for communication and so it is also referred as Two Wire Interface (TWI). Just like SPI, I2C is widely used in interfacing real-time clocks (RTC), digital sensors, memory chips and so on. It is as much as popular as SPI but compared to SPI it is slower and have some limitations. Up to 127 devices can coexist in an I2C bus. In an I2C bus however it is not possible, by conventional means to interface devices with same device IDs or devices with different logic voltage levels without logic level converters and so on. Still however, I2C is very popular because these issues rarely arise and because of its simplicity. Unlike other communications, there’s no pin/wire swapping as two wires connect straight to the bus – SDA to SDA and SCL to SCL.

i2c

Just like SPI, an I2C bus must contain one master device (usually a microcontroller) and one or more slaves. The master is solely responsible for generating clock signals and initiating communication. Communication starts when master sends out a slave’s ID with read/write command. The slave reacts to this command by processing the request from the master and sending out data.

To know more about I2C interface visit the following links:

Other protocols like SMBus and I2S have similarities with I2C and so learning about I2C advances learning these too.

Hardware Connection

i2c_cube

 

Code Example

This code demonstrates how to interface BH1750 I2C digital light sensor with STM8S003K3. A LCD is used to display the light sensor’s output in lux.

 

main.c

#include "STM8S.h"
#include "BH1750.h"
#include "lcd.h"
 
 
void clock_setup(void);
void GPIO_setup(void);
void I2C_setup(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
 
 
void main()
{                                                                                
     unsigned int LX = 0x0000;
     unsigned int tmp = 0x0000;
                                                                                     
     clock_setup();
     GPIO_setup();
     I2C_setup();
     LCD_init();  
     BH1750_init();
                                                                    
     LCD_clear_home(); 
     LCD_goto(0, 0);
     LCD_putstr("STM8 I2C");
     LCD_goto(0, 1);
     LCD_putstr("Lx");
     delay_ms(10);
                                                                                     
     while(TRUE)
     {
         tmp = get_lux_value(cont_L_res_mode, 20);
        
         if(tmp > 10)
         {
             LX = tmp;
         }
         else
         {
             LX = get_lux_value(cont_H_res_mode1, 140);
         }
         
         lcd_print(3, 1, LX);
         delay_ms(200);
     };
}
 
 
void clock_setup(void)
{
      CLK_DeInit();
                     
      CLK_HSECmd(DISABLE);
      CLK_LSICmd(DISABLE);
      CLK_HSICmd(ENABLE);
      while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);
      

      CLK_ClockSwitchCmd(ENABLE);
      CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
      CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV2);
      
      CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
      DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);
      
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, ENABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
      CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}
 
 
void GPIO_setup(void)
{   
    GPIO_DeInit(GPIOB);
    GPIO_Init(GPIOB, GPIO_PIN_4, GPIO_MODE_OUT_OD_HIZ_FAST);
    GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_OD_HIZ_FAST);
}
 
void I2C_setup(void)
{
    I2C_DeInit();
    I2C_Init(100000, 
             BH1750_addr, 
             I2C_DUTYCYCLE_2, 
             I2C_ACK_CURR, 
             I2C_ADDMODE_7BIT, 
             (CLK_GetClockFreq() / 1000000));
     I2C_Cmd(ENABLE);
}
 
 
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
    char tmp[5] = {0x20, 0x20, 0x20, 0x20, 0x20} ;
 
    tmp[0] = ((value / 10000) + 0x30);
    tmp[1] = (((value / 1000) % 10) + 0x30);
    tmp[2] = (((value / 100) % 10) + 0x30);
    tmp[3] = (((value / 10) % 10) + 0x30);
    tmp[4] = ((value % 10) + 0x30);
                                                                                   
    LCD_goto(x_pos, y_pos);
    LCD_putstr(tmp);   
}

  BH1750.h

#include "STM8S.h"
 
 
#define   BH1750_addr                             0x46
         
#define  power_down                               0x00
#define  power_up                                 0x01
#define  reset                                    0x07 
#define  cont_H_res_mode1                         0x10 
#define  cont_H_res_mode2                         0x11  
#define  cont_L_res_mode                          0x13    
#define  one_time_H_res_mode1                     0x20 
#define  one_time_H_res_mode2                     0x21
#define  one_time_L_res_mode                      0x23  
                        
 
void BH1750_init(void);
void BH1750_write(unsigned char cmd);      
unsigned int BH1750_read_word(void);
unsigned int get_lux_value(unsigned char mode, unsigned int delay_time);

 

BH1750.c

#include "BH1750.h"
 
 
void BH1750_init(void)
{ 
   delay_ms(10);  
   BH1750_write(power_down);
}                
 
 
void BH1750_write(unsigned char cmd)
   I2C_GenerateSTART(ENABLE);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
   
   I2C_Send7bitAddress(BH1750_addr, I2C_DIRECTION_TX); 
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
   
   I2C_SendData(cmd);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
   
   I2C_GenerateSTOP(ENABLE);   
}
 
 
unsigned int BH1750_read_word(void)
{                      
   unsigned long value = 0x0000;
   unsigned char num_of_bytes = 0x02;   
   unsigned char bytes[2] = {0x00, 0x00};
  
   while(I2C_GetFlagStatus(I2C_FLAG_BUSBUSY));
   
   I2C_GenerateSTART(ENABLE);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
   
   I2C_Send7bitAddress(BH1750_addr, I2C_DIRECTION_RX);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
   
   while(num_of_bytes)
   {
        if(I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED))
       {   
            if(num_of_bytes == 0)
            {
                 I2C_AcknowledgeConfig(I2C_ACK_NONE);
                 I2C_GenerateSTOP(ENABLE);   
             }
 
            bytes[(num_of_bytes - 1)] = I2C_ReceiveData();
            num_of_bytes--;
         }
   };   
   
   value = ((bytes[1] << 8) | bytes[0]);  
   
   return value;
} 
 
 
unsigned int get_lux_value(unsigned char mode, unsigned int delay_time)
{
   unsigned long lux_value = 0x00;  
   unsigned char dly = 0x00;
   unsigned char s = 0x08;
   
   while(s)
   {
       BH1750_write(power_up);
       BH1750_write(mode);
       lux_value += BH1750_read_word();
       for(dly = 0; dly < delay_time; dly += 1)
       {
           delay_ms(1);
       }
       BH1750_write(power_down);
       s--;
   }
   lux_value >>= 3;
   
   return ((unsigned int)lux_value);
}                                

 

Explanation

Firstly both the CPU and peripheral clocks are set. Note the CPU is slower than last few examples. This has no significance.

CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV2);
….
….
CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, ENABLE);

I2C I/Os are set as open drain outputs because they have external pull-up resistors that terminate the bus I/Os to VDD lines. SCL pin is always an output from host microcontroller’s end however SDA pin’s direction varies with reading and writing operations. This is automatically done by the I2C hardware.

GPIO_DeInit(GPIOB);
GPIO_Init(GPIOB, GPIO_PIN_4, GPIO_MODE_OUT_OD_HIZ_FAST);
GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_OD_HIZ_FAST);

I2C setup has many parameters to set, firstly the I2C bus clock speed, then its own ID, clock duty cycle, address mode, acknowledgement type and clock speed of the peripheral. Here the own ID and slave ID are both set same because we are not using our STM8 as a slave. It doesn’t matter. You can also set something else.

void I2C_setup(void)
{
    I2C_DeInit();
    I2C_Init(100000, 
             BH1750_addr, 
             I2C_DUTYCYCLE_2, 
             I2C_ACK_CURR, 
             I2C_ADDMODE_7BIT, 
             (CLK_GetClockFreq() / 1000000));
     I2C_Cmd(ENABLE);
}

If you have used compilers with built-in I2C library before then you may get some hiccups studying the following part. This is because those built-in libraries accomplish many tasks in the background that you never felt necessary. Flags and acknowledgments are such stuffs that are mostly automatically dealt by the compiler and ignore by most users. Personally, I had to struggle with these before settling this code. Another big difference is fact that the SPL’s functions, their operations and nomenclatures for I2C are different than most I2C libraries one has seen before. Lastly, I2C examples with STM’s SPL on the internet are rare and most of them demonstrate I2C example with 24 series EEPROMs only. I wanted to do something different and so I used BH1750 I2C digital sensor instead of repeating another EEPROM example.

unsigned int BH1750_read_word(void)
{                      
   unsigned long value = 0x0000;
   unsigned char num_of_bytes = 0x02;   
   unsigned char bytes[2] = {0x00, 0x00};
  
   while(I2C_GetFlagStatus(I2C_FLAG_BUSBUSY));
   
   I2C_GenerateSTART(ENABLE);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
   
   I2C_Send7bitAddress(BH1750_addr, I2C_DIRECTION_RX);
   while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
   
   while(num_of_bytes)
   {
        if(I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED))
       {   
            if(num_of_bytes == 0)
            {
                 I2C_AcknowledgeConfig(I2C_ACK_NONE);
                 I2C_GenerateSTOP(ENABLE);   
             }
 
            bytes[(num_of_bytes - 1)] = I2C_ReceiveData();
            num_of_bytes--;
         }
   };   
   
   value = ((bytes[1] << 8) | bytes[0]);  
   
   return value;
} 

As with SPI, we need to check first if I2C hardware is free. We, then, initiate a I2C start condition and check master/slave mode selection. Next, we send out slave device’s ID or address with read command, signalling that we wish to read from the slave. Again, a flag is checked before continuing. Here the sensor gives 16-bit light output data and so we need to extract two 8-bit data values. This is done in the while loop. At the end of data extraction process, we must generate stop as well as take care of acknowledgement. Finally, the two bytes are joined to form a word value representing light output.

The following function simplifies the task of determining lux value from the sensor. It selects mode of operation and latency. We will call this function in the main loop to extract average light value in lux.

unsigned int get_lux_value(unsigned char mode, unsigned int delay_time)
{
   unsigned long lux_value = 0x00;  
   unsigned char dly = 0x00;
   unsigned char s = 0x08;
   
   while(s)
   {
       BH1750_write(power_up);
       BH1750_write(mode);
       lux_value += BH1750_read_word();
       for(dly = 0; dly < delay_time; dly += 1)
       {
           delay_ms(1);
       }
       BH1750_write(power_down);
       s--;
   }
   lux_value >>= 3;
   
   return ((unsigned int)lux_value);
}                                

 

Demo

I2C

Video link: https://youtu.be/bpwki1RCOXU

 

Some Useful Tips

When using a new compiler, I evaluate some certain things. For instance, how do I include my own written library files, interrupt management, what conventions I must follow and what dos and don’ts must be observed.

Creation & Addition of libraries

At some point in working with any microcontroller, you’ll need two basic libraries more than anything else. These are LCD and delay libraries. LCDs are great tools for quickly projecting or presenting data apart from debugging a code with a debugger. Similarly, time-wasting delay loops help us slow down things at our liking. Humans are not as fast as machines. Delays can be avoided in many novel ways but delays keep things simple and so are necessities in some areas.

The Standard Peripheral Library only provides libraries for hardware peripherals and surely not for anything else. It is also practically impossible to provide library for all hardware on available on the planet. Thus, whenever when we will be needing new hardware integrations with STM8s, we will have to code and tag our libraries with our projects. So how can we do so?

Earlier in this article I discussed about alphanumeric LCDs and delays. If you check the datasheet of such LCDs, you’ll find initialization sequences in some while in others you may also find ready-made codes. These sequences are needed to be translated in code just like what we do with I2C or SPI-based devices.  Shown below is such an example:

LCD Sequence

Creating new libraries is simple. Just need to follow the following steps:

  • There should be a header file and a source file for every new module. For example, h and lcd.c.
  • Every header file should start with the inclusion of h header file (#include “stm8s.h”). This header is needed because it allows the access to the internal hardware modules available in a STM8 micro. For example, we will need access to GPIOs to develop our LCD library.
  • A good practice is that the header files only contain function prototypes, definitions, constants, enumerations and global variables.
  • The corresponding source file must only include its header file in beginning.
  • The source file should contain the body of codes for all functions declared in the header file.
  • When one library is dependent on the functions of another’s, the one that will be required in the new library must be included first. For example, we will need delay library in coding the LCD library because there are delay_ms functions in some parts of the library and so delay library should be included first. This should be the systematic order:

#include “stm8s_delay.h”

#include “lcd.h”

You can include these files at the bottom part of the stm8s_conf.h header file complying with right precedence as shown below:

File Inclusion

Alternatively, you can add them after the first line #include “stm8s.h” in your main source code.

 

Peripheral Clock Configurations

In most codes revealed so far, I made clock configurations every time. The reasons behind so are

  • Selection of right clock source.
  • Adjustment of peripheral and system clocks as per requirement. Again, it is mainly intended to balance off both power consumption and overall performance.
  • Disabling any unused hardware. This reduces power consumption and help us avoid certain hardware conflicts.

 

void clock_setup(void)
{
    CLK_DeInit();
                
    CLK_HSECmd(DISABLE);
    CLK_LSICmd(ENABLE);
    while(CLK_GetFlagStatus(CLK_FLAG_LSIRDY) == FALSE);
    CLK_HSICmd(ENABLE);
    while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);
                
    CLK_ClockSwitchCmd(ENABLE);
    CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
    CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
                
    CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
    DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);
                
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, ENABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
     CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}

The following lines select clock sources:

CLK_DeInit();
                
CLK_HSECmd(DISABLE);
CLK_LSICmd(ENABLE);
while(CLK_GetFlagStatus(CLK_FLAG_LSIRDY) == FALSE);
CLK_HSICmd(ENABLE);
while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);

What these lines do are enabling/disabling clock sources and wait for the sources to stabilize.

Then the following lines select clock prescalers and switching:

CLK_ClockSwitchCmd(ENABLE);
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
                
CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, 
DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE);

Finally, the last segment enables/disables peripheral clocks:

CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);                
CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, DISABLE);
CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);          
CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE); 

This segment is very important and should always be rechecked. Different chips have different internal hardware peripheral and so this segment will be different. For instance, STM8S105 has no UART1 module but it has UART2 instead. Research which hardware are available in your target micro and then code this segment. I ended up with several wasted hours of finding trouble in various cases only to find that I didn’t enable the required hardware.

Configuring Similar Stuffs Quickly

Sometimes you may end up doing the same stuff over and over again while you could have done it simply with one or two lines of code. For example, in the LCD library, the GPIOs that connect with the LCD share the same configurations. All are fast push-pull outputs.

 GPIO_Init(LCD_PORT, LCD_RS, GPIO_MODE_OUT_PP_HIGH_FAST);
 GPIO_Init(LCD_PORT, LCD_EN, GPIO_MODE_OUT_PP_HIGH_FAST);
 GPIO_Init(LCD_PORT, LCD_DB4, GPIO_MODE_OUT_PP_HIGH_FAST);
 GPIO_Init(LCD_PORT, LCD_DB5, GPIO_MODE_OUT_PP_HIGH_FAST);
 GPIO_Init(LCD_PORT, LCD_DB6, GPIO_MODE_OUT_PP_HIGH_FAST);
 GPIO_Init(LCD_PORT, LCD_DB7, GPIO_MODE_OUT_PP_HIGH_FAST);

This can be done in a more simplistic manner with just one line of code:

GPIO_Init(LCD_PORT, ((GPIO_Pin_TypeDef)(LCD_RS | LCD_EN | LCD_DB4 | LCD_DB5 
                     | LCD_DB6 | LCD_DB7)), GPIO_MODE_OUT_PP_HIGH_FAST);

As you can see it is just a bunch of logical OR operation. The same method is applicable for other peripherals that share the same initialization function.

 

Some Stuffs About Cosmic C and SPL

  • Functions, variables and definitions in Cosmic C are case sensitive.
  • Functions with no arguments must not have empty argument areas. For example, you cannot write: void setup ();
    You should write it as: void setup (void);
  • Definitions and constants can be declared as with any C compiler.
  • Wherever there are flags, you need to be careful. You should check and clear flags even if it is cleared by hardware. For instance, when reading ADC, the ADC End-Of-Conversion (EOC) flag is automatically cleared but still in the code you should check and clear it. ADC1_ClearFlag(ADC1_FLAG_EOC); Flags are so important that unless you check and clear them appropriately, you may not get the right result from your code. Personally, I didn’t care much until I got myself into trouble.
  • You can mix assembly codes with your C code to enhance performance and optimization. However, you need to have sound knowledge of the assembly instructions. This is a rare requirement. The delay library, for instance, uses no operation assembly instruction to achieve delays. This is written as shown:
    _asm ("nop"); 
  • Empty loops are ignored by the compiler as a part of code optimization.
  • Long ago, Atmel (now Microchip) published a document regarding ways to efficiently optimize C coding. This document holds true for most microcontrollers. For example, in that document it is stated that a decrementing do-while loop is much more faster and code efficient than an incrementing do-while You can apply the methods presented there and other similar tricks with STM8 microcontrollers too. The document can be found here:
    http://www.atmel.com/images/doc8453.pdf
  • Though I don’t feel it as a necessity and don’t recommend it, you can avoid using the SPL and still code by raw register level access. For example, you can blink a LED with the following code:
    #include "stm8s.h"
    
    
    void main (void)
    {
         GPIOD->DDR |= 0x01; 
         GPIOD->CR1 |= 0x01;
    
    
         for(;;) 
    
         {
             GPIOD->ODR ^= (1 << 0);
             delay_ms(100);
         };
    }
  • Don’t mess with configuration (fuse) bits unless needed. Most of the times you will never have to deal with them. AFRs are used when remapping is needed or to enable special GPIO functionality. These will be of most importance.
  • Bitwise and logic operations are useful. Not only they are fast, they just deal with the designated bits only. SPL has support for such operations but it is still better to know them. Here are some common operations:
#define bit_set(reg, bit_val)        reg |= (1 << bit_val)   
//For setting a bit of a register     

#define bit_clr(reg, bit_val)        reg &= (~(1 << bit_val))         
//For clearing a bit of a register

#define bit_tgl(reg, bit_val)        reg ^= (1 << bit_val)                   
//For toggling a bit of a register

#define get_bit(reg, bit_val)       (reg & (1 << bit_val))                   
//For extracting the bit state of a register

#define get_reg(reg, msk)           (reg & msk)                                 
//For extracting the states of masked bits of a register

 

Unlocking a Locked STM8 Chip

If you have accidentally locked a STM8 chip by setting the Readout Protection configuration bit and no longer able to use it, you can unlock it easily.

When you lock a chip, the programmer interface will give you a warning notification. If you retry to reprogram/erase a locked chip you’ll get an error like this:

LOCKED

No matter what you do, you won’t be able to use it.

To unlock, go to the light programmer interface of STVD and check the Unlock Device checkbox as shown below:

Unlock

Also select Erase before Programming radio button because it is highly likely that your target chip is not empty. Now once you retry to reprogram, it will get unlocked.

Mastering C Language

You need not to be a C whiz to work with microcontrollers but certain things will surely help you to resolve some critical problems with simple codes. You must check supported data types whenever you begin working in a new development environment and should always use unsigned-signed designations to avoid unnecessary mistakes. Likewise, variable size is also important. Pointer, structures, unions and arrays are helpful features of C-language. You must learn how to use and apply them successfully. Without these you can still work but things will look really dirty. When coding for a new work, you must try to settle what you wish from your system and how should it behave. There should be an organized workflow and thereby your code will automatically be formulated in a state-of-machine algorithm or as a real-time system. You must try to avoid delays and loops wherever possible. Try to avoid polling and use interrupt-based systems. This will make your device behave in real-time with zero latency. However, you must be careful in handling interrupts because interrupts within interrupts will cause your system to crash miserably. Functions make things modular and thus easy to modify or debug. Repeated tasks should be placed in functions. A blinking LED code may look simple and stupid but sometimes very useful for testing stuffs. Some basic knowledge on mathematics and algorithms are also requirements for becoming a good embedded-system specialist.

Epilogue

In the end, I would like to share that my tiny raw-level knowledge and experiences with STM32s (http://embedded-lab.com/blog/stm32-tutorials/) earlier paid off handsomely. Due to that I was able to compiler this article decently and quickly. Personally, I feel that whosoever knows STM8 micros well will master STM32s and vice-versa because except the cores all hardware in both architectures are not just similar but same sometimes. This is for the first time I have admired STM’s SPL. My experiences with STM32 SPL was not well as so I decided to go on my own. However, this time things were different. Things were joyful and less difficult.

I would like to thank a few people who influenced me in composing this article:

Though his methods are different than mine, his article guided me a lot in the beginning. It is perhaps the most popular site for tutorials on STM8s and well organized. He used STM8S105 Discovery.

His tutorials on STM8 are not based on SPL. He showed stuffs with raw-level register access and with IAR compiler. Still his blog is informative.

  • http://www.emcu.it/. This Italian site was helpful in getting some early info.
  • STMicroelectronics team for releasing the STM8CubeMX during my writeup.
  • Cosmic team for freeing up their C compiler.

 

I spent nearly three months straight putting together all these things and at present, I must say that I have great expectations from STM8 microcontrollers.

 

Code example files (use 7-Zip to extract ): Code Examples

PDF of this document: Starting STM8 Microcontrollers

 

Happy coding.

Author: Shawon M. Shahryiar

https://www.facebook.com/groups/microarena

https://www.facebook.com/MicroArena                                                  24.04.2017

Continue Reading ...

Related Posts

40 comments

  • Hi please someone tel me where can i find stm8s_delay.h and stm8s_delay.c
    Thanks

  • Hi , please someone tel me . Where can i find stm8s_delay.h and stm8s_delay.c
    Thanks ..

  • i am using stms003f3p6 controller..i have done as u told exactly but tim1 input capture is not displaying any value …according to your article it should display 10…i dont have any errors everything is fine…pulse on tim2 is ok ..it is coming as u said…but capture not working..please help me …thank you…

  • Hi Sir,
    i have just follwed your instruction provied in yout tutorial in main.c

    #include “stm8s.h”

    main()
    {
    while (1);
    }

    This error comes, can you please check why this error come, i am new and learn the controller programming.

    main.c:
    The command: “cxstm8 -i”d:\other datasheet\new folder\lib\stm8s_stdperiph_lib\libraries\stm8s_stdperiph_driver\inc” +debug -pxp -no -l +mods0 -pp -i”C:\Program Files (x86)\COSMIC\FSE_Compilers\CXSTM8\Hstm8″ -clDebug\ -coDebug\ main.c ” has failed, the returned value is: 1
    exit code=1.

    main.o – 2 error(s), 0 warning(s)

    Thanks,

  • Hi there, i solve the previous ADC problem. Anyway, u make some good tutorial on stm8 chips. Nice work

    • What was causing that issue? How did you solve it?

      • because i put all these

        ADC_DeInit(ADC1);
        ADC_SamplingTimeConfig(ADC1, ADC_Group_SlowChannels, ADC_SamplingTime_4Cycles);
        ADC_SchmittTriggerConfig(ADC1, ADC_Channel_7, DISABLE);
        ADC_ChannelCmd(ADC1, ADC_Channel_7, ENABLE);
        ADC_Init(ADC1, ADC_ConversionMode_Continuous, ADC_Resolution_12Bit, ADC_Prescaler_1);
        ADC_Cmd(ADC1, ENABLE);
        ADC_ExternalTrigConfig(ADC1, ADC_ExtEventSelection_None, ADC_ExtTRGSensitivity_All);

        in ‘while(1)’. It keeps looping.

        Once i put it before ‘while (1)’, it becomes normal.

  • Hi there, thanks for sharing. I learn alot thru your example, but I have encounter a problem with adc. Im currently using stm8l151k4t6. I wanna use the pin D7 as to read the voltage from my battery and monitor it (display on LCD). Can u please check where it goes wrong on my code?

    ADC_DeInit(ADC1);
    ADC_SamplingTimeConfig(ADC1, ADC_Group_SlowChannels, ADC_SamplingTime_4Cycles);
    ADC_SchmittTriggerConfig(ADC1, ADC_Channel_7, DISABLE);
    ADC_ChannelCmd(ADC1, ADC_Channel_7, ENABLE);
    ADC_Init(ADC1, ADC_ConversionMode_Continuous, ADC_Resolution_12Bit, ADC_Prescaler_1);
    ADC_Cmd(ADC1, ENABLE);
    ADC_ExternalTrigConfig(ADC1, ADC_ExtEventSelection_None, ADC_ExtTRGSensitivity_All);
    ADC_SoftwareStartConv(ADC1);
    while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
    battery = ADC_GetConversionValue(ADC1);

  • hi shawon great tutorials it would be nice too if you covered the coide compiler for arm it’s a good free compiler

  • hi shawon thanks for your response and support it turns out that my problem was a license issue I stupidly thought i use one license on multiple pc’s I used the license for a particular computer for mine that’s why I’ve been having compilation issues you have to use the license file stm8 sends you that is generated by your own pc else it won’t compile it’ll jus kip throwing you errors

  • It seems that you didn’t follow my instructions completely…. I found some issues:

    1. You have included header and source files of ADC2, CAN, UART2, etc which are unavailable in STM8S103…. Add only files for the peripherals available in your target MCU…. Exclude the rest….

    2. You have not included stm8s_delay header and source files…. Either disable it from stm8s_conf.h header file if you are not going to use it or add both the header and the source files for it in your project….

    Resolve these and you are good to go….

    Thanks….

  • I haven’t compiled any of the examples yet I just tried to compile the main source file just for test purpose n it just keeps showing me the same errors

  • yes the comic compiler was installed properly i even received a license file from my mail with the instructions to copy the license file to the license folder in the install directory if you fail to do this it will keep popping up for you to put in the license file. iincluded all the source files and header files as instructed i am working with the cheap stm8s103f i uncommented the stm8s103f in the stm8.h header file as you instructed and just to compile the main file it just throws me errors

  • no I just followed your instructions I just compiled it after setup i did not write any code yet just compiled the default main file

    • Need more details….

      1. The version of STVD and Cosmic
      2. Which example is giving this issue?
      3. The chip you are using if it other than STM8S003
      4. Have you tried to compile something else other than my examples?
      5. Are the paths to libraries and other folders properly added?
      6. Is the Cosmic compiler registered properly?

  • it throws this line The command: “cxstm8 -iinc +debug -pxp -no -l +mods0 -pp -i”C:\Program Files (x86)\COSMIC\FSE_Compilers\Hstm8″ -clDebug\ -coDebug\ main.c ” has failed, the returned value is: 1
    exit code=1.

  • hi I’m having compilation issues when I compile it just throws me error about comic compiler located in the program file ialso excluded unwanted header files but my problems still weren’t solved

  • hi thanks for the tutorials but i am having compiler error issues even when I remove unnecessary header files it say comic compiler error

  • I am new to STM8S003k3 Discovery board.I have done UART communication using STM standard peripheral library example. Now I want to define any other GPIO pin for UART communication. Is it possible to use d0 and d1 pin for the UART communication? If yes, then please let me know how?

  • thank you very much for this article!

  • Thank you for your time and effort.

  • Good job bro

  • Thank you for this exceptionally comprehensive article, much appreciated

    Rando!

Leave a Reply

Your email address will not be published. Required fields are marked *