I have been working on a project that involves two arduinos communicating with each other via Software Serial and have run into a problem. I have spent quite a bit of time looking online to resolve my issues (there were many to start) and thanks to the information I have found online and on this excellent site I have been able to get everything ironed out with the exception of one problem. But of course, as my luck runs, this one happens to be a major problem...
The project I am working on involves one arduino monitoring buttons and running predefined routines when the appropriate button has been pressed. As part of these scripts the main arduino sends a signal to the secondary arduino via Software Serial to let that arduino know it needs to run a certain script.
For testing/proof of concept purposes I am using a stand in for the main arduino that doesn't have anything else hooked up to it. The main arduino is a Mega 2560 and has a wire going from a ground pin, digital pin 12, digital pin 13, and a 5v pin to a four pin connector. This connector then goes through 20' of wire and connects to another four pin connector that attaches to the circuit of the other arduino which I have added a picture of to this message. The only other connection on the master arduino is a jumper wire connected to ground that I am using to simulate button presses with by connecting it to the different defined input pins momentary to trigger them.
Another important thing to note is that in the attached schematic I am using standard green = ground, white = neutral, and black = power on the AC side of things and red = power, black = ground on the DC side of things. I forgot to note that directly on the schematic.
The main arduino executes its code correctly as confirmed by checking the serial monitor.
The secondary arduino executes each set of code for each status update correctly and is not acting erratically. It simply stops sending a confirmation back to the main arduino and executing its code after a random number of correct activations. It almost seems as though it isn't receiving updates from the main arduino even though this arduino shows it is sending them. Sometimes the main arduino will also display multiple confirmations from the secondary arduino for one button press.
I initially had events for the secondary arduino timed using the millis function but noticed that the red button didn't have any timing or millis functions and was working reliably every time so I have since removed the millis functions from the other buttons and replaced them with delays to test this theory and simplify the code even further just to get this thing working.
And here is the code for the secondary box that activates relays depending on the status it receives from the main box:
#include <SoftwareSerial.h>
// Start a Software Serial channel using digital pins for RX and TX.
SoftwareSerial doorModule (8, 9);
// Define all board OUTPUTS.
const int redLight = 13;
const int greenLight = 12;
const int buzzer = 11;
const int doorLock = 10;
// Pin 9 is used by Software Serial Communication for RX and TX lines.
// Pin 8 is used by Software Serial Communication for RX and TX lines.
// Define all non-physical variables.
String currentStatus;
int statusGiven = 0;
int prevStatus = 5;
String doorMode;
unsigned long lightTimer = 0;
unsigned long buzzerTimer = 0;
unsigned long currentTime = 0;
void setup() {
doorModule.begin(9600);
Serial.begin(115200);
/**** Start Up Procedure *****
Starts the controller with all ports for equipment HIGH to turn relays off
and cut all equipment's power. This will keep the doors unlocked and allow
for maintenance checks and for actors to get to their rooms.
The Room Control Box sends a signal that sets the status to LIGHTS once it
has booted up.
*****************************/
digitalWrite (redLight, HIGH); // Red light stays off.
digitalWrite (greenLight, HIGH); // Green light stays off.
digitalWrite (doorLock, HIGH); // Door lock retracts. (Door unlocked).
digitalWrite (buzzer, HIGH); // Buzzer stops sounding.
pinMode (redLight, OUTPUT);
pinMode (greenLight, OUTPUT);
pinMode (buzzer, OUTPUT);
pinMode (doorLock, OUTPUT);
}
String statusCheck (){
if (doorModule.available() > 0) { // Checks to see if the Sofware Serial Connection is receiving data.
char inByte = doorModule.read(); // Creates character variable to store each character when it is received.
if (inByte == '<'){ // Ignores all input until the "<" character is received.
currentStatus = inByte; // Stores the "<" character and begins the string.
while (inByte != '>'){ // Reads and stores all data until the ">" character is received.
if (doorModule.available() > 0){ // If software serial is still sending data...
inByte = doorModule.read(); // Store each letter when it comes in...
currentStatus += inByte; // Add each letter to the string to create the full message.
}
}
}
return currentStatus;
}
}
void loop() {
doorMode = statusCheck();
// Begin RED BUTTON code.
if (doorMode == "<RED>"){
statusGiven = 1;
if (statusGiven != prevStatus){
Serial.println ("RED Status Received.");
Serial.println ("Door Module Confirms: RED");
doorModule.print ("<RED>");
Serial.println ("");
digitalWrite (redLight, LOW); // Red light turns on.
digitalWrite (greenLight, HIGH); // Green light stays off.
digitalWrite (doorLock, LOW); // Door lock extends. (Door locked).
digitalWrite (buzzer, HIGH); // Buzzer stays off.
currentStatus = "";
doorMode = "";
prevStatus = 1; // Change the Previous Status to prevent more than 1 activation.
}
}
// Begin GREEN BUTTON code.
if (doorMode == "<GREEN>"){
statusGiven = 2;
if (statusGiven != prevStatus){
Serial.println ("GREEN Status Received.");
Serial.println ("Door Module Confirms: GREEN");
doorModule.print ("<GREEN>");
Serial.println ("");
digitalWrite (redLight, HIGH); // Red light turns off.
digitalWrite (greenLight, LOW); // Green light turns on.
digitalWrite (doorLock, HIGH); // Door lock retracts. (Door unlocked).
digitalWrite (buzzer, LOW); // Buzzer starts sounding.
delay (2000);
digitalWrite (buzzer, HIGH); // Buzzer stops sounding.
currentStatus = "";
doorMode = "";
prevStatus = 2; // Change the Previous Status to prevent more than 1 activation.
}
}
// Begin LIGHTS BUTTON code.
if (doorMode == "<LIGHTS>"){
statusGiven = 3;
if (statusGiven != prevStatus){
Serial.println ("LIGHTS Status Received.");
Serial.println ("Door Module Confirms: LIGHTS");
doorModule.print ("<LIGHTS>");
Serial.println ("");
digitalWrite (redLight, LOW); // Red light turns on.
digitalWrite (greenLight, LOW); // Green light turns on.
digitalWrite (doorLock, HIGH); // Door lock retracts. (Door unlocked).
digitalWrite (buzzer, LOW); // Buzzer sounds 3 one second bursts. First burst starts. Delays for 1 second.
delay (1000);
digitalWrite (buzzer, HIGH); // First burst stops. Delays for 1 second.
delay (1000);
digitalWrite (buzzer, LOW); // Second burst starts. Delays for 1 second.
delay (1000);
digitalWrite (buzzer, HIGH); // Second burst stops. Delays for 1 second.
delay (1000);
digitalWrite (buzzer, LOW); // Third burst starts. Delays for 1 second.
delay (1000);
digitalWrite (buzzer, HIGH); // Third burst stops.
currentStatus = "";
doorMode = "";
prevStatus = 3; // Change the Previous Status to prevent more than 1 activation.
}
}
// Begin EMERGENCY BUTTON and/or ROOM EMERGENCY BUTTON code.
if (doorMode == "<EMERGENCY>"){
statusGiven = 4;
if (statusGiven != prevStatus){
Serial.println ("EMERGENCY Status Received.");
Serial.println ("Door Module Confirms: EMERGENCY");
doorModule.print ("<EMERGENCY>");
Serial.println ("");
digitalWrite (redLight, HIGH); // Red light turns off.
digitalWrite (greenLight, LOW); // Green light turns on.
digitalWrite (doorLock, HIGH); // Door lock retracts. (Door unlocked).
digitalWrite (buzzer, HIGH); // Buzzer stays off.
delay (1000);
digitalWrite (greenLight, HIGH); // Green light turns off.
delay (1000);
digitalWrite (greenLight, LOW); // Green light turns on.
currentStatus = "";
doorMode = "";
prevStatus = 4; // Change the Previous Status to prevent more than 1 activation.
}
}
}
I have been working on this for several days at this point and can't seem to figure this out so any help would be really appreciated!
It is not a good idea to use the String (capital S) class on an Arduino as it can cause memory corruption in the small memory on an Arduino. Just use cstrings - char arrays terminated with 0.
Using the String class can result in the sort of delayed problem that you describe.
When using Cstrings you must use strcmp() to compare values rather than ==
Have a look at the examples in Serial Input Basics - simple reliable ways to receive data. There is also a parse example to illustrate how to extract numbers from the received text.
The technique in the 3rd example will be the most reliable.
You can send data in a compatible format with code like this
Thank you for the quick reply Robin! I have thoroughly read the link you recommended which was a very good and informative read, thank you for pointing me to it! From what I learned there I have decided to simplify things and get that working instead of using full strings.
After reading your link I have:
Switched the wiring to using hardware serial.
Changed the code to require a single character to run instead of a string as that is all that is really
required.
Utilized starting and stopping characters on the sending arduino and a filter to remove them on the receiving arduino using your example as a model.
This has the serial functioning seemingly perfectly! As confirmed by the serial monitor on the receiving arduino, I am setting the value to the controlling char to a single number consistently! However, I have run into a new problem that is quite puzzling to me and have been working on getting it resolved since your post (that's the reason for my delayed response).
When I am simply executing an if statement and moving on everything works as it should. However, when I try to use millis statements to time events things start to act strange.
When I use the code attached to this post everything works as it should. I have left the projects running for several hours without a single hiccup. Which proves the serial part of this is solid.
unsigned long lightTimer = 0;
unsigned long buzzerTimer = 0;
unsigned long currentTime = 0;
boolean newData = false;
boolean endOfData = false;
// Define all board OUTPUTS.
const int redLight = 13;
const int greenLight = 12;
const int buzzer = 11;
const int doorLock = 10;
void setup() {
Serial.begin (115200);
pinMode (redLight, OUTPUT);
pinMode (greenLight, OUTPUT);
pinMode (buzzer, OUTPUT);
pinMode (doorLock, OUTPUT);
/**** Start Up Procedure *****
Starts the controller with all ports for equipment HIGH to turn relays off
and cut all equipment's power. This will keep the doors unlocked and allow
for maintenance checks and for actors to get to their rooms.
The Room Control Box sends a signal that sets the status to LIGHTS once it
has booted up.
*****************************/
digitalWrite (redLight, HIGH); // Red light stays off.
digitalWrite (greenLight, HIGH); // Green light stays off.
digitalWrite (doorLock, HIGH); // Door lock retracts. (Door unlocked).
digitalWrite (buzzer, HIGH); // Buzzer stops sounding.
}
char statusCheck(){
char statusCode;
while ((Serial.available() > 0) && (endOfData == false)) {
char inByte = Serial.read();
if (newData == true) {
if (inByte != '>') {
statusCode = inByte;
}
else {
newData = false;
endOfData = true;
}
}
if (inByte == '<'){
newData = true;
}
}
endOfData = false; // Reset the code to loop again.
return statusCode;
}
void redMode(){
digitalWrite (redLight, LOW);
digitalWrite (greenLight, HIGH);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, LOW);
}
void greenMode(){
digitalWrite (redLight, HIGH);
digitalWrite (greenLight, LOW);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, HIGH);
}
void lightsMode(){
digitalWrite (redLight, LOW);
digitalWrite (greenLight, LOW);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, HIGH);
}
void emergencyMode(){
digitalWrite (redLight, HIGH);
digitalWrite (greenLight, LOW);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, HIGH);
}
void loop() {
char doorMode = statusCheck();
Serial.print (doorMode);
if (doorMode == '1'){
redMode();
}
if (doorMode == '2'){
greenMode();
}
if (doorMode == '3'){
lightsMode();
}
if (doorMode == '4'){
emergencyMode();
}
}
As I said, things start to act weird when I attempt to use millis statements to time things though. For example, in the code below the serial monitor no longer shows the number 1 when it is sent to the box and does not run the "redMode" lines of the code. It simply ignores them, waits 5 seconds and resumes normal operation for commands 2, 3, and 4.
Since it doesn't output the number 1 to the screen I am thinking that the "Emergency Function" is not handing back control the the "Main" function and is instead jumping straight to the "RedMode" function. It seems somewhere along the way the "char 1" is getting lost. I have confirmed it is receiving the char by having it output the value of "doorMode" at the end of the "emergency function" and it also stops the "emergency function" it just never starts the "redMode" function.
The only part of this code that differs is the "If doorMode == '4' function" as the bottom.
unsigned long lightTimer = 0;
unsigned long buzzerTimer = 0;
unsigned long currentTime = 0;
boolean newData = false;
boolean endOfData = false;
// Define all board OUTPUTS.
const int redLight = 13;
const int greenLight = 12;
const int buzzer = 11;
const int doorLock = 10;
void setup() {
Serial.begin (115200);
pinMode (redLight, OUTPUT);
pinMode (greenLight, OUTPUT);
pinMode (buzzer, OUTPUT);
pinMode (doorLock, OUTPUT);
/**** Start Up Procedure *****
Starts the controller with all ports for equipment HIGH to turn relays off
and cut all equipment's power. This will keep the doors unlocked and allow
for maintenance checks and for actors to get to their rooms.
The Room Control Box sends a signal that sets the status to LIGHTS once it
has booted up.
*****************************/
digitalWrite (redLight, HIGH); // Red light stays off.
digitalWrite (greenLight, HIGH); // Green light stays off.
digitalWrite (doorLock, HIGH); // Door lock retracts. (Door unlocked).
digitalWrite (buzzer, HIGH); // Buzzer stops sounding.
}
char statusCheck(){
char statusCode;
while ((Serial.available() > 0) && (endOfData == false)) {
char inByte = Serial.read();
if (newData == true) {
if (inByte != '>') {
statusCode = inByte;
}
else {
newData = false;
endOfData = true;
}
}
if (inByte == '<'){
newData = true;
}
}
endOfData = false; // Reset the code to loop again.
return statusCode;
}
void redMode(){
digitalWrite (redLight, LOW);
digitalWrite (greenLight, HIGH);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, LOW);
}
void greenMode(){
digitalWrite (redLight, HIGH);
digitalWrite (greenLight, LOW);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, HIGH);
}
void lightsMode(){
digitalWrite (redLight, LOW);
digitalWrite (greenLight, LOW);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, HIGH);
}
void loop() {
char doorMode = statusCheck();
Serial.print (doorMode);
if (doorMode == '1'){
redMode();
}
if (doorMode == '2'){
greenMode();
}
if (doorMode == '3'){
lightsMode();
}
if (doorMode == '4'){
digitalWrite (redLight, HIGH);
digitalWrite (greenLight, LOW);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, HIGH);
lightTimer = millis();
while ((doorMode != '1') && (doorMode != '2') && (doorMode != '3')) {
currentTime = millis();
if ((currentTime - lightTimer) >= 1000){ // Delays 1 second.
digitalWrite (greenLight, HIGH); // Green light turns off.
}
if ((currentTime - lightTimer) >= 2000){ // Delays 1 second.
digitalWrite (greenLight, LOW); // Green light turns on.
lightTimer = millis();
}
doorMode = statusCheck();
}
}
}
The code attached to this post is another variation I have tried. I thought maybe setting the "emergency" function as an independant function like the others would help but it had no effect.
I also tried to add similar timing statements to the "greenMode" function. When I tried that the arduino would not display the '3' in the serial monitor but it WOULD run the code that the "greenMode" function contains.
If anyone has any ideas as to what might be happening please let me know!
Thanks!
The only part of this code that differs is the "emergencyMode" function just before the "main" function.
unsigned long lightTimer = 0;
unsigned long buzzerTimer = 0;
unsigned long currentTime = 0;
boolean newData = false;
boolean endOfData = false;
// Define all board OUTPUTS.
const int redLight = 13;
const int greenLight = 12;
const int buzzer = 11;
const int doorLock = 10;
void setup() {
Serial.begin (115200);
pinMode (redLight, OUTPUT);
pinMode (greenLight, OUTPUT);
pinMode (buzzer, OUTPUT);
pinMode (doorLock, OUTPUT);
/**** Start Up Procedure *****
Starts the controller with all ports for equipment HIGH to turn relays off
and cut all equipment's power. This will keep the doors unlocked and allow
for maintenance checks and for actors to get to their rooms.
The Room Control Box sends a signal that sets the status to LIGHTS once it
has booted up.
*****************************/
digitalWrite (redLight, HIGH); // Red light stays off.
digitalWrite (greenLight, HIGH); // Green light stays off.
digitalWrite (doorLock, HIGH); // Door lock retracts. (Door unlocked).
digitalWrite (buzzer, HIGH); // Buzzer stops sounding.
}
char statusCheck(){
char statusCode;
while ((Serial.available() > 0) && (endOfData == false)) {
char inByte = Serial.read();
if (newData == true) {
if (inByte != '>') {
statusCode = inByte;
}
else {
newData = false;
endOfData = true;
}
}
if (inByte == '<'){
newData = true;
}
}
endOfData = false; // Reset the code to loop again.
return statusCode;
}
void redMode(){
digitalWrite (redLight, LOW);
digitalWrite (greenLight, HIGH);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, LOW);
}
void greenMode(){
digitalWrite (redLight, HIGH);
digitalWrite (greenLight, LOW);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, HIGH);
}
void lightsMode(){
digitalWrite (redLight, LOW);
digitalWrite (greenLight, LOW);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, HIGH);
}
void emergencyMode(){
char newDoorMode;
digitalWrite (redLight, HIGH);
digitalWrite (greenLight, LOW);
digitalWrite (buzzer, HIGH);
digitalWrite (doorLock, HIGH);
lightTimer = millis();
while ((newDoorMode != '1') && (newDoorMode != '2') && (newDoorMode != '3')) {
currentTime = millis();
if ((currentTime - lightTimer) >= 1000){ // Delays 1 second.
digitalWrite (greenLight, HIGH); // Green light turns off.
}
if ((currentTime - lightTimer) >= 2000){ // Delays 1 second.
digitalWrite (greenLight, LOW); // Green light turns on.
lightTimer = millis();
}
newDoorMode = statusCheck();
}
}
void loop() {
char doorMode = statusCheck();
Serial.print (doorMode);
if (doorMode == '1'){
redMode();
}
if (doorMode == '2'){
greenMode();
}
if (doorMode == '3'){
lightsMode();
}
if (doorMode == '4'){
emergencyMode();
}
}
Sorry, I don't mean to flood you with posts. Was just trying to be as thorough as possible.
I really thought that the code in post #7 should have worked and don't know why it didn't. If you wouldn't mind looking at that one I would really appreciate it!
Of course, I'm happy to give any info I can if it will help you understand my problems better!
The project I am working on will consist of:
Main Arduino -
Monitors 4 connected buttons, 2 of which have momentary switches and the other 2 have locking switches
When it detects a button push it will send the corresponding number to the secondary arduino via serial communication in the format of <1>, <2>, <3>, or <4>.
Secondary arduino (the one the code is for) -
Receives the number of the button pushed. (<1>, <2>, <3>, or <4>)
Filters off the '<' and '>' symbols and assigns the numerical value to "char doorMode".
Runs the code associated with that door mode.
1 door mode (RED) consists of an "if statement" after the code is run once it is done.
The other 3 door modes need to have timing elements scripted:
GREEN needs to turn on the green light and sound the buzzer for 2 seconds then stop it.
LIGHTS needs to turn on the green and red lights, then sound three 1 second bursts of the buzzer.
EMERGENCY needs to blink the green light on and off for 1 second each until another button is pushed.
All of these routines need to run in a non-blocking manner to allow the box to detect when a different button has been pushed, stop the current routine, and switch to the new one that is correct for the new button. That's why I thought a while statement and millis statements would be the best option.
I have code similar to this working on the main arduino to run script when a certain button is pushed so I tried to use that format here as well but it seems that it doesn't work since this box is reading serial input instead of physical pins. I also REALLY want to keep using serial communication as it allows me to use a 4 pin connector between the boxes instead of a much more expensive 6 pin connector.
Right, so for that specific section of code this is what happens:
Code actions -
redLight is written HIGH, the relay cuts power to the light and the red light next to the door turns off.
greenLight is written LOW, the relay turns on and the green light next to the door turns on.
buzzer is written HIGH, the relay cuts power and the buzzer doesn't sound.
doorLock is written HIGH, the relay cuts power and the door stays unlocked.
lightTimer is set to the current value for millis to be able to time the on and off cycles for the green light.
I created a while loop that polls the statusCheck() function and sets doorMode to the value it returns each iteration to know when a new button has been pushed.
Within that loop currentTime is set to the current value for millis each iteration and then the 2 if statements use that value to determine when to activate.
The first if statement waits for 1 second then turns the green light by the door off.
The second if statement waits for another second then turns the green light back on. It then resets lightTimer to the current millis value so both "if statements" are valid again to keep the green light by the door blinking on and off for 1 second indefinitely.
At any point if statusCheck() returns a value of 1, 2, or 3 (AKA another button has been pushed) the loop is exited.
What I know -
As of right now I have confirmed that the statusCheck() function is updating the value for doorMode at the end of the loop via printing its value to the serial monitor.
When the value for doorMode is updated by sending a new number the "if doorMode == 4" loop is exited and the green light by the door stops in whatever state it is in (on or off).
The code does not print the number 1 (the button pushed) to the serial monitor.
Nothing happens for the next 5 seconds (the timed interval I have the main arduino waiting to send new numbers) , then the code resumes normal function when the 2 data is sent and works all the way to 4 before repeating the pause at number 1.
Hopefully this is more along the lines of the answer you were looking for?