Continuing the STM8 Expedition

STM8S105 Discovery

PWM Duty Capture using Software

It is possible to measure pulse widths with only one timer input capture pin and avoid PWMI mode. However, some software tricks must be applied to reliably do the measurements.

Hardware Connection

pwm

Code Example

 

stm8s_it.h (top part only)

#ifndef __STM8S_IT_H
#define __STM8S_IT_H


@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"


static bool state = FALSE;

extern unsigned long duty_cycle;
extern unsigned long start_time;
extern unsigned long end_time;


void TIM1_CH1_CCP_IRQHandler(void)
{
       if(TIM1_GetFlagStatus(TIM1_FLAG_CC1))
       {
          if(state == FALSE)
          {
              start_time = TIM1_GetCapture1();
              TIM1_ICInit(TIM1_CHANNEL_1, TIM1_ICPOLARITY_FALLING,                     TIM1_ICSELECTION_DIRECTTI, 1, 1);
          }
          else
          {
              end_time = TIM1_GetCapture1();
              TIM1_ICInit(TIM1_CHANNEL_1, TIM1_ICPOLARITY_RISING, TIM1_ICSELECTION_DIRECTTI, 1, 1);

              duty_cycle = (end_time - start_time);
          }

          state = ~state;
       }

       TIM1_ClearITPendingBit(TIM1_IT_CC1);
       TIM1_ClearFlag(TIM1_FLAG_CC1);
}
....

 

stm8_interrupt_vector.c (shortened)

#include "STM8S.h"
#include "stm8s_it.h"


typedef void @far (*interrupt_handler_t)(void);

struct interrupt_vector {
       unsigned char interrupt_instruction;
       interrupt_handler_t interrupt_handler;
};

extern void _stext();     /* startup routine */

struct interrupt_vector const _vectab[] = {
       {0x82, (interrupt_handler_t)_stext}, /* reset */
       {0x82, NonHandledInterrupt}, /* trap  */
       {0x82, NonHandledInterrupt}, /* irq0  */
       ....
       {0x82, (interrupt_handler_t)TIM1_CH1_CCP_IRQHandler}, /* irq12 */
       ....
       {0x82, NonHandledInterrupt}, /* irq29 */
};

 

main.c

#include "STM8S.h"
#include "lcd.h"


unsigned char data_value;

unsigned long duty_cycle = 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 print_I(unsigned char x_pos, unsigned char y_pos, signed 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("PWM Capture Test");
       LCD_goto(0, 1);
       LCD_putstr("T/ms:");
       delay_ms(10);

       while(TRUE)
       {
              print_I(13, 1, duty_cycle);
              delay_ms(900);
       };
}


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, ENABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, ENABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, 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_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_CCxCmd(TIM1_CHANNEL_1, ENABLE);
       TIM1_ICInit(TIM1_CHANNEL_1, TIM1_ICPOLARITY_RISING, TIM1_ICSELECTION_DIRECTTI, 1, 1);
       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_PWM2, TIM2_OUTPUTSTATE_ENABLE, 1000, TIM2_OCPOLARITY_LOW);
       TIM2_SetCompare1(1125);
       TIM2_Cmd(ENABLE);
}


void print_I(unsigned char x_pos, unsigned char y_pos, signed long value)
{
    unsigned char ch = 0x00;

    if(value < 0)
    {
        LCD_goto(x_pos, y_pos);
              LCD_putchar(0x2D);
        value = -value;
    }
    else
    {
        LCD_goto(x_pos, y_pos);
              LCD_putchar(0x20);
    }

    if(value > 9999)
    {
       ch = ((value / 10000) + 0x30);
       LCD_goto((x_pos + 1), y_pos);
       LCD_putchar(ch);

ch = (((value % 10000)/ 1000) + 0x30);
       LCD_goto((x_pos + 2), y_pos);
              LCD_putchar(ch);

ch = (((value % 1000) / 100) + 0x30);
LCD_goto((x_pos + 3), y_pos);
       LCD_putchar(ch);

ch = (((value % 100) / 10) + 0x30);
LCD_goto((x_pos + 4), y_pos);
       LCD_putchar(ch);

ch = ((value % 10) + 0x30);
LCD_goto((x_pos + 5), y_pos);
LCD_putchar(ch);
    }

    else if((value > 999) && (value <= 9999))
    {
ch = (((value % 10000)/ 1000) + 0x30);
LCD_goto((x_pos + 1), y_pos);
LCD_putchar(ch);

ch = (((value % 1000) / 100) + 0x30);
LCD_goto((x_pos + 2), y_pos);
LCD_putchar(ch);

ch = (((value % 100) / 10) + 0x30);
       LCD_goto((x_pos + 3), y_pos);
       LCD_putchar(ch);

ch = ((value % 10) + 0x30);
       LCD_goto((x_pos + 4), y_pos);
       LCD_putchar(ch);

LCD_goto((x_pos + 5), y_pos);
       LCD_putchar(0x20);
    }
    else if((value > 99) && (value <= 999))
    {
ch = (((value % 1000) / 100) + 0x30);
       LCD_goto((x_pos + 1), y_pos);
       LCD_putchar(ch);

ch = (((value % 100) / 10) + 0x30);
       LCD_goto((x_pos + 2), y_pos);
       LCD_putchar(ch);

ch = ((value % 10) + 0x30);
       LCD_goto((x_pos + 3), y_pos);
       LCD_putchar(ch);

LCD_goto((x_pos + 4), y_pos);
       LCD_putchar(0x20);

LCD_goto((x_pos + 5), y_pos);
       LCD_putchar(0x20);
    }
    else if((value > 9) && (value <= 99))
    {
ch = (((value % 100) / 10) + 0x30);
       LCD_goto((x_pos + 1), y_pos);
       LCD_putchar(ch);

ch = ((value % 10) + 0x30);
       LCD_goto((x_pos + 2), y_pos);
       LCD_putchar(ch);

LCD_goto((x_pos + 3), y_pos);
       LCD_putchar(0x20);

       LCD_goto((x_pos + 4), y_pos);
       LCD_putchar(0x20);

       LCD_goto((x_pos + 5), y_pos);
       LCD_putchar(0x20);
    }
    else
    {
ch = ((value % 10) + 0x30);
       LCD_goto((x_pos + 1), y_pos);
       LCD_putchar(ch);

       LCD_goto((x_pos + 2), y_pos);
       LCD_putchar(0x20);

       LCD_goto((x_pos + 3), y_pos);
       LCD_putchar(0x20);

       LCD_goto((x_pos + 4), y_pos);
       LCD_putchar(0x20);

       LCD_goto((x_pos + 5), y_pos);
       LCD_putchar(0x20);
    }
}

 

