Hi,
I'm porting code from Simblee/Arduino to Laird's BL651/Nordic SDK (nRF52810, SDK 15.3, S112). (Simblee discontinued.) For accurate timing of a GPIO pin, I use TIMER1, TIMER2, GPIOTE and PPI. GPIOTE watches an input pin for transition LOTOHI and HITOLO. PPI is used to start TIMER2 (timer mode) on input pin transition LOTOHI, then stop TIMER2 on transition HITOLO. PPI is also used to increment (COUNT) TIMER1 (counter mode) each time TIMER2 overflows. GPIOTE triggers interrupt call on input pin transition LOTOHI where code captures TIMER1 and TIMER2 values and calculates time between LOTOHI and HITOLO transitions of input pin and sends to BLE Central.
Here's the current code that works well on Simblee:
// Find three free PPI channels. uint32_t i; // Initialize Programmable Peripheral Interconnect // Find first free PPI Channel in the range 0-7. Note the channels 8-15 occupied by SoftDevice for (i = 0; i < 8; i++) { if (!(NRF_PPI->CHEN & (1 << i))) { chan_0 = i; // Find second free PPI Channel in the range 0-7. for (i = (chan_0 + 1); i < 8; i++) { if (!(NRF_PPI->CHEN & (1 << i))) { chan_1 = i; // Find third free PPI Channel in the range 0-7. for (i = (chan_1 + 1); i < 8; i++) { if (!(NRF_PPI->CHEN & (1 << i))) { chan_2 = i; break; } } break; } } break; } } // The approach here is to implement TIMER1 and TIMER2 as a real time counter, start the counter // when input pin goes LoToHi and stop counter when input pin goes HiToLo. TIMER1 counts the number of times // TIMER2 overflows, so we multiply the overflow count by 65535 and add it to the TIMER2 value to get // the total number of ticks between beam break and restore. Then calculate milliseconds // according to prescaler. // // The incrementing of TIMER1 on overflow of TIMER2, as well as the starting and stoping of TIMER2 on // input pin LoToHi and HiToLo all happen via GPIOTE, TIMER1, TIMER2 and PPI, so it is accurate and not // subject to CPU usage. GPIOTE calls interrupt routine to notify of LoToHi input pin transition. // Configure TIMER1 as counter NRF_TIMER1->TASKS_STOP = 1; // Stop timer NRF_TIMER1->MODE = TIMER_MODE_MODE_Counter; // Set to counter mode NRF_TIMER1->TASKS_CLEAR = 1; // Clear the counter NRF_TIMER1->BITMODE = TIMER_BITMODE_BITMODE_16Bit; // Set to 16 bit NRF_TIMER1->TASKS_START = 1; // Start counter // Configure TIMER2 as timer NRF_TIMER2->TASKS_STOP = 1; // Stop timer NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer; // Set to timer mode NRF_TIMER2->PRESCALER = 9; // 9 is highest prescaler; gives us enough accuracy and minimizes overflows NRF_TIMER2->TASKS_CLEAR = 1; // Clear the timer NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_16Bit; // Set to 16 bit NRF_TIMER2->CC[0] = 0; // Configure GPIOTE channel 0 as event that occurs when input pin changes from digital // low to hi. NRF_GPIOTE->CONFIG[0] = (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos) | (inputPin << GPIOTE_CONFIG_PSEL_Pos) | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos); // Configure GPIOTE channel 1 as event that occurs when input pin changes from digital // hi to low. NRF_GPIOTE->CONFIG[1] = (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) | (inputPin << GPIOTE_CONFIG_PSEL_Pos) | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos); // Interrupt only on input pin LoToHi transition. NRF_GPIOTE->INTENCLR = GPIOTE_INTENCLR_IN0_Msk | GPIOTE_INTENCLR_IN2_Msk | GPIOTE_INTENCLR_IN3_Msk; NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN1_Msk; // Clear all events. NRF_GPIOTE->EVENTS_IN[0] = 0; NRF_GPIOTE->EVENTS_IN[1] = 0; NRF_GPIOTE->EVENTS_IN[2] = 0; NRF_GPIOTE->EVENTS_IN[3] = 0; // Attach interrupt handler. dynamic_attachInterrupt(GPIOTE_IRQn, beamEventHandler); // Enable interrupts. NVIC_EnableIRQ(GPIOTE_IRQn); // Configure PPI channel 0 to increment TIMER1 when TIMER2 overflows. simblee_ppi_channel_assign(chan_0, &NRF_TIMER2->EVENTS_COMPARE[0], &NRF_TIMER1->TASKS_COUNT); // Configure PPI channel 1 to start TIMER2 when GPIOTE channel 0 event occurs. simblee_ppi_channel_assign(chan_1, &NRF_GPIOTE->EVENTS_IN[0], &NRF_TIMER2->TASKS_START); // Configure PPI channel 2 to stop TIMER2 when GPIOTE channel 1 event occurs. simblee_ppi_channel_assign(chan_2, &NRF_GPIOTE->EVENTS_IN[1], &NRF_TIMER2->TASKS_STOP);
As you can see, I access all peripherals directly rather than using TIMER, GPIOTE and PPI drivers. I read somewhere that it's better to use the peripheral drivers rather than direct manipulation (this is understandable). So I did so in porting to Nordic SDK (15.3 with SoftDevice S112. However, I've discovered that you cannot use the GPIOTE driver (routines like nrfx_gpiote_init(), etc.) to watch for more than one event per pin. I cannot use it to watch for both LoToHi and HiToLo. I could use toggle (NRF_GPIOTE_POLARITY_TOGGLE polarity), but would need to check transition in the interrupt routine to know which transition triggered the interrupt.
My questions are:
1) Is it critical to use the peripheral drivers rather than accessing peripherals directly?
2) Given the above limitation in GPIOTE, is it okay to manipulate GPIOTE directly?
The code that is working (using nRF52 dev kit) is below, but manipulates GPIOTE directly:
// GPIOTE interrupt routine void GPIOTE_IRQHandler(void) { // Clear the events. NRF_GPIOTE->EVENTS_IN[0] = 0; NRF_GPIOTE->EVENTS_IN[1] = 0; // Capture current timer1 value into capture register 1 nrfx_timer_capture(&m_timer1,1); uint32_t numberOfOverflows = nrfx_timer_capture_get(&m_timer1,1); printf("numberOfOverflows %u\n",numberOfOverflows); nrfx_timer_capture(&m_timer2,1); uint32_t timerValue = nrfx_timer_capture_get(&m_timer2,1); printf("timerValue %u\n",timerValue); // Factor in number of times TIMER2 overflowed. unsigned long durationInTicksToSend = timerValue + (numberOfOverflows * 65535); // Calculate duration in milliseconds * 100 to preserve accuracy. double durationMillisecondsTimes100Double = durationInTicksToSend * 3.2; // based on prescaler of 9 double durationMillisecondsDouble = durationMillisecondsTimes100Double / 100; unsigned long durationMilliseconds = durationMillisecondsDouble; // round to milliseconds // Clear the timers to get ready for next events nrfx_timer_clear(&m_timer1); nrfx_timer_clear(&m_timer2); printf("durationMilliseconds %u\n",durationMilliseconds); } // TIMER interrupt void timer_event_handler(nrf_timer_event_t event_type, void* p_context) { // never called. } // Initialize timers void timers_init() { // Configure TIMER1 as counter nrfx_timer_config_t timer1_cfg = NRFX_TIMER_DEFAULT_CONFIG; timer1_cfg.mode = NRF_TIMER_MODE_COUNTER; timer1_cfg.bit_width = NRF_TIMER_BIT_WIDTH_16; nrfx_timer_init(&m_timer1, &timer1_cfg, timer_event_handler); nrfx_timer_clear(&m_timer1); nrfx_timer_enable(&m_timer1); // Configure TIMER2 as timer nrfx_timer_config_t timer2_cfg = NRFX_TIMER_DEFAULT_CONFIG; timer2_cfg.mode = NRF_TIMER_MODE_TIMER; timer2_cfg.bit_width = NRF_TIMER_BIT_WIDTH_16; timer2_cfg.frequency = NRF_TIMER_FREQ_31250Hz; nrfx_timer_init(&m_timer2, &timer2_cfg, timer_event_handler); nrfx_timer_clear(&m_timer2); nrfx_timer_compare(&m_timer2,NRF_TIMER_CC_CHANNEL0,0,false); } // Initialize GPIOTE void gpiote_init(void) { // Configure GPIOTE channel 0 as event that occurs when input pin changes from digital // hi to low. NRF_GPIOTE->CONFIG[0] = (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) | (inputPin << GPIOTE_CONFIG_PSEL_Pos) | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos); // Configure GPIOTE channel 1 as event that occurs when input pin changes from digital // low to hi. NRF_GPIOTE->CONFIG[1] = (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos) | (inputPin << GPIOTE_CONFIG_PSEL_Pos) | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos); // Interrupt only on on LoToHi transition. NRF_GPIOTE->INTENCLR = GPIOTE_INTENCLR_IN0_Msk | GPIOTE_INTENCLR_IN2_Msk | GPIOTE_INTENCLR_IN3_Msk; NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN1_Msk; // Clear all events. NRF_GPIOTE->EVENTS_IN[0] = 0; NRF_GPIOTE->EVENTS_IN[1] = 0; NRF_GPIOTE->EVENTS_IN[2] = 0; NRF_GPIOTE->EVENTS_IN[3] = 0; // Enable interrupts. NVIC_EnableIRQ(GPIOTE_IRQn); } // Initialize PPI void ppi_init(void) { nrfx_err_t err_code = NRF_SUCCESS; // PPI channels uint8_t chan_0 = 255; uint8_t chan_1 = 255; uint8_t chan_2 = 255; err_code = nrfx_ppi_channel_alloc(&chan_0); APP_ERROR_CHECK(err_code); err_code = nrfx_ppi_channel_alloc(&chan_1); APP_ERROR_CHECK(err_code); err_code = nrfx_ppi_channel_alloc(&chan_2); APP_ERROR_CHECK(err_code); // Configure PPI channel 0 to increment TIMER1 when TIMER2 overflows. err_code = nrfx_ppi_channel_assign(chan_0, nrfx_timer_event_address_get(&m_timer2,NRF_TIMER_EVENT_COMPARE0), nrfx_timer_task_address_get(&m_timer1,NRF_TIMER_TASK_COUNT)); APP_ERROR_CHECK(err_code); // Configure PPI channel 1 to start TIMER2 when GPIOTE channel 0 event occurs. err_code = nrfx_ppi_channel_assign(chan_1, &NRF_GPIOTE->EVENTS_IN[0], nrfx_timer_task_address_get(&m_timer2,NRF_TIMER_TASK_START)); APP_ERROR_CHECK(err_code); // Configure PPI channel 2 to stop TIMER2 when GPIOTE channel 1 event occurs. err_code = nrfx_ppi_channel_assign(chan_2, &NRF_GPIOTE->EVENTS_IN[1], nrfx_timer_task_address_get(&m_timer2,NRF_TIMER_TASK_STOP)); APP_ERROR_CHECK(err_code); err_code = nrfx_ppi_channel_enable(chan_0); APP_ERROR_CHECK(err_code); err_code = nrfx_ppi_channel_enable(chan_1); APP_ERROR_CHECK(err_code); err_code = nrfx_ppi_channel_enable(chan_2); APP_ERROR_CHECK(err_code); }
Thanks for any help!
Tim