Multiple Serial Devices - Own Protocol

Hi.

Im trying to make a network of multiple Arduinos on a single 2-wire serial bus (well 4 wire as I am also powering the Nano's down the same 4-core cable. Im using quite a slow transmission rate as I will be spanning a good 300m in a chain.

I have written my own protocol (see code below) and it kind of works, I know there are a lot of ways that I can streamline the code using predefined functions but for now Im happy to use long form.

I am using a Mega2650 as my 'Master' device and (will be about 20) Nano's as my 'Slaves'.

The code I have written runs on both the master and slaves (it has a flag that switches is operation), but I basically need to be able to send a receive data on both devices.

The idea is that it is basically going to be a 'call and response' protocol. The master sends three bytes: an empty byte (signifying the start of a transmission), an address followed by a query. The slave at that address then responds with 4 bytes: an empty byte, its address an echo of the query followed by the return data. I am using a frame structure of Start (0), B0, B1, B2, B3, B4, B5, B6, B7, Parity (Odd), Stop (1)

I have a test setup at the moment, basically a Mega and a Nano on my workbench with 30cm of cable between them. When the Nano is transmitting an Alert to the Mega, the Mega receives the data perfectly. However if the Mega tries to send a query to the Nano the data isnt being received properly.

For instance if I send 85D (00101010101) from the Nano to the Mega that is exactly what I get!
If I send the same from the Mega to the Nano I receive 00010101010. The same happens for the empty start byte (00000000001) is received as 00000000000, and address of 10 (00101000001) is received as 00010100000.

For some reason it is shifting the bits by 1 and adding a 0 and the beginning the doesnt exist!

Im using variables such as:

int BitStreamTX[11]={0,0,0,0,0,0,0,0,0,0,0};
int BitStreamRX[11]={0,0,0,0,0,0,0,0,0,0,0};
int BitValue=1; //Current bit
int BitState=1; //Start with a high because the input line is high for idle
int LastBitState=1; //Last Bit - Used to detect a StartBit

int ReceivedMsg=0; //Value for the current received message
int LastSentMessage=0; //Value for the last sent message (in case of retransmission)
int RetVal=0; //Dump for return values

The code to transmit is:

int TXData(int Command) //Transmit data
{
  Serial.print("Command: ");
  Serial.print(Command);
  Serial.print("\n");
  LastSentMessage=Command;
  int ParityCount=0;
  int DecVal;
  BitStreamTX[0]=0; //Set StartBit Low
  BitStreamTX[1]=0; //Reset Bit1
  BitStreamTX[2]=0; //Reset Bit2
  BitStreamTX[3]=0; //Reset Bit3
  BitStreamTX[4]=0; //Reset Bit4
  BitStreamTX[5]=0; //Reset Bit5
  BitStreamTX[6]=0; //Reset Bit6
  BitStreamTX[7]=0; //Reset Bit7
  BitStreamTX[8]=0; //Reset Bit8
  BitStreamTX[9]=0; //Reset Parity Bit
  BitStreamTX[10]=1; //Set StopBit High
  DecVal=Command; //Use a temporary variable to calculate bits
  
if (DecVal-128>=0) //If the whole value of bit8 can be subtracted then set bit8 and increment parity
{
  BitStreamTX[8]=1;
  DecVal=DecVal-128;
  ParityCount++;
}
if (DecVal-64>=0) //As above but bit7
{
  BitStreamTX[7]=1;
  DecVal=DecVal-64;
  ParityCount++;
}
if (DecVal-32>=0) //As above but bit6
{
  BitStreamTX[6]=1;
  DecVal=DecVal-32;
  ParityCount++;
}
if (DecVal-16>=0) //As above but bit5
{
  BitStreamTX[5]=1;
  DecVal=DecVal-16;
  ParityCount++;
}
if (DecVal-8>=0) //As above but bit4
{
  BitStreamTX[4]=1;
  DecVal=DecVal-8;
  ParityCount++;
}
if (DecVal-4>=0) //As above but bit3
{
  BitStreamTX[3]=1;
  DecVal=DecVal-4;
  ParityCount++;
}
if (DecVal-2>=0) //As above but bit2
{
  BitStreamTX[2]=1;
  DecVal=DecVal-2;
  ParityCount++;
}
if (DecVal-1>=0) //As above but bit1
{
  BitStreamTX[1]=1;
  DecVal=DecVal-1;
  ParityCount++;
}
  if (ParityCount % 2) //If ParityCount is Odd
  {
    BitStreamTX[9]=1; //Set Parity High

  }
  else
  {
    BitStreamTX[9]=0; //Set Parity Low
  }


  //Write out the assembled BitStreamTX
  for (int i=0;i<=10;i++)
  {
    if (BitStreamTX[i]==1)
    {
      digitalWrite(TXLine,HIGH);
    }
    else
    {
      digitalWrite(TXLine,LOW);
    }
    delay(DelayTime); //Wait between sending bits
  }
}

And the code to receive is:

void loop() {
  //First portion of code is for the RX data..
  BitState=digitalRead(RXLine); //Read the current bit state
  BitStreamRX[0]=BitState; //Set this as the first bit in the bitstream
  if (BitState==0 && LastBitState==1) //if the current bit is a 0 and the last bit was a 1 then there is data being sent
  {
    delay(DelayTime); //wait for the predefined time
    RetVal=RXData(); //start the receiving bitstream..
  }
  LastBitState=BitState; //set the last bit state so we can compare.
}

with...

int RXData() //Receive data
{
  int ParityBit;
  int ParityCount;
  for (int i=1;i<=10;i++) //We already know the first bit (0) so we can begin at bit 1
  {
    BitState=digitalRead(RXLine); //Read the current bit
    BitStreamRX[i]=BitState; //Add it to the stream
    delay(DelayTime); //Wait the defined amount of time before continuing
  }
  //loop through data bits and count parity
  for (int i=1;i<=8;i++)
  {
    if (BitStreamRX[i]==1)
    {
      ParityCount++;
    }
  }
  //check for parity odd/even
  if (ParityCount % 2) //If ParityCount is Odd
  {
    ParityBit=1; //Set Parity High

  }
  else
  {
    ParityBit=0; //Set Parity Low
  }
  if (ParityBit==BitStreamRX[9]) //If the calculated parity bit is the same as the sent bit then we received data correctly
  {
    //Data received ok
    ReceivedMsg=(BitStreamRX[1]*1)+(BitStreamRX[2]*2)+(BitStreamRX[3]*4)+(BitStreamRX[4]*8)+(BitStreamRX[5]*16)+(BitStreamRX[6]*32)+(BitStreamRX[7]*64)+(BitStreamRX[8]*128);
    Serial.print (ReceivedMsg);
    MsgChecker(ReceivedMsg); //jump to the decision of what to do with the data
  }
  else
  {
    //Data not received ok,
    //send command to request retry..
    Serial.println("MSG Error");
    
  }
  
}

I have attached my full .ino in case you need to see the full code, but like I said earlier, I know I can streamline the code a fair bit with predefined functions, but I just figured while I cant get it to work right I would leave it long hand!!

I am assuming there must be a hardware difference between the Mega and the Nano. I can only assume that its a timing issue.. but I dont know why it works perfectly one way and not the other! If I had a 5 core cable I would have been tempted to use a clock signal, but this should work as its going slow enough!!

If anyone has any ideas please let me know as this is driving me mad!

Ive done a fair bit with arduino's and this has got me!

BitStreamRXTX_Test.ino (10.2 KB)

Two points...

  • Streamlining the code - will make it a LOT easier to localise and debug issues like this.
    Don't be afraid to use functions and other techniques that make life easier.
  • Why are you bit-bashing the serial stream?
    The multiple hardware serial ports on the Mega, and (after programming) the hardware serial on the slaves will make for much more robust comms.

Do it right the first time - you'll learn more, and have less work overall.

Consider one of the software serial libraries instead of re-inventing the wheel; I know t can be fun to re-invent.

For the problem, once you detect a start bit (falling edge) when receiving, you should wait half a bit period (25 ms) before starting to sample the databits, parity and stop bit.

See if that improves the situation.

Side note

 BitStreamTX[0] = 0; //Set StartBit Low
  BitStreamTX[1] = 0; //Reset Bit1
  BitStreamTX[2] = 0; //Reset Bit2
  BitStreamTX[3] = 0; //Reset Bit3
  BitStreamTX[4] = 0; //Reset Bit4
  BitStreamTX[5] = 0; //Reset Bit5
  BitStreamTX[6] = 0; //Reset Bit6
  BitStreamTX[7] = 0; //Reset Bit7
  BitStreamTX[8] = 0; //Reset Bit8
  BitStreamTX[9] = 0; //Reset Parity Bit
  BitStreamTX[10] = 1; //Set StopBit High

can be shortened to

  memset(BitStreamTX, 0, sizeof(BitStreamTX));
  BitStreamTX[10] = 1; //Set StopBit High

I believe (i.e. have not yet tried it) that RS485 makes long distance serial comms reliable and simple.

...R

What line drivers are you using? 5V from an Arduino pin isn't going to work very well over 300m.

A MAX3223 chip boosts the voltage up and acts as good protection for electrostatic discharges and simple mistakes like touching a signal wire with a power wire. That specific chip has an 'enable' pin that will allow the transmit output to float, so that the other slaves on the network may transmit.

Hi.

Thanks for the replies.

I'm using a single line as I need the ability to daisy chain the devices due to the cable I'm using - fire rated stuff which is fairly expensive.

I have implemented the 25ms delay after detecting the falling edge of the start bit and it works perfectly. I can't believe that I hadn't figured that out but when you have been staring at two laptops for an evening it gets mind numbing!

I was using a few transistors and some pull up/down resistors as my line drivers but after reading the data sheet for the max3223 I think I may go down that route instead!

the_mighty_pug205:
I'm using a single line as I need the ability to daisy chain the devices due to the cable I'm using - fire rated stuff which is fairly expensive.

RS485 is designed to do just this.