Explanation

Everything here is same as the previous capture example. Since we have to sense both edges for capturing a pulse’s width, we will have to do something more in the interrupt routine to take care of this issue.

We assume and select that the first edge that the timer input will capture will be the rising edge of the waveform. Given this fact, the timer is set as follows:

TIM1_ICInit(TIM1_CHANNEL_1, TIM1_ICPOLARITY_RISING, TIM1_ICSELECTION_DIRECTTI, 1, 1);

So when the capture interrupt is triggered, we know for sure that it was caused by the rising edge of the pulse captured.

In the input capture interrupt, the very first thing to do is to check the capture-compare flag and then we check last setup state of the timer. Since rising edge capture was initially configured, the first capture count is actually the start time of the pulse. Upon detecting this we must quickly reconfigure the timer capture hardware to sense falling edge.

Now the timer’s capture interrupt will be triggered on the falling edge and we have to measure the timer’s count on this next interrupt. This second count is the end time of the pulse. The difference between these counts represents the pulse width.

void TIM1_CH1_CCP_IRQHandler(void)
{
       if(TIM1_GetFlagStatus(TIM1_FLAG_CC1))
       {
          if(state == FALSE)
          {
              start_time = TIM1_GetCapture1();
             TIM1_ICInit(TIM1_CHANNEL_1, TIM1_ICPOLARITY_FALLING,     TIM1_ICSELECTION_DIRECTTI, 1, 1);
          }
          else
          {
              end_time = TIM1_GetCapture1();
              TIM1_ICInit(TIM1_CHANNEL_1, TIM1_ICPOLARITY_RISING, TIM1_ICSELECTION_DIRECTTI, 1, 1);

              duty_cycle = (end_time - start_time);
          }

          state = ~state;
       }

       TIM1_ClearITPendingBit(TIM1_IT_CC1);
       TIM1_ClearFlag(TIM1_FLAG_CC1);
}

Every time a TIM1 capture-compare interrupt occurs, the timer’s sense edge is altered along with a variable called “state”. This variable helps in remembering which edge it detected last time and which count was captured. Finally, before leaving the interrupt sub-routine pending and flag bits are reset.

Demo

PWMI (1)

Related Posts

2 comments

  • Hi SHAWON SHAHRYIAR

    I am wondering how to get a Max31855 to talk to a STM8s via SPI.

    JP

    • What’s to wonder about it? It is a simple SPI communication and SPI for STM8 is no different from the SPI of other MCUs…. The following lines are taken from the device’s datasheet and the write up there states how to communicate with it:

      “Drive CS low to output the first bit on the SO pin. A complete serial-interface read of the cold-junction compensated thermocouple temperature requires 14 clock cycles. Thirty-two clock cycles are required to read both the thermocouple and reference junction temperatures (Table 2 and Table 3.) The first bit, D31, is the thermocouple temperature sign bit, and is presented to the SO pin within tDV of the falling edge of CS. Bits D[30:18] contain the converted temperature in the order of MSB to LSB, and are presented to the SO pin within tD0 of the falling edge of SCK. Bit D16 is normally low and goes high when the thermocouple input is open or shorted to GND or VCC. The reference junction temperature data begins with D15. CS can be taken high at any point while clocking out con-version data. If T+ and T- are unconnected, the thermocouple temperature sign bit (D31) is 0, and the remainder of the thermocouple temperature value (D[30:18]) is 1.”

Leave a Reply

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