Continuing the STM8 Expedition

STM8S105 Discovery

PWM Input Mode (PWMI)

With timer capture input we can capture the frequency of an incoming waveform. However, this is not the only stuff that we can do with capture hardware. We can use it to determine the waveform’s duty cycle too. Deducing duty cycle enable us to do lot of other stuffs like decoding IR remote signal patterns, measuring time to determine distance measured by an ultrasonic rangefinder sensor, measuring time slot lengths of a one-wire sensor like DHT11 and so on. A very clever feature of STM8 timers is the PWM input capture mode. With this mode we can time the pulse widths of an incoming signal.

Hardware Connection

PWMM_HW

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"


extern signed long duty_cycle;


void TIM1_CH1_CCP_IRQHandler(void)
{
       duty_cycle = TIM1_GetCapture2();
       TIM1_ClearITPendingBit(TIM1_IT_CC2);
}
....

 

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 bl_state;
unsigned char data_value;

signed long duty_cycle = 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 int i = 100;

       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)
       {
              if(GPIO_ReadInputPin(GPIOB, GPIO_PIN_7) == RESET)
              {
                     GPIO_WriteLow(GPIOD, GPIO_PIN_0);
                     delay_ms(100);
                     while(GPIO_ReadInputPin(GPIOB, GPIO_PIN_7) == RESET);
                     GPIO_WriteHigh(GPIOD, GPIO_PIN_0);

                     i += 100;
                     if(i  > 1000)
                     {
                           i = 100;
                     }

                     TIM2_SetCompare1(i);
                     TIM2_SetCompare2(i);
              }

              print_I(11, 1, duty_cycle);
              delay_ms(100);
       };
}


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, ENABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, ENABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, ENABLE);
       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_TIMER4, DISABLE);
}


void GPIO_setup(void)
{     
       GPIO_DeInit(GPIOB);
       GPIO_Init(GPIOB, GPIO_PIN_7, GPIO_MODE_IN_PU_NO_IT);

       GPIO_DeInit(GPIOC);
       GPIO_Init(GPIOC, 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_OUT_PP_HIGH_FAST);
       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_CCxCmd(TIM1_CHANNEL_2, ENABLE);

       TIM1_PWMIConfig(TIM1_CHANNEL_1,
                        TIM1_ICPOLARITY_RISING,
                        TIM1_ICSELECTION_DIRECTTI,
                        TIM1_ICPSC_DIV1,
                        0);

       TIM1_PWMIConfig(TIM1_CHANNEL_2,
                        TIM1_ICPOLARITY_FALLING,
                        TIM1_ICSELECTION_INDIRECTTI,
                        TIM1_ICPSC_DIV1,
                        0);      

       TIM1_SelectInputTrigger(TIM1_TS_TI1FP1);
       TIM1_SelectSlaveMode(TIM1_SLAVEMODE_RESET);

       TIM1_ClearFlag(TIM1_FLAG_CC2);
       TIM1_ITConfig(TIM1_IT_CC2, 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_OC2Init(TIM2_OCMODE_PWM2, TIM2_OUTPUTSTATE_ENABLE, 1000, TIM2_OCPOLARITY_LOW);
       TIM2_SetCompare1(100);
       TIM2_SetCompare2(100);
       TIM2_Cmd(ENABLE);
}


void print_I(unsigned char x_pos, unsigned char y_pos, signed long value)
{
       char tmp[6] = {0x20, 0x20, 0x20, 0x20, 0x20, '\0'} ;

       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); 
}

 

Explanation

The setup and code for this example is same as the timer input capture example. There are few difference though. Unlike the timer input capture example in which TIM2’s output waveform’s period was measured, here the duty cycle or pulse on time of TIM2’s output waveform is measured. Additionally the following lines of code are added for TIM1 capture hardware:

TIM1_PWMIConfig(TIM1_CHANNEL_1,
                TIM1_ICPOLARITY_RISING,
                 TIM1_ICSELECTION_DIRECTTI,
                TIM1_ICPSC_DIV1,
                0);

TIM1_PWMIConfig(TIM1_CHANNEL_2,
                TIM1_ICPOLARITY_FALLING,
                TIM1_ICSELECTION_INDIRECTTI,
                TIM1_ICPSC_DIV1,
                0);       

TIM1_SelectInputTrigger(TIM1_TS_TI1FP1);
TIM1_SelectSlaveMode(TIM1_SLAVEMODE_RESET);

The above code can alternatively written as follows:

TIM1_ICInit(TIM1_CHANNEL_1,
            TIM1_ICPOLARITY_RISING,
            TIM1_ICSELECTION_DIRECTTI,
            TIM1_ICPSC_DIV1,
            0);

TIM1_ICInit(TIM1_CHANNEL_2,
            TIM1_ICPOLARITY_FALLING,
            TIM1_ICSELECTION_INDIRECTTI,
            TIM1_ICPSC_DIV1,
            0);    

TIM1_SelectInputTrigger(TIM1_TS_TI1FP1);
TIM1_SelectSlaveMode(TIM1_SLAVEMODE_RESET);  

The main theme of PWM input measurement is to capture both edges of a pulse and time the difference between these captures. Physically just one GPIO is needed for this purpose but internally two timer channels are assigned for the task. One is set to capture rising edge while the other falling edge. The first channel which is set to capture rising edge, triggers or starts the timer while the second resets it. For the first channel, we do not need to set any interrupt as it will start the timer automatically when it has sensed a rising edge. However, for the second channel, capture interrupt is needed to notify completion of a duty cycle capture.

TIM1_ClearFlag(TIM1_FLAG_CC2);
TIM1_ITConfig(TIM1_IT_CC2, ENABLE);

Owing to this, channel 2’s capture data is extracted to compute duty cycle.

duty_cycle = TIM1_GetCapture2();
TIM1_ClearITPendingBit(TIM1_IT_CC2);

 

Demo

PWMI (2)

Related Posts

8 comments

  • Sankalp Rai Gambhir

    Thanks Shawon, your blogs are indeed very helpful. Keep Growing.

  • why are you sending the received data back through TX pin?

  • 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 *