Communicating via RS485

Discussion specific to NXT-G, NXC, NBC, RobotC, Lejos, and more.
mattallen37
Posts: 1818
Joined: 02 Oct 2010, 02:19
Location: Michigan USA
Contact:

Re: Communicating via RS485

Post by mattallen37 »

Actually yes. I started development of an RS485 library that supported NXT addresses, commands, and user elements. It worked/works fine, but it doesn't have any error checking (like checksum or crc). I think it tested out fine with three NXTs, but at the time I wasn't able to try more than that.

Maybe when I get back home I will post the library in it's latest state (could be a week or so).
Matt
http://mattallen37.wordpress.com/

I'm all for gun control... that's why I use both hands when shooting ;)
HaWe
Posts: 2500
Joined: 04 Nov 2014, 19:00

Re: Communicating via RS485

Post by HaWe »

any news about advanced and simply accessable rs485 network functions?
mattallen37
Posts: 1818
Joined: 02 Oct 2010, 02:19
Location: Michigan USA
Contact:

Re: Communicating via RS485

Post by mattallen37 »

Thanks for the reminder.

Here is the library:

Code: Select all

/*
*  Matthew Richardson
*  matthewrichardson37<at>gmail.com
*  http://mattallen37.wordpress.com/
*  Jul. 7 / 2011
*
*  You may use this code as you wish, provided you give credit where it's due.
*
*  This is library for easy use of RS485 master/slave transactions.
*/

/*
My RS485 protocol data specs:

Sending byte array looks like this:
  Senders address, Target address, Commands(flags), Data1, Data2, ..., Data n

Sending Ping looks like this:
  Senders address, Target address, Ping Flag Value
  
Acknowledgement looks like this:
  Senders address, Target address, Acknowledgement Flag Value

Returned values
-2 timeout
-1 something went wrong, probably not right address
0  not acknowledged
Command = success
*/

#define Master485Address 0x00 //The address of this NXT
#define Slave1485Address 0x01 //Slave number 1's address

//Master to slave flags
#define PingValue       0x08   //Return an Acknowledgement
#define WriteValue      0x10   //Write registers
#define ReadBackValue   0x20   //Return Values

//Slave to master flags
#define Acknowledge     0x01   //Result returned from a ping or write-only command

void SendRS485ByteArray (byte SenderAddress, byte TargetAddress, byte Command, byte SendData[]){
  byte SendCompleteData[];
  byte SendCompleteDataLength=ArrayLen(SendData)+3;
  ArrayInit(SendCompleteData, 0, SendCompleteDataLength);
  SendCompleteData[0]=SenderAddress;
  SendCompleteData[1]=TargetAddress;
  SendCompleteData[2]=Command;
  byte i=0;
  repeat(ArrayLen(SendData)){
    SendCompleteData[i+3]=SendData[i];
    i++;
  }
  string StringSendData = FlattenVar(SendCompleteData);         //Flatten it into a string
  SendRS485String(StringSendData);                              //Send the string
  until(HSOutputBufferOutPtr() == StrLen(StringSendData)+1);    //Wait until the string has been sent
}

int MasterReadRS485ByteArray (byte My485Address, byte BytesToReceive, int TimeOut, byte & InArray[]){
  bool TimedOut=false;
  byte TimePassed=0;                                                              //Reset the timeout counter
  byte cnt;
  string InString;
  byte RawInArray[];
  byte RawInArraySize=BytesToReceive+3;
  ArrayInit(RawInArray, 0, RawInArraySize);

  until (HSInputBufferInPtr() >= (BytesToReceive+4) || TimedOut){ //Until whole string is received
    Wait(1);                                            //Wait 1 ms
    if (TimeOut==0){                          //If TimeOut equals 0, then don't time out.
      TimedOut=false;
    }
    else{
      TimePassed++;                                     //increase the timer
      if (TimePassed>=TimeOut){
        TimedOut=true;
      }
    }
  }
  if (TimedOut){                            //If the timeout took place
    return -2;
  }
  else{                                     //If the timeout did not take place
    cnt=HSInputBufferInPtr();
    GetHSInputBuffer(0, cnt, InString);                 //Read the buffer
    SetHSInputBufferInPtr(0);
    UnflattenVar(InString, RawInArray);                 //Unflatten the string into an array
    byte SendersAddress=RawInArray[0];
    byte TargetsAddress=RawInArray[1];
    byte Command       =RawInArray[2];
    if (TargetsAddress==My485Address)                   //If the RdataC[0] contains this NXT's address, read the rest
    {
      ArraySubset(InArray, RawInArray, 3, NA);
      return Command;
    }
  }
  return -1;
}

void MasterSendPing(byte My485Address, byte TargetAddress){
  byte PingData[3];
  ArrayBuild(PingData, My485Address, TargetAddress, PingValue); //Build the array to send
  string StringPingData = FlattenVar(PingData);                 //Flatten it into a string
  SendRS485String(StringPingData);                              //Send the string
  until(HSOutputBufferOutPtr() == StrLen(StringPingData)+1);    //Wait until the string has been sent
}

int MasterCheckAcknowledgement(byte My485Address, int TimeOut){
  bool TimedOut=false;
  byte TimePassed=0;                                                              //Reset the timeout counter
  string InString;
  byte RawInArray[];
  ArrayInit(RawInArray, 0, 3);

  until (HSInputBufferInPtr() == 4 || TimedOut){   //Until whole string is received
    Wait(1);                                                   //Wait 1 ms
    if (TimeOut==0){                     //If TimeOut equals 0, then don't time out.
      TimedOut=false;
    }
    else{
      TimePassed++;                      //increase the timer
      if (TimePassed>=TimeOut){
        TimedOut=true;
      }
    }
  }
  if (TimedOut){                         //If the timeout took place
    return -2;
  }
  else{                                  //If the timeout did not take place
    GetHSInputBuffer(0, 4, InString);                          //Read the buffer
    SetHSInputBufferInPtr(0);
    UnflattenVar(InString, RawInArray);                        //Unflatten the string into an array
    byte SendersAddress=RawInArray[0];
    byte TargetsAddress=RawInArray[1];
    byte Command       =RawInArray[2];
    if (TargetsAddress==My485Address)                          //If the TargetsAddress contains this NXT's address, read the rest
    {
      return Command;
    }
  }
  return -1;
}

int MasterPing(byte My485Address, byte TargetAddress, int TimeOut){
  MasterSendPing(My485Address, TargetAddress);
  return MasterCheckAcknowledgement(My485Address, TimeOut);
}

void SlaveAcknowledge(byte My485Address, byte TargetAddress){
  byte AcknowledgeData[3];
  ArrayBuild(AcknowledgeData, My485Address, TargetAddress, Acknowledge); //Build the array to send
  string StringAcknowledgeData = FlattenVar(AcknowledgeData);        //Flatten it into a string
  SendRS485String(StringAcknowledgeData);                                //Send the string
  until(HSOutputBufferOutPtr() == StrLen(StringAcknowledgeData)+1);      //Wait until the string has been sent
}

int SlaveReadAndRespondRS485ByteArray (byte My485Address, byte BytesToReceive, int TimeOut, byte SendValues[], byte & InArray[]){
  byte GoOn=0;
  int TimePassed=0;                                                              //Reset the timeout counter
  byte cnt;
  string InString;
  byte RawInArray[];

  until (HSInputBufferInPtr() >= (BytesToReceive+4) || GoOn!=0){   //Until whole string is received
    Wait(1);
    if(HSInputBufferInPtr()==4){                                   //If only a ping
      Wait(3);
      if(HSInputBufferInPtr()==4){                                 //Confirm only a ping
        GoOn=1;
      }
    }
    if (TimeOut!=0){                     //If TimeOut equals 0, then don't time out.
      TimePassed++;                      //increase the timer
      if (TimePassed>=TimeOut){
        GoOn=2;
      }
    }
  }
  
  if (GoOn==2){                          //If TimedOut
    return -2;                           // Return -2
  }

  else{                                  //If it didn't time out
    if (GoOn==0){
      byte RawInArraySize=BytesToReceive+3;
      ArrayInit(RawInArray, 0, RawInArraySize);
      cnt=HSInputBufferInPtr();
      GetHSInputBuffer(0, cnt, InString);  //Read the buffer
      SetHSInputBufferInPtr(0);
      UnflattenVar(InString, RawInArray);  //Unflatten the string into an array
      byte SendersAddress=RawInArray[0];
      byte TargetsAddress=RawInArray[1];
      byte Command       =RawInArray[2];
      if (TargetsAddress==My485Address)
      {
        if (ReadBackValue==(Command&ReadBackValue)){
          SendRS485ByteArray (My485Address, SendersAddress, Command, SendValues);
        }
        if (WriteValue==(Command&WriteValue)){
          ArraySubset(InArray, RawInArray, 3, NA);
        }
        return Command;
      }
    }
    if (GoOn==1){
      ArrayInit(RawInArray, 0, 3);
      cnt=HSInputBufferInPtr();
      GetHSInputBuffer(0, cnt, InString);  //Read the buffer
      SetHSInputBufferInPtr(0);
      UnflattenVar(InString, RawInArray);  //Unflatten the string into an array
      byte SendersAddress=RawInArray[0];
      byte TargetsAddress=RawInArray[1];
      byte Command       =RawInArray[2];
      if (TargetsAddress==My485Address)
      {
        if (PingValue==(Command&PingValue)){
          SlaveAcknowledge(My485Address, SendersAddress);
          return Command;
        }
      }
    }
  }
  return -1;
}
Here is an example master program:

