Measure waveform duration with EFM32 / TD1208

tim10Measuring a pulse duration in a pulse train was my last week headache with the TD1208 and the not so well documented EFM32. Basically, my objective was to activate a timer on a pulse train to measure the duration of the pulse. This duration was measured about 100uS and occurred at 1 Hz.

My choice was to rise an interrupt on each signal front (raising and falling) and measure the duration between the two first interrupt call. Some reset function ensure we are measuring the low state duration (100uS) not the high state duration (1s). This part will not be described here.  The purpose of this post is to describe the main hole I fallen in.

Configure GPIO correctly

To be able to raise an interrupt, the GPIO have to be configured, In my case I need to have all the falling and rising signal event. I was looking in the Interrupt to obtain this information, unfortunately I did not (I assume I could read the GPIO value by-the-way). But as my signal is pulled-up I’m sure that the first call is a falling one.

EFM32 GPIO architecture
EFM32 GPIO architecture

The EFM32 GPIO have a  a Glitch suppression filter. This tooks me a long time before discovering it was the reason why my timing were wrong 🙁 . The component is ensuring you have no up/down signal captured by the pin in less than 20ms. Is means if you have a pulse train under 20ms you will only see the first signal the other will be canceled. This system is really good for low frequency signal but  problem in my case. This is controlled over a flag with no #define value and difficult to capture from the sample code you have.

So the GPIO configuration was :

// mode gpioModeInput : we have an external pull-up
// the 0 after the mode is indicating the Glich suppression filter is disabled
GPIO_PinModeSet(INT_PORT, INT_BIT, gpioModeInput, 0);
TD_GPIO_SetCallback(
   ((INT_MASK & TD_GPIO_ODD_MASK)?TD_GPIO_USER_ODD:TD_GPIO_USER_EVEN),
     INTProcess,
     INT_MASK );
// The interrupt is configured to be executed on both front edge
GPIO_IntConfig(INT_PORT, INT_BIT, true, true, true);       
NVIC_ClearPendingIRQ((INT_MASK & TD_GPIO_ODD_MASK)?GPIO_ODD_IRQn:GPIO_EVEN_IRQn);
NVIC_EnableIRQ((INT_MASK & TD_GPIO_ODD_MASK)?GPIO_ODD_IRQn:GPIO_EVEN_IRQn);

Create the interrupt handler

The interrupt handler is simple to create and starts the timer measure the timing.

void INTProcess(uint32_t mask) {
    // The way to detect your first front depends on your application
    if ( firstCallDetected ) { 
         my_TIMER_Start();                   
         inSignal = true;
    } else if ( inSignal ) {
        my_TIMER_Stop();
        pulseDuration = my_TIMER_getMicros();
        inSignal = false;
    }
}

On the first call when the signal is falling down a high frequency timer is fired. This one is stopped on the next interrupt call and its value is read and store as the pulse duration. Here in micro-seconds.

Manage the timer

For measuring the duration we need a high speed timer. On of the issues is to manage it with the low power environment. TD framework is managing low consumption by activating the EM2 mode. This mode is disabling the high speed clock and wake the CPU up only on a 32KHz wakeup signal. To manage high speed timer, the sleep mode must be switch to EM1 during the time capture.

EFM32 Timer clock architecture
EFM32 Timer clock architecture

Then the timer must be initialized to match the expected timing. I really had difficulties with the prescaler value of 1 for the timer. The HFPERFCLK is a 14MHz signal.

EFM32 Timers are 16b timers (be careful : the counter are uint32_t values but they overflow at 16b … this is surprising)   You can cascade timers to get 32b counter by changing the source as indicated in the picture above.

The timer can fire an interrupt on overflow, underflow to a predefined value… you have many way to configure it and it is really useful.

void my_TIMER_Start()
{
  myMs = 0;

  TD_RTC_SetPowerMode(TD_RTC_EM1);

  CMU_ClockEnable(cmuClock_GPIO, true);
  CMU_ClockEnable(cmuClock_TIMER0, true);

  // Set Top Value for 1ms <=> 14,000,000 / 128
  TIMER_TopSet(TIMER0, 109);

  // Configure timer
  static TIMER_Init_TypeDef mytimerInit = {
  .enable     = true,
  .debugRun   = false,
  .prescale   = timerPrescale128,
  .clkSel     = timerClkSelHFPerClk,
  .fallAction = timerInputActionNone,
  .riseAction = timerInputActionNone,
  .mode       = timerModeUp,
  .dmaClrAct  = false,
  .quadModeX4 = false,
  .oneShot    = false,
  .sync       = false,
  };
  TIMER_Init(TIMER0, &mytimerInit);

  // Enable TIMER interrupt vector in NVIC
  NVIC_EnableIRQ(TIMER0_IRQn);
  // Enable overflow interrupt
  TIMER_IntEnable(TIMER0, TIMER_IF_OF);
  // Enable timer
  TIMER_Enable(TIMER0, true);
}

The interrupt handler is counting the Ms on each call.

void TIMER0_IRQHandler(void)
{
  // Clear flag for TIMER0 overflow interrupt
  TIMER_IntClear(TIMER0, TIMER_IF_OF);
  myMs++;
}

A function returns the time in micro-seconds based on this timer. TIMER0->CNT is the timer internal counter value.

uint32_t my_TIMER_getMicros() {
  return myMs*1000+(9*TIMER0->CNT);
}

When the timer needs to be stopped, the EM2 power saving mode is set back.

void my_TIMER_Stop(void)
{
  TIMER_Enable(TIMER0, false);   // Disable

  NVIC_DisableIRQ(TIMER0_IRQn);
  TIMER_IntDisable(TIMER0, TIMER_IF_OF);

  TD_RTC_SetPowerMode(TD_RTC_EM2);
}

 

This entry was posted in Hardware and tagged , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.