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

Modifying wireless timer sync using RTC

Hello,

I'm modifying the code of Wireless timer synchronization among nRF5 devices blog to use RTC instead of two TIMERs. I started with the code on Git for SDK 16. Writing for nRF52832.

Timers on multiple nRF5 devices are synced by one device (master) sending sync packets containing captured timer values to other devices (nodes). (TIMER3 is free running, TIMER2 counts TIMER3 overflows.) A receiving node calculates an appropriate offset between the master timers and its own and compensates accordingly when its timer value is requested.

With the current code, PPI is used to capture the TIMER3 and TIMER2 values just before the master sends a sync packet to other nodes, and (for a node) upon receipt of a sync packet from the master. (See ppi_radio_rx_configure() and ppi_radio_tx_configure().) The event NRF_RADIO->EVENTS_READY is used for when the master sends a sync packet. For receiving sync packets, the event NRF_RADIO->EVENTS_ADDRESS is used.

Since PPI cannot be used to capture the COUNTER value of an RTC, for when the master sends a sync packet, I see that the timeslot_begin_handler() routine directly sets the rtc_val of the packet to be sent (p_pkt) to the current rtc value (m_params.rtc->COUNTER) as soon as NRF_RADIO->EVENTS_READY becomes 1. That makes sense.

What about when a node receives a sync packet? Would that best be done in RADIO_IRQHandler()? Something like:

if (NRF_RADIO->EVENTS_ADDRESS != 0)
{
    NRF_RADIO->EVENTS_ADDRESS = 0;
    (void)NRF_RADIO->EVENTS_ADDRESS;
    
    if (m_radio_state == RADIO_STATE_RX)
    {
        // Save current RTC value into global.
        m_local_rtc_at_time_of_sync_packet_arrival = m_params.rtc->COUNTER;
    }
}

And then in sync_timer_offset_compensate():

master_rtc_timer  = p_pkt->rtc_val;
master_rtc_timer += TX_CHAIN_DELAY;
local_rtc_timer = m_local_rtc_at_time_of_sync_packet_arrival;

m_rtc_counter_offset = master_rtc_timer - local_rtc_timer;

if (m_rtc_counter_offset == 0)
{
    // Already in sync
    return false;
}

return true;

Unrelated to the above, another question: in sync_timer_button_init() of main.c we see:

ts_params.high_freq_timer[0] = NRF_TIMER3;
ts_params.high_freq_timer[1] = NRF_TIMER2;
ts_params.rtc             = NRF_RTC1;
ts_params.egu             = NRF_EGU3;
ts_params.egu_irq_type    = SWI3_EGU3_IRQn;
ts_params.ppi_chg         = 0;
ts_params.ppi_chns[0]     = 1;
ts_params.ppi_chns[1]     = 2;
ts_params.ppi_chns[2]     = 3;
ts_params.ppi_chns[3]     = 4;
ts_params.rf_chn          = 125; /* For testing purposes */
memcpy(ts_params.rf_addr, rf_address, sizeof(rf_address));

And then in update_radio_parameters() of time_sync.c:

NRF_RADIO->FREQUENCY = m_params.rf_chn;

In preparing this code for production, any considerations relating to this?

Many thanks,

Tim

