Problem with VirtualWire on the long run

Hi all,

I'm trying to make 2 arduinos communicate through 433MHz RF. The idea is to make the 1st one an "RF card" plugged in a PC (USB) to send data to the distant arduino. And to make the overall goal clearer I use both arduinos to send infra red signals to 2 air conditionners, one being in my living room, the other in the bedroom. I use a "full duplex" communication as I want to send orders as well as to receive an acknoledgement (RF is not that reliable) and sensors information (temperature).
My problem is that at some point the message received becomes incorrect, only 1, 2 or 4 bytes (depending on the test) are received instead of 9, and from this point on the communication never comes back to normal unless I reset the arduinos (and the final "distant" device is actually the microcontroler only, not the full arduino, no reset button has been added, I need it to be reliable).

Starting from the examples transmitter and receiver with a Seeduino (<=> Arduino duemilanove) and an arduino Uno, I launch the following codes :

Transmitter:

#include <VirtualWire.h>

const int LEDPin = 13;
uint8_t message[] = {0x09, 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF};

uint8_t buf[VW_MAX_MESSAGE_LEN];
uint8_t buflen = VW_MAX_MESSAGE_LEN;
void setup()
{	
  Serial.begin(9600);
  pinMode(LEDPin,OUTPUT);
  vw_set_ptt_inverted(true); // Required for DR3100
  vw_setup(2000);	 // Bits per sec
  vw_rx_start();       // Start the receiver PLL running
}

void loop()
{
    sendRadioMessage();
    Serial.println();
    delay(250);
}

void sendRadioMessage(){
  boolean timeoutOK;
  vw_send((uint8_t *)message, message[0]);
  vw_wait_tx(); // Wait until the whole message is gone
}

Receiver:

#include <VirtualWire.h>

const int LEDPin = 5;

void setup()
{
  Serial.begin(9600);	// Debugging only
  Serial.println("setup");

  pinMode(LEDPin, OUTPUT);

  // Initialise the IO and ISR
  vw_set_ptt_inverted(true); // Required for DR3100
  vw_setup(2000);	 // Bits per sec

  vw_rx_start();       // Start the receiver PLL running
}

void loop()
{
  uint8_t buf[VW_MAX_MESSAGE_LEN];
  uint8_t buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) // Non-blocking
  {
    flashLED (buf[0] == buflen);
    Serial.print("messageSize= ");
    Serial.println(buf[0]);
    for (int i = 0; i < buflen; i++)
    {
      Serial.print(buf[i], HEX);
      Serial.print(" ");
    }
    Serial.println();
  }
}

void flashLED(boolean messageOK){
  if (messageOK){
    digitalWrite(LEDPin, HIGH); 
    delay(10);
    digitalWrite(LEDPin, LOW); 
  }
  else{
    for(int i = 0 ; i < 10 ; i++){
      digitalWrite(LEDPin, HIGH); 
      delay(30);
      digitalWrite(LEDPin, LOW); 
      delay(30);
    }
  }
}

The first byte of the buffer sent indicates the message length.

This test is OK, it prints (on the receiver side) for hours:
[...]
messageSize= 9
9 DE AD BE EF DE AD BE EF
messageSize= 9
9 DE AD BE EF DE AD BE EF
[...]

I then modify the code to include a response. The receiver sends back the message to the emitter.

Emitter:

#include <VirtualWire.h>

const int LEDPin = 13;
uint8_t message[] = {0x09,0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF};

long timeoutMs = 1000;

uint8_t buf[VW_MAX_MESSAGE_LEN];
uint8_t buflen = VW_MAX_MESSAGE_LEN;
void setup()
{	
  Serial.begin(9600);
  pinMode(LEDPin,OUTPUT);
  vw_set_ptt_inverted(true); // Required for DR3100
  vw_setup(2000);	 // Bits per sec
  vw_rx_start();       // Start the receiver PLL running
}

void loop()
{
    sendRadioMessage();
    Serial.println();
    delay(250);
}

