Hardware Serial with 3 Arduinos

Hello - I got a very good start on Serial communication between Ardunios thanks to this thread. I have successfully connected two nanos:

  • Nano1 reads a potentiometer and sends a two-digit number with a line break over hardware serial to Nano2, every 250ms.
interval = 250;
void loop() {
  if ((millis() - potentiometerMillis) > potentiometerIntervals) {
    potentiometerMillis = millis();
    Serial.println(potentiometerValue);
  }
}
  • Nano2 runs the recvWithStartEndMarkers() function from the link above, in an environment like this:
void loop() {
 for (int i = 0; i < 10; i++) {
        recvWithEndMarker();
        doSomeLEDstuff(); //my function which alters the status of 20 LEDs
        delay(50);
 }
 showNewNumber();
}

The above flow works perfectly. I can turn the potentiometer knob and see the serial data being updated in short order. It can run thousands of loops in a row without errors.

Now I am trying to add another Arduino in between them. This is an Arduino Mega and the goal would be for it to process inputs and send outputs over its various hardware serials. The reason for this, in my newbie brain, is that I have very limited memory (using around 80%) in the Nano2 which drives 200 LEDs.

My process now looks like this:

  1. Nano1 reads a potentiometer and sends a two-digit number with a line break over hardware serial to MEGA, every 250ms.
interval = 250;
void loop() {
  if ((millis() - potentiometerMillis) > potentiometerIntervals) {
    potentiometerMillis = millis();
    Serial.println(potentiometerValue);
  }
}
  1. MEGA reads the value and transmits it out to Nano 2, via code like this:
void loop() {
  recv1WithEndMarker();  
  show1NewNumber();
  Serial1.println(value1ToSend); //this sends the 2-digit number onwards to Nano2
}
  1. Nano2 runs the exact same loop as was working in the 2-Arduino example:
void loop() {
 for (int i = 0; i < 10; i++) {
        recvWithEndMarker();
        doSomeLEDstuff(); //my function which alters the status of 20 LEDs
        delay(50);
 }
 showNewNumber();
}

This 3-Ardunio chain works perfectly for a few seconds, and then the data gets garbled forever onwards. For example, if the number being sent from Nano1 is 49, Nano2 will ingest something like:
49
49
49
49
4994
444
4
444999994
etc.

I have confirmed that the Nano1 --> MEGA --> portion is working perfectly. That is, I can plug the MEGA into my laptop and use the serial monitor and see it is spitting out the proper 2-digit number (in this case, 49) over and over and over. So it seems like there is an issue with the Nano2 reading the data and processing it. I just cannot figure out why it works perfectly when Nano1 and Nano2 are connected, but fails when MEGA is in between but receiving and transmitting data at a very rapid pace.

Thank you for any tips you might have. I am also happy to paste the code from all 3 Arduinos if helpful, but there is a ton of unrelated stuff in there (like the LEDs) which I thought would be distracting.

are you using SoftwareSerial on the nanos and the hardware serial ports on the mega?
what baudrate are the serial communications using

Why do you feel the need to use 3 boards when the Mega alone could almost certainly do what you want ?

Follow Robin's code. And understand it. There is a flag that tells you that new data is available from recvWithEndMarker(). Use it to take action.

Maybe you're already using it but nobody can tell except you. That is why snippets are bad.

horace I am only using Software Serial to process GPS data. I am using hardware serial ports for everything else (I think). I will paste the full code from all devices below. I am using 38400 baud. This is the fastest I could get Software Serial to run successfully, and I use this speed so I can process GPS data at 5hz. (It might be more baud than I need, I am not sure). I also set up the hardware serials at the same rate because I think I needed to in order to be able to see data in Serial Monitor to help me debug.

UKHeliBob There are a few reasons for using all of the boards. One is that I am not sure all of the code can fit on one, by the time I am done. (There will be a bunch more gadgets I add to this). Another is that I thought this might make it easier to get each of the physical components working. E.g. get the code working by itself, then integrate with other boards and gadgets. Another is that I frankly don't think I have the programming skill to combine a lot of different things at once, e.g. getting GPS data, updating a bunch of LEDs, calculations for what I want the speedometer display to do, etc. Additionally, even if it all fits in one giant loop, I don't think I would like the latency involved.

