Reputation: 45
I have a STM32F302CBT6 (running at 72MHz) project where i need to measure the frequencies of 4 signals, that are around 250kHz each. The signals are connected to TIM1 channels 1 - 4.
Now, understanding, that 250kHz is too fast (or is it?) to handle all those input capture interrupts simultaneously (because they might be synced or happen at the same time...) i figured to measure each channel one by one. I initialized all the channels at the start of my program and thought to enable the corresponding interrupts one by one after each channel is measured. Is this an appropriate idea or am i missing something?
The problem is that after serving the first interrupt for channel 1, the next ones never get served because although the interrupts are not enabled, status register has multiple other interrupts pending (CCxIF and CCXOF, and also CxIF) and also the overcapture flags set. I have tried to avoid this problem by reading all the capture values or setting the TIMx->SR = 0 but no help.
How would i go about measuring those signals and what would be the correct way to ensure each channel gets captured correctly?
I am quite lost on this and would appreciate some insight how this kind of processing is/should be done or if you can point out what i am doing wrong. Thanks.
My current relevant code is right below.
Here is the interrupt handler:
void TIM1_CC_IRQHandler(void) {
if (TIM_GetITStatus(IC_TIMER, IC_CH1) == SET) {
/* Clear TIM1 Capture compare interrupt pending bit */
TIM_ClearITPendingBit(IC_TIMER, IC_CH1);
//Read the capture value
raw_captures[capture_index] = TIM_GetCapture1(IC_TIMER);
capture_index++;
//Also read the others to avoid overcaptures
TIM_GetCapture2(IC_TIMER);
TIM_GetCapture3(IC_TIMER);
TIM_GetCapture4(IC_TIMER);
if(capture_index == 2) {
TIM_ITConfig(IC_TIMER, IC_CH1, DISABLE);
}
} else if (TIM_GetITStatus(IC_TIMER, IC_CH2 == SET)) {
TIM_ClearITPendingBit(IC_TIMER, IC_CH2);
//Read the capture value
raw_captures[capture_index] = TIM_GetCapture2(IC_TIMER);
capture_index++;
TIM_GetCapture1(IC_TIMER);
TIM_GetCapture3(IC_TIMER);
TIM_GetCapture4(IC_TIMER);
if(capture_index == 4) {
TIM_ITConfig(IC_TIMER, IC_CH2, DISABLE);
}
} else if (TIM_GetITStatus(IC_TIMER, TIM_IT_CC3 == SET)) {
//Read the capture value
raw_captures[capture_index] = TIM_GetCapture3(IC_TIMER);
capture_index++;
TIM_GetCapture1(IC_TIMER);
TIM_GetCapture2(IC_TIMER);
TIM_GetCapture4(IC_TIMER);
if(capture_index == 6) {
TIM_ITConfig(IC_TIMER, IC_CH3, DISABLE);
}
} else if (TIM_GetITStatus(IC_TIMER, TIM_IT_CC4 == SET)) {
TIM_ClearITPendingBit(IC_TIMER, TIM_IT_CC4);
//Read the capture value
raw_captures[capture_index] = TIM_GetCapture4(IC_TIMER);
capture_index++;
TIM_GetCapture2(IC_TIMER);
TIM_GetCapture3(IC_TIMER);
TIM_GetCapture1(IC_TIMER);
if(capture_index == 8) {
TIM_ITConfig(IC_TIMER, IC_CH4, DISABLE);
}
} else {
//LOG_WARNING("Unhandled interrupt in the TIM1_CC_IRQHandler"NL);
IC_TIMER->SR = 0; //Clear all other pending interrupts
}
}
Here is my initialization code, that largely based on the Std_Periph_Example:
void input_capture_setup(void) {
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
/* TIM clock enable */
RCC_APB2PeriphClockCmd(IC_CLK, ENABLE);
/* GPIOA clock enable */
RCC_AHBPeriphClockCmd(IC_PORT_CLK, ENABLE);
/* TIM1 channels 1 - 4 pins PA8 - PA11 configuration */
GPIO_InitStructure.GPIO_Pin = IC1_PIN | IC2_PIN | IC3_PIN | IC4_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Connect TIM pins to AF1 */
GPIO_PinAFConfig(IC_PORT, IC1_PINSRC, GPIO_AF_6);
GPIO_PinAFConfig(IC_PORT, IC2_PINSRC, GPIO_AF_6);
GPIO_PinAFConfig(IC_PORT, IC3_PINSRC, GPIO_AF_6);
GPIO_PinAFConfig(IC_PORT, IC4_PINSRC, GPIO_AF_11);
/* Enable the TIM global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* TIM configuration: Input Capture mode ---------------------
The external signals are connected to TIM1 CH1 - CH4 pin (PA8 - PA11)
The Rising edge is used as active edge,
The TIM1 CCR1 - CCR4 are used to compute the frequency value
------------------------------------------------------------ */
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV8;
TIM_ICInitStructure.TIM_ICFilter = 0x0;
//Initialize all channels one by one
TIM_ICInitStructure.TIM_Channel = IC1;
TIM_ICInit(IC_TIMER, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = IC2;
TIM_ICInit(IC_TIMER, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = IC3;
TIM_ICInit(IC_TIMER, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = IC4;
TIM_ICInit(IC_TIMER, &TIM_ICInitStructure);
/* TIM enable counter */
TIM_Cmd(IC_TIMER, ENABLE);
}
In the mainloop i have the following code to trigger the next channel after the previous one has been registered:
void node_handle_capture(void) {
if(!capture_enabled) {
TIM_ITConfig(IC_TIMER, IC_CH1, ENABLE);
capture_enabled = true;
}
else {
switch (capture_index) {
case 2:
LOG_DEBUG("CH1 captured"NL);
TIM_ITConfig(IC_TIMER, IC_CH2, ENABLE);
break;
case 4:
LOG_DEBUG("CH2 captured"NL);
TIM_ITConfig(IC_TIMER, IC_CH3, ENABLE);
break;
case 6:
LOG_DEBUG("CH3 captured"NL);
TIM_ITConfig(IC_TIMER, IC_CH4, ENABLE);
break;
case 8:
LOG_DEBUG("All channels captured"NL);
capture_index = 0;
break;
default:
break;
}
}
}
Upvotes: 2
Views: 4764
Reputation: 4455
Your primary issue here appears to be a typo in the parenthesis placement for channels 2-4 in the interrupt handler, with TIM_GetITStatus(IC_TIMER, IC_CHx == SET)
instead of TIM_GetITStatus(IC_TIMER, IC_CHx)
A further issue is that the interrupt handler accepts data on any enabled channels in any order, and therefore potentially skipping the channel disable steps due to the 2nd/4th/6th or 8th sample having been captured on another channel and then proceeding to induce a buffer-overflow.
My suggestion would be to rewrite the interrupt handler so as to accept captured data in any order. At 250 kHz on four channels at 72 MHz yield 72 cycles per capture, which ought to be doable with carefully written code.
Possible something along these, entirely untested, lines:
enum { SAMPLES_PER_CHANNEL = 2 };
struct Capture_Buffer_t {
volatile size_t index;
volatile uint32_t data[SAMPLES_PER_CHANNEL];
} capture_channels[4];
void TIM1_CC_IRQHandler(void) {
// Determine and acknowledge all latched channels still enabled
TIM_TypeDef *const timer = IC_TIMER;
uint_fast16_t enable = timer->DIER;
uint_fast16_t status = timer->SR & enable;
timer->SR = ~status;
// Process each flagged channel in order
do {
// Extract the first set status bit
uint_fast16_t flag = status & -status;
status &= ~flag;
// Read out the capture value and decode the status bit into a channel index
uint_fast32_t sample;
struct Capture_Buffer_t *buffer;
switch(flag) {
case IC_CH4:
sample = timer->CCR1;
buffer = &capture_channels[0];
break;
case IC_CH3:
sample = timer->CCR2;
buffer = &capture_channels[1];
break;
case IC_CH2:
sample = timer->CCR3;
buffer = &capture_channels[2];
break;
case IC_CH1:
default:
sample = timer->CCR4;
buffer = &capture_channels[3];
break;
}
// Store the sample into the appropriate buffer
size_t index = buffer->index;
buffer->data[index++] = sample;
buffer->index = index;
// Disable interrupts for the channel once its buffer has been filled
if(index == SAMPLES_PER_CHANNEL)
enable &= ~status;
// Continue until all flagged channels have been inspected
} while(status);
// Finally commit the new interrupt status
timer->DIER = enable;
}
...
// Have all channels completed yet?
if(!IC_TIMER->DIER) {
// Then process the data..
}
Alternatively you may try programming four DMA channels to automatically capture data from each of the source channels in parallel into the destination buffers without CPU intervention. This option offers to reliable low-latency timing if issues persist with lost capture events. However my experience is that these peripherals can be somewhat subtle to program and suffer from various restrictions, so going this route would not be my first choice.
Upvotes: 6