Trouble with two-way communication

Started by jrdoner, January 24, 2016, 10:46:59 PM

TomWS

Quote from: jrdoner on January 30, 2016, 08:59:17 PM
As to one node controlling another, why should I need to do that?  Consider the air traffic control system; the pilots don't tell the controllers when to talk, nor vice versa.  If these radios require that, they're of very limited utility. 
I do believe that Air Traffic controllers/Pilots operate at different frequencies in a totally full duplex link.  A model very different than one you've created.
Quote
But the point is, why should this run for some period of time, then stop?  If its due to hardware, it usually is something like a missing pullup/down resistor.  If its software, then maybe its some sort of memory/variable overflow.  I have fiddled with this for a week of evenings, and I have used other radios that worked fine in similar contexts.   This seems like a perfectly reasonable way to check for traffic.
I will 'suggest' that EVERY ping pong protocol will fail at 'some' time if there is no contingency for lost packets or realization that 500us at one node does NOT equal 500uS at the other.

Tom

luisr320

Pilot/controller comms are on a single comm frequency. Normally a controller calls a pilot by his flight number and all other pilots wait until the controller finishes his message. Then they wait for the pilot to acknowledge the message by repeating it back to the controller. Then the controller fires another instruction to another pilot. Generally there is a small gap between each communication so that some other pilot may send some urgent message. Sometimes the controller speaks at the same time as a pilot, which will be noticed by all the other pilots on that frequency. When the controllers stops speaking, someone just say "Blocked" and the controller repeats his message. A duplex system would be nice to have, but not really required.

jrdoner

In response to Felix's recommendation, I went back and rewrote my ping-pong code to poll the radio every time I pass through the loop.  The code is as shown below.  The two programs involved differ only in that the one shown starts the messaging in the setup() procedure, and of course, the node ID's.    I still use a timer to send messages, but hey, surely they put timers in microcontrollers because virtually every real-world application uses them.   

/* Simple demo program providing radio transmission from a Moteino with minimum configutation*/

#include <RFM69.h>
#include <SPI.h>

#define NETWORKID    100                 //all nodes in network must use same network ID
#define NODEID       1                   //this node is 1
#define FREQUENCY    RF69_433MHZ
//#define ENCRYPTKEY  "adjjsk#6))L3ooa*"   
//16 characters, and the same for all communicating nodes

RFM69 radio;

boolean newData;
boolean sendMsg;
volatile unsigned long clockTick;
volatile unsigned long secTick;
int LEDPin = 9;


typedef struct
{
  int nodeID;
  int cmdType;
  int param1;
  int param2; 
  int param3;
  int param4;
}payload;

payload inData;

void setup( )
{
  radio.initialize(FREQUENCY,NODEID,NETWORKID);
  //radio.encrypt(ENCRYPTKEY);
 
  Serial.begin(115200);
  pinMode(LEDPin, OUTPUT);
  digitalWrite(LEDPin, LOW);
  
  //setup 2 kHz. timer for various timing operations
   
  cli();                                 //disable all interrupts
                                          //setting up timer2 for 0.5 ms. cycle
   TCCR2A = 0;                            //clear both timer2 configuration registers: gonna use as a 0.5 msec. timer
   TCCR2B = 0;  
   TCNT2  = 0;                            //set the counter register to 0, and set  
   OCR2A = 255;                           //128x64/16000000 = 0.512 ms.  
   TCCR2A = TCCR2A | B00000010;           //turn on CTC mode
   TCCR2B = B00001011;                    //CS1:2:0 to 101 to operate at clock speed/64 
                                          //this also establishes that WGM13:0 = 0100, i.e., CTC mode
   TIMSK2 = TIMSK2 | B00000010;           //enable the CTC interrupt                                                 
   sei();  

   newData = false;
   sendMsg = false;
   clockTick = 0;
   secTick = 0;

   inData.nodeID = 1;
   inData.param1 = 0;
   radio.sendWithRetry(2, (const void*)(&inData), sizeof(inData));            
   Serial.println("Initial data sent");
}  //setup


ISR(TIMER2_COMPA_vect)           
{
  clockTick++;                                                //counts half milliseconds
  if ((clockTick % 2000) == 0) secTick++;
  if ((clockTick % 1000) == 0) sendMsg = true; 
}//ISR(Timer0..

void wait(int millisec)
{
  unsigned long later;
  
  later = 2*millisec + clockTick;
  while (clockTick < later) {};
}  


