Combining two codes (or more) is relatively painless if the codes to be combined are non-blocking.
For example:
https://wokwi.com/projects/410224559941422081
It runs three steppers, 1 servo, 5 LEDs, 1 strip of Neopixels, and is reactive to 4 buttons at any time.
This simulation is a combination of three programs:
- Mobatools Example/Reference2 -- Three steppers with homing and a state machine.
- Demonstration Code for Several Things At the Same Time.
- A FastLED sketch
If the component programs are written in a cooperative non-blocking style, you can fairly easily combine a number of them into a single program. If your source programs are not written cooperatively, they will likely require a rewrite to share the processor with other programs.
Combined code for the above simulation
// https://wokwi.com/projects/410224559941422081
// For https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/118
// Example of combining three non-blocking codes, cut-and-pasted together:
/*
1) https://wokwi.com/projects/410122241449166849 -- Mobatools Reference2 Example by @MicroBahner
2) https://wokwi.com/projects/366664987128970241 -- SeveralThingsAtTheSameTime by @Robin2
3) https://wokwi.com/projects/410037772389117953 -- Some Fastleds by @DaveX
*/
////////// Example Reference for MoToStepper - attaching a bipolar stepper with step/dir and enable ////////////
// https://github.com/MicroBahner/MobaTools/blob/master/examples/_Stepper/Stepper_Reference_2/Stepper_Reference_2.ino
// https://wokwi.com/projects/410122241449166849
// for https://forum.arduino.cc/t/wokwi-simulations-for-arduino-built-in-examples/1304754/6
/* An Example with 3 steppers ( 2 unipolar, 1 bipolar ).
A reference run is started in setup. 2 limit switches are required for this.
This example does not run on ESP8266
*/
//============== Declarations
//======================================================
//================= Declarations First program
//======================================================
#include <MobaTools.h>
MoToTimer Pause;
MoToStepper Step_X(4096); // X-Achse, unipolarer Schrittmotor 28BYJ-48
MoToStepper Step_Y(4096); // Y-Achse, unipolarer Schrittmotor 28BYJ-48
MoToStepper Step_Z(200, STEPDIR); // Z-Achse, bipolarer Schrittmotor mit Treiber wie A4988, DRV8825 oder vergleichbare
const byte pinRef[] = { A0, A1 }; // pinRef_X, pinRef_Y
const byte nbrOfButtons = sizeof(pinRef); // Anzahl der angeschlossenen Taster
enum {X_AXIS = 0, Y_AXIS = 1};
//======================================================
//================= Declarations Second program
//======================================================
//Several Things At The Same Time
// -----LIBRARIES
#include <Servo.h>
// ----CONSTANTS (won't change)
const int onBoardLedPin = 14; // the pin numbers for the LEDs
const int led_A_Pin = 15;
const int led_B_Pin = 16;
const int buttonLed_Pin = 17;
const int buttonPin = 20; // the pin number for the button
const int servoPin = 19; // the pin number for the servo signal
const int onBoardLedInterval = 500; // number of millisecs between blinks
const int led_A_Interval = 2500;
const int led_B_Interval = 4500;
const int blinkDuration = 500; // number of millisecs that Led's are on - all three leds use this
const int buttonInterval = 300; // number of millisecs between button readings
const int servoMinDegrees = 20; // the limits to servo movement
const int servoMaxDegrees = 150;
//------- VARIABLES (will change)
byte onBoardLedState = LOW; // used to record whether the LEDs are on or off
byte led_A_State = LOW; // LOW = off
byte led_B_State = LOW;
byte buttonLed_State = LOW;
Servo myservo; // create servo object to control a servo
int servoPosition = 90; // the current angle of the servo - starting at 90.
int servoSlowInterval = 80; // millisecs between servo moves
int servoFastInterval = 10;
int servoInterval = servoSlowInterval; // initial millisecs between servo moves
int servoDegrees = 2; // amount servo moves at each step
// will be changed to negative value for movement in the other direction
unsigned long currentMillis = 0; // stores the value of millis() in each iteration of loop()
unsigned long previousOnBoardLedMillis = 0; // will store last time the LED was updated
unsigned long previousLed_A_Millis = 0;
unsigned long previousLed_B_Millis = 0;
unsigned long previousButtonMillis = 0; // time when button press last checked
unsigned long previousServoMillis = 0; // the time when the servo was last moved
//======================================================
//==== Declarations Third program
//======================================================
//
// fastled DaveX from https://wokwi.com/projects/410037772389117953
//
#include "FastLED.h"
const int DaveXButton = 21;
const int DaveXLED = 18;
#define NUM_LEDS1 26
#define DATA_PIN1 35
CRGB array_LED_sconce [NUM_LEDS1];
uint32_t interval = 1000, timeout;
// subset of ledstrip to color
int ledBlink[] = {4, 12, 20, 0, 8, 16, 24};
int fastledState;
CHSV daveXringColor = CHSV(70, 255, 255);
//======================================================
//================= Arduino minimal program requirements
//======================================================
void setup() {
Serial.begin(115200);
Serial.println("Starting a Combination of Three Programs");
setupMoba();
setupSTATST();
setupDaveX();
}
void loop() {
loopMoba();
loopSTATST();
loopDaveX();
}
//======================================================
//======== renamed functions from component programs
//======================================================
void setupMoba() {
Serial.println("MobaTools Reference 2 Example https://wokwi.com/projects/410123341513466881"); // so we know what sketch is running
for (byte i = 0; i < nbrOfButtons; i++) {
pinMode(pinRef[i], INPUT_PULLUP); // gedrückt = LOW
}
Step_X.attach( 12, 11, 10, 9 ); // IN1, IN2, IN3, IN4
Step_Y.attach( 5, 4, 3, 2 ); // IN1, IN2, IN3, IN4
Step_Z.attach( 7, 8 ); // STEPpin, DIRpin
Step_Z.setSpeed( 800 ); // = 80 U/Min (motorspezifisch)
Step_Z.setRampLen( 50 ); // Beschleunigung (motorspezifisch)
Step_Z.write(360); // Winkel 360 Grad drehen
// Pause.setTime( 2500 ); // Dreh- und Pausenzeit
}
void loopMoba() {
enum {SEEK_ZERO, MOVE_FORWARD, MOVE_BACWARD};
static byte step_X = SEEK_ZERO; // Schrittkettenstatus X-Achse
static byte step_Y = SEEK_ZERO; // Schrittkettenstatus Y-Achse
static int16_t angle = 360;
//-- X-Achse
switch (step_X) {
case SEEK_ZERO:
if (seekZeropoint(Step_X, X_AXIS)) {
step_X = MOVE_FORWARD;
}
break;
case MOVE_FORWARD:
if ( !Step_X.moving() ) { // warten bis die Bewegung abgeschlossen ist
Step_X.setSpeed( 200 ); // = 20 U/Min (motorspezifisch)
Step_X.setRampLen( 100 ); // Beschleunigung (motorspezifisch)
Step_X.writeSteps(4096);
step_X = MOVE_BACWARD;
}
break;
case MOVE_BACWARD:
if ( !Step_X.moving() ) { // warten bis die Bewegung abgeschlossen ist
Step_X.writeSteps(-4096);
step_X = MOVE_FORWARD;
}
break;
}
//-- Y-Achse
switch (step_Y) {
case SEEK_ZERO:
if (seekZeropoint(Step_Y, Y_AXIS)) {
step_Y = MOVE_FORWARD;
}
break;
case MOVE_FORWARD:
if ( !Step_Y.moving() ) { // warten bis die Bewegung abgeschlossen ist
Step_Y.setSpeed( 150 ); // = 20 U/Min (motorspezifisch)
Step_Y.setRampLen( 100 ); // Beschleunigung (motorspezifisch)
Step_Y.writeSteps(4096); // Bewegung starten
step_Y = MOVE_BACWARD;
}
break;
case MOVE_BACWARD:
if ( !Step_Y.moving() ) { // warten bis die Bewegung abgeschlossen ist
Step_Y.writeSteps(-4096); // Bewegung starten
step_Y = MOVE_FORWARD;
}
break;
}
//-- Z-Achse
if ( !Step_Z.moving() ) { // warten bis die Bewegung abgeschlossen ist
if ( !Pause.running() ) { // warten bis Ablauf der Zeit
Pause.setTime( 5000 ); // Zeit für Bewegung und Pause
angle *= -1; // andere Richtung
Step_Z.write(angle); // Bewegung starten
}
}
}
bool seekZeropoint(MoToStepper &Step, byte axis) { // keine Kopie, sondern eine Referenz des Schrittmotorobjektes
enum {REFLEAVE, REFREACH, REACHZERO};
static byte step[] = {REFLEAVE, REFLEAVE}; // Schrittkettenstatus
switch (step[axis]) {
case REFLEAVE:
Step.setSpeed( 50 ); // = 5 U/Min (motorspezifisch) Schleichfahrt
Step.setRampLen( 5 ); // Beschleunigung (motorspezifisch)
Step.rotate(1); // vom Referentpunkt runterbewegen
if (digitalRead(pinRef[axis])) { // Referenzsensor nicht (mehr) betätigt
Step.doSteps(100); // mit etwas Entfernung anhalten
step[axis]++;
}
break;
case REFREACH:
if ( !Step.moving() ) { // warten bis die Bewegung abgeschlossen ist
Step.rotate(-1); // Richtung Referentpunkt bewegen
step[axis]++;
}
break;
case REACHZERO:
if (!digitalRead(pinRef[axis])) { // Ref_X betätigt
Step.setZero(100); // setze Nullpunkt 100 Schritte von Referenzpunkt entfernt
Step.writeSteps(0); // zum Nullpunkt bewegen
step[axis]++;
}
break;
default:
step[axis] = REFLEAVE;
return true;
}
return false;
}
//======================================================
//================= Second program functions
//======================================================
//###########################################################
// SeveralThingsAtTheSameTimeRev1.ino
// from:
// https://forum.arduino.cc/t/demonstration-code-for-several-things-at-the-same-time/217158/1
// https://forum.arduino.cc/t/demonstration-code-for-several-things-at-the-same-time/217158/2
// implemented on a Wokwi simulation per
// https://forum.arduino.cc/t/circuit-with-1-button-and-3-leds/1134897/3
// Wokwi: https://wokwi.com/projects/366664987128970241
// Note that the Adafruit series at https://learn.adafruit.com/multi-tasking-the-arduino-part-1/all-together-now
// expands on this to do two steppers using classes
// An implementation of the class-based Adafruit one in Wokwi is at
// https://wokwi.com/projects/397172393690134529
// An expansion of the BlinkWithoutDelay concept to illustrate how a script
// can appear to do several things at the same time
// this sketch does the following
// it blinks the onboard LED (as in the blinkWithoutDelay sketch)
// it blinks two external LEDs (LedA and LedB) that are connected to pins 12 and 11.
// it turns another Led (buttonLed connected to pin 10) on or off whenever a button
// connected to pin 7 is pressed
// it sweeps a servo (connected to pin 5) back and forth at different speeds
// One leg of each LED should be connected to the relevant pin and the other leg should be connected to a
// resistor of 470 ohms or more and the other end of the resistor to the Arduino GND.
// If the LED doesn't light its probably connected the wrong way round.
// On my Uno and Mega the "button" is just a piece of wire inserted into pin 7.
// Touching the end of the wire with a moist finger is sufficient to cause the switching action
// Of course a proper press-on-release-off button switch could also be used!
// The Arduino is not capable of supplying enough 5v power to operate a servo
// The servo should have it's own power supply and the power supply Ground should
// be connected to the Arduino Ground.
// The sketch is written to illustrate a few different programming features.
// The use of many functions with short pieces of code.
// Short pieces of code are much easier to follow and debug
// The use of variables to record the state of something (e.g. onBoardLedState) as a means to
// enable the different functions to determine what to do.
// The use of millis() to manage the timing of activities
// The definition of all numbers used by the program at the top of the sketch where
// they can easily be found if they need to be changed
//=======
//========
void setupSTATST() {
// Serial.begin(9600);
Serial.println("Starting SeveralThingsAtTheSameTimeRev1.ino"); // so we know what sketch is running
// set the Led pins as output:
pinMode(onBoardLedPin, OUTPUT);
pinMode(led_A_Pin, OUTPUT);
pinMode(led_B_Pin, OUTPUT);
pinMode(buttonLed_Pin, OUTPUT);
// set the button pin as input with a pullup resistor to ensure it defaults to HIGH
pinMode(buttonPin, INPUT_PULLUP);
myservo.write(servoPosition); // sets the initial position
myservo.attach(servoPin);
}
//=======
void loopSTATST() {
// Notice that none of the action happens in loop() apart from reading millis()
// it just calls the functions that have the action code
currentMillis = millis(); // capture the latest value of millis()
// this is equivalent to noting the time from a clock
// use the same time for all LED flashes to keep them synchronized
readButton(); // call the functions that do the work
updateOnBoardLedState();
updateLed_A_State();
updateLed_B_State();
switchLeds();
servoSweep();
}
//========
void updateOnBoardLedState() {
if (onBoardLedState == LOW) {
// if the Led is off, we must wait for the interval to expire before turning it on
if (currentMillis - previousOnBoardLedMillis >= onBoardLedInterval) {
// time is up, so change the state to HIGH
onBoardLedState = HIGH;
// and save the time when we made the change
previousOnBoardLedMillis += onBoardLedInterval;
// NOTE: The previous line could alternatively be
// previousOnBoardLedMillis = currentMillis
// which is the style used in the BlinkWithoutDelay example sketch
// Adding on the interval is a better way to ensure that succesive periods are identical
}
}
else { // i.e. if onBoardLedState is HIGH
// if the Led is on, we must wait for the duration to expire before turning it off
if (currentMillis - previousOnBoardLedMillis >= blinkDuration) {
// time is up, so change the state to LOW
onBoardLedState = LOW;
// and save the time when we made the change
previousOnBoardLedMillis += blinkDuration;
}
}
}
//=======
void updateLed_A_State() {
if (led_A_State == LOW) {
if (currentMillis - previousLed_A_Millis >= led_A_Interval) {
led_A_State = HIGH;
previousLed_A_Millis += led_A_Interval;
}
}
else {
if (currentMillis - previousLed_A_Millis >= blinkDuration) {
led_A_State = LOW;
previousLed_A_Millis += blinkDuration;
}
}
}
//=======
void updateLed_B_State() {
if (led_B_State == LOW) {
if (currentMillis - previousLed_B_Millis >= led_B_Interval) {
led_B_State = HIGH;
previousLed_B_Millis += led_B_Interval;
}
}
else {
if (currentMillis - previousLed_B_Millis >= blinkDuration) {
led_B_State = LOW;
previousLed_B_Millis += blinkDuration;
}
}
}
//========
void switchLeds() {
// this is the code that actually switches the LEDs on and off
digitalWrite(onBoardLedPin, onBoardLedState);
digitalWrite(led_A_Pin, led_A_State);
digitalWrite(led_B_Pin, led_B_State);
digitalWrite(buttonLed_Pin, buttonLed_State);
}
//=======
void readButton() {
// this only reads the button state after the button interval has elapsed
// this avoids multiple flashes if the button bounces
// every time the button is pressed it changes buttonLed_State causing the Led to go on or off
// Notice that there is no need to synchronize this use of millis() with the flashing Leds
if (millis() - previousButtonMillis >= buttonInterval) {
if (digitalRead(buttonPin) == LOW) {
buttonLed_State = ! buttonLed_State; // this changes it to LOW if it was HIGH
// and to HIGH if it was LOW
previousButtonMillis += buttonInterval;
}
}
}
//========
void servoSweep() {
// this is similar to the servo sweep example except that it uses millis() rather than delay()
// nothing happens unless the interval has expired
// the value of currentMillis was set in loop()
if (currentMillis - previousServoMillis >= servoInterval) {
// its time for another move
previousServoMillis += servoInterval;
servoPosition = servoPosition + servoDegrees; // servoDegrees might be negative
if (servoPosition <= servoMinDegrees) {
// when the servo gets to its minimum position change the interval to change the speed
if (servoInterval == servoSlowInterval) {
servoInterval = servoFastInterval;
}
else {
servoInterval = servoSlowInterval;
}
}
if ((servoPosition >= servoMaxDegrees) || (servoPosition <= servoMinDegrees)) {
// if the servo is at either extreme change the sign of the degrees to make it move the other way
servoDegrees = - servoDegrees; // reverse direction
// and update the position to ensure it is within range
servoPosition = servoPosition + servoDegrees;
}
// make the servo move to the next position
myservo.write(servoPosition);
// and record the time when the move happened
}
}
//=====END
//======================================================
//================= Third program functions
//======================================================
// Non-blocking Fastled animation with a button-led function
//
// https://wokwi.com/projects/410037772389117953
// https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/89?u=davex
void setupDaveX()
{
FastLED.addLeds<WS2812B, DATA_PIN1, GRB>(array_LED_sconce, NUM_LEDS1);
FastLED.setBrightness(255);
// Serial.begin(115200);
pinMode(DaveXLED, OUTPUT);
pinMode(DaveXButton, INPUT_PULLUP);
Serial.println("DaveX FastLEDs https://wokwi.com/projects/410037772389117953"); // so we know what sketch is running
}
void loopDaveX()
{
wiperNB();
somethingElse();
}
void somethingElse() {
auto now = millis();
const typeof(now) interval = 100;
static typeof(now) last = -interval;
if (now - last >= interval) {
last += interval;
digitalWrite(DaveXLED, !digitalRead(DaveXButton));
}
}
void wiperNB() {
// size 7 gives 0-6 up 7 blank, 8-14 down, 15 blank
const int rangeLen = sizeof(ledBlink) / sizeof(ledBlink[0]);
const int maxState = 2 * rangeLen + 1;
if (millis() - timeout > interval) { // wait for interval
timeout += interval; // restart the "lap" timer
// Serial.print(fastledState); Serial.print("/");
// Serial.print(maxState); Serial.print(" ");
switch (fastledState) {
case 0 ... rangeLen-1: // wiping up
ledOnProcessing(ledBlink[fastledState]);
break;
case rangeLen:
allLedsOff();
break;
case rangeLen+1 ... maxState-1: // wiping down
{
const int LastLedIndex = rangeLen - 1;
int upwardStep = fastledState - (rangeLen + 1);
int ledToChange = LastLedIndex - upwardStep;
ledOnProcessing(ledBlink[ledToChange]);
}
break;
case maxState:
allLedsOff();
break;
default:
Serial.print(fastledState);
}
++fastledState; // prepare for next step
if (fastledState > maxState) { // move to next cycle
fastledState = 0;
}
FastLED.show();
}
}
void ledOnProcessing(int fastledState) {
array_LED_sconce[fastledState] = daveXringColor;
}
void allLedsOff() {
fill_solid(array_LED_sconce, NUM_LEDS1, CHSV(0, 0, 0));
}
The core idea of this combination is this:
...
void setup() {
Serial.begin(115200);
Serial.println("Starting a Combination of Three Programs");
setupMoba();
setupSTATST();
setupDaveX();
}
void loop() {
loopMoba();
loopSTATST();
loopDaveX();
}
...
This was a quick cut-and-paste job, but since the component programs were each written to be cooperative non-blocking programs, all that needed was a little adjustment to make sure the function names and pins didn't conflict.
The basic process was to:
- Move all the globals and declarations to the top of the program
- rename the setup() and loop() functions to not conflict with each other.
- Put calls to the newly named setups and loops into a global setup() and loop().
- Reassign the pins so they do not conflict.
The main requirement is that your component programs ought to work cooperatively, as explained in these:
Also see this collection of Arduino Built-In-Examples with Wokwi simulations: