I/O control over serial

Hi,
I am working a simple little project that has 2 Arduinos that exchange the status of 6 inputs and 6 outputs via serial.

Uno 1 reads the status of 6 pins and writes the status of those pins byte by byte with a start flag and an end flag to be read by Uno 2 to which outputs the status to its own outputs.
Uno1: sends S100000E (to indicate input pin 1 is HIGH)
Uno2: receives S100000E (set output pin 1 HIGH)
Uno2: sends S011100E (indicates pins 2,3,4 are HIGH)
Uno1: receives S011100E (set output pins 2,3,4 to HIGH)

I am trying to use the same code for both Unos and just have the master selectable via a jumper.
I believe I have this working, but I am trying to build in some redundancy. My main concern is the possibility of corrupted data then having some kind of buffer overflowing.

Any comments and suggestions are much appreciated.

This is a link to the code saved on the arduino online editor. Arduino Cloud

Many thanks,
Josh

Please read the first topic telling how to use this forum and how to post code.

It's not bad, you are on the right track. There are a few basic things to make it run smooth which you don't know yet.

Could you describe the protocol of the serial data ?
Should your sketch allow that there is a CarriageReturn or LineFeed at the end ? Are there always 6 pins ? It is allowed to continuously transmit data without any delay in between ? and so on.

There are a few basic rules to do such a project:

  • Always be ready to receive something. Always, all the time. Never delay.
  • If the same sketch is used on both Arduino boards, then there is no Master. They both send the inputs to the other one.
  • We don't use the 'Serial.readBytesUntil()' functions. It is better to make your own code to read the incoming data.
  • In my opinion, the 'S' and 'E' and the fixed length are good enough. You can add a checksum if you want. You can go over-the-top and confirm the data, to let the sender know that the right data is received. You could even encrypt the data, so that no one else can control the output pins. You can do anything that you can think of.

That means:

  • Because receiving data should be possible all the time, sending the data can be done in a millis-timer, or send something when a input changes.
  • In the loop(), read the available data, and check if a full command line has been received. Process the data when it is a full command. Do not wait to receive a full command.
  • You could add a error counter. If the received data is not okay, increase the error counter. Then you know if something went wrong. The goal is 100% reliability.

Give it a try and how us the new sketch here on this forum (put it between code tags, use the </> button).

1 Like

Hi Railroader,

I didn't realise the forum transcribed the code into formatted colour coded it (thats rather clever), I uploaded the code into the editor to make it easier to read. Here is the oriiginal code. Like wise, your thoughts are much appreciated.

Regards,
Josh

const int InputPins[6] = {14, 15, 16, 17, 18, 19};
const int OutputPins[6] = {2, 3, 4, 5, 6, 7};
const int MasterSelectPin = 10;
const int StatusLED = 13;
bool MasterSelect;
char ReceivedData[32];

void setup() {
  Serial.begin(9600);
  pinMode(MasterSelectPin, INPUT_PULLUP);
  pinMode(StatusLED, OUTPUT);
  for (int p = 0; p <= 5; p++) {
    pinMode(InputPins[p], INPUT_PULLUP);
    pinMode(OutputPins[p], OUTPUT);
  }
  if (digitalRead(MasterSelectPin) == LOW) {              //  Check the master select pin to see if this unit is the master or slave.
    MasterSelect = HIGH;
    digitalWrite(StatusLED, HIGH);
  } else {
    MasterSelect = LOW;
  }
}

void loop() {
  if (MasterSelect == HIGH) {
    SendData();
    ReceiveData();
  } else {
    ReceiveData();
    SendData();
  }
  delay(500);
}

void SendData() {
  Serial.print('S');                                      // Send start flag.
  for (int i = 0; i <= 5; i++) {                          // Read digital input pins then send them one by one via serial. 1 = off, 2 = on.
    if (digitalRead(InputPins[i]) == LOW) {
      Serial.print('1');
    } else {
      Serial.print('2');
    }
  }
  Serial.print('E');                                        // Send end flag.
}

void ReceiveData() {
  if (Serial.available() >= 8 && Serial.read() == 'S') {   // If there is 8 bytes of data in the receive buffer AND the buffer starts with 'S'
    Serial.readBytesUntil('E', ReceivedData, 7);           // read the received buffer into the ReceivedData array until 'E' is found.
    for (int r = 0; r <= 5; r++) {
      if (ReceivedData[r] == '2') {
        digitalWrite(OutputPins[r], HIGH);                 // If a 2 is received, turn on the output.
      } else {
        digitalWrite(OutputPins[r], LOW);                  // If anything else is received, turn off.
      }
    }
  }
}

You are sending pin stauts which is either '1 (ASCII0x31)' or '0 (ASCII 0x30)'. Why are you sending '2 (ASCII 0x32)' instead of '0'?

You have said that you are communicating between two Arduinos; then where is the sketch of UNO-2? I see the UNO-2's codes are with UNO-1. Are you testing through loop back connection. If you are using two UNOs, then connect them using software Serial Port (SUART) as the hardware UART Port remains engaged with Serial Monitor/IDE.

Moreover, your codes of ReceiveData() functiion could also be written as follows:

