Bits and bytes

Hello all,

Who would have thought that a blink LED would keep a grown man occupied so many hours! :~ As my collection of arduino's grow (don't let the wife know) that blinking LED is for ever haunting me, and with the recent arrival of my maxim rs485 IC's I'm officially hooked! So here is the scenario, two arduino's hooked up via the rs485 IC's all works well. Now I want to have more than one input on the sending arduino and more than one output on the receiving arduino, to keep this simple lets say the sending one has two potentiometers and the receiving one has two LED's. From my basic knowledge and endless reading I have figured out that I need to send the data in and organised way over the serial comms. So I though I would structure the message as follows; Start, Device ID, Component ID Value 1, Value 2, Checksum, End

Start = a constant value so the receiving arduino can start to grab data Device ID = slave 1, slave 2 etc Component ID = LED 1, LED 2 etc Value 1 = just that Value 1 = just that Checksum = adds up all the values except Start & End to confirm they have all got there ok End = a constant value so the receiving arduino stops grabbing data.

Now the problem I have is I want to send anything from 0 to 255 (reading from the pot) over the serial, without breaking it into individual digits! So the message would look like this Start, Device ID, Component ID, Value 1, Value 2, Checksum, End 1 12 100 122 068 302 4

Clearly I'm in way over my head, so any sort of help if extremely welcomed!

Thanks in advance,

Mark (I know I talk to much) :)

you can use a structure to organise the data.

struct MessageHeader{
  char Start;
  char DeviceID;
  char ComponentID;
  int Val1;
  int Val2;
  int CRC;
  char End;
};

MessageHeader m_Data;

//Fill structure...

//Send the data one byte at a time...
char *c_Buffer = ( char* ) &m_Data;

for( int i_Index = 0 ; i_Index < sizeof( MessageHeader ) ; ++i_Index ){

  Send( c_Buffer[ i_Index ] );
}

Receive data

MessageHeader m_Data;
char *c_Buffer = ( char* ) &m_Data;
int i_Index = 0;

while( 'incoming data' && i_Index < sizeof( MessageHeader ) ){
  c_Buffer[ i_Index++ ] = Read();
}

A reasonably well designed packet protocol you've designed there! You have a possible synchronization issue - the end and start values are also valid data values, so the receiver could be confused and out-of-phase for a while if synchronization were lost. Its likely it would recover quickly though.

Often encoding schemes are chosen that have unambiguous "start of frame" values - inside the block that start-of-frame value isn't allowed - this guarantees resynchronization will be achieved as fast as possible.

But nonetheless you have a reasonable good protocol there :)

pYro_65 Thanks! I will give it a try, can't promise it won't generate more questions though! ;)

Thanks MarkT I must say though I'm a total newbie at communications and I must pass on the structure protocol layout kudos to great minds such as Nick Gammon http://www.gammon.com.au/forum/?id=11428 and IgorR http://real2electronics.blogspot.com/2009/09/arduino-and-rs485-english-version.html who really just put it out there for me to understand! :astonished:

Thanks again,

Mark

Hello pYro_65,

Sadly my programming knowledge goes as far as the arduino.cc reference page and the oomlout stater pack :) I really tried getting my head round the code you posted yesterday, I understood some stuff, any chance of some more explanation on the code or pointing me some where?

I managed to write the following bit of code, looks a bit cumbersome, so any advise is extremely welcomed.

/*test sketch for creating message from various variables
created and developed by Mark Netto markmnetto@gmail.com */

String start; //start transmission constant.
String complete; //end transmission constant.
String device_ID; //device ID i.e. slave 1, slave 2 or slave 3.
String Comp_ID; //componet ID i.e. motor 1, motor 2, LED 1 etc..
int value_1; //value number one to set identified component.
int value_2; //value number two to set identified component.
int checksum;//variable to store result of value addition.
int input_1 = A2;//analog input variable.
int input_1 = A3;//analog input variable.

void setup () //runs once

{
  Serial.begin(115200);
  pinMode(A2, INPUT);
  pinMode(A3, INPUT);
}