void loop()                      //node 1 -- transmit first, wait for reply
{
   if (radio.receiveDone() && !newData)
   {
     inData = *(payload*)radio.DATA;   
     Serial.print("RSSI  "); Serial.print(radio.RSSI);
     Serial.print("  data: "); Serial.println(inData.param1);
     inData.param1 = inData.param1 + 1;
     newData = true;
   }  
  
  
   if (sendMsg && newData)    //this is triggered every 0.5 sec. if there is data to send
   {
     radio.sendWithRetry(2, (const void*)(&inData), sizeof(inData));
     sendMsg = false;
     newData = false;
   }  
}//loop


The results with this recoding is the same.  This ping-pong process might run for 10 cycles, or it might run for 200 cycles, but after a while, it hangs.

Things I've tried;

checking the RF environment;
changing out the SPI library for a different one;
not talking to either serial port while running.

No luck with anything.  And one other strangeness to report.  I didn't originally have the newData variable in there to prevent reading the radio even when no message was expected.  However, without the newData restriction anded with radioReceiveDone, the loop would drop into the radioReceiveDone code two or three times in succession, even though the data had already been pulled out on the first pass.

So at the moment, I am at my wit's end as to how to get these to work in any asynchronous system.   Suggestions please.


jrdoner

In desperation, I have taken Felix's advice and recoded my ping-pong program to read the radio every time I loop.  The code is as below, and the other program is identical, except for node ID and the fact that the program shown here sends the first message in setup().

#include <RFM69.h>
#include <SPI.h>

#define NETWORKID    100                 //all nodes in network must use same network ID
#define NODEID       1                   //this node is 1
#define FREQUENCY    RF69_433MHZ
#define ENCRYPTKEY  "adjjsk#6))L3ooa*"   
//16 characters, and the same for all communicating nodes

RFM69 radio;

boolean newData;
boolean sendMsg;
volatile unsigned long clockTick;
volatile unsigned long secTick;
int LEDPin = 9;


typedef struct
{
  int nodeID;
  int cmdType;
  int param1;
  int param2; 
  int param3;
  int param4;
}payload;

payload inData;

void setup( )
{
  radio.initialize(FREQUENCY,NODEID,NETWORKID);
  //radio.encrypt(ENCRYPTKEY);
 
  Serial.begin(115200);
  pinMode(LEDPin, OUTPUT);
  digitalWrite(LEDPin, LOW);
  
  //setup 2 kHz. timer for various timing operations
   
  cli();                                 //disable all interrupts
                                          //setting up timer2 for 0.5 ms. cycle
   TCCR2A = 0;                            //clear both timer2 configuration registers: gonna use as a 0.5 msec. timer
   TCCR2B = 0;  
   TCNT2  = 0;                            //set the counter register to 0, and set  
   OCR2A = 255;                           //128x64/16000000 = 0.512 ms.  
   TCCR2A = TCCR2A | B00000010;           //turn on CTC mode
   TCCR2B = B00001011;                    //CS1:2:0 to 101 to operate at clock speed/64 
                                          //this also establishes that WGM13:0 = 0100, i.e., CTC mode
   TIMSK2 = TIMSK2 | B00000010;           //enable the CTC interrupt                                                 
   sei();  

   newData = false;
   sendMsg = false;
   clockTick = 0;
   secTick = 0;

   inData.nodeID = 1;
   inData.param1 = 0;
   radio.sendWithRetry(2, (const void*)(&inData), sizeof(inData));            
   Serial.println("Initial data sent");
}  //setup


ISR(TIMER2_COMPA_vect)           
{
  clockTick++;                    //counts half milliseconds
  if ((clockTick % 2000) == 0) secTick++;
  if ((clockTick % 1000) == 0) sendMsg = true; 
}//ISR(Timer0..

void wait(int millisec)
{
  unsigned long later;
  
  later = 2*millisec + clockTick;
  while (clockTick < later) {};
}  


void loop()                      //node 1 -- transmit first, wait for reply
{
   if (radio.receiveDone() && !newData)
   {
     inData = *(payload*)radio.DATA;   
     Serial.print("RSSI  "); Serial.print(radio.RSSI);
     Serial.print("  data: "); Serial.println(inData.param1);
     inData.param1 = inData.param1 + 1;
     newData = true;
   }  
  
  
   if (sendMsg && newData)    //this is triggered every 0.5 sec. if there is data to send
   {
     radio.sendWithRetry(2, (const void*)(&inData), sizeof(inData));
     sendMsg = false;
     newData = false;
   }  
}//loop


The recoding has not helped.  The ping-pong game may last for five cycles, or it may last for 200, but it always eventually hangs.

Things I have tried, besides recoding.

1. checked the RF environment;
2. tried a different SPI library;
3. ran the program without talking to the serial ports.

Besides the fact that this process always hangs, I noticed one other strange thing.  If I remove the newData variable from the control statement reading the radio, sometimes the radio will be read several times in succession, even though the data was read the first time, and another message has certainly not arrived.

At this point, I have no idea why this doesn't work, and I certainly welcome any new suggestions.  Otherwise, I've got more than $100 worth of Moteinos, that I'll have to demote to Nanos.

luisr320

#19
I've spent the whole morning around this and I think I figured out what is wrong.

You probably have your two Moteinos right next to each other while making these tests.
The problem, I think, is that they just get overloaded with each other transmitting power, causing some kind of distortion and nothing gets trough after a while.

I have loaded two ping-pong sketches on two different Moteinos, making sure that, as I described on a couple of posts back regarding the way pilots and controllers talk, one of the Moteinos is the "Master" (the Controller) and the other is a "Slave" (the Pilot). This way, if the communication is lost, one of them, the "Master" takes the initiative to resend the data and the other goes back to be a listener. And once the data is received, an acknowledge (ACK) is sent back to the sender to make sure all data went trough.

The idea that works is as follows:

The Master has a sendMsg flag defined initially as true and the Slave as false.

Both radios have no restriction to enter the "if radio.Receivedone()" loop. So if something is received, they just check if the contents was destined to that node and act upon it.

So, when the Master first enters the loop and has his sendMsg as true, it sends the first data, a "0", to the Slave and waits for an ACK to be received back.

And when the Slave first enters the loop and has its SendMsg as false, it keeps running over the "if radio.Receivedone() loop" until something gets trough to it.
If something does came trough and it is was sent to that node, it shown the data content on the serial monitor, sends an ACK back to the Master and sets its sendMsg flag to true.
As the sendMsg flag now is true, it enters the radio.Sendwithretry() loop, sends a "1" and waits for an ACK to be received from the Master.

After the Master receives the ACK to the "0" that it sent to the Slave, it sets its sendMsg to false and waits for the Slave to send a new data, a "1".
When the "1" comes trough, it sends a ACK back to the Slave and sets if SendMsg flag to true and all starts over.

Here is a printscreen of both radios after sending more then a 150.000 successful ping pongs between them:


And here are the sketches:

MASTER:
#include <RFM69.h>
#include <SPI.h>

#define NETWORKID    300                 //all nodes in network must use same network ID
#define NODEID       1                   //this node is node 1, the "MASTER"
#define DESTINATION_NODE 2               //this is the "SLAVE" node
#define ACK_TIME 50                      //Time in ms to wait for an ACK to be received before it tries again
#define FREQUENCY    RF69_433MHZ
#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!

RFM69 radio;

boolean sendMsg = true; //I'm the "MASTER", so I will start first

//Data Struct
typedef struct
{
  unsigned long param1;
}payload;

payload inData;

void setup( )
{
  radio.initialize(FREQUENCY,NODEID,NETWORKID);
  #ifdef IS_RFM69HW
    radio.setHighPower();
  #endif
 
  Serial.begin(115200);
  Serial.println ("I'm the Master. I'll start first. Sending first packet now.");

  inData.param1 = 0;

}

void loop()
{
   if (radio.receiveDone()) //as often as possible
   {
     if (radio.TARGETID == 1)//Check if the packet destination is this radio (NODE 1)
      {
       inData = *(payload*)radio.DATA;  
       Serial.print("RSSI  "); Serial.print(radio.RSSI);
       Serial.print("  data: "); Serial.print(inData.param1); Serial.println("  A Moteino is not a Nano!");
       inData.param1 = inData.param1 + 1;
       radio.sendACK(); //Tell the "SLAVE" that all was received well
       sendMsg = true; 
      }
   }

   if (sendMsg) //If the sendMsg flag is set, send a new message to the "SLAVE"
   {
     if (radio.sendWithRetry(DESTINATION_NODE, (const void*)(&inData), sizeof(inData), 3, ACK_TIME))//Send the data to node 2 for processing, try that 3 times and wait 50ms each time for an ACK
     { 
        sendMsg = false;  // ACK received
     }
     else
     {
        sendMsg = true; // ACK was not received yet and the waiting timed out. Go back to be a "sender"
     }
   } 
}


