RS485 Hardware Network 2 – Multi-Byte Communication

Edit: I don’t really make a point of saying it, but the common ground between the modules isn’t necessary.

Here’s a video of the software running on the boards.

So for this example,  there are 4 bytes being transmitted from the master to the slaves.

  1. Start of Transmission (0xDB)
  2. ID of the slave to talk to
  3. The type of command
  4. The data that is transmitted

A transmission to set the pwm value of the green led on the slave with the ID 1 to 100 would be:

0xDB 0x01 0x01 0x64

This is the schematic of the circuit for this example:

This is the code running on the master Arduino:

#include <SoftwareSerial.h>

int MAX485_Receiver_Output_PIN = 10;
int MAX485_Driver_Input_PIN = 11;
int MAX485_Driver_Output_Enable_PIN = 12;

#define NUMSWITCHES 3

int ID_switches[NUMSWITCHES] = {2, 3, 4};
int mode_switch = 5; 

byte IDs[NUMSWITCHES] = {1, 2, 3};

#define TXSIZE 4

SoftwareSerial software_serial (MAX485_Receiver_Output_PIN, MAX485_Driver_Input_PIN); // RX, TX

byte target_node_ID;

struct transmission {

  byte start = 0xDB;
  
  byte ID;
  byte type;
  byte message;

  const int tx_size = TXSIZE;
};

void setup()
{
  software_serial.begin(9600); // begin software serial
  
  pinMode(MAX485_Driver_Output_Enable_PIN, OUTPUT);
  digitalWrite(MAX485_Driver_Output_Enable_PIN, HIGH); // this disables Receiver Output Enable and enables Driver Output Enable
  
  for (int index = 0; index < NUMSWITCHES; index++)
  {
    pinMode(ID_switches[index], INPUT); 
  }
  
  pinMode(mode_switch, INPUT);
  
  target_node_ID = 0;
}

void loop()
{
  for (int index = 0; index < NUMSWITCHES; index++)
  {
    if ((byte)digitalRead(ID_switches[index]))
    {
      target_node_ID = IDs[index];
      break;
    }
  }
  
  transmission t;
  
  t.ID = target_node_ID;
  t.type = digitalRead(mode_switch);
  t.message = (byte)map(analogRead(0), 0, 1023, 0x00, 0xFF);
  
  byte message[TXSIZE] = {t.start, t.ID, t.type, t.message};
  
  software_serial.write(message, t.tx_size);
}

This is the code running on the slave Arduinos:

#include <SoftwareSerial.h>

int MAX485_Receiver_Output_PIN = 10;
int MAX485_Driver_Input_PIN = 11;
int MAX485_Driver_Output_Enable_PIN = 12;

int led_1_PIN = 5; 
int led_2_PIN = 6; 

unsigned long time = 0;  
unsigned long oldtime = 0; 
int state = 0;
int next_ID_button_PIN = 2;

#define NUMSTATES 3
int states[NUMSTATES] = {7, 8, 9};
byte IDs[NUMSTATES] = {1, 2, 3};

#define MESSAGELENGTH 3

SoftwareSerial software_serial (MAX485_Receiver_Output_PIN, MAX485_Driver_Input_PIN); // RX, TX

void setup()
{
  software_serial.begin(9600); // begin software serial
  
  pinMode(MAX485_Driver_Output_Enable_PIN, OUTPUT);
  digitalWrite(MAX485_Driver_Output_Enable_PIN, LOW);
  
  pinMode(led_1_PIN, OUTPUT);
  pinMode(led_2_PIN, OUTPUT);
  
  pinMode(next_ID_button_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(next_ID_button_PIN), next_ID, RISING); 
  
  set_LEDs();
}