sterretje I started with precisely Robin's code and have made very little changes. In terms of the flag you are referring to... are you suggesting something like this?

if (newData == false) {
recvWithEndMarker()
}

That way, I guess it would receive precisely one line of data (my two-digit speed reading) and then would stop trying to get more data?

Here is all of the code for all of the boards. I tried to provide snippet to save some effort (I know I am asking a lot for help here) but if this is easier, I am certainly happy to paste it in.

Nano1 which takes a Neo 6M GPS and a potentiometer as input, and displays a 7-segment output from 0 to 88:

Arduino Mega which attempt to gather serial data from the above Nano and relay it onwards. (And has some placeholders to do so from other Nano inputs in the future):

And Nano2 which takes an input value from a potentiometer and uses it to determine how to light up a matrix of 10 horizontal by 20 vertical LEDs:

Again, what is making me crazy is that this all works without the Mega in the middle. And all it is doing is pretty cleanly (I think) taking data in and spitting it out. But Serial seems to really be sensitive to how fast data is coming in and how fast it is being read and I think I just struck the balance right with the Nano --> Nano setup, but not with the Nano --> Mega --> Nano setup.

No.

void loop()
{
  recvWithEndMarker();
  if (newData == true)
  {
    newData = false;
    doSomeLEDstuff();
  }
}

The rest of the logic depends on what you exactly want to achieve.

There are more compelling reasons for using only one board. Less complication of code and project wiring for one.

Using hardware Serial on the Nano for communication between boards really isn't feasible when you want to add debugging Serial print commands and SoftwareSerial has speed limitations as well as using system resources

Debugging a 3 board project will be a nightmare

I agree - much of the time will be spent debugging and synchronising the communications
if one board will meet the IO requirements (the Mega has plenty of IO capabilities) use it

I worked on a project a few years ago where we had a one PCB - someone decided to distribute the IO to several PCBs communicating over I2C - a year later we went back to one PCB

sterretje Thank you for that detailed outline. I will give that a shot as a starting point and see if I can make it work.

UKHeliBob and horace I was in fact planning on about 6 boards all in all :slight_smile: But it would basically be the "Nano1" input to the Mega, and then the Mega would output in a very similar manner to about 4 Nanos. It felt like if I could get this communication line working to this first Nano output, it would be trivial to get the rest of the ouputs working.

I totally agree on the wiring issues being a pain. But as for the code complexity, it seems easier in my mind to have each board focused on running one physical gadget. Having all of the code for all gadgets running simultaneously on a single board is really worrisome.

I think I'm close with the serial communication so I might give that another few days of tinkering. But I definitely see the case for a single board and that might be a good fallback plan. I'm in no hurry to finish this, I just want it all to be resilient and easy to further customize down the road.

Perhaps not as successfully as you think. If you must use 38400 on the peripheral try it on hardware serial and talk to Mega with software 9600.

Nick_Pyner Thank you for the suggestion. I tried tinkering the baud rate down to 9600 but it didn't seem to change any of the behavior I was seeing.

I had a pretty great breakthrough this morning and I think I have the Nano-->Mega-->Nano serial transmission working with limited latency. (And I might be able to tinker with that a bit to further improve, but I am happy with it right now).

I think the major issue I was running into was that the serial buffer was filling up faster than I could clear it. As a result, the data communication would work for precisely some number of seconds (between 4 seconds and 16 seconds under various code versions, but always the same amount for a given code version) and then it would get more and more sluggish to respond to changes in the potentiometer knob in the Nano1. Or it would be grabbing way too many digits from the buffer and would be spitting out numbers like 454444555445455555 instead of the intended 45.