void ReceiveData() 
{
  byte n = Serial.available();
  if(n != 0)
  {
    if(flag == false)
    {
      char x = Serial.read();
      if(x == 'S')
      {
        flag = true;
      }
    }
    else
    {
      byte m = Serial.readBytesUntil('E', ReceivedData, 20);
      ReceivedData[m] = '\0'; //null-byte
      Serial.println(ReceivedData);
      //----- process data of the array-----------
      flag = false;
    }
  }

Hi GolamMostafa,
Thank you for the comments and ideas.
There is no specific reason for using '2' or '1'. I was experimenting with sending binary, hex, chars, integers. I was sending b'00000001' for on, and if anything else was received that was preceived as off. The thought behind this was a simple read the serial port, and write that value to the port register. The reason I stopped was I thought it might have been problematic if some random bit of interferance was received it could mess with my intentions. So I changed it over to sending 0x01 in hex, and to avoid the same problem I send 0x02 to at least give it some protection against random bits jumping in.
That gave me a little trouble so I started to send the bytes as chars and I just didnt change it to 0x30 and 0x31.

I wanted to run the same code on both Uno's and just select the master v slave with a jumper pin that is only read on startup and it sets the sequence for the rest of the code.
I have simulated the circuit and it is working well.

Thank you Koepel for the ideas, comments and code.
Are you referring to the hardware or the firmware side of the protocol (232 v 485 v 422)?
If you are referring to serial protocol for the firmware:
The data is sent is packets of 8 bytes with no line carriage.
[0x53] [Pin1Byte]...[Pin6Byte] [0x45] example "S101010E" = 53 31 30 31 30 31 30 45.
From a hardware perspective:
Each Uno will be connected to an USR232-E2 (TTL to TCP) adapter. I have used these in the past for retro fitting older 485 devices to get them online.

My understanding is when you use Serial.read() that current byte is removed from the rx buffer. What happens to the rx buffer if data is not read and the buffer is filled up? Can this happen or is there some kind of overflow error or does it just go back to the beginning and any data not read is written over?

Thanks,
Josh

JP1 is the "MasterSelect".

Puzzled.

I do trust you have resistors in series with each of the LEDs in the bargraph? 470 Ohms each should do.

So "RX/D0" is shorted to "TX/D1" for a loopback test. The only concern there is that since your transmit and receive modules each presume to perform a complete packet, you are relying on the receive buffer and if the sending was buffered, you would enter the receive routine before the complete packet had been sent in which case it may baulk.

As Koepel says,

I would prefer to see a state machine implementation where sending and receiving are performed simultaneously.

And while it is clearly not your own representation, I wonder why "RX/D0" and "TX/D1" are wrongly labelled? :roll_eyes:

That is the "new" forum of this year. :grin:

Hi Paul,
Thanks for the comment.

This was only in simulation, but I do use resistors in practice. The rest of the simulation I have drawn up has the outputs driving relays through an ULN2003A and the inputs isolated through 4N25 opto couplers.
This is the circuit i have so far.

I did wonder this myself. I agree it is a strange order to start with D1 instead of D0. I presume it has something todo with the onboard 328P pinouts, avoiding un-nessessary via's or something to do with FTDI/PL2303 chipset, I would like to hear if someone knows the actual reason for this.

Would adding the following after the read sequence help in removing any unwanted jibberish from the receive buffer that may have gotten stuck? I assume that running the MCU at 16Mhz and the baud of the serial port only at 9600, data couldn't possibly come in that fast that it would get stuck before the buffer is empty.

void ReceiveData() {
  if (Serial.available() == 8 && Serial.read() == 'S') {   // If there is 8 bytes of data in the receive buffer AND the buffer starts with S
    Serial.readBytes(ReceivedData, 7);           // read the received buffer into the ReceivedData array.
    for (int r = 0; r <= 5; r++) {
      switch (ReceivedData[r]) {
        case '1':
          digitalWrite(OutputPins[r], HIGH);               // If '1' is received, turn off output.
          break;
        case '2':                                          // If a 2 is received, turn on the output.
          digitalWrite(OutputPins[r], LOW);
          break;
      }
    }
  }
  while (Serial.available() > 8){
    Serial.read();                        //  Read until the serial buffer is empty.
  }
}

Kind regards,
Josh

It's a stuff-up on the diagram you used, as I mentioned.


The actual names are RXO where "O" is "Output" and TXI where "I" is "Input" as indicated by the little arrows. Don't get upset about it. :grin:

The use of optocouplers in #8 above makes no sense. Transistors would be more appropriate. :astonished:

Different to the offical diagram from Arduino.cc

So it seems - they appear to have reversed the order, making it inconsistent with the UNO.

What a mix-up. Also mixed-up is the "Products" page when I attempted to find the reference for the "Mini" which is missing. :roll_eyes:

(Starts searching to actually find a Nano to examine. Gives up after finding some "Nano 3" variant boards. Too late at night ...) I give up. :dizzy_face:

These days, I'm not sure the "Arduino team" could reliably organise a chook raffle!

This has gotten off track. Anyone reading please refer back to comment #8.