cancel
Showing results for 
Search instead for 
Did you mean: 

Strange readings using multiple timer captures to read PWM duty cycle

cam
Associate
Posted on January 10, 2015 at 23:40 Hi there, I'm using an STM32F4 Discovery to build my own quadcopter flight controller. I have wired up a RC receiver's throttle channel to pin B6 on the discovery. My receiver sends a PWM signal over that wire which I am attempting to read the duty cycle and frequency of using multiple channels of a timer. I have set all of this up, and it works most of the time. Unfortunately I'm getting a problem where the duty cycle value I calculate skyrockets to Further inspection shows that the calculation is 100 because the TIM_GetCapture1(TIM4) and TIM_GetCapture2(TIM4) function calls are returning the same value. I haven't been able to understand why this might be happening. I am measuring a different PWM signal on another timer without issues. The other measurement uses the same NVIC_IRQChannelPreemptionPriority and NVIC_IRQChannelSubPriority. I don't know whether this is a problem or not. Is anyone able to help me understand what might be going on? Code is below. Cheers Cam


RCC_ClocksTypeDef RCC_Clocks;


void
InitialisePWMInputThrottle() {


/* Note that this timer is on AHB1, which means it runs at 80,000,000 Hz. */

/* AHB1 Peripherals run at half the HCLK Speed */

pwmInputTimer4.hclckDivisor = 2.0f;


/* Enable the timer clock */

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);



/* Initialise duty cycle and freq to zero */

pwmInputTimer4.dutyCycle = 0.0;

pwmInputTimer4.frequency = 0.0;

pwmInputTimer4.ic1value = 0;

pwmInputTimer4.ic2value = 0;


/* Work out the system / bus / timer clock speed */

RCC_GetClocksFreq(&RCC_Clocks);


/* Enable the clock to the GPIO Port */

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);


/* Turn on PB06, it will be connected to Timer 4, Channel 1.

* Timer Channel 2 will also be used, I believe this renders pin PB7 unusable.

*/

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;

GPIO_Init(GPIOB, &GPIO_InitStructure);


/* Connect TIM pin to alternate function */

GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4);


/* init the timer:

* It doesn't really matter what prescaler we use, because the duty cycle is calculated as a percentage.

* (as long as the prescalar ensures that the counter will not overflow)

* The maximum (16bit) period should never be reached, as we will reset the counter before we get there.

*/

TIM_TimeBaseInitTypeDef timerInitStructure;

timerInitStructure.TIM_Prescaler = 1000;

timerInitStructure.TIM_CounterMode = TIM_CounterMode_Up;

timerInitStructure.TIM_Period = 65535;

timerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;

timerInitStructure.TIM_RepetitionCounter = 0;

TIM_TimeBaseInit(TIM4, &timerInitStructure);


/* Enable the Timer counter */

TIM_Cmd(TIM4, ENABLE);


/* We're attempting to not have a prescalar, that is, not divide the incoming signal.

* I wonder this prescalar differs from the above prescalar.

* Channel 1 is configured to capture on the rising edge.

* */

TIM_ICInitTypeDef TIM_ICInitStructure1;

TIM_ICInitStructure1.TIM_Channel = TIM_Channel_1;

TIM_ICInitStructure1.TIM_ICPolarity = TIM_ICPolarity_Rising;

TIM_ICInitStructure1.TIM_ICSelection = TIM_ICSelection_DirectTI;

TIM_ICInitStructure1.TIM_ICPrescaler = 0;

TIM_ICInitStructure1.TIM_ICFilter = 0;

TIM_ICInit(TIM4, &TIM_ICInitStructure1);


/* Channel 2 is configured to capture on the falling edge. */

TIM_ICInitTypeDef TIM_ICInitStructure2;

TIM_ICInitStructure2.TIM_Channel = TIM_Channel_2;

TIM_ICInitStructure2.TIM_ICPolarity = TIM_ICPolarity_Falling;

TIM_ICInitStructure2.TIM_ICSelection = TIM_ICSelection_IndirectTI;

TIM_ICInitStructure2.TIM_ICPrescaler = 0;

TIM_ICInitStructure2.TIM_ICFilter = 0;

TIM_ICInit(TIM4, &TIM_ICInitStructure2);


/* Ensure that Channel two is set up as a slave, and that it resets the counters on a falling edge */

TIM_SelectInputTrigger(TIM4, TIM_TS_TI1FP1);

TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Reset);

TIM_SelectMasterSlaveMode(TIM4, TIM_MasterSlaveMode_Enable);


/* Enable the interrupt that gets fired when the timer counter hits the period */

TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);


/* Enable the Timer interrupts */

NVIC_InitTypeDef nvicStructure;

nvicStructure.NVIC_IRQChannel = TIM4_IRQn;

nvicStructure.NVIC_IRQChannelPreemptionPriority = 0;

nvicStructure.NVIC_IRQChannelSubPriority = 1;

nvicStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&nvicStructure);

}



void
TIM4_IRQHandler()