void loop()
{

  byte value_1;
  byte value_2;
  byte checksum;
  
  start = "STR";
  complete = "COMP";
  device_ID = "SL1";
  Comp_ID = "M1";
  value_1 = analogRead((input_1)/4);
  value_2 = analogRead((input_2)/4);
  checksum = value_1+value_2;

  Serial.println (start);
  delay(1);
  Serial.println (device_ID);
  delay(1);
  Serial.println (Comp_ID);
  delay(1);
  Serial.println (value_1);
  delay(1);
  Serial.println (value_2);
  delay(1);
  Serial.println (checksum);
  delay(1);
  Serial.println (complete);
    delay(1000);
}

Thanks again,

Mark

The code I posted isn't really a solution. It just shows how you can serialize a data structure, then put it back together.

Have a read on structs/classes. http://www.cplusplus.com/doc/tutorial/structures/

I have a few Ideas but there is a few things you should decide ( if not already ).

  1. Do both arduinos need to talk, or does one just listen to the other.
  2. Will there ever be a case of more than two arduinos communicating. ( Do you need a network or point to point connection )
  3. Do you have an intention of sending variable sized packets ( more controls in the future, want to avoid redesigning everything for an extra POT or encoder )

'String' types need to be avoided, they are useful for debugging messages but they impose too much of a performance/memory hit if they are ever modified.

I'll have a crack at a little implementation.

I have transposed your sketch into mine, I have stated the basics for communication.
I have done for UDP style one way communication. It can easily be expanded to 2-way and connection based.

Sender sketch

#include "Network.h"

void setup()
  {
    Network::Initialise( 115200 ); //Initialise() implicitly starts the serial object.
    pinMode(A2, INPUT);
    pinMode(A3, INPUT);    
    return;
  }
  
void loop()
  {
    //Store the current values here.
    static char c_OldReading1 = 0x0;
    static char c_OldReading2 = 0x0;    
    
    //Read the current values to compare.
    char c_NewReading1 = analogRead( A2 ) / 4;
    char c_NewReading2 = analogRead( A3 ) / 4;
    
    //Only do stuff if values have changed
    if( ( c_OldReading1 != c_NewReading1 ) || ( c_OldReading2 != c_NewReading2 ) ){
      
      //Create the packet.
      Packet p_Data( "DEV1", "COM1", c_NewReading1, c_NewReading2 );
      char   c_Tries = 3;
      
      //Save new data set.
      c_OldReading1 = c_NewReading1;
      c_OldReading2 = c_NewReading1; 
      
      //Send packet, retry a max of 3 times if fails.
      while( !Network::SendPacket( p_Data ) && c_Tries-- ) Network::SendSyncPackets( 5 );
    }
    return;
  }

Network code

