This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

PPI Auto Reset Timer In Counter Mode

  // motion frequency capture
  NRF_PPI->CH[CFG_PPI_TASKER_CHANNEL_MOTION_CAPTURE].EEP = CFG_TASKER_PIF_TIMER->EVENTS_COMPARE[1]; // wire timer compare
  NRF_PPI->CH[CFG_PPI_TASKER_CHANNEL_MOTION_CAPTURE].TEP = CFG_MOTION_PIF_TIMER->TASKS_CAPTURE[1]; // wire ppi to timer capture
  NRF_PPI->CH[CFG_PPI_TASKER_CHANNEL_MOTION_CLEAR].EEP = CFG_TASKER_PIF_TIMER->EVENTS_COMPARE[1]; // wire timer compare
  NRF_PPI->CH[CFG_PPI_TASKER_CHANNEL_MOTION_CLEAR].TEP = CFG_MOTION_PIF_TIMER->TASKS_CLEAR; // wire ppi to reset count
  NRF_PPI->CHENSET = 1 << CFG_PPI_TASKER_CHANNEL_MOTION_CAPTURE_POS;  // enable ppi channel
  NRF_PPI->CHENSET = 1 << CFG_PPI_TASKER_CHANNEL_MOTION_CLEAR_POS;  // enable ppi channel



