ANT Master Operation

Configure dev board as ANT master and tx/rx data [VIDEO]

Platform Test Tools SAM3U2 Firmware nRF51422 Firmware Software
ANT USB Module
USB RS-232 Converter
MPG User Code
AP2 Emulator 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_app.h add the following constants:

Constants / Definitions
/* Required constants for ANT channel configuration */
#define ANT_CHANNEL_USERAPP             (u8)0                 // Channel 0 - 7
#define ANT_SERIAL_LO_USERAPP           (u8)0x34              // Low byte of two-byte Device #
#define ANT_SERIAL_HI_USERAPP           (u8)0x12              // High byte of two-byte Device #
#define ANT_DEVICE_TYPE_USERAPP         (u8)1                 // 1 - 255
#define ANT_TRANSMISSION_TYPE_USERAPP   (u8)1                 // 1-127 (MSB is pairing bit)
#define ANT_CHANNEL_PERIOD_LO_USERAPP   (u8)0x00              // Low byte of two-byte channel period 0x0001 - 0x7fff
#define ANT_CHANNEL_PERIOD_HI_USERAPP   (u8)0x20              // High byte of two-byte channel period 
#define ANT_FREQUENCY_USERAPP           (u8)50                // 2400MHz + this number 0 - 99 MHz

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

In user_app.c, expose G_stAntSetupData and add initialization:

/* Existing variables (defined in other files -- should all contain the "extern" keyword) */
extern AntSetupDataType G_stAntSetupData;                         /* From ant.c */

extern u32 G_u32AntApiCurrentDataTimeStamp;                       /* From ant_api.c */
extern AntApplicationMessageType G_eAntApiCurrentMessageClass;    /* From ant_api.c */
extern u8 G_au8AntApiCurrentData[ANT_APPLICATION_MESSAGE_BYTES];  /* From ant_api.c */

void UserAppInitialize(void)
  /* Configure ANT for this application */
  G_stAntSetupData.AntChannel          = ANT_CHANNEL_USERAPP;
  G_stAntSetupData.AntSerialLo         = ANT_SERIAL_LO_USERAPP;
  G_stAntSetupData.AntSerialHi         = ANT_SERIAL_HI_USERAPP;
  G_stAntSetupData.AntDeviceType       = ANT_DEVICE_TYPE_USERAPP;
  G_stAntSetupData.AntTransmissionType = ANT_TRANSMISSION_TYPE_USERAPP;
  G_stAntSetupData.AntChannelPeriodLo  = ANT_CHANNEL_PERIOD_LO_USERAPP;
  G_stAntSetupData.AntChannelPeriodHi  = ANT_CHANNEL_PERIOD_HI_USERAPP;
  G_stAntSetupData.AntFrequency        = ANT_FREQUENCY_USERAPP;
  G_stAntSetupData.AntTxPower          = ANT_TX_POWER_USERAPP;

  /* If good initialization, set state to Idle */
  if( AntChannelConfig(ANT_MASTER) )
    UserApp_StateMachine = UserAppSM_Idle;
    /* The task isn't properly initialized, so shut it down and don't run */
    UserApp_StateMachine = UserAppSM_FailedInit;

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 presenting 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. It is very important to always manage the message buffer. 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.

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

  1. G_u32AntApiCurrentDataTimeStamp – the system time when the message was received
  2. G_eAntApiCurrentMessageClass – the type of message (ANT_DATA or ANT_TICK)
  3. G_au8AntApiCurrentData[] – the 8 bytes of message data

In UserAppSM_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( AntReadData() )
     /* New data message: 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 AntReadData() */

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 as follows:

  3. BUTTON2 STATUS (MPG1 board only)
  4. BUTTON3 STATUS (MPG2 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)

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( AntReadData() )
  /* 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.