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

Reading 3 Encoders using PPI, Timers, and GPIOTE with NRFX

The encoders I am using generating about 500 pulses per second, and I need to read 3 of them at the same time.  I assume a good approach for that is to use the hardware timer to count the pulses and then using a separate timer to read the pulses.  I am new to PPI and Timers, and I wrote some code that should I think be capturing the counter value of one of the encoders, but when the event comes in my nRF is crashing.  Code attached below.

My approach would be to use 3 separate timer instances (1,2,3) for counting the pulses and another time (4) to periodically read the counter values.  A few questions I have:  

1) Could I just use a single timer instance to capture all 3 counts separately (maybe using a different channel?)

2) Could I use the app_timer to generate the software interrupt to read the hardware counters (for instance, app_timer would run every 250ms).

nrfx_err_t err_code;
  const size_t err_code_str_len = 60;
  char err_code_str[err_code_str_len] = { 0 };

  uint32_t encoder1_nrfPin = digitalPinToPinName(GC_PIN_MOTOR1_ENCODER_A); // grab the nRF pin/port

  nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
  timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
  timer_cfg.mode = NRF_TIMER_MODE_LOW_POWER_COUNTER;

  err_code = nrfx_timer_init(&_timer_counter_encoder1, &timer_cfg, NULL);
  if (err_code != NRFX_SUCCESS) {
    // todo: print string
    Serial.print("Error "); Serial.println(err_code);
    return;
  }

  nrfx_timer_extended_compare(&_timer_counter_encoder1, NRF_TIMER_CC_CHANNEL0, 3, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);

  if (!nrfx_gpiote_is_init()) {
    err_code = nrfx_gpiote_init(7); // lowest priority
    if (err_code != NRFX_SUCCESS) {
      // todo: print string
      Serial.print("Error "); Serial.println(err_code);
      return;
    }
  }

  nrfx_gpiote_in_config_t gpiote_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(false);
  gpiote_config.pull = NRF_GPIO_PIN_PULLUP;

  err_code = nrfx_gpiote_in_init(encoder1_nrfPin, &gpiote_config, GPIOTE_handler);
  if (err_code != NRFX_SUCCESS) {
    // todo: print string
    Serial.print("Error "); Serial.println(err_code);
    return;
  }

  err_code = nrfx_ppi_channel_alloc(&_ppi_encoder1_gpio_count);
  if (err_code != NRFX_SUCCESS) {
    // todo: print string
    Serial.print("Error "); Serial.println(err_code);
    return;
  }

  err_code = nrfx_ppi_channel_assign(_ppi_encoder1_gpio_count,
                                     nrfx_gpiote_in_event_addr_get(encoder1_nrfPin),
                                     nrfx_timer_task_address_get(&_timer_counter_encoder1, NRF_TIMER_TASK_COUNT));
  if (err_code != NRFX_SUCCESS) {
    // todo: print string
    Serial.print("Error "); Serial.println(err_code);
    return;
  }

  err_code = nrfx_ppi_channel_enable(_ppi_encoder1_gpio_count);
  if (err_code != NRFX_SUCCESS) {
    // todo: print string
    Serial.print("Error "); Serial.println(err_code);
    return;
  }

  nrfx_gpiote_in_event_enable(encoder1_nrfPin, true);

  Serial.println("encoders_begin(): <<");

  • Hi,

    1) Could I just use a single timer instance to capture all 3 counts separately (maybe using a different channel?)

    No, the TIMER peripherals have several capture/compare registers, but only a single counter. So you will need to use 3 timers for this. (See Figure 1: Block schematic for timer/counter for an overview).

    2) Could I use the app_timer to generate the software interrupt to read the hardware counters (for instance, app_timer would run every 250ms).

    Yes, the app_timer is perfect for such use cases.

  • Thanks for the reply!  So, is my workflow correct?


    1) Setup 3 separate timer instances used for counting

    2) Setup a single app_timer that fires every 250ms

    3) Enable GPIOTE for the pin receiving the pulse

    4) Use nrfx_ppi_channel_assign to perform a NRF_TIMER_TASK_COUNT for the GPIOTE.

    5) ??? How do I capture and reset the COUNTER in the app_timer interrupt?  I don't need PPI for that right?

  • Hi,

    1-4 is good.

    Regarding 5, you are right that you do not need PPI. you need to trigger the capture task from SW in order to copy the counter value to a capture register so that it can be read and read it. Both these tasks are integrated in the nrfx_timer_capture() function. Regarding clearing the counter you can do that by calling nrfx_timer_clear(). However, as this does not happen at the same time as you call nrfx_timer_capture() you risk counting one more between reading out the counter and resetting it (the counting happens in HW using PPI, so critical sections are no help here). That means that unless that is not a problem or you know this will not happen, then it would probably be better to never reset the counter but instead remember the previous counter value as well, so that you can calculate the difference between each time you capture and read the value. Note that then you also need to handle when the counter overflows and wraps around.

Related