The only way I can see to do it is by two separate PPI calls. So Timer1 "tasker" is firing both the capture and clear... but will they execute sequentially?

  • Hello,

    If you capture and clear at the same time, then you have a chance of capturing 0. PPI tasks are done by HW directly, so it will not wait for the capture to finish before clearing.

    I am not sure exactly what your timer is doing, but often when this question comes up it usually turns out that the clearing of the timer is not strictly necessary. And why do you need to capture when you know the value of the timer? (It clears when it reaches the set value, right?)

    Best regards,

    Edvin

  • No, I'm capturing pulses for frequency measurement.  The code below may explain better.

    It sounds like PPI or FORK can't guarantee the logic, so I'll have to have code to subtract the previous value plus manage a wrap around condition. Best approach?

      // tasker fires every 500ms
      
      // motion frequency capture
      NRF_PPI->CH[CFG_PPI_TASKER_CHANNEL_MOTION_CAPTURE].EEP = CFG_TASKER_PIF_TIMER->EVENTS_COMPARE[1]; // wire timer compare
      NRF_PPI->CH[CFG_PPI_TASKER_CHANNEL_MOTION_CAPTURE].TEP = CFG_MOTION_PIF_TIMER->TASKS_CAPTURE[1]; // wire ppi to timer capture
      NRF_PPI->FORK[CFG_PPI_TASKER_CHANNEL_MOTION_CAPTURE].TEP = CFG_MOTION_PIF_TIMER->TASKS_CLEAR; // wire ppi to reset count
      NRF_PPI->CHENSET = 1 << CFG_PPI_TASKER_CHANNEL_MOTION_CAPTURE_POS;  // enable ppi channel
    
      // motion pin
      NRF_GPIOTE->CONFIG[CFG_GPIOTE_MOTION_CHANNEL] = (CFG_PIN_MOTION_ZEROCROSS << GPIOTE_CONFIG_PSEL_Pos) | (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
      NRF_PPI->CH[CFG_PPI_MOTION_CHANNEL_COUNT].EEP = NRF_GPIOTE->EVENTS_IN[CFG_GPIOTE_MOTION_CHANNEL]; // wire gpio event to ppi
      NRF_PPI->CH[CFG_PPI_MOTION_CHANNEL_COUNT].TEP = CFG_MOTION_PIF_TIMER->TASKS_COUNT; // wire ppi to timer count
      NRF_PPI->CHENSET = 1 << CFG_PPI_MOTION_CHANNEL_COUNT_POS;  // enable ppi channel




  • I see. Yes. For pulse length measurement I would suggest to subtract the previous measurement. If you set the timer in 32bit mode (instead of default 24 bit), you don't really need to worry about wraparound at all.

    uint32_t a = 0x0000 0001
    uint32_t b = 0xFFFF FFFF
    
    uint32_t c = a-b; // c = 0x0000 0002

    In C programming this operation will automatically wrap around, so as long as the pulse is not longer than one full clock cycle (which is unlikely), you don't need to worry about the wrap around. 

    So you can remove the fork: CFG_MOTION_PIF_TIMER->TASKS_CLEAR;

    I am a bit confused about your counter mode timer, and how that works in your application, but it may be working. I am just used to seeing a timer being used directly, and capturing the CC of a timer mode timer on a hitolo/lotohi. However, when you read out the capture value, compare it to the previous capture compare value, and this should give the difference. Something like this (pseudo):

    volatile uint32_t prev_capture = 0x00;
    volatile uint32_t curr_capture = 0x00;
    
    void timer_interrupt_handler(void)
    {
        curr_capture = TIMERX->CC[1];
        uint32_t diff_capture = curr_capture - prev_capture;
        NRF_LOG_INFO("diff_capture %08x", diff_capture);
        prev_capture = curr_capture;
    }

    Best regards,

    Edvin

  • I should be careful with the wording - I'm not doing pulse measurement as the source is inductive/noisy and quite slow (10-200Hz). Instead I have one timer running at 2Hz and another timer counting the amount of pulses resulting in simple division to calculate the frequency. The 2Hz timer triggers the capture on the count timer.

    But your approach will work - set the timer in manual count mode to 32 bit and minus the previous value. I'll go with this!

    A related question, what's the difference between TIMER_MODE_LowPowerCounterMode? It says the other (non low power?) mode is depreciated but no details are given.

    CFG_MOTION_PIF_TIMER->MODE = TIMER_MODE_MODE_LowPowerCounter << TIMER_MODE_MODE_Pos;


    So my code now with the 32 bit setting is now...

    inline void MOTION_Init (void) {
    
      // motion count config; 32 bit, manual clock
      CFG_MOTION_PIF_TIMER->MODE = TIMER_MODE_MODE_LowPowerCounter << TIMER_MODE_MODE_Pos;
      CFG_MOTION_PIF_TIMER->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
    
      // motion frequency capture
      NRF_PPI->CH[CFG_PPI_TASKER_CHANNEL_MOTION_CAPTURE].EEP = CFG_TASKER_PIF_TIMER->EVENTS_COMPARE[1]; // wire tasker timer compare
      NRF_PPI->CH[CFG_PPI_TASKER_CHANNEL_MOTION_CAPTURE].TEP = CFG_MOTION_PIF_TIMER->TASKS_CAPTURE[1]; // wire ppi to timer capture
      NRF_PPI->CHENSET = 1 << CFG_PPI_TASKER_CHANNEL_MOTION_CAPTURE_POS;  // enable ppi channel
    
      // motion pin trigger motion timer count
      NRF_GPIOTE->CONFIG[CFG_GPIOTE_MOTION_CHANNEL] = (CFG_PIN_MOTION_ZEROCROSS << GPIOTE_CONFIG_PSEL_Pos) | (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
      NRF_PPI->CH[CFG_PPI_MOTION_CHANNEL_COUNT].EEP = NRF_GPIOTE->EVENTS_IN[CFG_GPIOTE_MOTION_CHANNEL]; // wire gpio event to ppi
      NRF_PPI->CH[CFG_PPI_MOTION_CHANNEL_COUNT].TEP = CFG_MOTION_PIF_TIMER->TASKS_COUNT; // wire ppi to timer count
      NRF_PPI->CHENSET = 1 << CFG_PPI_MOTION_CHANNEL_COUNT_POS;  // enable ppi channel
    }

    Note the default timer mode is 16 bit not 24 bit ;)

    The final issue I may have is the 2Hz tasker timer fires the capture of the counter so that it's ready to be used in the interrupt, but I'm seeing potential for a race between the capture completing and the tasker interrupt executing. I haven't tested it yet but might need a second compare on the tasker slightly after the first to give the capture time to complete.

    Perhaps in NRF54 there'd be an EVENT_CAPTURE to complement the EVENT_COMPARE. Task/event sequencing would be a nice to have as well.

  • Here's my 'final' approach (hopefully).

    The tasker timer uses 6 compare points at 0.25s each with a short on 6.

    Compare points 0,2,4 trigger the pulse capture. It has 0.25s to complete in hardware. Plenty I'm sure.

    The tasker timer fires an interrupt on all compares. The compare point is stored, a %2 check determines if the frequency calculation should run (I also use the tasker for other matters).

      compareAt++;
    
      // 0.25s tasks
      // TODO: led patterns
    
      // 0.5s tasks
      if (compareAt % 2) {
        // TODO: TASKS HERE
        if (compareAt == 6) {
          compareAt = 0;
        }
      }

    So the capture is always used 0.25s after it's been fired and the interrupt has 0.25s to fire and complete.

      status.motion.frequency = CFG_MOTION_PIF_TIMER->CC[0] - lastPulseCount; // (frequency is Hz/2)
      lastPulseCount = CFG_MOTION_PIF_TIMER->CC[0];


    I've probably reinvented what the softdevice is already doing with RTC0 or RTC1 but I prefer separation of concerns and I have the timer spare.

Related