ANT Slave

Prerequisite Modules

This module will setup and run the development board in Slave mode. Start with the main Master code branch from the ANT Master module.

We will write a program that will do the following:

  1. Show if a channel is open and searching (green), paired (blue), loss of sync (blinking blue) and closed(yellow)
  2. Display any NEW data from transmitting devices (repeated messages will be ignored)
  3. Respond to a special command that we define to set the RGB backlight LED to a certain color

There are also some very important things to note:

  1. The default Broadcast message data for the ANTware Master is all zeroes except for the last byte which is a one-byte counter that increments every message (e.g. 00-00-00-00-00-00-00-xx)
  2. If you configure a Slave device with wildcard parameters, as soon as the Slave pairs with a Master the Slave will automatically update itself to the specific Channel ID of the Master. Even if the Slave channel is closed and then re-opened, the Channel ID will still be set to the last Master it paired with. There are very good reasons for this! If your application needs to reset back to wildcards, then simply resend the channel configuration message after the channel is closed.
  3. A Slave that is not paired will not provide regular messages to the host (unlike a Master that always sends messages at the Channel Period)

ANT_TICK and Events

Once a Slave has paired, the status of the connection is continuously updated with events to the host. The events are defined by ANT and are provided in the antdefines.h header file. The ones we care about are shown here:

These events are forwarded to the application by ant_api through the ANT_TICK message. An ANT_TICK message is just an array of 8 bytes, but each byte is specified to mean something specific, per the definition in ant_api.h:

When processing an ANT_TICK message, you can index the 8-byte array with the following symbols so that your code is as readable as possible:

What is most important is to understand that the ANT_TICK message contains the EVENT_CODE at index [1]. The index is assigned the name ANT_TICK_MSG_EVENT_CODE_INDEX.

The 8 data bytes are presented to your application exactly the same way as they were presented in the ANT Master module, namely the global variable G_au8AntApiCurrentMessageBytes[8] that is updated when AntReadAppMessageBuffer() is called.

Therefore to get a copy of the current event code in an ANT_TICK message so you can react to it, the code would look something like this:

Understanding the ANT_TICK message is absolutely essential, not only for the specific reason of looking at events, but because propagating information like this in an embedded system happens all the time. As a designer, you assign meaning to otherwise meaningless bytes and build an entire protocol on which your system works.

Initialization

To set up the user task, set the ANT channel parameters up with mostly wildcard values (wildcard = 0).

In userapp.h:

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

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

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

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

#define U8_ANT_TRANSMISSION_TYPE_USERAPP (u8)0 /* 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 */

In userapp.c:

Copy in the external global definitions from ant_api.c like you did in the ANT Master module.

Also add some debug counters to your user_app that will be useful to see how the system is behaving:

Add the code for WaitAntReady() state in the same way that was done for ANT Master. When the channel is configured, update the LED status and go to the Idle state. Build and test the code to this point. Watch the status LEDs – you should see the red LED turn on briefly before changing to yellow to indicate the channel is configured and the Idle state is active.

Add the code for WaitAntReady state in the same way that was done for ANT Master. When the channel is configured, update the LED status and go to the Idle state. Build and test the code to this point.

The user_app1 state machine

Managing an ANT slave channel offers new challenges because the task must be able to react to different conditions depending on the ANT radio state that will change depending on whether or not an ANT Master device is present. Trying to manage everything in a single state would involve a lot of “if” statements and be difficult to follow.

The state diagram below shows how the system will progress through four main states. To keep things as clear as possible, the conditions that hold a state are not explicitly shown. The WaitAntReady state is also not shown.

We can consider both the Idle and ChannelOpen states “steady states” since the radio is in a known state at those times and we expect a button press or other action to change states. That is not to say that things are not happening. In the ChannelOpen state, the system will continually be reading messages, monitoring the channel, and updating the LCD. WaitChannelOpen and WaitChannelClose are transitory states because we should never be there for more than about one second while ANT opens or closes a channel. Therefore both these states use a timeout counter to ensure the system does not get stuck while waiting for the external device to do something.

Hardly any of the possible problems that could come up are addressed in this example code. A robust system would handle every error case, provide debugging information, and do its best to get the radio back to a known, working state if anything went wrong. Of course that would require substantially more code and testing — something you would be expected to do if this were a commercial product.

To implement the new states, copy the following framework into user_app.c and do not forget to add the required function prototypes in user_app.h for each new state.

Idle State
This state looks for BUTTON0. When pressed, it queues OpenChannel(), sets up the LEDs for the next state, initializes the timeout counter and sets the state machine to execute WaitChannelOpen on the next cycle. In most cases, you always set up the static conditions for the next state as you exit out of the current state. Note the use of UserApp1_u32Timeout which is global to the task and provides timing between states.

WaitChannelOpen State

Just like for ANT Master, this state allows the system time to open the ANT channel. When AntRadioStatusChannel() returns ANT_OPEN the state can advance. Since the system is waiting on an external event, UserApp1_u32Timeout is monitored and will kick the task back to Idle if the timeout occurs. This is a minimal reaction as it does nothing to figure out why the channel did not open, but good enough for this example.

ChannelOpen State
The most interesting part of this application is when the channel is open. To get ready for all of the functionality of this state, add the following variables local to UserApp1SM_ChannelOpen:

