Starting STM8 Microcontrollers

STM8S003K3 Discovery

Analog-to-Digital Converter (ADC)

ADC is a very important peripheral in any modern-day microcontroller. It is used to read analogue outputs from sensors, sense voltage levels and so on. For example, we can use an ADC to read a LM35 temperature sensor. The voltage output from the sensor is proportional to temperature and so we can use the voltage info to back-calculate temperature. STM8S003K3 has four ADC channels associated with one ADC block. Other STM8 micros have more ADC channels and ADC blocks. The ADC of STM8 micros is just as same as the ADCs of other micros. There are a few additional features. Shown below is the block diagram of the STM8’s ADC peripheral:

Block Diagram

A few things must be noted before using the ADC. These enhance performance significantly:

  • Input impedance should be less than 10kΩ.
  • It is better to keep ADC clock within or less than 4MHz.
  • Schmitt triggers must be disabled.
  • Opamp-based input buffer and filter circuits are preferred if possible.
  • If the ADC has reference source pins, they should be connected to a precision reference source like LM336. It is recommended to use a good LDO regulator chip otherwise.
  • Unused ADC pins should not be configured or disabled. This will reduce power consumption.
  • Rather taking single samples, ADC readings should be sampled at fixed regular intervals and averaged to get rid of minute fluctuations in readings.
  • Right-justified data alignment should be used as it is most convenient to use.
  • PCB/wire tracks leading to ADC channels must be short to reduce interference effects.

 

Hardware Connection

ADC_CubeMX

Code Example

#include "STM8S.h"
 
 
void clock_setup(void);
void GPIO_setup(void);
void ADC1_setup(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
 
 
void main()
{
   unsigned int A0 = 0x0000;
                
   clock_setup();
   GPIO_setup();
   ADC1_setup();
                
   LCD_init();  
   LCD_clear_home(); 
                
   LCD_goto(0, 0);
   LCD_putstr("STM8 ADC");
   LCD_goto(0, 1);
   LCD_putstr("A0");
                
   while(TRUE)
   {
       ADC1_StartConversion();
       while(ADC1_GetFlagStatus(ADC1_FLAG_EOC) == FALSE);
                                
       A0 = ADC1_GetConversionValue();
       ADC1_ClearFlag(ADC1_FLAG_EOC);
                                
       lcd_print(4, 1, A0);
       delay_ms(90);
   };
}
 
 
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_HSIDIV2);
   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, ENABLE);
   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_0, GPIO_MODE_IN_FL_NO_IT);
                
   GPIO_DeInit(GPIOC);
                
   GPIO_DeInit(GPIOD);
   GPIO_Init(GPIOD, GPIO_PIN_3, GPIO_MODE_IN_PU_NO_IT);
}
 
 
void ADC1_setup(void)
{
   ADC1_DeInit();         
                
   ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, 
             ADC1_CHANNEL_0,
             ADC1_PRESSEL_FCPU_D18, 
             ADC1_EXTTRIG_GPIO, 
             DISABLE, 
             ADC1_ALIGN_RIGHT, 
             ADC1_SCHMITTTRIG_CHANNEL0, 
             DISABLE);
                                                                                  
   ADC1_Cmd(ENABLE);
}
 
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
   char chr = 0x00;
                                
   chr = ((value / 1000) + 0x30);     
   LCD_goto(x_pos, y_pos);
   LCD_putchar(chr); 
                
   chr = (((value / 100) % 10) + 0x30);
   LCD_goto((x_pos + 1), y_pos);
   LCD_putchar(chr); 
                
   chr = (((value / 10) % 10) + 0x30);
   LCD_goto((x_pos + 2), y_pos);
   LCD_putchar(chr); 
                
   chr = ((value % 10) + 0x30);
   LCD_goto((x_pos + 3), y_pos);
   LCD_putchar(chr); 
}

 

Explanation

First, we need to enable the peripheral clock of the ADC module:

CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);

Secondly, we have to set out ADC pin as a floating GPIO with no interrupt capability:

GPIO_Init(GPIOB, GPIO_PIN_0, GPIO_MODE_IN_FL_NO_IT);

ADC setup needs a few info regarding the desired ADC channel:

void ADC1_setup(void)
{
   ADC1_DeInit();         
                
   ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, 
             ADC1_CHANNEL_0,
             ADC1_PRESSEL_FCPU_D18, 
             ADC1_EXTTRIG_GPIO, 
             DISABLE, 
             ADC1_ALIGN_RIGHT, 
             ADC1_SCHMITTTRIG_CHANNEL0, 
             DISABLE);
                                                                                  
   ADC1_Cmd(ENABLE);
}

The second line of the above function states that we are going to use ADC channel 0 (PB0) with no Schmitt trigger. We are also not going to use external triggers from timer/GPIO modules. Since the master clock is running at 8MHz, the ADC prescaler divides the master/peripheral clock to get a sampling frequency of 444 kHz. We are also going to use continuous conversion mode because we want to continually read the ADC input and don’t want to measure it in certain intervals. Lastly right-justified data alignment is used as it is easy to read from such.

In the main loop, we need to start ADC conversion and wait for the conversion to complete. We are not using interrupt methods and so we need to poll if ADC conversion has completed. At the end of conversion, we can read the ADC and clear ADC End of Conversion (EOC) flag.

