Starting to play around with my pair of Moteinos, I added a simple packet retry with exponential backoff delay to the "Send" demo sketch (based on how I vaguely recall Ethernet collision algorithms work). My application is measuring pulse width on an input pin, and sending the measurement out via RF packet. It was simple if we only send once and assume it works. Once I've allowed for possible packet retries, I may need to deal with more than one thing happening at once, if several retries may be needed meanwhile data is still coming in. So I need to put the "not yet ACK'd" packets into a buffer and send them out according to a schedule, while maintaining CPU availability to acquire the input data.
This is probably a common scenario and maybe someone has already published an example? (Just checking in case I can save some programming time.)
The examples I provided are simplistic, no complex buffering scheme. You can certainly do that, but from my perspective, most packets should go through on the first or perhaps second retry, which takes a dozen or two milliseconds, maybe more if it's a longer packet + roundtrip. After that point, queuing means the data is most likely stale at that point. Think of reading some sensor or doing some action that needs to be executed NOW, not 1 second later (which might be too late).
The receiving end is interrupt based, so you could send your packet, and then periodically check for the ACK (which is just a packet with an extra bit to indicate the ACK). But keep in mind any extra received packets will overwrite the current received packet in the RX buffer. Anyway, this way you can do other things while checking for an expected ACK.
Yes, my slightly modified versions of your Send and Receive sketch report that a 1-byte packet is about 10 msec (including the return ACK) and an 88-byte packet is 33 msec. In my application, I prefer to get the data through even if it is several seconds old, so I'm willing to live with several retries and pauses. In case of interest, my current test code is below
// Simple RFM12B sender program, with ACK
// [email protected], mods JBeale Oct. 15 2013
#include <RFM12B.h>
#include <avr/sleep.h>
// You will need to initialize the radio by telling it what ID it has and what network it's on
// The NodeID takes values from 1-127, 0 is reserved for sending broadcast messages (send to all nodes)
// The Network ID takes values from 0-255
// By default the SPI-SS line used is D10 on Atmega328. You can change it by calling .SetCS(pin) where pin can be {8,9,10}
#define NODEID 2 //network ID used for this unit
#define NETWORKID 99 //the network ID we are on
#define GATEWAYID 1 //the node ID we're sending to
#define ACK_TIME 50 // # of ms to wait for an ack
#define RETRY_LIMIT 8 // # of times to retry if no ACK. Must be < 16
#define SLOT_TIME 33 // pretend we have a fixed packet transmission time for CDMA collision algorithm
#define MAX_SEND 25 // maximum packet size
#define SERIAL_BAUD 115200
//encryption is OPTIONAL
//to enable encryption you will need to:
// - provide a 16-byte encryption KEY (same on all nodes that talk encrypted)
// - to call .Encrypt(KEY) to start encrypting
// - to stop encrypting call .Encrypt(NULL)
uint8_t KEY[] = "ABCDABCDABCDABCD";
int interPacketDelay = 1000; //wait this many ms between sending packets
char input = 0;
unsigned char LED = 9; // LED is D9 on Motetino
// Need an instance of the Radio Module
RFM12B radio;
byte sendSize=0;
char payload[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890~!@#$%^&*(){}[]`|<>?+=:;,.";
bool requestACK=false;
long totalRetries = 0;
long totalPackets = 0;
unsigned short retryHist[MAX_SEND];
void setup()
{
pinMode(LED, OUTPUT);
Serial.begin(SERIAL_BAUD);
radio.Initialize(NODEID, RF12_915MHZ, NETWORKID);
radio.Encrypt(KEY);
radio.Sleep(); //sleep right away to save power
Serial.println("JPB Test Transmitting...\n\n");
for (int i=0;i<MAX_SEND;i++) {
retryHist[i]=0; // initialize retries histogram to 0
}
}
void loop()
{
long ackTime; // time between TX and RX of ACK
digitalWrite(LED,HIGH);
Serial.print("Sending[");
Serial.print(sendSize+1);
Serial.print("]:");
requestACK = true; // Pay attention. Everything I say is important :-)
unsigned short retries = 0;
radio.Wakeup();
long sendTime = millis();
unsigned short slots = 1;
radio.Send(GATEWAYID, payload, sendSize+1, requestACK);
totalPackets++;
if (requestACK)
{
Serial.print(" - wait ");
unsigned char timeout = 12 + (sendSize+1)/4; // Actual: 1 char = 10 msec, 88 characters = 33 msec
Serial.print(timeout);
while (!waitForAck(timeout) && (retries < RETRY_LIMIT)) {
long dtime = SLOT_TIME * random(slots); // see: wikipedia "exponential backoff"
delay(dtime);
radio.Send(GATEWAYID, payload, sendSize+1, requestACK);
retries++;
slots = slots *2;
}
ackTime = millis() - sendTime;
if (retries < RETRY_LIMIT) {
Serial.print(" On try ");
Serial.print(retries+1);
Serial.print(" ACK after ");
} else {
Serial.print(" No ACK after ");
}
}
radio.Sleep();
digitalWrite(LED,LOW);
totalRetries += retries;
retryHist[sendSize] += retries; // record retry count in histogram
Serial.print(ackTime); Serial.print(" msec. Packets: ");
Serial.print(totalPackets); Serial.print(" Retries: ");
Serial.print(totalRetries);
sendSize = (sendSize + 1) % MAX_SEND;
Serial.println();
if (!(totalPackets % 50)) {
for (int i=0;i<MAX_SEND;i++) {
Serial.print(i+1);
Serial.print(",");
Serial.println(retryHist[i]); // print how many total retries for each packet length
}
}
delay(interPacketDelay); // low power sleep mode?
}
// wait a few milliseconds for proper ACK, return true if received
static bool waitForAck(unsigned char timeout) {
long now = millis();
while (millis() - now <= timeout)
if (radio.ACKReceived(GATEWAYID))
return true;
return false;
}
Thanks for sharing and formatting the code with the code tags :)
I'm wondering though .. were you able to send an 88 byte packet? Or was it truncated at 63 bytes even if you dumped 88 bytes in the buffer?
The reason I ask is because the chip has a 66 byte buffer of which a few bytes of overhead from the library.
My as-modified code example only goes to 25 data payload bytes, but previously with your original code, it did print out
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890~!@#$%^&*(){}[]`|<>?+=:;,." and say CRC OK, so I assume it was successfully receiving them all.
Oh wait, I thought it was RFM69, never mind!
The RFM12B lib can send up to 128 bytes messages :)