void sendRadioMessage(){
  boolean timeoutOK;
  vw_send((uint8_t *)message, message[0]);
  vw_wait_tx(); // Wait until the whole message is gone
  long timerStart = millis();
  timeoutOK = true;
  while (!vw_get_message(buf, &buflen) && timeoutOK){
    timeoutOK = (millis() - timerStart < timeoutMs);
  }

  boolean messageOK = true;
  if (!timeoutOK){
    Serial.print("No Ack!");
    messageOK = false;
  }
  else{
    //Serial.print(" => ");
    for (int i = 0 ; i < buflen ; i++){
      Serial.print(buf[i],HEX);
      Serial.print(" ");
      if (buf[i] != message[i]){
        messageOK = false;
      }
    }
    Serial.println();
  }
  flashLED(messageOK);
}

void flashLED(boolean messageOK){
  if (messageOK){
    digitalWrite(LEDPin, HIGH); 
    delay(30);
    digitalWrite(LEDPin, LOW); 
  }
  else{
    for(int i = 0 ; i < 10 ; i++){
      digitalWrite(LEDPin, HIGH); 
      delay(30);
      digitalWrite(LEDPin, LOW); 
      delay(30);
    }
  }
}

Receiver: only add a response at the end of the loop

[...]
void loop()
{
  uint8_t buf[VW_MAX_MESSAGE_LEN];
  uint8_t buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) // Non-blocking
  {
    flashLED (buf[0] == buflen);

    Serial.print("messageSize= ");
    Serial.println(buf[0]);
    for (int i = 0; i < buflen; i++)
    {
      Serial.print(buf[i], HEX);
      Serial.print(" ");
    }
    Serial.println("");
    vw_send((uint8_t *)buf, buflen);
    vw_wait_tx(); // Wait until the whole message is gone
  }
}
[...]

The first thing that annoys me is that if I don't connect the Serial monitor to the emitter it doesn't receive the response 1 time out of 3... I suppose it's a delay problem but I am unable to determine where it lies. Anyway the use case in my project is to communicate with it through the Serial interface so it should work. When connected I really seldom have a timeout.

This works for some time then the message received back from the initial emitter is only 2 bytes.
I read the Serial.prints from the emitter but the receiver still flashes the LED one time if the message is ok (at least if buf[0] == message length) so I can see if the problem comes from the initial message or the response.
Output (after a few dozains of minutes):

[...]
9 DE AD BE EF DE AD BE EF 
9 DE AD BE EF DE AD BE EF 
9 DE AD BE EF DE AD BE EF 
9 DE AD BE EF DE AD BE EF 
No Ack!
9 
9 
9 
9 
9

I don't know why at a time the problem occurs, but it never comes back to a normal operation. This problem seems to be on the emitter's side (the receiver flashes normally, and a reset of the emitter makes the behaviour normal again).

My initial project had a similar problem but on the receiver side, which made the distant arduino useless once the problem occured (which was almost immediate). I was trying to target the problem starting from a simple sketch but I wasn't expecting the problem to occur on a code that simple.
Does someone know where it comes from ? Is the problem in my code or in the library ? By the way I use $Id: VirtualWire.h,v 1.6 2013/02/14 22:02:11

Thanks in advance (and sorry for the message length :sweat_smile: ).

You are using the same variable for tx and rx buffer lengths.

The receiver, if it ever sees a packet that isn't 9 bytes long (perhaps due to transmission
error) will start sending packets with that erroneous length.

Hi, thanks for your answer.