Code: Select all

/*
*  Matthew Richardson
*  matthewrichardson37<at>gmail.com
*  http://mattallen37.wordpress.com/
*  Jun. 23 / 2011
*
*  You may use this code as you wish, provided you give credit where it's due.
*
*  This is an example master program for my RS485 library.
*/

/*
Returned values
-2 timeout
-1 something went wrong, probably not right address
0  not acknowledged
Command = success
*/
#include "MyRS485 lib.nxc" //Note, the main library

#define My485Address     0x00 //The address of this NXT
#define Master485Address 0x00 //The address of the master
#define Slave1485Address 0x01 //Slave number 1's address

//Master to slave flags
#define PingValue       0x08   //Return an Acknowledgement
#define WriteValue      0x10   //Write registers
#define ReadBackValue   0x20   //Return Values

//Slave to master flags
#define Acknowledge 0x01   //Result returned from a ping or write-only command

#define BytesToReceive 10
#define TimeOut        15

byte Sdata[];           //Array to send data

byte Rdata[BytesToReceive];

byte Svar0,Svar1,Svar2,Svar3,Svar4,Svar5,Svar6,Svar7,Svar8,Svar9;

int result;

task display()          //Display the results
{
  while(true)
  {
    ClearScreen();
    NumOut(0, LCD_LINE1,Rdata[0]);
    NumOut(0, LCD_LINE2,Rdata[1]);
    NumOut(0, LCD_LINE3,Rdata[2]);
    NumOut(0, LCD_LINE4,Rdata[3]);
    NumOut(0, LCD_LINE5,Rdata[4]);

    NumOut(50, LCD_LINE1,Rdata[5]);
    NumOut(50, LCD_LINE2,Rdata[6]);
    NumOut(50, LCD_LINE3,Rdata[7]);
    NumOut(50, LCD_LINE4,Rdata[8]);
    NumOut(50, LCD_LINE5,Rdata[9]);

    NumOut(0, LCD_LINE8,result);

    Wait(50);
  }
}

task RS()
{
  while (true)
  {
    Svar0=Random();
    Svar1=Random();
    Svar2=Random();
    Svar3=Random();
    Svar4=Random();
    Svar5=Random();
    Svar6=Random();
    Svar7=Random();
    Svar8=Random();
    Svar9=Random();
    ArrayBuild(Sdata, Svar0,Svar1,Svar2,Svar3,Svar4,Svar5,Svar6,Svar7,Svar8,Svar9);

    SendRS485ByteArray (My485Address, Slave1485Address, (WriteValue|ReadBackValue), Sdata);
    result=MasterReadRS485ByteArray (My485Address, BytesToReceive, TimeOut, Rdata);

    //result=MasterPing(My485Address, Slave1485Address, TimeOut);
    
    if (result==-1){
      PlayToneEx(500,100,3,0);
    }
    if (result==-2){
      PlayToneEx(1000,10,1,0);
    }
    Wait(500);
  }
}