{

/* makes sure the interrupt status is not reset (and therefore SET?) */

if
(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) {

/* ensure that the interrupt doesn't get triggered again */

TIM_ClearITPendingBit(TIM4, TIM_IT_Update);


uint32_t IC1Value = TIM_GetCapture1(TIM4);

uint32_t IC2Value = TIM_GetCapture2(TIM4);


/* As a percentage */

float
updatedDutyCycle = (IC2Value * 0f) / IC1Value;


/* HCLK is the Advanced High Speed Bus (AHB) Clock Speed, which is a

factor of the System Clock (one, at the moment, hence is the same) */

float
updatedFrequency = (RCC_Clocks.HCLK_Frequency) / pwmInputTimer4.hclckDivisor / (IC1Value * 1000);


pwmInputTimer4.ic1value = IC1Value;

pwmInputTimer4.ic2value = IC2Value;

pwmInputTimer4.dutyCycle = updatedDutyCycle;

pwmInputTimer4.frequency = updatedFrequency;

}

}

#stm32f4 #input #discovery #pwm
4 REPLIES 4
Posted on January 11, 2015 at 02:10

The Update interrupt would seem the least appropriate one to use here, try CC1, and do some basic sanity checks that the servo signal is within expected ranges, and you don't divide by zero.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
cam
Associate
Posted on January 11, 2015 at 07:28

Cheers, I'll give it a go.

pbjacquemin
Associate
Posted on March 03, 2015 at 20:37

My thoughts on reading multiple RC receiver channels with PWM pulse-width signals in input capture mode - STM32F4

I believe you need more than one timer to read multiple RC receiver channels and not just use the internal timer channels (GetCapture1 for rising edge and GetCapture2 for falling edge detection) of TIM4.

RC receivers use shift registers to switch from one channel to the other so there is no delay time between from the end of one channel to the start of the next channel.

Thus the falling edge for receiever ch i occurs at the same time as the rising edge of receiver ch i+1.

STM32F4 has 16 GPIO pins so then 11 of those pins can be used for capturing pulse-width counts or times for each channel of a 10 channel receiver.

General purpose timers TIM2 to TIM5 can be used for rising and falling edge detection in input capture mode running at 1 MHz each using a single common RCC clock to all timers.

TIM3 & TIM4 are 16-bit and TIM2 & TIM5 are 32-bit.

A 16-bit timer provides 65536 / 1 MHz = 65 msec max which is larger than a 2.5 msec pulse-width for any RC receiver channel.

RC receiver pulse-width range: 0.5 to 2.5 msec with 1.5 msec representing RC servo center position.

I propose the following architecture:

GPIO pin 1 connects to receiver ch 1 and to TIM2 which is set to rising edge detection and measures the 20 msec (50 Hz) sample rate pulse rate frequency or refresh time.

Reset TIM2 and wait for next rising edge interrupt.

GPIO pin 2 connects to receiver ch 1 and to TIM3 which is set to falling edge detection and measures the pulse-width for ch 1 where the falling edge for ch1 is the rising edge for ch 2.

Reset TIM3 and wait for next falling edge interrupt.

GPIO pin 3 connects to receiver ch 2 and to TIM3 which is set to falling edge detection and measures the pulse-width for ch 2 from the difference of current falling edge to previous falling edge.

Reset TIM3 and wait for next falling edge interrupt.

GPIO pin 4 connects to receiver ch 3 and to TIM3 which is set to falling edge detection and measures the pulse-width for ch 3 from the difference of current falling edge to previous falling edge.

Reset TIM3 and wait for next falling edge interrupt.

and so on until n=10.

GPIO pin n+1 connects to receiver ch n and to TIMn which is set to falling edge detection and measures the pulse-width for ch n from the difference of current falling edge to previous falling edge.

Reset TIM3 and wait for next falling edge interrupt.

Loop back to top and sequence through a switch-case control as TIM3 multiplex switches the timer address to the various GPIO pins at each interrupt.

The number of GPIO pins can be reduced to three by connecting all the ''odd'' receiver channels to the inputs of multiple ''OR'' gates.

Similarly connect all the ''even'' receiver channels to a multiple input ''OR'' logic gate.

This even/odd connection of receiver channels configuration works because when one channel is active (or on) then all the other channels are inactive (or off).

Then the outputs of the two even/odd ''OR'' gates go to separate GPIO input pins and timers (such as TIM3 & TIM4) which are set to input capture mode detection on a falling edge.

Control to a software variable counter or incrementer in the interrupt function keeps track of receiver channel sequencing and measuring the respective pulse-widths.

I am told that a MUX also works to take the number of RC receiver input channels/signals and multiplex to a reduced number of input (GPIO) pins.

If this works for you then could you please post the code.

You can check out my LinkedIn profile at

https://ca.linkedin.com/pub/peter-jacquemin/5a/84a/b84

or contact me at pbjacquemin_at_gmail_dot_com.

Does anyone have code to drive up to 10 RC servos with PWM using a signal driver protocol such as SPI?

BTW I am not a savvy programmer like you so I find your code to be the most informative that I can find.

Posted on March 03, 2015 at 22:55

Does anyone have code to drive up to 10 RC servos with PWM using a signal driver protocol such as SPI?

I think that's PPM it's been discussed here before. As I recall I suggested modulating the PWM output via a circular DMA table, fixed pulse widths, different periods, that when totalled matched the periodicity of the PPI stream.

PWM Input is limited to one timer per servo channel, as Ch1 and Ch2 of the timer pair. Input Compare could definitely support at least 2 servo inputs. On STM32 timers supporting BothEdge mode 4 servo inputs per timer.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..