ANT Master

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.

Branch the latest Master repository for the starting point. There are two layers in the EiE ANT stack: ant.c which is all the interface code to the nRF using the ANT protocol, and ant_api.c which is an abstraction to further reduce the complexity of using the ANT radio. The API should be sufficient for most of what you would do with ANT on the EiE development board, but don’t be surprised if you need to work at the ant.c level for certain things. You might add additional functions to ant_api.c to do that.

ANT Initialization

Follow the instructions in ant_api.c to setup and initialize an ANT channel in the user_app1.c task. The steps are shown below, starting with copying the required “extern” globals to user_app1.c

In user_app1.h, add the following definitions but update ANT_DEVICE_LO and ANT_DEVICE_HI to the last four digits of your ID number so it is unique. The text can be copied below and should appear in user_app1.h as shown.

#define U8_ANT_CHANNEL_USERAPP (u8)ANT_CHANNEL_0 /* Channel 0 – 7 */

#define U8_ANT_DEVICE_LO_USERAPP (u8)0x34 /* Low byte of two-byte Device # */

#define U8_ANT_DEVICE_HI_USERAPP (u8)0x12 /* High byte of two-byte Device # */

#define U8_ANT_DEVICE_TYPE_USERAPP (u8)1 /* 1 – 255 */

#define U8_ANT_TRANSMISSION_TYPE_USERAPP (u8)1 /* 1-127 (MSB is pairing bit) */

#define U8_ANT_CHANNEL_PERIOD_LO_USERAPP (u8)0x00 /* Low byte of two-byte channel period */

#define U8_ANT_CHANNEL_PERIOD_HI_USERAPP (u8)0x20 /* High byte of two-byte channel period */

#define U8_ANT_FREQUENCY_USERAPP (u8)50 /* 2400MHz + this number 0 – 99 */

#define U8_ANT_TX_POWER_USERAPP RADIO_TX_POWER_4DBM /* RADIO_TX_POWER_xxx */

Look at the function information for AntAssignChannel() in ant_api.c. Copy and paste the example setup code into UswerApp1Initialize().

Replace the constants from the user_app1.h definitions entered in the previous step. Leave the AntChannelType and AntNetwork values unchanged.

The channel assignment messages are queued by AntAssignChannel() but are not sent immediately since the system state machine must run. The ant_api task will proceed to send out the configuration messages and reports back through the debug interface. AntRadioStatusChannel(ANT_CHANNEL_0) can be called to wait for the channel to be configured. Therefore, the User App should wait until the ANT is ready.

To do this, change the starting state to a new state called “UserApp1SM_WaitAntReady” instead of the usual Idle state.

Add this state to the User App and write code to wait until AntRadioStatusChannel() returns “ANT_CONFIGURED” (see the function header for the return types). When this is true, call AntOpenChannelNumber() and move to the Idle state. Copying the Idle state code and updating the text for the new state name is a fast way to add a state.

WaitChannelOpen() will again call AntRadioStatusChannel until “ANT_OPEN” is returned. At that point, you can enter the final state “ChannelOpen” where messages and be sent and received. What should be obvious is that a good program would handle endless waiting or error states better, but for this example we’ll keep it simple.

Build the code just to make sure everything compiles without errors.

Set up ANTware to be listening on the channel that was configured. Do NOT use a scanning channel — you must use a “regular” ANT channel since we want to send data back to the dev board (which is not allowed with a scanning channel). Press the “Auto-Open” button when you have the Slave channel configured correctly in ANTware. Make sure your are monitoring the debug output in a terminal window.

Run the code and watch the debug output and the ANT data window. Once the “Ant channel 0 open OK” message appears in the debug window, the data window in ANTware should show incoming BROADCAST_DATA_0x4E messages 4 times per second. Wait for about 8 seconds after this – what do you see in the debug window and why?

Processing ANT messages

The ANT processor ensures that the Master ANT channel ALWAYS sends a message at the channel period time when the channel is open. So even though we have not given it any data it starts sending. The default data packet is 00-00-00-00-00-00-00-00 which starts sending as soon as the “Open channel” function is called and the command is processed.

Whenever the ANT Master sends a message, a confirmation message is provided to the host (the EiE dev board processor) as an EVENT_TX message. This does NOT mean that the Slave received the message, just that the local Master ANT device sent the data. ANT can provide the host a variety of different messages and of course will forward data received, too, if anything comes back from the Slave to which the Master is paired.

