cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F103 - Trying to measure Duty Cycle on 20Khz signal

dirk23
Associate II
Posted on February 21, 2014 at 23:16

Hello,

I am trying to use a STM32F103 (8Mhz external clock) to get the duty cycle of a 20 Khz PWM signal. I am using Timer2 and trying to look at the rising edge using channel 1 and pointing channel 2 at channel 1 input to capture the falling edge. To help me visualize what is going on, I am toggling one output for a rising edge and another for a falling. What I see on the oscilliscope compared to the incoming signal (using a Pulse generator around 1kHz at 50% DC) is that the rising edge output comes about 40 uS after the input rising edge and the falling edge output toggles about 6 uS after the input's falling edge. I think I have all sampling turned off on these channels. Why the large delay? Needless to say, with these kinds of delays getting to the interrupt, I won't be able to handle the 20kHz pwm coming in. Do I have a setting wrong that is delaying my interrupt? 40uS is a long time considering my program isn't really doing anything else. Any help would be greatly appreciated. Code is attached below Thanks!

//################### Includes ######################
#include ''stm32f10x.h''
#include ''stm32f10x_tim.h''
#include ''stm32f10x_rcc.h''
#include ''stm32f10x_gpio.h''
#include ''stm32f10x_exti.h''
#include ''misc.h''
#include ''stdio.h''
//################ Private Variables ##################
#define output1 GPIO_Pin_6
#define output2 GPIO_Pin_7
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; 
//Initialize variables to set up Timers
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
int16_t reg_h=0; 
// High Register for T2
uint16_t reg_l=0; 
// Low Regiser for T2 (For Input Capture)
uint16_t T2_DC =0; 
// Timer Two Duty Cycle
unsigned 
char
update_per=0; 
// Flag to update period, set by T2 Input Capture
unsigned 
char
T2_Overrun=0; 
// Timer 2 overrun count
unsigned 
char
temp=0,temp1=0;
//########################## Private Function Prototypes ##########################
void
RCC_Configuration(
void
);
void
GPIO_Configuration(
void
);
void
NVIC_Configuration(
void
);
void
Timer_Configuration(
void
);
void
TIM2_IRQHandler(
void
);
//########################## Main Program #########################################
int
main(
void
)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_0);
/* System Clocks Config */
RCC_Configuration();
/* GPIO Config */
GPIO_Configuration();
/* Interrupt Config */
NVIC_Configuration();
/* Timer Config */
Timer_Configuration();
while
(1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_0);
if
(update_per)
{
T2_DC = (reg_h*100/(reg_l+reg_h));
reg_h=0;
reg_l=0; 
// Clear variables
update_per=0; 
// Reset update period flag
}
}
}
//#################### Timer 2 Interrupt Handler ################
void
TIM2_IRQHandler(
void
)
{
if
(TIM_GetITStatus(TIM2, TIM_IT_CC1) == SET) 
// If compare capture has occured
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
reg_l = TIM_GetCapture1(TIM2); 
// Get timer counts for Period
// Get timer counts during wave high
GPIOA->ODR ^= output2;
update_per=1; 
// Set Flag to update period
}
if
(TIM_GetITStatus(TIM2, TIM_IT_CC2) == SET) 
// If compare capture has occured
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
reg_h= TIM_GetCapture2(TIM2);
GPIOA->ODR ^= output1;
}
if
(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); 
// Counter overflow, reset interrupt
if
(T2_Overrun<127){T2_Overrun++;}
}
}
//##################### RCC Configuration Routine ####################
void
RCC_Configuration(
void
)
{
/* TIM2 clock enable*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB1PeriphResetCmd(RCC_APB1Periph_TIM2, DISABLE);
/* GPIOA GPIOB and GPIOC clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_AFIO, ENABLE);
}
//################### GPIO Configuration Routine ########################
void
GPIO_Configuration(
void
)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* GPIOB Config */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//################## Interrupt Configuration Routine #########################
void
NVIC_Configuration(
void
)
{
NVIC_InitTypeDef NVIC_InitStructure;
//enable tim2 irq
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void
Timer_Configuration(
void
){
//********************************************************************
//******** Set Timer 2 Interrupt (Input Pulse Capture) ***********
//********************************************************************
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Prescaler = 2;
TIM_TimeBaseStructure.TIM_Period = 60000;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
/* Channel 2 Config*/
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
TIM_ITConfig(TIM2, TIM_IT_CC2, ENABLE);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
/*More*/
TIM_SelectInputTrigger(TIM2,TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE);
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void
assert_failed(uint8_t* file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf(''Wrong parameters value: file %s on line %d\r\n'', file, line) */
while
(1)
{}
}
#endif

#interrupt #stm32f103 #timer
5 REPLIES 5
Posted on February 22, 2014 at 00:29

You're interrupt is occurring at 40 KHz, consider if you could use PWM Input mode which would allow you to read period and duty in a single interrupt.

STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\TIM\PWM_Input

I'm not sure where the saturation threshold for the F1 @ 72 MHz is, but it's going to be impaired running from flash with wait states, and there are no accelerator/cache function like the F2/F4 to hide that. Consider having the vector table, interrupt code running from RAM.
Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Posted on February 22, 2014 at 00:40

You sure you can't use a Prescaler of zero (DIV1) instead of DIV3? That's going to limit the granularity.

Don't think you need this :

        TIM_SelectInputTrigger(TIM2,TIM_TS_TI1FP1);

        TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);

You're clock the part internally at 72 MHz or 8 MHz?

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
dirk23
Associate II
Posted on February 25, 2014 at 16:25

Thank you for the response.  I will look into the link that you mentioned above.  Yes, I can take the clock down to DIV1.  I have and 8 Mhz external clock hooked up and have system clock running 72Mhz.

dirk23
Associate II
Posted on February 26, 2014 at 16:40

Tried the sample program mentioned above.  I like how it works better, only one interrupt.  But I am still seeing the problem of 44 uS before the interrupt is fired.  My frequency is correct, but the 44 uS delay is skewing the Duty Cycle result.  I can allow for that and correct it in the math, but that still restricts the frequency to where it stops recognizing pulse because of the delay.  I'll look into the RAM suggestion.

dirk23
Associate II
Posted on February 27, 2014 at 00:19

I did a little more probing with the oscilliscope and saw that my optocoupler is introducing quite a bit of delay in my circuit, not the STM32.  I reduced the ohms on my pullup resistor on the microcontroller side of the circuit and quickly got the delay down to 15 uS by having it pull up to 3.3v harder.  Probably won't be able to get up to 20Khz, but much improved.  Thanks for the help, I will use the example to calc my duty cycle.

Turns out (taking the signal straight into the uC, no optocoupler) that the interrupt delay was only on the order of about 3.5-4.5 uS.