void loop() // run over and over
{
  
  byte start_of_message = 0xDB;
  byte incoming_byte;
  byte message[MESSAGELENGTH];
  
  while (true)
  {
    if (software_serial.available())
    { 
      incoming_byte = software_serial.read();
      
      if (incoming_byte == start_of_message)
      {
        software_serial.readBytes(message, MESSAGELENGTH);
        process_message(message);
      }
    }
  }
}
 
void process_message(byte message[])
{
  byte system_ID = IDs[state];

  byte incoming_ID = message[0];
  byte incoming_mode = message[1];
  byte LED_brightness = message[2];
   
  if (incoming_ID == system_ID)
  {
    byte pin;
    
    if (incoming_mode == 0)
    {  
      pin = led_1_PIN;
    }
    else if (incoming_mode == 1)
    {
      pin = led_2_PIN; 
    }
    analogWrite(pin, LED_brightness);  
  }
}

void next_ID()
{
  time = millis();
  
  //soft debounce
  if (time - oldtime > 200) 
  {
    oldtime = time;
    
    state++;
    
    if (state >= (NUMSTATES))
    {
      state = 0;
    }
  }
  set_LEDs();
}

void set_LEDs()
{
  for (int index = 0; index < NUMSTATES; index++)
  {  
    if (index == state)
    {
      digitalWrite(states[index], HIGH);
    }
    else 
    {
      digitalWrite(states[index], LOW);
    }
  } 
}

RS485 Hardware Network 1 – Getting Started

For another project I’m currently working on, I need a way to control multiple microcontrollers in a multi-point, multi-drop network configuration. Luckily, we live in amazing times. As I write this, you you can buy a fully assembled breakout board for the MAX485 chip from Maxim Integrated for a mere $0.45 USD shipped from China.

I bought a 5 pack, here are some of the boards:

RS485 is an old protocol, but is the logical next step for devices I’m already communicating with via RS232. For this example, I’m using 4 Arduino boards of various types.

  • An Arduino Micro as the master
  • 2 Slave Arduino Leonardos
  • 1 Slave Arduino Pro Mini (5v)

Here is a video of the setup:

The schematic is really straightforward as well. The only tricky bit is that I’m using a software serial port on each of the Arduinos for ease of debugging. Here’s a schematic:

The code to acomplish this is really intuitive as well.

Here is the code for the master Arduino:

#include <SoftwareSerial.h>

int MAX485_Receiver_Output_PIN = 10;
int MAX485_Driver_Input_PIN = 11;
int MAX485_Driver_Output_Enable_PIN = 12;

int debug_led = 13;

SoftwareSerial software_serial (MAX485_Receiver_Output_PIN, MAX485_Driver_Input_PIN); // RX, TX

void setup()
{
  Serial.begin(9600); // begin hardware serial
  software_serial.begin(9600); // begin software serial
  
  pinMode(MAX485_Driver_Output_Enable_PIN, OUTPUT);
  digitalWrite(MAX485_Driver_Output_Enable_PIN, HIGH); // this disables Receiver Output Enable and enables Driver Output Enable
}

void loop()
{
  
  byte to_send = 0; // declare the byte to be sent to the slaves though, init as 0
  int rate;
  
  while (true) 
  {
    // invert the byte to be sent
    if (to_send == 1) to_send = 0;
    else if (to_send == 0) to_send = 1; 
    
    Serial.print("Sending: ");
    Serial.println(to_send);
    
    digitalWrite(debug_led, to_send);
    
    rate = map(analogRead(5), 0, 1023, 0, 1000);
    
    software_serial.write(to_send); // send our byte out to the MAX485
    
    delay(rate);
  }
}

This is the code for the slave Arduinos:

#include <SoftwareSerial.h>

int MAX485_Receiver_Output_PIN = 10;
int MAX485_Driver_Input_PIN = 11;
int MAX485_Driver_Output_Enable_PIN = 12;

int debug_led_PIN = 9; 

SoftwareSerial software_serial (MAX485_Receiver_Output_PIN, MAX485_Driver_Input_PIN); // RX, TX

