ANT Master Operation

Configure dev board as ANT master and tx/rx data

Platform Test Tools SAM3U2 Firmware nRF51422 Firmware Software
ANT USB Module
USB RS-232 Converter
Master User Code
AP2 Emulator IAR
ANTware II

Prerequisite Modules


This module will setup and run the development board in Master mode and communicate to an ANTware Slave. The user application will monitor the state of the buttons and update a field in the broadcast data corresponding to the button presses. When data is received, it will be printed to the LCD along with a received message count. Checkout the latest Master repository for the starting point.

ANT Initialization

Follow the instructions in ant_api.c to setup and initialize G_stAntSetupData. Update the initialization check to open the ANT channel if configuration is successful.

In user_app1.h add the following constants:

Constants / Definitions
/* Required constants for ANT channel configuration */
#define ANT_CHANNEL_USERAPP             ANT_CHANNEL_0         // Channel 0 - 7
#define ANT_CHANNEL_PERIOD_LO_USERAPP   (u8)0x00              // LO; 0x0001 - 0x7fff
#define ANT_CHANNEL_PERIOD_HI_USERAPP   (u8)0x20              // HI; 0x0001 - 0x7fff
#define ANT_DEVICEID_LO_USERAPP         (u8)0x34              // Device # Low byte
#define ANT_DEVICEID_HI_USERAPP         (u8)0x12              // Device # High byte
#define ANT_DEVICE_TYPE_USERAPP         (u8)1                 // 1 - 255
#define ANT_TRANSMISSION_TYPE_USERAPP   (u8)1                 // 1-127; MSB is pairing
#define ANT_FREQUENCY_USERAPP           (u8)50                // 2400MHz + 0 - 99 MHz
#define ANT_TX_POWER_USERAPP            RADIO_TX_POWER_4DBM   // Max tx power

**Update the ANT_DEVICEID_LO/HI_USERAPP constants to your unique number.**

In user_app1.c, expose the required variables from ant_api.c in the “Existing Variables” section (around line 49):

/* Existing variables */
extern u32 G_u32AntApiCurrentMessageTimeStamp;                    
extern AntApplicationMessageType G_eAntApiCurrentMessageClass;    
extern u8 G_au8AntApiCurrentMessageBytes[ANT_APPLICATION_MESSAGE_BYTES];  
extern AntExtendedDataType G_sAntApiCurrentMessageExtData;                

In user_app1.c, add initialization:

void UserApp1Initialize(void)
  u8 au8WelcomeMessage[] = "ANT Master";

  /* Set a message up on the LCD. Delay is required to let the clear command send. */
  for(u32 i = 0; i < 10000; i++);
  LCDMessage(LINE1_START_ADDR, au8WelcomeMessage);

 /* Configure ANT for this application */
  UserApp1_sChannelInfo.AntChannel          = ANT_CHANNEL_USERAPP;
  UserApp1_sChannelInfo.AntChannelType      = ANT_CHANNEL_TYPE_USERAPP;
  UserApp1_sChannelInfo.AntChannelPeriodLo  = ANT_CHANNEL_PERIOD_LO_USERAPP;
  UserApp1_sChannelInfo.AntChannelPeriodHi  = ANT_CHANNEL_PERIOD_HI_USERAPP;
  UserApp1_sChannelInfo.AntDeviceIdLo       = ANT_DEVICEID_LO_USERAPP;
  UserApp1_sChannelInfo.AntDeviceIdHi       = ANT_DEVICEID_HI_USERAPP;
  UserApp1_sChannelInfo.AntDeviceType       = ANT_DEVICE_TYPE_USERAPP;
  UserApp1_sChannelInfo.AntTransmissionType = ANT_TRANSMISSION_TYPE_USERAPP;
  UserApp1_sChannelInfo.AntFrequency        = ANT_FREQUENCY_USERAPP;
  UserApp1_sChannelInfo.AntTxPower          = ANT_TX_POWER_USERAPP;

  UserApp1_sChannelInfo.AntNetwork = ANT_NETWORK_DEFAULT;
  for(u8 i = 0; i < ANT_NETWORK_NUMBER_BYTES; i++)
    UserApp1_sChannelInfo.AntNetworkKey[i] = ANT_DEFAULT_NETWORK_KEY;
  /* Attempt to queue the ANT channel setup */
  if( AntAssignChannel(&UserApp1_sChannelInfo) )
    UserApp1_u32Timeout = G_u32SystemTime1ms;
    UserApp1_StateMachine = UserApp1SM_AntChannelAssign;
    /* The task isn't properly initialized, so shut it down and don't run */
    UserApp1_StateMachine = UserApp1SM_Error;

} /* end UserApp1Initialize() */

