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

ANT: Time Sync feature doesn't work if Adv. Burst feature is enabled.

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:

  1. Under what circumstances a Time Sync packet is invalidated by the stack?
  2. Under what circumstances the stack doesn't compute the transmission time offset in a Time Sync packet? Is this normal?
  3. 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
    }
}


Related