void setup()
{
  software_serial.begin(9600); // begin software serial
  
  pinMode(MAX485_Driver_Output_Enable_PIN, OUTPUT);
  digitalWrite(MAX485_Driver_Output_Enable_PIN, LOW);
  
  pinMode(debug_led_PIN, OUTPUT);
  
}

void loop() // run over and over
{
  byte k;
  
  if (software_serial.available() > 0) // make sure there is something to read
  { 
    k = software_serial.read(); // read in a single byte from the serial port    
    digitalWrite(debug_led_PIN, k); 
  }
}

#include <SoftwareSerial.h>

int MAX485_Receiver_Output_PIN = 10;
int MAX485_Driver_Input_PIN = 11;
int MAX485_Driver_Output_Enable_PIN = 12;

int debug_led_PIN = 9; 

SoftwareSerial software_serial (MAX485_Receiver_Output_PIN, MAX485_Driver_Input_PIN); // RX, TX

void setup()
{
  software_serial.begin(9600); // begin software serial
  
  pinMode(MAX485_Driver_Output_Enable_PIN, OUTPUT);
  digitalWrite(MAX485_Driver_Output_Enable_PIN, LOW);
  
  pinMode(debug_led_PIN, OUTPUT);
  
}

void loop() // run over and over
{
  byte k;
  
  if (software_serial .available() &gt; 0) // make sure there is something to read
  { 
    k = software_serial.read(); // read in a single byte from the serial port    
    digitalWrite(debug_led_PIN, k); 
  }
}

In subsequent posts, things will start getting more advanced. For now however this should be enough to start from scratch.

Thanks for reading.

Parsing Serial Data Sent To Arduino

I use this code constantly. It basically packages serial data for strtok_r to split into pieces paced on predefined deliminators. Each bit of data is separated by a “,” and the end of the set of data is a “.”

If you send in a string like:

10,50,100.

You can split it into three varaibles that equate to those different values. In this case:

int x = atoi(subStr(serialbuf, ",", 1))

The Variable x would equate to 10.

Here’s the code:

const char EOPmarker = '.'; //This is the end of packet marker
char serialbuf[32]; //This gives the incoming serial some room. Change it if you want a longer incoming.

#include <SoftwareSerial.h>
#include <string.h> // we'll need this for subString
#define MAX_STRING_LEN 20 // like 3 lines above, change as needed.

SoftwareSerial SoftSer(11, 10); // RX, TX

void setup(){
  Serial.begin(9600);
  SoftSer.begin(9600);
}

void loop() {
    if (SoftSer.available() > 0) { //makes sure something is ready to be read
      static int bufpos = 0; //starts the buffer back at the first position in the incoming serial.read
      char inchar = SoftSer.read(); //assigns one byte (as serial.read()'s only input one byte at a time
      if (inchar != EOPmarker) { //if the incoming character is not the byte that is the incoming package ender
        serialbuf[bufpos] = inchar; //the buffer position in the array get assigned to the current read
        bufpos++; //once that has happend the buffer advances, doing this over and over again until the end of package marker is read.
      }
      else { //once the end of package marker has been read
        serialbuf[bufpos] = 0; //restart the buff
        bufpos = 0; //restart the position of the buff

        /*

          THIS IS WHERE THE CODE HAPPENS

        */

      }
    }
}

char* subStr (char* input_string, char *separator, int segment_number) {
  char *act, *sub, *ptr;
  static char copy[MAX_STRING_LEN];
  int i;
  strcpy(copy, input_string);
  for (i = 1, act = copy; i <= segment_number; i++, act = NULL) {
    sub = strtok_r(act, separator, &ptr);
    if (sub == NULL) break;
  }
 return sub;
}

Here’s an example.

Say you have a serial device hooked up to your softserial port and in inputs “10,50,100.” to the arduino to be split up. If you want to set each of these numbers to separate integers and then print them to the serial console, you’d do it like this.