The EiE ANT task tries to simplify all of this by collecting 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 will be lost so it is very important to always manage this 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. Therefore, after 8 seconds of broadcasting the buffer is full so the board reports this out the debug interface.

Therefore, any ANT application you code needs to make sure it regularly processes the messages in the buffer to keep it from filling up. 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 variables with the oldest data from the buffer:

  1. G_u32AntApiCurrentMessageTimeStamp – the system time when the message was received.
  2. G_eAntApiCurrentMessageClass– the type of message (ANT_DATA or ANT_TICK) – more on this later.
  3. G_au8AntApiCurrentMessageBytes[] – the 8 bytes of message data.
  4. G_sAntApiCurrentMessageExtData – control data that might be useful later.

It is safe to call AntReadAppMessageBuffer() as many times as you want., though you must make sure to deal with the new data each time as it will be overwritten every call. This is exactly what you would do until it returns “FALSE” meaning it has no messages. Since we can expect only one message every 250ms, calling it once per 1ms loop in your User App code is probably sufficient, though you cannot guarantee that and sometimes ANT will send more than one message. Never assume any timing based on how many messages are in the queue.

In UserApp1SM_ChannelOpen(), add code to check and handle a message. For now, set up the code to distinguish between an ANT_DATA and ANT_TICK message based on the value in G_eAntApiCurrentMessageClass if there was a message present.

Build and run the code and notice that you no longer get the “No space…” messages in the debug output. Although nothing is being done with the messages, the act of reading them clears them out and keeps the buffer empty.

Place a breakpoint inside the code that will run when a new message is present. Add the 4 global ANT data variables to a watch window and see what happens when the code stops.

This is going to be a very important debugging option for any ANT application you write, so be sure you understand what is going on. Since ANT will only be sending “ANT_TICK” messages right now, the MessageBytes or ExtData information is not useful, but once ANT_DATA messages are coming in it will be very useful.

Remove the breakpoint and restart the code. You should get some error messages on the Debug output, and the program might crash. This is because the ANT processor does not halt when the main processor is halted by the debugger.

Unfortunately, there’s nothing you can do. In most cases, debugging ANT will be left to sending debug messages to try to understand what is not going on. You can still use the debugger carefully to stop code at certain points to see what is happening, but be aware that you will need to reset and re-run the code afterwards. This can still be very helpful, for example to look at the whole Ant_au9AntRxBuffer to see ALL the data that has come in (this buffer is circular, but fairly large so you can see a lot of data).

Sending data

Now you will give data to send over ANT. We are going to allocate the 8 bytes of the ANT data packet as follows:

  1. BUTTON0 STATUS
  2. BUTTON1 STATUS
  3. BUTTON2 STATUS
  4. BUTTON3 STATUS
  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. The “message counter” simple counts how many times the message has been queued to send. How many messages can this counter keep track of?

Set up a static array inside UserApp1SM_ChannelOpen() where the message will be constructed:

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). Make sure to correctly code the 3-byte counter to increment properly!

Build and run the code and make sure you observe the message counter increasing now in the ANTware data window. The data is shown in hex. The first “00” in the message is NOT part of the 8-byte data packet. The next 8 bytes are, so you can see four “00” that will hold the button status, the 0xA5 static value, and the 3 bytes of message counter. If you watch for at least 256 messages, you should see the middle byte increment. If you leave it running over night, you’ll eventually see the high byte increment.

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

Build and test the code. Watch the message window as you press and release the different buttons. Notice the delay between pressing a button and when you see the data message change.

Receiving data

Now we can program handling data messages from ANT. There are two types of ANT data messages: Broadcast and Acknowledged. Broadcast messages are the most efficient as they are “fire and forget” from the sender’s perspective. Acknowledged messages cause the receiver to “ACK” the received message with a response. This occurs on the same channel period as the message was sent, so it requires extra power because the sender must activate its receiver to hear the response. However, there is nothing in the protocol to check whether or not the “ACK” was received (this is an endless chicken-and-egg problem to solve).

ANTware can send both types of messages. 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.

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

Set a breakpoint on LcdMessage(), build and run the code, and take a look at G_au8AntApiCurrentMessageBytes in a debug window when the code stops at the break point to see how the data sent from ANTware appears to the API. Make sure you see the matching numbers between the data you sent in ANTware and what appears in the data array.

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’). Note there is a utility function called “HexToASCIICharUpper()” that might help. Alternatively, you could make it display the actual ASCII characters, but you’ll have to decide how to handle non-printable characters.

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