And SLAVE:
#include <RFM69.h>
#include <SPI.h>

#define NETWORKID    300                 //all nodes in network must use same network ID
#define NODEID       2                   //this is the "SLAVE" node
#define DESTINATION_NODE 1               //this node is node 1, the "MASTER"
#define ACK_TIME 50                      //Time in ms to wait for an ACK to be received before it tries again
#define FREQUENCY    RF69_433MHZ
#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!

RFM69 radio;

boolean sendMsg = false; //I'm the "SLAVE" I will wait for the first packet

//Data Struct
typedef struct
{
  unsigned long param1;
}payload;

payload inData;

void setup( )
{
  radio.initialize(FREQUENCY,NODEID,NETWORKID);
  //radio.encrypt(ENCRYPTKEY);
  #ifdef IS_RFM69HW
    radio.setHighPower();
  #endif
 
  Serial.begin(115200);
  Serial.println ("I'm the Slave. I'm waiting for the first packet...");

  inData.param1 = 0;

}

void loop()
{
   if (radio.receiveDone()) //as often as possible
   {
   if (radio.TARGETID == 2) //Check if the packet destination is this radio (NODE 2)
    {
      inData = *(payload*)radio.DATA;   
      Serial.print("RSSI  "); Serial.print(radio.RSSI);
      Serial.print("  data: "); Serial.print(inData.param1); Serial.println("  Yes, I know...");
      inData.param1 = inData.param1 + 1;
      radio.sendACK(); //Tell the "MASTER" that all was received well
      sendMsg = true; 
    }
   }
 
   if (sendMsg) //If the sendMsg flag is set, send a new message to the "MASTER"
   {
     if (radio.sendWithRetry(DESTINATION_NODE, (const void*)(&inData), sizeof(inData), 3, ACK_TIME))//Send the data to node 1 for processing, try that 3 times and wait 50ms each time for an ACK
     { 
       sendMsg = false; // ACK received
     }
     else
     {
       sendMsg = false; //  ACK was not received yet and the waiting timed out. Go back to be a "listener"
     }
   } 
 }



Now say it: "A Moteino is not a Nano". Nothing is like a Moteino.  :)



jrdoner

LuisR320,

Thanks for the very excellent examples.  My problem was not overload, my RSSI readings are at -20.   I had tried using sendWithRetry in a previous version, with no improvement.  So the distinction in your code is indeed that you will keep trying to send from the master, even if the first sendWithRetry() wasn't enough. 

In my case, I have a master node, and 5 remotes.  The remotes are watching the world around them, and may communicate at any time.  Likewise for the master.  So I am going to try to adapt this so that everybody gets through to everybody, and if somebody dies, it is duly recorded by the master.

At any rate, your work is tutorially first rate.  You should write a guide to Moteinos. 


jrdoner

One other thought:  if more than one node were trying to get to the master and both continually transmitted, they would probably just create a continuing wall of intererence.  So if the first sendWithRetry() fails, the software should insert a short random delay before the next attempt.

luisr320

#22
As Felix said, delay() is a bad practice. You shouldn't block the access to the radio.ReceiveDone().
I have a lot of Moteinos firing all kind of traffic to my Gateway, just another Moteino that I considered as such, and they all mange to pass their information correctly without dropping any data.
The trick is to not have the Remote nodes firing data at every milisecond but only when necessary, like when some event happened, or every 10 seconds or whatever. Just not all the time.
Don't make them wait. Just make them check on each loop if something is in the mailbox (radio) for them. If there is, send an ACK to that node and do something with the new data.
I have my nodes sending a ping every 10 seconds to the Gateway so that the Gateway may access if any node is down and set some flag to advise me if no Pings are received from any node for more then 10 seconds.

Felix

It's all about how receiveDone() works. You have to understand it, and the effect it has on the state of the radio - which can be in several modes of operation, only 1 at 1 time - RX, STANDBY, TX etc.

After you've put the radio in RX, and it receives a packet, it stops receiving, generates an interrupt to the RFM69 library handler, which reads the packet into a memory buffer. Then it's ready for you to pick up later. It is assumed that after a receiveDone = true you will immediately read the packet from the library buffers before calling receiveDone() again, - which will effectively clear everything and put the radio in RX again. That's just how it was done. You can make your own fork and change that state machine to fit your needs.