#ifndef HEADER_NETWORK
  #define HEADER_NETWORK

  #define DEFAULT_BAUD_RATE     115200
  #define SYNC_PACKET_SIZE      4
  #define INITIAL_RESET_SIZE    10
  
  #define SYNC_PACKET_HEADER    B10111111
  #define SYNC_PACKET_CONTENTS  B11111111
  
  //Forward declaration so Packet can see Network.
  class Network;
  
  class Packet{
    
    public:
    
      Packet( void ); //Only use for read mode.
      Packet( char *c_DeviceID, char *c_ComponentID, unsigned char u_Value1, unsigned char u_Value2 );
    
    protected:    
    
      friend class Network;
      int CRC16( void );
    
    private:
      
      //Must be first value.
      const char         NULLBYTE;
      
      //Data for packet.
      char               DeviceID[ 5 ];
      char               ComponentID[ 5 ];
      unsigned char      Val1;
      unsigned char      Val2;
      
      //Must be last value.
      int                CRC;      
  };
  
  
  class Network{
    
    public:
    
      static void  Initialise( unsigned int u_BaudRate = DEFAULT_BAUD_RATE );
      static bool  SendPacket( Packet &p_Packet ); 
      static void  SendSyncPackets( int i_Packets );        
      static bool  GetPacket( Packet &p_Packet ); 

    protected:
    private:
    
  };
  
  //You can move this bottom part to a file "Network.cpp", just make sure you include this file into it.
  //If you do, leave the #endif at the bottom of the file in this file.
  
  
  #if defined(ARDUINO) && ARDUINO >= 100
    #include <Arduino.h>
  #else
    #include <WProgram.h>
  #endif  
  

  
  //====================================================================================================
  
  Packet::Packet( void ) : NULLBYTE( 0xFF )
      {
        return; 
      }
      
  Packet::Packet( char *c_DeviceID, char *c_ComponentID, unsigned char u_Value1, unsigned char u_Value2 )
                                                                          : NULLBYTE( 0x0 ),
                                                                            Val1( u_Value1 ),
                                                                            Val2( u_Value2 )
      {
        //Copy over ID strings.
        char c_Index = 5;
        while( c_Index-- ){
          
          this->DeviceID[ c_Index ] = c_DeviceID[ c_Index ];
          this->ComponentID[ c_Index ] = c_ComponentID[ c_Index ];
        }
        
        //Calculate crc
        this->CRC = this->CRC16();
        return; 
      }
      
      
  //100% Ripped off from http://www.cplusplus.com/forum/general/855/
  int Packet::CRC16( void ){
    
    char *c_Data = ( char* ) this;
    int i_Size = sizeof( Packet ) - sizeof( int ); //Calculate crc - crc storage space
    int i_Crc = 0;
  
    while( --i_Size >= 0 ){
      
      i_Crc = i_Crc ^ ( int ) *c_Data++ << 8;
      
      for( int i_Index = 0; i_Index < 8; ++i_Index ){
        
        if( i_Crc & 0x8000 )
          i_Crc = i_Crc << 1 ^ 0x1021;
        else
          i_Crc = i_Crc << 1;
      }
    }
    return ( i_Crc & 0xFFFF );
  }          

  //====================================================================================================        
        
    
  void Network::Initialise( unsigned int u_BaudRate )
    {
      //Start the serial object.
      Serial.begin( u_BaudRate );
      Network::SendSyncPackets( INITIAL_RESET_SIZE );
      return; 
    }

  bool Network::SendPacket( Packet &p_Packet )
    {
      //Sync packets.
      SendSyncPackets( 1 );
      
      //Write data and verify the correct length was sent.
      return Serial.write( ( byte* ) &p_Packet, sizeof( Packet ) ) == sizeof( Packet ); 
    }  
   
  void Network::SendSyncPackets( int i_Packets )
    {
      while( i_Packets-- ){
        
        //SYNC packet header
        Serial.write( SYNC_PACKET_HEADER );
        
        //SYNC packet contents.
        char c_Index = SYNC_PACKET_SIZE;
        while( --c_Index ) Serial.write( SYNC_PACKET_CONTENTS );           
      }
      return; 
    }          
   
  bool Network::GetPacket( Packet &p_Packet )
    {
      //Ensure enough data has been received for at least full packet.
      while( Serial.available() >= ( sizeof( Packet ) + SYNC_PACKET_SIZE ) ){
        
        //Verify SYNC packet header then contents.
        if( Serial.read() == SYNC_PACKET_HEADER ){
          
          char c_Step = SYNC_PACKET_SIZE;
          while( ( Serial.read() == SYNC_PACKET_CONTENTS ) && --c_Step ); //c_Step must count to 0 if a valid SYNC packet found.
          
          //If a valid SYNC packet is found check for a real 'Packet' NULLBYTE
          if( !c_Step && !Serial.peek() ){
            
            //The algorithm is 'hopefully' ensured to leave Serial with enough bytes for a full 'Packet' structure.
            byte *b_Buffer = ( byte* ) &p_Packet;
            char c_Count = sizeof( Packet );
            while( c_Count-- ) *b_Buffer++ = Serial.read();
            
            //Validate packet.
            return p_Packet.CRC16() == p_Packet.CRC;
          }
        }
      }
      return false;
    }
    
#endif

and receiver code.
You have no sketch provided so here is a ‘basics’

#include "Network.h" 
  
void setup(){
  Network::Initialise( 115200 ); //Initialise() implicitly starts the serial object.
}
 
  
void loop()
  {
    Packet p_Packet;
    
    if( Network::GetPacket( p_Packet ) ){
     
      //Do something with packet. 
    }
    return;
  }

Oh, by the way, this code is 100% untested, that can be your job :slight_smile:

WOW! 8) How did you do that so quick! That is fantastic! I will give it a try and keep the you and the post updated! Pyro_65 thanks so muck for your help!, I can't promise I will get back to you before Monday as I'm away for the weekend, but I will certainly put your code threw the mill! Thanks again,

Mark