There also seems to be some practical speed at which serial data seems to arrive intact, but below which it gets jumbled. 250ms is so far as fast as I seem to be able to push it, but I will try to tinker this downwards and see how low it can go. Baud rate didn't seem to affect this number. This limit on sending data and having it be sent intact was pretty surprising and counterintuitive to me. Clearly I don't know much about this stuff, so maybe it makes sense to others... but for now I am including the 250ms delays on sending serial data because it "just works" and I'll continue to test with it in the future and report back if I can deduce anything else.

Here's a simplified version of the timing from board to board which seems to work:

  • Nano1 sends a two-digit reading to serial at 250ms intervals
  • Mega runs through 10 iterations of Robin2's recvWithEndMarker(); and showNewNumber(); to ensure the buffer is clear, then pauses for 250ms and then sends off the data to serial
  • Nano2 runs recvWithEndMarker(); 10 times in 500ms (while doing some other stuff with LEDs in parallel), then runs showNewNumber(); 1 time and uses that data to inform how the LEDs will be updated in the next loop of Nano2.

So far it appears this is close to 100% reliable. I haven't tested it for more than 5 minutes or so, but issues have historically popped up much faster than that, so I am optimistic. My next steps will be to tinker with the number of iterations of recvWithEndMarker(); to see how low I can push it, and the 250ms delay to see how low I can push that as well.

That is not a characteristic of serial, but rather a characteristic of the code reading/writing process. It's a symptom that you've not got a bulletproof approach, that's all.
With three or more processors in the mix, you're going to need to work this out. Map out who's sending and receiving what, and what all the failure modes are; one, most important, is to determine what happens when you get out of sync, for whatever reason. A missing character, or extra character, must never cause anything more than the loss of one 'sentence'.

consider using an RS485 bus for multi serial communications
make one of the nodes master which controls the actions of the slaves?

Why are you doing multiple iterations of anything ?

Surely the idea is to read data whenever it is available then when the complete message has been received use it in some way

camsysca I agree about the missing or extra character. Since Robin's code helpfully translates my numeric characters to be interpreted as actual numbers, I have code to ignore any number > 88. I can also likely add something to ignore anything < 10, because it's not critical to have accurate input at such a low speed (my LED matrix is mostly at a minimum at that point anyways)... and that should rid me of the occasional issue where a digit is dropped and the code thinks it is receiving a 1-digit speed with the line break. So then I'd have protection to ensure something between 10 and 88 is being interpreted, and that would protect me from any instance of one missing piece of data, e.g. the tens digit, the ones digit, or the line break being dropped. If I miss more than one piece of data in rapid succession, I don't have a great answer for that yet. I think it would take a lot more complex code and I think spending time firming up my wiring connections is more important.

horace I have not heard of the RS485 but I will look into it, thank you!

UKHeliBob My understanding of Robin's code is that it looks for one character of data at a time, adds it to an array, and uses loop functionality to keep doing that until it hits a line break end marker. I am not 100% sure why it does this one character at a time but at the moment I don't have any known alternative. If my code loops end up not processing all of the data received, the array gets longer and longer and quickly gets to the point where it will never catch up in a reasonable amount of time. So I not only run recvWithEndMarker() enough times to process the data I expect, but also a few extra because timing this stuff precisely 1:1 across multiple boards and other functionality with its own timing seems beyond my capabilities at this point.

You will do better at synching the data if you use start and end markers.

It is because that is how Serial data arrives

Then you have not understood that the input is only regarded as complete only when the end marker character is received

cattledog Thank you for that tip! I saw that in the documentation from Robin and it makes sense.

UKHeliBob From a code perspective, it looks like it would never make an error, and would always receive 2 digit and then and end marker. I can only speak to the reality I see in the Serial Monitor, which is that things would end up in disarray and it would end up seeing the first digit a bunch of times in a row, or a missing end marker, or any number of other ways in which some data seemed to be either duplicated or deleted. I have been debugging in different ways to try to isolate the cause, but all I could pinpoint so far is that having a reasonably long delay (e.g. 250ms) between sending the digits and end marker, and going a little overboard on attempts to grab and process the data, is as far as I've gotten.

The only successful implementation that I know of was a "token ring" network.

The more problems that you describe the more it reinforces my belief that you should use a single Arduino and not three