task main()
{
  SetSensorType(IN_4, SENSOR_TYPE_HIGHSPEED);   //Set up for RS485
  SetHSState(HS_INITIALISE);                    //      ''
  SetHSFlags(HS_UPDATE);                        //      ''
  SetHSSpeed(HS_BAUD_921600);
  SetHSInputBufferInPtr(0);
  SetHSInputBufferOutPtr(0);
  start display;
  start RS;
}
And an example slave program:

Code: Select all

/*
*  Matthew Richardson
*  matthewrichardson37<at>gmail.com
*  http://mattallen37.wordpress.com/
*  Jun. 23 / 2011
*
*  You may use this code as you wish, provided you give credit where it's due.
*
*  This is an example slave program for my RS485 library.
*/

/*
My RS485 protocol data specs:

Sending byte array looks like this:
  Senders address, Target address, Commands/flags, Data1, Data2, ..., Data n

Sending Ping looks like this:
  Senders address, Target address, Ping Flag Value

Acknowledgement looks like this:
  Senders address, Target address, Acknowledgement Flag Value

Returned values
-2 timeout
-1 something went wrong
0  not acknowledged
Command = success
*/
#include "MyRS485 lib.nxc" //Note, the main library

#define My485Address     0x01 //The address of this NXT
#define Master485Address 0x00 //The address of the master
#define Slave1485Address 0x01 //Slave number 1's address

//Master to slave flags
#define PingValue       0x08   //Return an Acknowledgement
#define WriteValue      0x10   //Write registers
#define ReadBackValue   0x20   //Return Values

//Slave to master flags
#define Acknowledge 0x01   //Result returned from a ping or write-only command

#define BytesToReceive 10
#define TimeOut        500

byte Sdata[];

byte Rdata[BytesToReceive];

byte Svar0,Svar1,Svar2,Svar3,Svar4,Svar5,Svar6,Svar7,Svar8,Svar9;


int result;

task display()
{
  while(true)
  {
    ClearScreen();
    NumOut(0, LCD_LINE1,Rdata[0]);
    NumOut(0, LCD_LINE2,Rdata[1]);
    NumOut(0, LCD_LINE3,Rdata[2]);
    NumOut(0, LCD_LINE4,Rdata[3]);
    NumOut(0, LCD_LINE5,Rdata[4]);

    NumOut(50, LCD_LINE1,Rdata[5]);
    NumOut(50, LCD_LINE2,Rdata[6]);
    NumOut(50, LCD_LINE3,Rdata[7]);
    NumOut(50, LCD_LINE4,Rdata[8]);
    NumOut(50, LCD_LINE5,Rdata[9]);

    NumOut(0, LCD_LINE8,result);
    Wait(75);
  }
}


task RS()
{
  while (true)
  {
    Svar0=Random();
    Svar1=Random();
    Svar2=Random();
    Svar3=Random();
    Svar4=Random();
    Svar5=Random();
    Svar6=Random();
    Svar7=Random();
    Svar8=Random();
    Svar9=Random();
    ArrayBuild(Sdata, Svar0,Svar1,Svar2,Svar3,Svar4,Svar5,Svar6,Svar7,Svar8,Svar9);

    result=SlaveReadAndRespondRS485ByteArray (My485Address, BytesToReceive, TimeOut, Sdata, Rdata);
    if (result==-1){
      PlayToneEx(500,100,3,0);
    }
    if (result==-2){
      PlayToneEx(1000,10,1,0);
    }
  }
}

task main()
{
  SetSensorType(IN_4, SENSOR_TYPE_HIGHSPEED);
  SetHSState(HS_INITIALISE);
  SetHSFlags(HS_UPDATE);
  SetHSSpeed(HS_BAUD_921600);
  SetHSInputBufferInPtr(0);
  SetHSInputBufferOutPtr(0);
  start display;
  start RS;
}
I think this all worked fine the last time I tried it.

Basically, the master sends it's own address, the target address, a command (ping and or write values and or return values), and an array of user data using "SendRS485ByteArray". The master then awaits a response from the slave, in the receive function "MasterReadRS485ByteArray". If there was a timeout, the function will return -2. If it succeeded, it will return the value of the command byte that the slave sent in return (which is the command the master sent to the slave, requesting a response).

On the slave side of things, the program waits for a message to come in (using SlaveReadAndRespondRS485ByteArray). It will keep returning -2 indicating a timeout, until it gets a message from the master. I suggest you put it in a loop so that the master can access it at just about any time. You provide an array for it to send, assuming the master asks for it, and an array to store values coming from the master.