Checking channel status

It takes time for any message from the Host to ANT to transfer, and in most cases ANT will send a response to confirm that it has received a message. You may also discover that your task has no idea if a particular channel is open or closed, or even configured. The API offers a function to determine that:

AntChannelStatusType AntRadioStatusChannel(AntChannelNumberType eChannel_)

The lower level ANT task always watches for information about each channel and will update this in the system. Every channel is monitored. If you are waiting for a channel to be assigned and configured, you can use a state and condition on getting a "ANT_CONFIGURED" status in return. This is how the example code handles this.

State Machine Function Definitions
/* Wait for ANT channel assignment */
static void UserApp1SM_AntChannelAssign()
    /* Channel assignment is successful, so open channel and
    proceed to Idle state */
    UserApp1_StateMachine = UserApp1SM_Idle;
  /* Watch for time out */
  if(IsTimeUp(&UserApp1_u32Timeout, 3000))
    UserApp1_StateMachine = UserApp1SM_Error;    
} /* end UserApp1SM_AntChannelAssign */

Once the channel has been assigned, it is immediately opened (in this case). Now we can set up ANTware to be listening on the channel that was configured. Do NOT using a scanning channel -- you must use a "regular" ANT channel since we want to send data back to the dev board. Make sure your are monitoring the debug output in a terminal window.

Build and run the code. Wait for about 8 seconds after the debug reports that the channel is open. What do you see in the debug window and why?


Processing ANT messages

A Master ANT channel MUST send a message every time it broadcasts, so even though we have not given it any data it starts sending (the default is 00-00-00-00-00-00-00-00) as soon as the channel is open. Whenever ANT sends a message, a confirmation is provided to the host as an event (EVENT_TX). This does NOT mean that the Slave received the message.

ANT can provide the host with many different event messages and of course will forward data received, too. The ANT task simplifies all of this by collecting all of the messages, determining what they are, and then presents them to the application as easily as possible through the API. This is done through a FIFO buffer that has space for 32 messages. Once the buffer is full, new messages or data will be lost and the ANT task will warn you. Right now the system is broadcasting the default message and since we broadcast at 4Hz, it takes 8 seconds to fill up the 32 spaces.

It is very important to always manage the message buffer.

To check for a message in the buffer, call AntReadAppMessageBuffer() which will return TRUE if there is at least one message. If there is a message, AntReadAppMessageBuffer() loads the following with the oldest data:

  1. G_u32AntApiCurrentMessageTimeStamp- the system time when the message was received
  2. G_eAntApiCurrentMessageClass- the type of message (ANT_DATA or ANT_TICK)
  3. G_au8AntApiCurrentMessageBytes[] - the 8 bytes of message data
  4. G_sAntApiCurrentMessageExtData - Extended message data (if available)

In UserApp1SM_Idle(), add code to check and handle a message. Since this will occur every ms, it is guaranteed to keep the message buffer from overflowing.

  if( AntReadAppMessageBuffer() )
     /* New message from ANT task: check what it is */
    if(G_eAntApiCurrentMessageClass == ANT_DATA)
      /* We got some data */
    else if(G_eAntApiCurrentMessageClass == ANT_TICK)
     /* A channel period has gone by: typically this is when new data should be queued to be sent */
  } /* end AntReadAppMessageBuffer() */

Build and run the code and notice that we no longer get the "No space..." messages in the debug output. Although we are not doing anything with the messages, the act of reading them clears them out and keeps the buffer empty.

Sending data

