In the above picture you’ll see the MOSI (Master out Slave in) and RTS connection point for the AP2 module on the other side. There was a short. A super fine wire or a sliver of solder. Either way, there was a barely noticeable connection. More after the break including the working code!
I noticed this when I tried to incorporate the RTS line into the code to check when the AP2 thinks it’s okay to send. It didn’t work. I decided maybe the TX line floats and the signal isn’t getting through so I took a 10k resistor between TX and ground. Nothing. Then I thought, maybe the AP2 RTS line needs external termination. I didn’t think so, but figured I’d try by holding a resistor to the RTS. When I went to hold it in place I saw the sliver. I knew what could fix this!
My trusty Gerber Clutch that cost me a total of 14.99 at Canadian Tire and has saved the day more than once. I cut the connection and voila! The event counts match up and the ANT+ USB stick with ANT Display Simulator never missed a beat! Below is the serial output with the counts. The last count is 20, and the last update is 20 in the Display Simulator. I included another one that has the received codes for a couple of updates.
My code is below. The ANT+ network key is VETTED as a requirement of ANT+ licence agreement as an ANT+ Adopter. I love ANT+, I want to promote it, but I don’t want to anger the good folks at Dynastream / Garmin / and any hard working ANT+ Alliance member. ANT+, it just works… so long as you are decent at soldering?
Cheers
/*
Keith Wakeham Power Meter
ANT+
*/
#define UCHAR unsigned char
#define MESG_TX_SYNC ((UCHAR)0xA4)
#define MESG_SYSTEM_RESET_ID ((UCHAR)0x4A)
#define MESG_NETWORK_KEY_ID ((UCHAR)0x46)
#define MESG_ASSIGN_CHANNEL_ID ((UCHAR)0x42)
#define MESG_CHANNEL_ID_ID ((UCHAR)0x51)
#define MESG_CHANNEL_RADIO_FREQ_ID ((UCHAR)0x45)
#define MESG_CHANNEL_MESG_PERIOD_ID ((UCHAR)0x43) // Set channel period 0x43
#define MESG_RADIO_TX_POWER_ID ((UCHAR)0x47) // Set Tx Power 0x47
#define MESG_CHANNEL_SEARCH_TIMEOUT_ID ((UCHAR)0x44) // Set Channel Search Timeout 0x44
#define MESG_OPEN_CHANNEL_ID ((UCHAR)0x4B) // ID Byte 0x4B
#define MESG_BROADCAST_DATA_ID ((UCHAR)0x4E)
// inslude the SPI library:
#include <SPI.h>
#include <TimerOne.h>
const int slaveSelectPinL = 9; // set pin 8 as the slave select for Left ADC
const int slaveSelectPinR = 8; // set pin 8 as the slave select for Left ADC
const int Mag_pickup = 6; // set pin 8 as the slave select for Left ADC
//For ANT+ output
byte eventcount = 0;
uint16_t ANT_power = 0;
uint8_t ANT_icad = 0; // Instant Cadence
uint16_t accucrankperiod = 0; //Accumulated Crank Period
uint16_t ANT_Torque = 0;
//ADC data
short zeroL = 0;
short zeroR = 0;
long ADC_L = 0;
short ADCL_count = 0;
long ADC_R = 0;
short ADCR_count = 0;
boolean wipe = 0;
double TorqueL = 0;
double TorqueR = 0;
double Torque_sum = 0;
uint8_t ANT_LR = 0;
uint16_t ANT_Period = 0;
long ANT_cranktick = 0;
long ANT_cranktick_OLD = 0;
byte ANT_Cadence = 0;
boolean skipper = 0;
//message processor
boolean msgsync = 0;
uint8_t sync = 0;
uint8_t msglength = 0;
uint8_t msgbuf[10];
uint8_t sendcount = 0;
int buttonState; // the current reading from the input pin
int lastButtonState = LOW; // the previous reading from the input pin
long lastDebounceTime = 0; // the last time the output pin was toggled
long debounceDelay = 30; // the debounce time; increase if the output flickers
boolean sent = 0;
void setup() {
pinMode (slaveSelectPinL, OUTPUT); // set the slaveSelectPin Left as an output:
pinMode (slaveSelectPinR, OUTPUT); // set the slaveSelectPin Left as an output:
pinMode(Mag_pickup, INPUT);
digitalWrite(slaveSelectPinL,HIGH);
digitalWrite(slaveSelectPinR,HIGH);
SPI.setClockDivider(SPI_CLOCK_DIV4); // 8mhz, set the divider to 4
SPI.begin(); // initialize SPI:
Serial1.begin(4800);
Serial.begin(115200);
delay(10);
Serial1.flush();
reset();
delay(5000);
Serial1.flush();
delay(50);
initiate();
delay(20);
//Timer1.initialize(600000);
//Timer1.attachInterrupt(callback);
zeroL = zeroadcL();
zeroR = zeroadcR();
// Serial.print("LZ:");
// Serial.print(zeroL);
// Serial.print(" RZ:_");
// Serial.println(zeroR);
}
void loop() {
readadc_B();
ANTrec();
}
void ANTrec()
{
int sbuflength = Serial1.available();
uint8_t msg = 0;
while(sbuflength > 0)
{
//Serial.print("sbuf: ");
//Serial.println(sbuflength);
if (msgsync == 0)
{
msg = Serial1.read();
if (msg == 0xA4)
{
msgsync = 1;
sync = 0;
}
sbuflength = Serial1.available();
}
else if (msgsync == 1 && sync == 0)
{
msglength = Serial1.read();
sbuflength = Serial1.available();
msglength += 3;
msgbuf[0] = 0xA4;
msgbuf[1] = (msglength-3);
sync++;
}
else if (msgsync == 1 && sync > 0 && sync < msglength)
{
msgbuf[sync+1] = Serial1.read();
sbuflength = Serial1.available();
sync++;
}
else if (msgsync == 1 && sync == msglength)
{
msgbuf[sync+1] = Serial1.read();
sbuflength = Serial1.available();
ANTrecproc(msgbuf, msglength);
//Serial.println("");
msgsync = 0;
sync = 0;
msglength = 0;
}
}
}
void ANTrecproc(uint8_t ANTbuf[], uint8_t ANTlength)
{
if (ANTbuf[2] == 0x40)
{
if (ANTbuf[4] == 0x01) //Transmit Event
{
if (ANTbuf[5] ==3)
{
//Serial.println("transmit success");
if (sendcount < 4)
{
basicpower();
sendcount++;
//Serial.println("Crank Torque");
}
else if (sendcount == 4)
{
basicpower();
//cranktorque();
sendcount = 0;
//Serial.println("Basic Power");
}
}
}
else if (ANTbuf[4] == 0x46 ) //Set Network Key
{
if (ANTbuf[5] ==0)
{
Serial.println("Set Netowork Key: No Error");
}
}
else if (ANTbuf[4] == 0x42 ) // Assign Channel Assigned
{
if (ANTbuf[5] == 0)
{
Serial.println("Assign Channel: No Error");
}
}
else if (ANTbuf[4] == 0x51 ) // Set Channel ID
{
if (ANTbuf[5] == 0)
{
Serial.println("Set Channel ID: No Error");
}
}
else if (ANTbuf[4] == 0x45 ) // Set Frequency
{
if (ANTbuf[5] == 0)
{
Serial.println("Set Frequency: No Error");
}
}
else if (ANTbuf[4] == 0x43 ) // Set Period
{
if (ANTbuf[5] == 0)
{
Serial.println("Set Period: No Error");
}
}
else if (ANTbuf[4] == 0x47 ) //Transmit Power
{
if (ANTbuf[5] == 0)
{
Serial.println("Transmit Power: No Error");
}
}
else if (ANTbuf[4] == 0x44 ) //Set Timeout
{
if (ANTbuf[5] == 0)
{
Serial.println("Set Timeout: No Error");
}
}
else if (ANTbuf[4] == 0x4B ) //Open Channel
{
if (ANTbuf[5] == 0)
{
Serial.println("Open Channel: No Error");
}
}
}
// for(int i = 0 ; i <= ANTlength ; i++)
// {
// Serial.print(ANTbuf[i], HEX);
// Serial.print(" ");
// }
}
void callback()
{
wipe = 1;
eventcount++;
ANT_cranktick_OLD = ANT_cranktick;
ANT_cranktick = millis();
ANT_Period += uint16_t(double(ANT_cranktick - ANT_cranktick_OLD)*2.048); // divide 1000 and in
ANT_icad = uint8_t(long(60000/(ANT_cranktick - ANT_cranktick_OLD)));
readadc_B_reset();
ANT_power += uint16_t(double(Torque_sum * (double(ANT_icad)/60)*6.28));
}
void readadc_B()
{
if (wipe == 1)
{
ADC_L = 0;
ADCL_count = 0;
ADC_R = 0;
ADCR_count =0;
wipe = 0;
}
else if (wipe == 0)
{
for (int i = 0; i < 100; i++)
{
ADC_L += long(readadcL());
ADCL_count++;
ADC_R += long(readadcR());
ADCR_count++;
Crank_Pickup();
}
}
}
void Crank_Pickup()
{
int reading = digitalRead(Mag_pickup);
if (reading != lastButtonState) {
// reset the debouncing timer
lastDebounceTime = millis();
sent = 0;
}
if ((millis() - lastDebounceTime) > debounceDelay) {
buttonState = reading;
if (buttonState == 1)
{
if (sent == 0)
{
// Serial.println(eventcount);
// eventcount++;
sent = 1;
callback();
}
}
}
lastButtonState = reading;
// if (digitalRead(Mag_pickup) == 1)
// {
// if ((millis()-ANT_cranktick_OLD) > 350)
// {
// //callback();
//
// }
// }
}
void readadc_B_reset()
{
ADC_L = ADC_L/ADCL_count;
ADC_R = ADC_R/ADCR_count;
TorqueL = double((double(zeroL) - double(ADC_L)) * 0.1725/22.7);
if (TorqueL < 0)
{
TorqueL = 0;
}
TorqueR = double((double(ADC_R) - double(zeroR)) * 0.1725/21.7);
if (TorqueR < 0)
{
TorqueR = 0;
}
Torque_sum = TorqueL + TorqueR;
ANT_Torque += uint16_t(double(Torque_sum*32.0));
if (Torque_sum < 0.5)
{
ANT_LR = 50;
ANT_LR = ANT_LR^0x80;
}
else
{
ANT_LR = uint8_t(double((TorqueR / Torque_sum) * 100));
ANT_LR = ANT_LR^0x80;
}
// Serial.print(" ADCsum(L/R)-C: ");
// Serial.print(ADC_L);
// Serial.print(" / ");
// Serial.print(ADC_R);
// Serial.print("Torque_L: ");
// Serial.print(TorqueL,2);
// Serial.print(" Torque_R: ");
// Serial.print(TorqueR,2);
// Serial.print(" Torque_Sum: ");
// Serial.print(Torque_sum,2);
// Serial.print(" ANT_Torque: ");
// Serial.print(ANT_Torque);
// Serial.print(" ANT_LR: ");
// Serial.println(ANT_LR);
ADC_L = 0;
ADCL_count = 0;
ADC_R = 0;
ADCR_count = 0;
}
short readadcL1000()
{
long adcl = 0; //initialize a long variable
for (int i=0; i < 1000; i++){adcl += long(readadcL());} //Read 1000 times
return (short((adcl/1000))); //Output the average
}
short readadcR1000()
{
long adcr = 0; //initialize a long variable
for (int i=0; i < 1000; i++){adcr += long(readadcR());} //Read 1000 times
return (short((adcr/1000))); //Output the average
}
short zeroadcL()
{
long adcl = 0; //initialize a long variable
for (int i=0; i < 20000; i++){adcl += long(readadcL());} //Read 1000 times
return (short((adcl/20000))); //Output the average
}
short zeroadcR()
{
long adcr = 0; //initialize a long variable
for (int i=0; i < 20000; i++){adcr += long(readadcR());} //Read 1000 times
return (short((adcr/20000))); //Output the average
}
short readadcL() {
digitalWrite(slaveSelectPinL,LOW); // take the SS pin low to select the chip:
byte one = SPI.transfer(0xFF); // send in the address and value via SPI:
byte two = SPI.transfer(0xFF);
byte three = SPI.transfer(0xFF);
digitalWrite(slaveSelectPinL,HIGH); // take the SS pin high to de-select the chip:
return (one << 14 | two << 6 | three >> 2); //output the value from the three bytes as one short
}
short readadcR() {
digitalWrite(slaveSelectPinR,LOW); // take the SS pin low to select the chip:
byte one = SPI.transfer(0xFF); // send in the address and value via SPI:
byte two = SPI.transfer(0xFF);
byte three = SPI.transfer(0xFF);
digitalWrite(slaveSelectPinR,HIGH); // take the SS pin high to de-select the chip:
return (one << 14 | two << 6 | three >> 2); //output the value from the three bytes as one short
}
UCHAR checkSum(UCHAR *data, int length)
{
int i;
UCHAR chksum = data[0];
for (i = 1; i < length; i++)
chksum ^= data[i]; // +1 since skip prefix sync code, we already counted it
return chksum;
}
void reset ()
{
uint8_t buf[5];
buf[0] = MESG_TX_SYNC; // SYNC Byte
buf[1] = 0x01; // LENGTH Byte
buf[2] = MESG_SYSTEM_RESET_ID; // ID Byte
buf[3] = 0x00; // Data Byte N (N=LENGTH)
buf[4] = checkSum(buf,4);
ANTsend(buf,5);
}
void SetNetwork()
{
uint8_t buf[13];
buf[0] = MESG_TX_SYNC; // SYNC Byte
buf[1] = 0x09; // LENGTH Byte
buf[2] = MESG_NETWORK_KEY_ID; // ID Byte
buf[3] = 0x00; // Data Byte N (
buf[4] = 0xXX; // Data Byte N (N=LENGTH)
buf[5] = 0xXX; // Data Byte N (N=LENGTH)
buf[6] = 0xXX; // Data Byte N (N=LENGTH)
buf[7] = 0xXX; // Data Byte N (N=LENGTH)
buf[8] = 0xXX; // Data Byte N (N=LENGTH)
buf[9] = 0xXX; // Data Byte N (N=LENGTH)
buf[10] = 0xXX; // Data Byte N (N=LENGTH)
buf[11] = 0xXX; // Data Byte N (N=LENGTH)
buf[12] = checkSum(buf, 12);
ANTsend(buf,13);
}
void assignch()
{
uint8_t buf[7];
buf[0] = MESG_TX_SYNC; // SYNC Byte
buf[1] = 0x03; // LENGTH Byte
buf[2] = MESG_ASSIGN_CHANNEL_ID; // ID Byte
buf[3] = 0x00; // Channel Number
buf[4] = 0x10; // Channel Type
buf[5] = 0x00; // Network ID
buf[6] = checkSum(buf,6);
ANTsend(buf,7);
}
void SetChID()
{
uint8_t buf[9];
buf[0] = MESG_TX_SYNC; // SYNC Byte
buf[1] = 0x05; // LENGTH Byte
buf[2] = MESG_CHANNEL_ID_ID; // Assign Channel ID 0x51
buf[3] = 0x00; // channel number
buf[4] = 0x05; // Device number
buf[5] = 0x00; // Device number
buf[6] = 0x0B; //Device type ID
buf[7] = 0x00; //Transmission type -CHANGED
buf[8] = checkSum(buf, 8);
ANTsend(buf,9);
}
void ANTsend(uint8_t buf[], int length){
//Serial.print("ANTsend TX: ");
for(int i = 0 ; i <= length ; i++)
{
//Serial.print(buf[i], HEX);
//Serial.print(" ");
Serial1.write(buf[i]);
}
//Serial.println("");
}
void SetFreq()
{
uint8_t buf[6];
buf[0] = MESG_TX_SYNC; // SYNC Byte
buf[1] = 0x02; // LENGTH Byte
buf[2] = MESG_CHANNEL_RADIO_FREQ_ID; // Set Channel RF Freq 0x45
buf[3] = 0x00; // Channel number
buf[4] = 0x39; // Frequency
buf[5] = checkSum(buf, 5);
ANTsend(buf,6);
}
void SetPeriod()
{
uint8_t buf[7];
buf[0] = MESG_TX_SYNC; // SYNC Byte
buf[1] = 0x03; // LENGTH Byte
buf[2] = MESG_CHANNEL_MESG_PERIOD_ID; // Set channel period 0x43
buf[3] = 0x00; // Channel number
buf[4] = 0xF6; // Messaging Period byte1
buf[5] = 0x1f; // Messaging period byte2
buf[6] = checkSum(buf, 6);
ANTsend(buf,7);
}
void SetPower()
{
uint8_t buf[6];
buf[0] = MESG_TX_SYNC; // SYNC Byte
buf[1] = 0x02; // LENGTH Byte
buf[2] = MESG_RADIO_TX_POWER_ID; // Set Tx Power 0x47
buf[3] = 0x00; // Channel Number
buf[4] = 0x03; // Tx power
buf[5] = checkSum(buf, 5);
ANTsend(buf,6);
}
void SetTimeout()
{
uint8_t buf[6];
buf[0] = MESG_TX_SYNC; // SYNC Byte
buf[1] = 0x02; // LENGTH Byte
buf[2] = MESG_CHANNEL_SEARCH_TIMEOUT_ID; // Set Channel Search Timeout 0x44
buf[3] = 0x00; // Channel number
buf[4] = 0x1E; // Set timeout
buf[5] = checkSum(buf, 5);
ANTsend(buf,6);
}
void OpenChannel()
{
uint8_t buf[5];
buf[0] = MESG_TX_SYNC; // SYNC Byte
buf[1] = 0x01; // LENGTH Byte
buf[2] = MESG_OPEN_CHANNEL_ID; // ID Byte 0x4B
buf[3] = 0x00;
buf[4] = checkSum(buf, 4);
ANTsend(buf,5);
}
void initiate()
{
SetNetwork();
delay(100);
assignch();
delay(100);
SetChID();
delay(100);
SetFreq();
delay(100);
SetPeriod();
delay(100);
SetPower();
delay(100);
SetTimeout();
delay(100);
OpenChannel();
delay(100);
}
void basicpower()
{
uint8_t buf[13];
buf[0] = MESG_TX_SYNC; // SYNC Byte
buf[1] = 0x09; // LENGTH Byte
buf[2] = MESG_BROADCAST_DATA_ID; // 0x4E
buf[3] = 0x00; // Channel number
buf[4] = 0x10; // Basic power page identifier
buf[5] = eventcount; // Event count
buf[6] = ANT_LR; // Power differential
buf[7] = ANT_icad; // Instant Cadence
buf[8] = byte(ANT_power & 0xFF); // Accumulated power LSB
buf[9] = byte((ANT_power >> 8) & 0xFF); // Accumulated power MSB
buf[10] = 0x06; // Instant power LSB
buf[11] = 0x07; // Instant power MSB
buf[12] = checkSum(buf, 12);
ANTsend(buf, 13);
}
void cranktorque()
{
uint8_t buf[13];
buf[0] = MESG_TX_SYNC; // SYNC Byte
buf[1] = 0x09; // LENGTH Byte
buf[2] = MESG_BROADCAST_DATA_ID;
buf[3] = 0x00; // Channel number
buf[4] = 0x12; // Crank torque identifier
buf[5] = eventcount; // Update Event count
buf[6] = eventcount; // Crank ticks
buf[7] = ANT_icad; // Instant Cadence
buf[8] = byte(ANT_Period & 0xFF); // Accumulated Crank Period LSB
buf[9] = byte((ANT_Period >> 8) & 0xFF); // Accumulated Crank Period MSB
buf[10] = byte(ANT_Torque & 0xFF); // Accumulated crank Torque LSB
buf[11] = byte((ANT_Torque >> 8) & 0xFF); // Accumulated crank Torque MSB
buf[12] = checkSum(buf, 12);
ANTsend(buf, 13);
}