const char EOPmarker = '.'; //This is the end of packet marker
char serialbuf[32]; //This gives the incoming serial some room. Change it if you want a longer incoming.

#include <SoftwareSerial.h>
#include <string.h> // we'll need this for subString
#define MAX_STRING_LEN 20 // like 3 lines above, change as needed.

SoftwareSerial SoftSer(11, 10); // RX, TX

void setup(){
  Serial.begin(9600);
  SoftSer.begin(9600);
}

void loop() {
    if (SoftSer.available() > 0) { //makes sure something is ready to be read
      static int bufpos = 0; //starts the buffer back at the first position in the incoming serial.read
      char inchar = SoftSer.read(); //assigns one byte (as serial.read()'s only input one byte at a time
      if (inchar != EOPmarker) { //if the incoming character is not the byte that is the incoming package ender
        serialbuf[bufpos] = inchar; //the buffer position in the array get assigned to the current read
        bufpos++; //once that has happend the buffer advances, doing this over and over again until the end of package marker is read.
      }
      else { //once the end of package marker has been read
        serialbuf[bufpos] = 0; //restart the buff
        bufpos = 0; //restart the position of the buff

        int x = atoi(subStr(serialbuf, ",", 1));
        int y = atoi(subStr(serialbuf, ",", 2));
        int z = atoi(subStr(serialbuf, ",", 3));

        Serial.print("The first number, x is: ");
        Serial.print(x);
        Serial.print(" - The second number, y is: ");
        Serial.print(y);
        Serial.print(" - The third number, z is: ");
        Serial.print(z);
        Serial.println();

      }
    }
}

char* subStr (char* input_string, char *separator, int segment_number) {
  char *act, *sub, *ptr;
  static char copy[MAX_STRING_LEN];
  int i;
  strcpy(copy, input_string);
  for (i = 1, act = copy; i <= segment_number; i++, act = NULL) {
    sub = strtok_r(act, separator, &ptr);
    if (sub == NULL) break;
  }
 return sub;
}

Spitting incoming serial data arduino and visual basic

UPDATED CODE HERE


Hello! as you can probably tell, my last post was written in a fury of incoherency, but I needed to get the code out there so it is what it is.

The main focus of this post is to showcase the arduino program. The visual basic in the video is very simple, and there will be much more on that later.

This below program will take a string of characters fed to the arduino and split them into usable parts. This is a very valuable tool for working with serial and arduino. It’s pretty well commented, but if you have any questions, PLEASE leave a comment. I’d love to see some conversation here.

const char EOPmarker = '.'; //This is the end of packet marker
char serialbuf[32]; //This gives the incoming serial some room. Change it if you want a longer incoming.

#include  // we'll need this for subString
#define MAX_STRING_LEN 20 // like 3 lines above, change as needed.

#include  //we'll need this for the lcd
LiquidCrystal lcd(7, 8, 9, 10, 11, 12); //pins for the lcd, I set it up using the ladyada tutorial.

void setup(){
 lcd.begin(16, 2);
 Serial.begin(9600); //changing this to other speeds has not been tested using this meathod
}

void loop() {
 if (Serial.available() > 0) { //makes sure something is ready to be read
 lcd.clear(); //clears for incoming stuff, won't clear if there isin't data to be read
 static int bufpos = 0; //starts the buffer back at the first position in the incoming serial.read
 char inchar = Serial.read(); //assigns one byte (as serial.read()'s only input one byte at a time
 if (inchar != EOPmarker) { //if the incoming character is not the byte that is the incoming package ender
 serialbuf[bufpos] = inchar; //the buffer position in the array get assigned to the current read
 bufpos++; //once that has happend the buffer advances, doing this over and over again until the end of package marker is read.
 }
 else { //once the end of package marker has been read
 serialbuf[bufpos] = 0; //restart the buff
 bufpos = 0; //restart the position of the buff
 lcd.write(subStr(serialbuf, ",", 1)); //witres the first bit of content before the first comma (or other seperator) to the lcd. You could also do math or anything else with these. You could use atoi to change them to integers.
 lcd.write("|separator|"); //this signifies that the first seperation has occured
 lcd.write(subStr(serialbuf, ",", 2)); //same thing as 2 lines above, but with the second parts. this can be repeated
 }
 }
}