We are going to allocate the 8 bytes of data our Master sends as follows:

  3. BUTTON2 STATUS (EIE1 board only)
  4. BUTTON3 STATUS (EIE1 board only)
  5. The constant 0xA5
  6. Message counter HI byte
  7. Message counter MID byte
  8. Message counter LO byte

If a button is pressed, the STATUS is 0xFF; if a button is not pressed, the STATUS is 0x00.

First, set up a static array where the message will be constructed:

static u8 au8TestMessage[] = {0, 0, 0, 0, 0xA5, 0, 0, 0};

In the ANT_TICK section, add code to manage the last three bytes in the array as the message counter and call AntQueueBroadcastMessage to queue it to ANT (we will worry about the other bytes later):

    else if(G_eAntApiCurrentMessageClass == ANT_TICK)
     /* Update and queue the new message data */
      if(au8TestMessage[7] == 0)
        if(au8TestMessage[6] == 0)
      AntQueueBroadcastMessage(ANT_CHANNEL_USERAPP, au8TestMessage);

Build and run the code and make sure you observe the message counter increasing now.

Now add code to set the other bytes according to the current state of the buttons:

  /* Check all the buttons and update au8TestMessage according to the button state */ 
  au8TestMessage[0] = 0x00;
  if( IsButtonPressed(BUTTON0) )
    au8TestMessage[0] = 0xff;

Build and test the code. Watch the message window as you press and release the different buttons.


Receiving data

Now we can program handling data messages from ANT. Remember that you cannot send data messages from ANTware if you are receiving on a scanning channel, so make sure you are opening a dedicated channel using the AUTO-OPEN button. Test that this is working by sending an "Acknowledged Data" message from ANTware. Even though the user application is not processing any data messages yet, the ANT protocol will automatically handle acknowledging messages.

  1. Keep the firmware running with the channel open in ANTware so you can see data arriving.
  2. Select the Ack tab in the bottom right and enter 8 bytes of random data in the data area.
  3. Click "Send Acknowledged" and watch the message window. You should see "Acknowledged Result: Pass" (HINT: click off the "Scroll to New Msgs" box as soon as you see the message so the window stops scrolling).


So we know the system received the message. From the ANT API's perspective, this would be an ANT_DATA message so add the code in that section of user_app to parse out the data into a nicely formatted string for display on the LCD. Start by defining a message placeholder and send this to the LCD.

u8 au8DataContent[] = "xxxxxxxxxxxxxxxx";

if( AntReadAppMessageBuffer() )
  /* New data message: check what it is */
  if(G_eAntApiCurrentMessageClass == ANT_DATA)
    LCDMessage(LINE2_START_ADDR, au8DataContent); 

Set a break point on LCDMessage(), build and run the code, and take a look at G_au8AntApiCurrentData in a debug window when the code stops at the break point to see how the data sent from ANTware appears to the API. Note that when you halt the code, the ANT radio continues to send messages. It is very likely the system will have an error when restarting the code, but this is expected and it should recover.


Write a loop to parse the hex values of each of the numbers into ASCII characters for the LCD display (each byte of the hex number must be masked off and converted to ASCII e.g. 0x41 is '4' and '1'):

if(G_eAntApiCurrentMessageClass == ANT_DATA)
   /* We got some data: parse it into au8DataContent */
   for(u8 i = 0; i < ANT_DATA_BYTES; i++)
     au8DataContent[2 * i]     = HexToASCIICharUpper(G_au8AntApiCurrentData[i] / 16);
     au8DataContent[2 * i + 1] = HexToASCIICharUpper(G_au8AntApiCurrentData[i] % 16);
   LCDMessage(LINE2_START_ADDR, au8DataContent);

Practice sending different Broadcast and Acknowledged messages from ANTware to test the system. If everything is working well, consider yourself an ANT Master master.


2018-MAR-21: Fix missing semi-colon in ANT_DATA processing
2017-APR-20: Update to support new ant_api
2017-JAN-30: Add G_stAntSetupData.AntChannelType; update code formatting
2016-OCT-17: Fix G.stAntSetupData to G_stAntSetupData
2016-MAR-02: First release.