Parents
  • I shared your code changes to the actual author of the code in the link you provided in Wireless timer synchronization among nRF5 devices blog

    As you figured out PPI cannot be used to capture RTC counter, and since RTC resolution is relatively low as compared to TIMER, so it should still be ok to have a bit of software latency.

    Capturing the time offset from the master on each of the nodes makes a lot of sense. You could make operations on the m_rtc_counter_offset atomic variable inside the RAM to make the timestamp calculation a bit faster. Other than that, the code looks good to Audun (the author of the blog) and it also looks good to me.

  • Thank you Susheel. Another thought ... for capturing local RTC counter when receiving sync packets, I could use PPI and an available timer to time from NRF_RADIO->EVENTS_ADDRESS to RADIO_IRQHandler() invocation (event NRF_RADIO->EVENTS_ADDRESS, task NRF_TIMER4->TASKS_START). I'm looking for millisecond accuracy, so I imagine this time will be negligible.

    Could I ask you or Audun to respond to my other question about NRF_RADIO->FREQUENCY? The blog post code has ts_params.rf_chn = 125; /* For testing purposes */. This makes me wonder what must be considered when designing code for production. And in general, any other considerations when integrating this great blog post into a production product. I'm relatively new to direct manipulation of NRF_RADIO via time slots.

    Many thanks,

    Tim

  • I would suggest you to change the freqency in the mid sweep bandwidth. That is in betweel 40-60 instead of 125. Even though the hardware seems to still support the value for testing purpose, 125 is not officially supported. So for your production code use the value below 100.

  • Thanks Susheel. Given no other considerations for frequency, I'll just use 50.

    Would also appreciate some guidance regarding radio address configuration. Our product is a mobile timing system where one "system" comprises 2, 3 or 4 peripheral devices. These will use this method of syncing clocks. I have it working well using RTC instead of two TIMERs giving the required accuracy.

    The code needs to also allow for multiple "systems" used in close proximity and need to be sure one system of devices does not interfere with another system of devices when listening for or sending sync packets. I presume that the devices of each system can simply use a different radio address. Each peripheral device has a unique serial number (uint16) and I presume I can construct a unique radio address based on the serial number of one of the peripherals in a system of devices. Reasonable?

    Would appreciate any guidance about this. How best to construct a unique address and configure the appropriate address registers (NRF_RADIO->PREFIX0 and NRF_RADIO->BASE0).

    Many thanks,

    Tim

  • Hi Tim, 

    Tim said:
    The code needs to also allow for multiple "systems" used in close proximity and need to be sure one system of devices does not interfere with another system of devices when listening for or sending sync packets.

    I would say that using a unique serial number per "System" as part of the device address and if you can use the DAB/DAP feature to whitelist the incoming packets. would reduce noise from neighbouring systems considerably at the hardware RX level itself. 

    I haven't experimented with this, but I am quite confident that this can be easily achievable considering that you have a quite deep understanding of the hardware already.

  • Thank you Susheel. I did try changing:

    uint8_t rf_address[5] = {0xDE, 0xAD, 0xBE, 0xEF, 0x19};
    // Radio address config
    NRF_RADIO->PREFIX0 = rf_address[0];
    NRF_RADIO->BASE0 = (rf_address[1] << 24 | rf_address[2] << 16 | rf_address[3] << 8 | rf_address[4]);

    to:

    uint8_t rf_address[5] = {0xDE, 0xAD, 0xBE, 0xEF, 0x10};
    // Radio address config
    NRF_RADIO->PREFIX0 = rf_address[0];
    NRF_RADIO->BASE0 = (rf_address[1] << 24 | rf_address[2] << 16 | rf_address[3] << 8 | rf_address[4]);

    for a second system (note change in last byte of rf_address) and found, as expected, that packets transmitted from a peripheral using the first address array are not received by peripherals listening with second address array.

    What you say suggests to me that white listing and DAB/DAP can be helpful to reduce noise and interference between two systems of devices. I'm actually new to NRF_RADIO and am not experienced with this topic. If you know of any helpful learning resources for radio addressing, white listing, DAB/DAP, I would be grateful.

    Many thanks,

    Tim

  • I am afraid there is no step by step guide on this. 

    But I found that the zephyr source code can be used to understand how to configure the registers.

    Please take a look at RADIO configuration in Zephyr BLE code. Even though this is desgined for BLE specific part, the register configuration shows how radio configuration can be adapted for other protocols  as well. Please pay attention to radio_filter_configure

Reply
  • I am afraid there is no step by step guide on this. 

    But I found that the zephyr source code can be used to understand how to configure the registers.

    Please take a look at RADIO configuration in Zephyr BLE code. Even though this is desgined for BLE specific part, the register configuration shows how radio configuration can be adapted for other protocols  as well. Please pay attention to radio_filter_configure

Children
Related