// below is just function logic, which I do not fully understand. but it works.
char* subStr (char* input_string, char *separator, int segment_number) {
 char *act, *sub, *ptr;
 static char copy[MAX_STRING_LEN];
 int i;

strcpy(copy, input_string);

for (i = 1, act = copy; i <= segment_number; i++, act = NULL) {

sub = strtok_r(act, separator, &ptr);
 if (sub == NULL) break;
 }
 return sub;
}

//esologic.com
//Thanks to http://arduino.cc/forum/index.php?topic=119429

So for example if you inputted

[code]
123,456.
[/code]

it would output

[code]
123|separator|456
[/code]

to the lcd, or the serial monitor if you tweaked the code.

Now for the code in the video. The only different part about this is that it writes the two values to the servos.


const char EOPmarker = ‘.’; //This is the end of packet marker
char serialbuf[32]; //This gives the incoming serial some room. Change it if you want a longer incoming.

#include // we’ll need this for subString
#define MAX_STRING_LEN 20 // like 3 lines above, change as needed.

#include //we’ll need this for the lcd
LiquidCrystal lcd(7, 8, 9, 10, 11, 12); //pins for the lcd, I set it up using the ladyada tutorial.

#include
Servo left_servo;
Servo right_servo;

int left_servo_pos;
int right_servo_pos;
void setup(){
lcd.begin(16, 2);
left_servo.attach(2);
right_servo.attach(3);
Serial.begin(9600); //changing this to other speeds has not been tested using this meathod
}

void loop() {
if (Serial.available() > 0) { //makes sure something is ready to be read
lcd.clear(); //clears for incoming stuff, won’t clear if there isin’t data to be read
static int bufpos = 0; //starts the buffer back at the first position in the incoming serial.read
char inchar = Serial.read(); //assigns one byte (as serial.read()’s only input one byte at a time
if (inchar != EOPmarker) { //if the incoming character is not the byte that is the incoming package ender
serialbuf[bufpos] = inchar; //the buffer position in the array get assigned to the current read
bufpos++; //once that has happend the buffer advances, doing this over and over again until the end of package marker is read.
}
else { //once the end of package marker has been read
serialbuf[bufpos] = 0; //restart the buff
bufpos = 0; //restart the position of the buff

left_servo_pos = atoi(subStr(serialbuf, “,”, 1));
lcd.write(“Left Servo:”);
lcd.write(subStr(serialbuf, “,”, 1)); //witres the first bit of content before the first comma (or other seperator) to the lcd
left_servo.write(left_servo_pos);

lcd.setCursor(0, 1);

right_servo_pos = atoi(subStr(serialbuf, “,”, 2));
lcd.write(“Right Servo:”); //this signifies that the first seperation has occured
lcd.write(subStr(serialbuf, “,”, 2)); //same thing as 2 lines above, but with the second parts. this can be repeated
right_servo.write(right_servo_pos);
}
}
}

// below is just function logic, which I do not fully understand. but it works.
char* subStr (char* input_string, char *separator, int segment_number) {
char *act, *sub, *ptr;
static char copy[MAX_STRING_LEN];
int i;

strcpy(copy, input_string);

for (i = 1, act = copy; i <= segment_number; i++, act = NULL) {

sub = strtok_r(act, separator, &ptr);
if (sub == NULL) break;
}
return sub;
}

//esologic.com
//Thanks to http://arduino.cc/forum/index.php?topic=119429