As @Delta_G indicates, you want to keep the tasks small if you can. I buy Arduino Nano Everys by the six pack and for more complicated builds, have each one handle a specific task and pass along control as needed to a central MCU, Mega being a good choice.
One key is to keep the logic levels consistent. Don't involve level shifting, it just makes things harder.
Same goes for buttons. pinMode (myButton, INPUT_PULLUP);
saves time, parts and headaches.
Also as @Delta_G indicates, the tutorial by @Robin2 is a great way to go for multi serial comms. I just send single chars as in example #1 and read them in on a central Mega. It really does make complex systems come alive since one MCU is essentially just directing traffic. And that doesn't even touch using interrupts or simple digitalRead(myArduinoTurnpike);
to look to pass through routines from Nano Every B say, through Mega A to Nano Every C!
Lastly, don't forget that when you need to lock in certain routines on an Arduino, you're probably looking to build a finite state machine. So you have at least one variable (serial input, or maybe an ultrasonic sensor, maybe a five-way guitar pick up switch) that dictates the state that drives the machine. Sorry if this is old hat to you, but anyways, here's a couple quick sketches that illustrate the point that may be useful to you. These are based on a solution I offered up to a fellow who wanted to control different devices in his pet lizard's vivarium. Did you know that a lizard aquarium is called a vivarium? Neither did I, but here's the sketches:
sketch one - use alone with the Serial monitor to type stuff in or use in conjuction with sketch two:
/* May also be used with companion sketch "charDriverUltrasonicSender"
or just used as is by typing in the chars to Serial Monitor */
char mode;
const int light = 3;
const int pump = 4;
const int fan = 5;
void setup() {
Serial.begin(115200);
pinMode(light, OUTPUT);
pinMode(pump, OUTPUT);
pinMode(fan, OUTPUT);
Serial.println(F("charDrivenThreeDeviceStateMachine"));
Serial.println();
Serial.println(F("Type 0-7 to change functions"));
Serial.println();
mode = '0'; // start state variable at mode 0
modeZero(); // let us know and init devices to off
}
void loop() {
if (Serial.available() > 0) {
mode = Serial.read(); // could be any switch or sensor driving this
switch (mode) {
case '0':
modeZero();
break;
case '1':
modeOne();
break;
case '2':
modeTwo();
break;
case '3':
modeThree();
break;
case '4':
modeFour();
break;
case '5':
modeFive();
break;
case '6':
modeSix();
break;
case '7':
modeSeven();
break;
default: // optional, but leave in even if you don't use, some say
break;
}
}
}
void modeZero() {
Serial.println(F("mode 0: light off, pump off, fan off"));
Serial.println("");
allOff();
}
void modeOne() {
Serial.println(F("mode 1: light on, pump on, fan on"));
Serial.println("");
allOn();
}
void modeTwo() {
Serial.println(F("mode 2: light on, pump on, fan off"));
Serial.println("");
lightAndPump();
}
void modeThree() {
Serial.println(F("mode 3: light off, pump on, fan on"));
Serial.println("");
fanAndPump();
}
void modeFour() {
Serial.println(F("mode 4: light on, pump off, fan on"));
Serial.println("");
lightAndFan();
}
void modeFive() {
Serial.println(F("mode 5: light on, pump off, fan off"));
Serial.println("");
justLight();
}
void modeSix() {
Serial.println(F("mode 6: light off, pump on, fan off"));
Serial.println("");
justPump();
}
void modeSeven() {
Serial.println(F("mode 7: light off, pump off, fan on"));
Serial.println("");
justFan();
}
void allOff() {
digitalWrite(light, LOW);
digitalWrite(pump, LOW);
digitalWrite(fan, LOW);
}
void allOn() {
digitalWrite(light, HIGH);
digitalWrite(pump, HIGH);
digitalWrite(fan, HIGH);
}
void lightAndPump() {
digitalWrite(light, HIGH);
digitalWrite(pump, HIGH);
digitalWrite(fan, LOW);
}
void fanAndPump() {
digitalWrite(light, LOW);
digitalWrite(pump, HIGH);
digitalWrite(fan, HIGH);
}
void lightAndFan() {
digitalWrite(light, HIGH);
digitalWrite(pump, LOW);
digitalWrite(fan, HIGH);
}
void justLight() {
digitalWrite(light, HIGH);
digitalWrite(pump, LOW);
digitalWrite(fan, LOW);
}
void justPump() {
digitalWrite(light, LOW);
digitalWrite(pump, HIGH);
digitalWrite(fan, LOW);
}
void justFan() {
digitalWrite(light, LOW);
digitalWrite(pump, LOW);
digitalWrite(fan, HIGH);
}
sketch two:
/*
Companion sketch: charDrivenThreeDeviceStateMachine
Read and act on 3 ultrasonic sensors, based on Tim Eckel's
example NewPing library sketch that pings 3 sensors 20 times a second.
Receiving sketch is set up to handle 8 states (0-7) to act on three devices
(a light, a pump and a fan if so configured in such a circuit)
*/
#include <NewPing.h>
#define SONAR_NUM 3 // Number of sensors.
#define MAX_DISTANCE 200 // Maximum distance (in cm) to ping.
NewPing sonar[SONAR_NUM] = {
// Sensor object array.
NewPing(4, 5, MAX_DISTANCE), // sensor 0 trigger pin, echo pin, and max distance.
NewPing(6, 7, MAX_DISTANCE), // sensor 1 trigger pin, echo pin, and max distance.
NewPing(8, 9, MAX_DISTANCE) // sensor 2 trigger pin, echo pin, and max distance.
};
int mode = -1;
int lastMode = -2;
void setup() {
Serial.begin(115200); // Open serial monitor at 115200 baud to see ping results.
Serial.println(F("charDriverUltrasonicSender"));
Serial.println();
}
void loop() {
/* uncomment this function to just read and set up sensors */
// justReadSensors();
/* comment out everything else in loop() if just using above function */
checkSensorPings();
switch (mode) {
case 0:
allOff();
break;
case 1:
allOn();
break;
case 2:
lightAndPump();
break;
case 3:
fanAndPump();
break;
case 4:
lightAndFan();
break;
case 5:
justLight();
break;
case 6:
justPump();
break;
case 7:
justFan();
break;
default:
// do nothing
break;
}
lastMode = mode;
}
void checkSensorPings() {
if (sonar[0].ping_cm() == 0 && sonar[1].ping_cm() == 0 && sonar[2].ping_cm() == 0) {
mode = 0;
} else if ((sonar[0].ping_cm() > 50 && sonar[0].ping_cm() < 100) && (sonar[1].ping_cm() > 50 && sonar[1].ping_cm() < 100) && (sonar[2].ping_cm() > 50 && sonar[2].ping_cm() < 100)) {
mode = 1;
} else if ((sonar[0].ping_cm() > 50 && sonar[0].ping_cm() < 100) && (sonar[1].ping_cm() > 50 && sonar[1].ping_cm() < 100) && (sonar[2].ping_cm() == 0)) {
mode = 2;
} else if ((sonar[0].ping_cm() == 0) && (sonar[1].ping_cm() > 50 && sonar[1].ping_cm() < 100) && (sonar[2].ping_cm() > 50 && sonar[2].ping_cm() < 100)) {
mode = 3;
} else if ((sonar[0].ping_cm() > 50 && sonar[0].ping_cm() < 100) && (sonar[1].ping_cm() == 0) && (sonar[2].ping_cm() > 50 && sonar[2].ping_cm() < 100)) {
mode = 4;
} else if ((sonar[0].ping_cm() > 5 && sonar[0].ping_cm() < 15) && (sonar[1].ping_cm() == 0) && (sonar[2].ping_cm() == 0)) {
mode = 5;
} else if ((sonar[0].ping_cm() == 0) && (sonar[1].ping_cm() > 50 && sonar[1].ping_cm() < 100) && (sonar[2].ping_cm() == 0)) {
mode = 6;
} else if ((sonar[0].ping_cm() == 0) && (sonar[1].ping_cm() == 0) && (sonar[2].ping_cm() > 50 && sonar[2].ping_cm() < 100)) {
mode = 7;
}
}
void allOff() {
if (lastMode != mode) {
Serial.print('0'); // allOff(); to companion sketch
}
}
void allOn() {
if (lastMode != mode) {
Serial.print('1'); // allOn(); to companion sketch
}
}
void lightAndPump() {
if (lastMode != mode) {
Serial.print('2'); // lightAndPump(); to companion sketch
}
}
void fanAndPump() {
if (lastMode != mode) {
Serial.print('3'); // fanAndPump(); to companion sketch
}
}
void lightAndFan() {
if (lastMode != mode) {
Serial.print('4'); // lightAndFan(); to companion sketch
}
}
void justLight() {
if (lastMode != mode) {
Serial.print('5'); // justLight(); to companion sketch
}
}
void justPump() {
if (lastMode != mode) {
Serial.print('6'); // justPump(); to companion sketch
}
}
void justFan() {
if (lastMode != mode) {
Serial.print('7'); // justFan(); to companion sketch
}
}
void justReadSensors() {
for (uint8_t i = 0; i < SONAR_NUM; i++) { // Loop through each sensor and display results.
delay(50); // Wait 50ms between pings (about 20 pings/sec). 29ms should be the shortest delay between pings.
Serial.print(i);
Serial.print("=");
Serial.print(sonar[i].ping_cm());
Serial.print("cm ");
Serial.println();
}
}