1

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;
    }
}
}
Martin1
  • 35
  • 1
  • 6
  • I don't quite follow your problem. Can't you just infer the channel by switch on `capture_index` directly instead of bothering with the status flags? Though if you're concerned about performance then a more straightforward approach may be to program four DMA channels to run the capture for your. – doynax Apr 16 '17 at 18:54
  • Well, which flag do you mean? I am deciding on the capture_index on which channel to capture. The problem is, the node_handle_capture function never reaches the case 4: case 6: ... because right after the first channel gets captured sucessfully, the interrupt handler is fed up with pending interrupts and always ends up in the "else" part of the handler where i am trying to clear the Status Register – Martin1 Apr 16 '17 at 18:59
  • Well, the interrupt status flag tests via `TIM_GetITStatus`. You no longer care about the first channel's status once it has completed its captures, and you implicitly know which channel fired the interrupt based upon the `capture_index` anyway. So can't you do away with the status test and switch on the index directly and only processing what you are presently interested in? – doynax Apr 16 '17 at 19:02
  • Oh, sorry. I see the confusion. You've got a parenthesis issue with the latter ` == SET` tests on `TIM_GetITStatus`. You may still end up with previous channels overloading the system and masking out latter ones though. – doynax Apr 16 '17 at 19:04
  • Okay, thanks, that is indeed a good observation, the problem still remains, that in addition to `TIM_IT_CCx` something else is also triggering the interrupts, and the `CCxIF` flags are set even when the interrupts are not enabled, so say i enable the CC3 interrupt and the flag is already set the interrupt fires before the actual "next" capture happens. – Martin1 Apr 16 '17 at 19:06
  • Well, yes, that seems to be the issue, the previously set flags are overloading the interrupt handler, even though the interrupts for these flags are not enabled. EDIT: Also wouldn't this overloading problem also persist if DMA is used? – Martin1 Apr 16 '17 at 19:07
  • Well, that too may case starvation but your main issue looks to be typos with `TIM_GetITStatus(IC_TIMER, IC_CHx == SET)` instead or `TIM_GetITStatus(IC_TIMER, IC_CHx) == SET` on the latter channels. – doynax Apr 16 '17 at 19:08
  • Thank you very much! It was the parenthesis typo. I had been looking at this code for the past few hours now, didn't notice it. What a stupid mistake. – Martin1 Apr 16 '17 at 19:14
  • _Shrug_, it happens. Anyway, with DMA you would try to trigger four channels in parallel directly on the individual captures instead of sequencing, grabbing two samples each before completing, assuming that the hardware is sufficiently flexible. Still, I wouldn't bother unless timing and you need to catch the present pulses and can't afford to wait for the next ones on missing captures. – doynax Apr 16 '17 at 19:18
  • Yes, well i will think about it depending on my timing needs. If you post your advice about using DMA and the parenthesis typo you pointed out i will accept it as an answer. Thanks again. – Martin1 Apr 16 '17 at 19:21

1 Answers1

6

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.

doynax
  • 4,285
  • 3
  • 23
  • 19