Make sure you set BytesToReceive to the number of bytes you want the NXT to be looking for. This is the number of user-bytes, not total bytes. According to the documentation of the EFW, it should support 60+ user-bytes (a 60+ element byte array of user data). In the examples above, I used 10.

Make sure you also set TimeOut to proper values. The slave should have a high timeout, and the master relatively short. I usually use 500 and 15 ms. You also need to be sure that the slave's control loop will be faster than the master's, so that the master can always access it on time.

I haven't used the library much yet, but all tests seemed really good IIRC. I used it for one of my larger rock-crawlers, and it worked perfectly every time.

EDITED for correction.
Last edited by mattallen37 on 21 Nov 2012, 01:08, edited 1 time in total.
Matt
http://mattallen37.wordpress.com/

I'm all for gun control... that's why I use both hands when shooting ;)
HaWe
Posts: 2500
Joined: 04 Nov 2014, 19:00

Re: Communicating via RS485

Post by HaWe »

matt,
thanks, that looks great!
Ill try it as sonn I will have some days off in the next 1-2 weeks!
I'll post a link to your post also to the TO!

thanks again!
alphasucht
Posts: 10
Joined: 27 Dec 2011, 14:30

Re: Communicating via RS485

Post by alphasucht »

Hi Matt,
thank you for your code. I tried it with two NXTs and it worked fine.

I tried it also with 3 NXTs. So I defined a 2nd Slave Address in the master program and changed the "My485Address" in the slave program for one NXT also.

The Master should talk alternating to the NXTs.

Code: Select all

    if(i=1){
    SendRS485ByteArray (My485Address, Slave1485Address, (WriteValue|ReadBackValue), Sdata);
    result=MasterReadRS485ByteArray (My485Address, BytesToReceive, TimeOut, Rdata);
    }

    else if(i=2){
    SendRS485ByteArray (My485Address, Slave2485Address, (WriteValue|ReadBackValue), Sdata);
    result=MasterReadRS485ByteArray (My485Address, BytesToReceive, TimeOut, Rdata);
    }
    i++;
    if(i>2)i=1;
But only the first slave is responding. If I use the Address of the 2nd Slave first, than only the 2nd Slave is responding? :? What did I wrong? Or can you post an extended version of your program for more than 2 NXTs?

Thank you very much!

Karl
HaWe
Posts: 2500
Joined: 04 Nov 2014, 19:00

Re: Communicating via RS485

Post by HaWe »

did you already try
if (i==1)
...
if(i==2)
...

instad of i=1, i=2 ?
:)
afanofosc
Site Admin
Posts: 1256
Joined: 26 Sep 2010, 19:36
Location: Nashville, TN
Contact:

Re: Communicating via RS485

Post by afanofosc »

Good eye, Doc! These sort of errors can be hard to notice. It would be nice if the compiler warned you about such things.

John Hansen
Multi-platform LEGO MINDSTORMS programming
http://bricxcc.sourceforge.net/
mattallen37
Posts: 1818
Joined: 02 Oct 2010, 02:19
Location: Michigan USA
Contact:

Re: Communicating via RS485

Post by mattallen37 »

As Helmut caught, try fixing it so it will only do one at a time (change it to ==). I didn't test it with more than 2 NXT's, because I only have two of the NXT battery packs, and it's hard to get ahold of rechargeable AA's in this house.

If fixing the conditionals doesn't help, let me know. It should work fine with up to about 32 NXT's, but it's possible I messed up in the code somewhere.
Matt
http://mattallen37.wordpress.com/

I'm all for gun control... that's why I use both hands when shooting ;)
alphasucht
Posts: 10
Joined: 27 Dec 2011, 14:30

Re: Communicating via RS485

Post by alphasucht »

:oops:
It works.
7 Posts. In the half of them I have disgraced myself. Better result than in real life. ;)
HaWe
Posts: 2500
Joined: 04 Nov 2014, 19:00

Re: Communicating via RS485

Post by HaWe »

don't bother, I'm happy that it works for you.
Maybe, would you like to publish your complete code?
I'm curious what data you are exchanging between all NXT's.
(is it a master-slave-architecture?)
Post Reply

Who is online

Users browsing this forum: No registered users and 0 guests