ADC1_StartConversion();
while(ADC1_GetFlagStatus(ADC1_FLAG_EOC) == FALSE);
                                
A0 = ADC1_GetConversionValue();
ADC1_ClearFlag(ADC1_FLAG_EOC);

The rest of the code is about printing the ADC data on a LCD.

 

Demo

ADC

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

 

Analog Watchdog (AWD)

The AWD is one additional feature that most microcontrollers in the market do not have. AWD is more like a comparator but with the exception that we can set both the upper and lower limits of this comparator as per our requirement unlike fixed levels in other micros. The region between the upper and lower limits is called guarded zone. Beyond the boundaries of the guarded zone, the AWD unit kicks off.

The AWD unit is very useful in situations where we need to monitor the output of a sensor for example and take quick actions. For instance, consider a temperature controller. We would want the controller to turn on a heater should temperature fall below some level and turn it off when temperature rises to some high value without complex calculations and constant monitoring in our application firmware. In other microcontrollers, we would have accomplished this simple task using conditional IF-ELSE statements.

AWD Block Diagram

AWD


Hardware Connection

AWD_CubeMX

Code Example

#include "STM8S.h"
 
 
void clock_setup(void);
void GPIO_setup(void);
void ADC1_setup(void);
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value);
 
 
void main()
{
     unsigned int a1 = 0x0000;
                
     clock_setup();
     GPIO_setup();
     ADC1_setup();
                
     LCD_init();  
     LCD_clear_home(); 
                
     LCD_goto(0, 0);
     LCD_putstr("STM8 AWD");
     LCD_goto(0, 1);
     LCD_putstr("A1");
                
     while (TRUE)
     {
          ADC1_ClearFlag(ADC1_FLAG_EOC);                       
                                
          ADC1_StartConversion();
          while(ADC1_GetFlagStatus(ADC1_FLAG_EOC) == 0);
                                
          a1 = ADC1_GetConversionValue();
          lcd_print(4, 1, a1);
                                
          if(ADC1_GetFlagStatus(ADC1_FLAG_AWD))
          {
               GPIO_WriteReverse(GPIOD, GPIO_PIN_0);
               ADC1_ClearFlag(ADC1_FLAG_AWD);
          }
          else
          {
               GPIO_WriteHigh(GPIOD, GPIO_PIN_0);
          }
                                
          delay_ms(90);
     };
}
 
 
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_HSIDIV2);
     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, ENABLE);
     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_1, GPIO_MODE_IN_FL_NO_IT);
                
     GPIO_DeInit(GPIOD);
     GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_PP_HIGH_FAST);
     GPIO_Init(GPIOD, GPIO_PIN_3, GPIO_MODE_IN_PU_NO_IT);
}
 
 
void ADC1_setup(void)
{
     ADC1_DeInit();         
     ADC1_Init(ADC1_CONVERSIONMODE_SINGLE, 
               ADC1_CHANNEL_1, 
               ADC1_PRESSEL_FCPU_D10, 
               ADC1_EXTTRIG_GPIO, 
               DISABLE, 
               ADC1_ALIGN_RIGHT, 
               ADC1_SCHMITTTRIG_CHANNEL1, 
               DISABLE);
                
     ADC1_AWDChannelConfig(ADC1_CHANNEL_1, ENABLE);
     ADC1_SetHighThreshold(600);
     ADC1_SetLowThreshold(200);
                
     ADC1_Cmd(ENABLE);
}
 
 
void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value)
{
     char chr = 0x00;
                
     chr = ((value / 1000) + 0x30);     
     LCD_goto(x_pos, y_pos);
     LCD_putchar(chr); 
                
     chr = (((value / 100) % 10) + 0x30);
     LCD_goto((x_pos + 1), y_pos);
     LCD_putchar(chr); 
                
     chr = (((value / 10) % 10) + 0x30);
     LCD_goto((x_pos + 2), y_pos);
     LCD_putchar(chr); 
                
     chr = ((value % 10) + 0x30);
     LCD_goto((x_pos + 3), y_pos);
     LCD_putchar(chr); 
}

 

Explanation

The code for the AWD example is just as the one demonstrated in the ADC example. However, this time the ADC channel is channel 1 (PB1). Setting up the AWD is simple. We just need to set the limits, specify which channel to be monitored and enable the AWD unit.

ADC1_AWDChannelConfig(ADC1_CHANNEL_1, ENABLE);
ADC1_SetHighThreshold(600);
ADC1_SetLowThreshold(200);

Here we have set 600 and 200 ADC counts as upper and lower limits respectively.

In the main function, we are simply polling AWD flag. If an AWD (beyond boundary zone) event on PB1 pin occurs the LED on PD0 starts flashing. If PB1 senses voltage between 200 and 600 ADC counts, the LED is turned off, indicating guarded zone.

if(ADC1_GetFlagStatus(ADC1_FLAG_AWD))
{
    GPIO_WriteReverse(GPIOD, GPIO_PIN_0);
    ADC1_ClearFlag(ADC1_FLAG_AWD);
}
else
{
    GPIO_WriteHigh(GPIOD, GPIO_PIN_0);
}

 

Demo

AWD (1) AWD (2)

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

Continue Reading ...

Related Posts

5 comments

Leave a Reply

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