I'm not sure to see how that's a problem... The sender will keep sending the same message (in the real case it takes the message length given by the PC side program through USB).
The variable is reset in the begining of the loop (not sure it's a good thing either), and set again by the vw_get_message function. So if I'm correct even if a message is not properly received it should get back to normal on the next message...

On a side note, I really am uncomfortable with the communication being much much less reliable when no Serial connection is established (in that case only the flashing leds indicate that the communication is not received by the receiver). I had the problem using different pins than the default (there was no RF communication working if no serial connection was established in parallel), it was a little better when using the default pins, so I thought that there could be a problem with the timers associated with the pins, but I don't see why these would work differently when using the Serial connection than when not using it...

The receiver is using the actual received length as the transmit length, the transmitter sets the transmit
length to the received length - this is a circular dependency.

Hi,
Didn't see your answer when I came back home after hollydays, sorry.

Well maybe the virtualWire modifies the message buffer but on the emitter's code the "message" buffer is never modified, and always begins with 0x09, which is the size used in the send_message call (vw_send((uint8_t *)message, message[0]);).
The answer is received in a dedicated buffer (vw_get_message(buf, &buflen)) so I don't see when the message buffer would be modified, giving the circular dependency.

I'm not saying it's not there, only that I can't see it...

I'll run the test with a buffer reinit just to be sure when I can, for now the breadboard has been used for another projects and I have to reset the whole thing :confused:

[Edit]: OK, after reading the comments on the vw_get_message function I've seen the problem :
// Get the last message received (without byte count or FCS)
// Copy at most *len bytes, set *len to the actual number copied
// Return true if there is a message and the FCS is OK
I didn't think the len value was used to limit the buffer, just to return the actual size.

Five years old, but still bad enough to run into it.
I'm using a Nano to watch some values and receivers (voltages, NRF24L01 signals and a 433MHz receiver). With softwareserial, it informs the partner cpu and also receives a value which it sets a pwm led output to.
The code is simple (and could be simplified in the 2.4GHz section, I know), but brings up a problem after several minutes up to hours when it's running:

The 433MHz signals sent from another remote cpu is always 6 bytes long (as it also informs other devices, so I know it does send 6 bytes), but the signal I receive becomes shorter and shorter over time, until its length is 1 (or did it disappear completely?). Using Serial.println(buflen) I saw that buflen sometimes decreases and never goes back to 6.

As a workaround, and only because I know I'll stick to 6 bytes, I changed the 'byte buflen=6' to 'const byte buflen=6' which seems to help (still working since yesterday), but I'm simply afraid of variables becoming corrupted in memory, or why is buflen not set to the length of the message received?
I can't find out what &buflen does here... 'buflen' is not working, and '&buflen' invites a decreasing problem or is being corrupted (decreased) sometimes. Strange.

#include <SoftwareSerial.h>
#include <VirtualWire.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(7, 8); // CNS, CE
const byte address[6] = "00001";
SoftwareSerial mySerial(4, 9); // RX, TX

// const int led_pin = 13; // in use by RF24
// const int transmit_pin = 5; // we don't transmit
const int vw_receive_pin = 5;
// const int transmit_en_pin = 9; // no need
byte buf[8];  // uint8_t buf[VW_MAX_MESSAGE_LEN];
byte buflen = 6;  // uint8_t buflen = VW_MAX_MESSAGE_LEN;
// const byte buflen = 6; // This would prevent buflen from decreasing, but it's stuck to 6 then.
int i=0;
int Code=0;
byte serbyte=0;
unsigned long voltreadtimer=0;
int acvoltage;
int dcvoltage;
bool acpowerloss=LOW;
bool lightison=LOW; 
unsigned long lightonmillis=0;

void setup()
{
    Serial.begin(9600);  // Debugging only
    mySerial.begin(9600);
    Serial.println("setup");

    pinMode (5,INPUT); // VirtualWire
    pinMode (6,OUTPUT); // LED PWM
    pinMode (7,OUTPUT); // RF24
    pinMode (8,OUTPUT); // RF24

    digitalWrite(6,LOW);
    
    // Initialise the IO and ISR
    // vw_set_tx_pin(transmit_pin);
    vw_set_rx_pin(vw_receive_pin);
    // vw_set_ptt_pin(transmit_en_pin);
    // vw_set_ptt_inverted(true); // Required for DR3100
    vw_setup(2000);  // Bits per sec
    vw_rx_start();       // Start the receiver PLL running

    radio.begin();
    radio.openReadingPipe(0, address);
    radio.setChannel(108);
    radio.setPALevel(RF24_PA_MIN); // RF24_PA_MIN = 0, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_PA_ERROR
    radio.setDataRate(RF24_250KBPS); //RF24_1MBPS = 0, RF24_2MBPS, RF24_250KBPS
    radio.startListening();

}


void loop()
{

  // ##################### 433MHz #######################
  if (vw_get_message(buf, &buflen)) { // Non-blocking    
    // Message with a good checksum received, print it.
    Serial.print("Got: ");
    for (i = 0; i < buflen; i++) {
      Serial.print(buf[i]);
      Serial.print(' ');
    }
    Serial.println();
    
    if ((buf[0]==3) and (buf[1]==3)) { // Confirm Address
      
      mySerial.write(4); // Inform CPU1
      Serial.println("Address confirmed");
    }
  }

  // #################### 2.4GHz ########################
  if (radio.available()) {
    radio.read(&Code, sizeof(Code));
    Serial.println(Code);
    if (Code == 1) { // Tuergong normal
      mySerial.write(1);
    }
    if (Code == 2) { // Tuergong Entwarnung
      mySerial.write(2);
    }
    if (Code == 3) { // Tuergong Ohne Schlafzimmer
      mySerial.write(3);
    }
  }

  // ################### SW-Serial ######################
  if (mySerial.available()) {
    serbyte = mySerial.read();
    Serial.println(serbyte);

    if (serbyte==1) { digitalWrite(6,LOW); lightison=LOW; }
    if ((serbyte>1) and (serbyte<250)) {
      analogWrite(6,serbyte);
      if (lightison==LOW) { lightison=HIGH; lightonmillis=millis(); }
    }
    if (serbyte==250) {
      digitalWrite(6,HIGH);
      if (lightison==LOW) { lightison=HIGH; lightonmillis=millis(); }
    }
  }

  // ################# Led on > 45min ################
  if (lightison==HIGH) {
    if (millis()-lightonmillis > 2700000) {
      digitalWrite(6,LOW);
    }
  }
  

  // ################# AC and Battery Voltage ################
  if (millis()-voltreadtimer > 3000) {
    voltreadtimer=millis();
    acvoltage=analogRead(1);
    Serial.print("AC Voltage: "); Serial.println(acvoltage);
    dcvoltage=analogRead(0);
    Serial.print("DC Voltage: "); Serial.println(dcvoltage);

    if ((acpowerloss==LOW) and (acvoltage < dcvoltage)) {
      // AC power lost
      acpowerloss=HIGH;
      mySerial.write(7);
    }
    if ((dcvoltage < 760) and (dcvoltage > 5)) {
      // low battery
      mySerial.write(8);
    }
    if ((acpowerloss==HIGH) and (acvoltage > dcvoltage)) {
      // AC power restored
      acpowerloss=LOW;
      mySerial.write(9);
    }
  }
}

vw_get_message takes a pointer to buflen to tell it how big the buffer is, but it also uses it to return the number of bytes that were read. Apparently that isn't always six.

Make buflen const and use another variable to pass to vw_get_message. Set it to buflen before you make the call.

Great! An even better workaround than mine, as it's future proof!
So you mean...

const byte fixedmaxbuflen = 80;
byte receivedbuflen = 80;

...

void loop()
{
receivedbuflen = fixedmaxbuflen;
if (vw_get_message(buf, &receivedbuflen))
...
}

That should be mentioned in the examples, or.. I wonder why nobody ever ran into it.
Thank you for the quick response and help!

Edit: It seems to be 'mentioned' in an example. I just wanted to 'repair' another project and found:
uint8_t buflen = VW_MAX_MESSAGE_LEN;
standing INSIDE the main loop, where it makes sure it's reset to the maximum on each run. I had declared it outside the loop in my project not knowing what that would mean.

That's what I meant, although if your buffer is still only eight bytes, it may cause issues.

The behavior of vw_get_message is documented here