From the state diagram, ChannelOpen state has two conditions for exit that we will code first. A BUTTON0 press is the first condition. You must take care of all the state transitions including re-initializing any static variables that must be reset for the next time the state is entered. AntCloseChannelNumber() must be called and the state WaitChannelClose should be created. Don’t forget to update the timeout counter:

The other exit condition for the state is if the Slave searches too long without finding a Master and automatically closes the channel. This is configured by the value assigned to U8_ANT_SEARCH_TIMEOUT in ant.h which gets loaded when AntAssignChannel() is called. In this example it is set for 10 seconds. Note this was updated 2024-FEB-07 so if you are using an older code base there are some other updates required — see if you can figure it out!

The user app1 code handles the auto-close with calls to AntRadioStatusChannel() to make sure the channel is still open. There’s some redundant code for the state change here that is the same as what BUTTON0 does, but don’t worry about it.

Lastly, the state must process the ANT messages that will always be arriving at the message period. When a Master and Slave are paired, there will be data messages at each period. When paired, the Slave then expects to see data from the Master at the message period. If the Slave does not see data, it reports this to the host at the same message period. In other words, the host can rely on getting SOME kind of message at the message period for timing purposes.

The framework is set up to be ready to code the handling of these event messages. Notice that we have added the debug message counters to track the number of ANT_DATA and ANT_TICK messages received.

WaitChannelClose State
Before adding code to process the event messages, lets finish and test the state machine. WaitChannelClose allows the system time to close the ANT Channel properly which might take a few message periods. When AntRadioStatusChannel() returns ANT_CLOSED the state can return to Idle. Since the system is waiting on an external event, UserApp1_u32Timeout is monitored and will kick the task to error if the timeout occurs.

Build the code to make sure there are no errors. Test that ChannelOpen works as expected when a channel is open with no Master sending in ANTware, but also when the Master channel is not broadcasting so the system times out. Make sure you test going through the whole state machines and exercising all possible conditions.

Again, if you have a code base before 2024-FEB-07, there is a missing line of code in ant.c that will prevent AntRadioStatusChannel() from returning ANT_CLOSED. Add the code shown below to clear the CHANNEL_CLOSE_PENDING flag in the MESG_CLOSE_CHANNEL_ID case in ant.c

ANT_TICK Processing

Now we add some intelligence to look into the ANT_TICK messages and respond to them appropriately. All we want to do is update the state of the LEDs and for debugging we will print out the event that occurred. Only new events will be displayed otherwise we will get way too many event messages that are not useful in this case. The event codes of interest are:

  • EVENT_RX_FAIL (EVENT 2): when an expected message from a synced master is missed
  • EVENT_RX_FAIL_GO_TO_SEARCH (EVENT 8): when several consecutive messages from the master are missed
  • EVENT_RX_FAIL_SEARCH_TIMEOUT (EVENT 1): when no master is found while searching for the amount of time configured by U8_ANT_SEARCH_TIMEOUT.

Add the code below to the ANT_TICK processing section in the ChannelOpen state. There are three response codes to be processed for the state machine, though a few others are added to complete the system.

Build and run the code. When the code is running, use ANTware on a Master channel to send messages to the EiE board. Try to get all the different event codes and understand why. The LED status is not yet entirely correct as there is more code to write, but it will be close. If the slave never times out, scroll back up to find out how to set the search timeout value.

Also try setting a break point on UserApp1_u32TickMsgCount++. Run through this several times to understand what is happening with the EVENT_CODES, u8LastState, and the switch. This will show you how halting the host processor will also cause some trouble for ANT since the system will stop processing messages. Set break points in different parts of the switch statement and experiment with turning the Master on and off in ANTware to see how the Slave behaves.

Reading and responding to new data

Now add code to process ANT_DATA. Use bGotNewData to track if the message data is new since there is no need to update repeated data. All 8 data bytes must be tracked and checked, which is why we set up the au8LastAntData[] variable. If there is new data, update it to the LCD and reply back with a message to the Master.

Note the LED adjustments at the beginning of the function as well. If data is being received, the blue LED remains solid. It is normal to receive regular EVENT 2 messages (a master message was missed) because that’s the nature of radio transmissions. A wireless data system must be able to handle missed messages (and there are many ways to do it depending on the importance of the data being transmitted).

Build and run the code and try sending some Acknowledged data messages from ANTware along with the regular Broadcast messages. Watch the EiE board LCD screen to confirm the Acknowledged messages are coming through. You can also change the content of the Broadcast data messages. Note the setting in the “General” tab that increments the last byte of the message.

Lastly, write code to look for a special Master message that starts with 0xA5. If the data packet contains this in byte 0, then look at bytes 1, 2, and 3. Turn on LCD LEDs corresponding to each byte if there is a “1” present, otherwise turn the LED off. This shows how easy it is to use the data that is sent in ANT.

Test the code and change the Broadcast data in ANTware to show the LCD backlight changing color.

You now have essentially everything you need to run an ANT wireless ultra low power embedded system. There are still a great many things to learn as far as designing and implementing a product, but much of that will depend on your specific implementation. The following exercises are good first steps to fortifying what you have learned and will let you build your experience with ANT:

  1. Send the full data string out the serial port for logging and display
  2. Show the received signal strength indicator value
  3. Have setable channel ID, period and frequency via the debug port or the user interface on the development board
  4. Offer scanning channel which accesses extended data to show all ANT devices in the vicinity and report the sender’s information

[LAST UPDATE: 2024-FEB-07]