Hi,
In my current project I'm implementing an ANT Shared Channel topology, where in each channel period the master polls a slave with an acknowledge message and receives the slave's data through a reverse direction acknowledge message or Advance Burst transfer. For data synchronization purposes, all slaves RTCs must be synchronized with the master's RTC, so every N channel periods the master sends an ANT Time Sync packet to all slaves. Each Time Sync packet contains the transmission time offset, calculated by the ANT stack, and the master's RTC value. With this information, and the time stamp of the received message, each slave can compute its offset relative to master.
While doing some testing, I noticed a really weird behavior: "If in the channel period previous to a Time Sync packet the master sends an acknowledge message and receives a reverse direction acknowledge message or (Advance) Burst transfer, then the Time Sync packet will be invalidated and the stack won't compute the transmission time offset".
To exemplify this, I wrote a code that will be attached at the end. In each received Time Sync packet the slave prints (over UART) the page number, the user data (master's RTC value) and the transmission time offset. Valid page numbers are set to 0x01 and invalid page numbers are set to 0xFF.
// Slave's output
Page number: 255
User data: 186, 248, 248
Offset: 186, 248
Page number: 255
User data: 63, 106, 249
Offset: 63, 106
Page number: 255
User data: 196, 219, 249
Offset: 196, 219
As you can see, received Time Sync packets are invalidated and the transmission time offset is equal to the master's RTC value, that is, the transmission time offset isn't being computed by the ANT stack. Now, if I disable the master's Adv. Burst feature, I get the following:
// Slave's output. Adv. burst disabled in master.
Page number: 255
User data: 42, 190, 17
Offset: 173, 0
Page number: 255
User data: 175, 47, 18
Offset: 173, 0
Page number: 255
User data: 52, 161, 18
Offset: 173, 0
The Time Sync packets are still getting invalidated, but the transmission time offset is being computed. On the other hand, if the Adv. Burst feature is enabled and the master sends broadcast messages instead of acknowledge messages, then the Time Sync packets are valid and the transmission time offset is computed. Something similar happens if the slave sends broadcast messages instead of ack/burst.
I tested that this behavior is independent of
- Channel configuration (channel period, channel RF, shared channel or independent channel).
- Time Sync invalidation enabled/disabled.
- Number of messages between Time Sync packets.
A possible fix is to disable the master's Adv. Burst feature when a Time Sync packet is being sent, and for now is working well. Nevertheless, I still have some doubts:
- Under what circumstances a Time Sync packet is invalidated by the stack?
- Under what circumstances the stack doesn't compute the transmission time offset in a Time Sync packet? Is this normal?
- Are there any incompatibilities between the Adv. Burst feature and the Time Sync feature?
I've already checked the following documents, but I couldn't find anything that helps:
- ANT Message Protocol and Usage, Rev 5.1.
- ANT Burst transfers, Rev 3.0.
- ANT Time Sync Application Note, Rev 1.0.
I'm using a PCA10040 DK with a nRF52832 marked QFAAEQ and a MDK V2.0 with a nRF52832 marked QFAAB0. Both are using SDK16.0 and SoftDevice S332 6.1.1.
Thanks in advance,
Rodrigo.
#include <stdint.h> #include "nrf_sdh.h" #include "nrf_sdh_ant.h" #include "nrfx_rtc.h" #include "app_error.h" #include "ant_interface.h" #include "ant_parameters.h" #include "ant_channel_config.h" // ANT channel config. #define CHAN_ID_DEV_TYPE 1 ///< Channel ID, Device Type. #define CHAN_ID_DEV_NUM 1 ///< Channel ID, Device Number. #define CHAN_ID_TRANS_TYPE 1 ///< Channel ID, Transmission type. #define CHAN_PERIOD 9687 ///< Channel Period (in 32 kHz counts). #define RF_FREQ 66 ///< RF Frequency: 2466 MHz. #define ANT_CHANNEL_DEFAULT_NETWORK 0x00 ///< ANT Channel Network. #define ANT_CHANNEL_NUMBER 0x00 ///< ANT Channel Number. #define EXT_TYPE 0 ///< Extended assign. #define APP_ANT_OBSERVER_PRIO 1 ///< Application's ANT observer priority. You shouldn't need to modify this value. // Timesync Page Numbers #define TIME_SYNC_PAGE 0x01 #define INVALIDATION_BYTE 0xFF // Misc. defines #define FIRST_BYTE(a) ((a) & 0xFF) #define SECOND_BYTE(a) ((a >> 8) & 0xFF) #define THIRD_BYTE(a) ((a >> 16) & 0xFF) #define MSGS_BETWEEN_TIMESYNCS 3 // RTC1 instance const nrfx_rtc_t m_rtc = NRFX_RTC_INSTANCE(1); // Message counter uint8_t msg_counter = 0; /** @brief: Function for handling the RTC1 interrupts. */ static void rtc_dummy_handler(nrfx_rtc_int_type_t int_type) { } /** @brief Function for initialization and configuration of RTC driver instance. */ void rtc_init(void) { uint32_t err_code; // Initialize RTC instance nrfx_rtc_config_t config = NRFX_RTC_DEFAULT_CONFIG; err_code = nrfx_rtc_init(&m_rtc, &config, rtc_dummy_handler); APP_ERROR_CHECK(err_code); // Power on RTC instance nrfx_rtc_enable(&m_rtc); } /**@brief Function for configuring the ANT channel. */ void antmaster_ant_setup(void) { uint32_t err_code; ANT_TIME_SYNC_CONFIG syncConfig; // Configure ANT Time Synchronization using RTC1 syncConfig.ucTimeBase = ANT_TIME_BASE_ALT1; syncConfig.bInvalidationEnabled = true; syncConfig.ucInvalidationByte = INVALIDATION_BYTE; err_code = sd_ant_time_sync_config_set(&syncConfig); APP_ERROR_CHECK(err_code); // ANT Channel init ant_channel_config_t channel_config = { .channel_number = ANT_CHANNEL_NUMBER, .channel_type = CHANNEL_TYPE_MASTER, .ext_assign = EXT_TYPE, .rf_freq = RF_FREQ, .transmission_type = CHAN_ID_TRANS_TYPE, .device_type = CHAN_ID_DEV_TYPE, .device_number = CHAN_ID_DEV_NUM, .channel_period = CHAN_PERIOD, .network_number = ANT_CHANNEL_DEFAULT_NETWORK, }; err_code = ant_channel_init(&channel_config); APP_ERROR_CHECK(err_code); // Configure Adv. Burst uint8_t adv_burst_config[] = {ADV_BURST_MODE_ENABLE, ADV_BURST_MODES_SIZE_24_BYTES, 0, 0, 0, 0, 0, 0}; err_code = sd_ant_adv_burst_config_set(adv_burst_config, sizeof(adv_burst_config)); APP_ERROR_CHECK(err_code); // Channel open err_code = sd_ant_channel_open(ANT_CHANNEL_NUMBER); APP_ERROR_CHECK(err_code); // Send the first ACK msg uint8_t buffer[8] = {0}; err_code = sd_ant_acknowledge_message_tx(ANT_CHANNEL_NUMBER, ANT_STANDARD_DATA_PAYLOAD_SIZE, buffer); APP_ERROR_CHECK(err_code); } /** @brief Function for setting payload for ANT timesync message and sending it. */ void send_timesync_message() { uint32_t err_code; uint8_t message_payload[ANT_STANDARD_DATA_PAYLOAD_SIZE] = {0}; uint32_t rtc_counter; // Retrieve current RTC counter rtc_counter = nrfx_rtc_counter_get(&m_rtc); // Time sync page message_payload[0] = TIME_SYNC_PAGE; // Place RTC current value in User Data. message_payload[3] = FIRST_BYTE(rtc_counter); message_payload[4] = SECOND_BYTE(rtc_counter); message_payload[5] = THIRD_BYTE(rtc_counter); /* RTC counter LSBs. * This value is then used by the ANT stack to calculate offset. */ message_payload[6] = FIRST_BYTE(rtc_counter); message_payload[7] = SECOND_BYTE(rtc_counter); // Broadcast the data as a time sync packet. err_code = sd_ant_time_sync_broadcast_tx(ANT_CHANNEL_NUMBER, ANT_STANDARD_DATA_PAYLOAD_SIZE, message_payload); APP_ERROR_CHECK(err_code); } /** * @brief Handle ANT events */ static void ant_event_handler(ant_evt_t * p_ant_evt, void * p_context) { switch (p_ant_evt->event) { case EVENT_TX: // Fall through case EVENT_TRANSFER_TX_COMPLETED: // Fall through case EVENT_TRANSFER_TX_FAILED: msg_counter++; if (msg_counter == MSGS_BETWEEN_TIMESYNCS) { // Reset counter msg_counter = 0; // Send time sync send_timesync_message(); } else { // Send ACK message uint8_t buffer[8] = {0}; uint32_t err_code = sd_ant_acknowledge_message_tx(ANT_CHANNEL_NUMBER, ANT_STANDARD_DATA_PAYLOAD_SIZE, buffer); APP_ERROR_CHECK(err_code); } break; case EVENT_RX: // Do nothing with the messages received break; default: break; } } // Register the ant_evt_handler as an ANT observer. NRF_SDH_ANT_OBSERVER(m_ant_observer, APP_ANT_OBSERVER_PRIO, ant_event_handler, NULL); /**@brief Function for ANT stack initialization. * * @details Initializes the SoftDevice and the ANT event interrupt. */ void softdevice_setup(void) { ret_code_t err_code = nrf_sdh_enable_request(); APP_ERROR_CHECK(err_code); ASSERT(nrf_sdh_is_enabled()); err_code = nrf_sdh_ant_enable(); APP_ERROR_CHECK(err_code); } int main(void) { // Setup SoftDevice and events handler softdevice_setup(); // RTC1 init. rtc_init(); // Setup ANT and start channel antmaster_ant_setup(); // Enter main loop for (;;) { // Do nothing } }
#include <stdint.h> #include "nrf_sdh.h" #include "nrf_sdh_ant.h" #include "nrfx_rtc.h" #include "app_error.h" #include "ant_interface.h" #include "ant_parameters.h" #include "ant_channel_config.h" #include "ant_search_config.h" #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" // ANT channel config. #define CHAN_ID_DEV_TYPE 1 ///< Channel ID, Device Type. #define CHAN_ID_DEV_NUM 1 ///< Channel ID, Device Number. #define CHAN_ID_TRANS_TYPE 1 ///< Channel ID, Transmission type. #define CHAN_PERIOD 9687 ///< Channel Period (in 32 kHz counts). #define RF_FREQ 66 ///< RF Frequency: 2466 MHz. #define ANT_CHANNEL_DEFAULT_NETWORK 0x00 ///< ANT Channel Network. #define ANT_CHANNEL_NUMBER 0x00 ///< ANT Channel Number. #define EXT_TYPE 0 ///< Extended assign. #define APP_ANT_OBSERVER_PRIO 1 ///< Application's ANT observer priority. You shouldn't need to modify this value. // Timesync Page Numbers #define TIME_SYNC_PAGE 0x01 #define INVALIDATION_BYTE 0xFF // RTC1 instance const nrfx_rtc_t m_rtc = NRFX_RTC_INSTANCE(1); // Wheter the slave periodically sends data to master. bool sending_flag = false; // Message counter uint8_t msg_counter = 0; /** *@brief Function for initializing logging. */ static void log_init(void) { ret_code_t err_code = NRF_LOG_INIT(NULL); APP_ERROR_CHECK(err_code); NRF_LOG_DEFAULT_BACKENDS_INIT(); } /** @brief: Function for handling the RTC1 interrupts. */ static void rtc_dummy_handler(nrfx_rtc_int_type_t int_type) { } /** @brief Function for initialization and configuration of RTC driver instance. */ void rtc_init(void) { uint32_t err_code; // Initialize RTC instance nrfx_rtc_config_t config = NRFX_RTC_DEFAULT_CONFIG; err_code = nrfx_rtc_init(&m_rtc, &config, rtc_dummy_handler); APP_ERROR_CHECK(err_code); // Power on RTC instance nrfx_rtc_enable(&m_rtc); } /**@brief Function for configuring the ANT channel. */ void antslave_ant_setup(void) { uint32_t err_code; // Configure received message timestamp to use RTC1 (4 byte counter) ANT_TIME_STAMP_CONFIG stampConfig; stampConfig.ucTimeBase = ANT_TIME_BASE_ALT1; stampConfig.bTimeStampEnabled = true; err_code = sd_ant_time_stamp_config_set(&stampConfig); APP_ERROR_CHECK(err_code); // ANT Channel init ant_channel_config_t channel_config = { .channel_number = ANT_CHANNEL_NUMBER, .channel_type = CHANNEL_TYPE_SLAVE, .ext_assign = EXT_TYPE, .rf_freq = RF_FREQ, .transmission_type = CHAN_ID_TRANS_TYPE, .device_type = CHAN_ID_DEV_TYPE, .device_number = CHAN_ID_DEV_NUM, .channel_period = CHAN_PERIOD, .network_number = ANT_CHANNEL_DEFAULT_NETWORK, }; err_code = ant_channel_init(&channel_config); APP_ERROR_CHECK(err_code); // Configure Adv. Burst uint8_t adv_burst_config[] = {ADV_BURST_MODE_ENABLE, ADV_BURST_MODES_SIZE_24_BYTES, 0, 0, 0, 0, 0, 0}; err_code = sd_ant_adv_burst_config_set(adv_burst_config, sizeof(adv_burst_config)); APP_ERROR_CHECK(err_code); // Set an infinite High priority search timeout. ant_search_config_t ant_search_config = DEFAULT_ANT_SEARCH_CONFIG(ANT_CHANNEL_NUMBER); ant_search_config.high_priority_timeout = ANT_HIGH_PRIORITY_TIMEOUT_DISABLE; err_code = ant_search_init(&ant_search_config); APP_ERROR_CHECK(err_code); // Channel open err_code = sd_ant_channel_open(ANT_CHANNEL_NUMBER); APP_ERROR_CHECK(err_code); } /** * @brief Handle ANT events */ static void ant_event_handler(ant_evt_t * p_ant_evt, void * p_context) { uint32_t err_code; switch (p_ant_evt->event) { case EVENT_TX: // Fall through case EVENT_TRANSFER_TX_COMPLETED: // Fall through case EVENT_TRANSFER_TX_FAILED: if(sending_flag) { // Queue an ACK message for next channel period uint8_t buffer[8] = {0}; err_code = sd_ant_acknowledge_message_tx(ANT_CHANNEL_NUMBER, ANT_STANDARD_DATA_PAYLOAD_SIZE, buffer); APP_ERROR_CHECK(err_code); // Queue an Adv. Burst message for next channel period // uint32_t buffer[32] = {0}; // err_code = sd_ant_burst_handler_request(ANT_CHANNEL_NUMBER, // 32, // buffer, // BURST_SEGMENT_START | BURST_SEGMENT_END); // APP_ERROR_CHECK(err_code); } break; case EVENT_RX: switch (p_ant_evt->message.ANT_MESSAGE_ucMesgID) { case MESG_ACKNOWLEDGED_DATA_ID: if(!sending_flag) { // Raise the sending flag. Next msgs will be queue after the first one. sending_flag = true; // Queue the first ACK message for next channel period uint8_t buffer[8] = {0}; err_code = sd_ant_acknowledge_message_tx(ANT_CHANNEL_NUMBER, ANT_STANDARD_DATA_PAYLOAD_SIZE, buffer); APP_ERROR_CHECK(err_code); } break; case MESG_BROADCAST_DATA_ID: // Print page number, user data and transmission time offset if ((p_ant_evt->message.ANT_MESSAGE_aucPayload[0] == TIME_SYNC_PAGE) || (p_ant_evt->message.ANT_MESSAGE_aucPayload[0] == INVALIDATION_BYTE)) { NRF_LOG_RAW_INFO("Page number: %lu\n", p_ant_evt->message.ANT_MESSAGE_aucPayload[0]); NRF_LOG_RAW_INFO("User data: %lu, %lu, %lu\n", p_ant_evt->message.ANT_MESSAGE_aucPayload[3], p_ant_evt->message.ANT_MESSAGE_aucPayload[4], p_ant_evt->message.ANT_MESSAGE_aucPayload[5]); NRF_LOG_RAW_INFO("Offset: %lu, %lu\n\n", p_ant_evt->message.ANT_MESSAGE_aucPayload[6], p_ant_evt->message.ANT_MESSAGE_aucPayload[7]); } break; } break; default: break; } } // Register the ant_evt_handler as an ANT observer. NRF_SDH_ANT_OBSERVER(m_ant_observer, APP_ANT_OBSERVER_PRIO, ant_event_handler, NULL); /**@brief Function for ANT stack initialization. */ void softdevice_setup(void) { ret_code_t err_code = nrf_sdh_enable_request(); APP_ERROR_CHECK(err_code); ASSERT(nrf_sdh_is_enabled()); err_code = nrf_sdh_ant_enable(); APP_ERROR_CHECK(err_code); } int main(void) { // Log init log_init(); // Setup SoftDevice and events handler softdevice_setup(); // RTC1 init. rtc_init(); // Setup ANT and start channel antslave_ant_setup(); // Enter main loop for (;;) { // Do nothing } }