You are definitely on the right track: delay()
will indeed block the rest of the code execution and you can use millis()
to get around that limitation.
millis()
returns the number of milliseconds since the arduino code started running. You could use an extra variable to build a make-shift stopwatch like mechanism:
- In
setup()
would store the current millis()
in a variable. For that variable, temporarily, time froze :)
- In
loop()
, if you continuously call millis()
you'll get an increasing value. If you compare the current time (most recent millis()
call) with the earlier "frozen" millis result you can tell the difference between the two moments in time. This is somewhat similar how in the real world you would have a lap timer and a button press/update will take a snapshot of the time passed while time keeps ticking away.
Here's a very basic sketch to illustrate the idea:
long lastMillis;
const long fiveSeconds = 5 * 1000;
void setup() {
Serial.begin(9600);
lastMillis = millis();
}
void loop() {
long millisNow = millis();
long millisDifference = millisNow - lastMillis;
Serial.print("time between setup complete and now:");
Serial.println(millisDifference);
if(millisDifference >= fiveSeconds){
Serial.println("5 seconds or more passed");
}
}
If you open Serial Monitor with baud rate set to 9600 you should see some debug text appear. Hopefully the commented text illustrated the points above.
The third thing to do is to reset/update the lastMillis
so the condition can be met every 5 seconds instead of just continuously after 5 seconds like the code above:
long lastMillis;
const long fiveSeconds = 5 * 1000;
void setup() {
Serial.begin(9600);
lastMillis = millis();
}
void loop() {
long millisNow = millis();
long millisDifference = millisNow - lastMillis;
Serial.print("time between setup complete and now:");
Serial.println(millisDifference);
if(millisDifference >= fiveSeconds){
Serial.println("5 seconds or more passed");
lastMillis = millis();
}
}
You can use this to build a system to move the sweep between 0 and 20 without using a for/delay
block loop, but simply incrementing at each loop()
iteration:
#include <Servo.h>
Servo myservo;
long lastMillis;
const long INTERVAL = 4 * 1000;
int pos = 0;
int targetPos = 0;
void setup() {
Serial.begin(9600);
lastMillis = millis();
myservo.attach(9);
myservo.write(pos);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
long millisNow = millis();
long millisDifference = millisNow - lastMillis;
Serial.print("time between setup complete and now:");
Serial.println(millisDifference);
if(millisDifference >= INTERVAL){
Serial.println("4 seconds or more passed");
lastMillis = millis();
targetPos = 20 - targetPos;
}
updateServo();
}
void updateServo(){
int positionDifference = targetPos - pos;
if(positionDifference > 0){
pos++;
myservo.write(pos);
}else{
pos--;
myservo.write(pos);
}
}
Note the above code isn't tested, therefore not guaranteed to work, but hopefully will illustrate the intention.
You will need to break your homework down into smaller steps still
I have an Arduino Uno, a Servo Motor & 2 LEDs (Green & Red). The Servo motor rotates 20 degrees and back every 4 seconds.
I would like the Red led (LedR) to be on for the first 4 seconds of code then low for the next 12 seconds.
I would like the Green led (ledG) to be on from the 8th second of code until the 12th then low for the next 12 seconds.
So your tasks become:
- figure out how to trigger an action every X seconds (non-blocking): sorted above
- figure out how to move servo (non-blocking): sorted above
- figure out how to control the LEDs based on 4 second intervals within a 12 seconds pattern
The keyword here is pattern. It's nice that the servo goes every 4 seconds and the leds go on 4 seconds then off 12 seconds.
There are multiple ways to go about this, but one way could to take advantage of the fact that 12 is divisible by 4. Instead of a millis based extra variable and condition for every single timer, you could use a single 4 seconds timer and make everything in relation to that, like a beat on a drum machine.
Wouldn't it be fun to look at your tasks as an 808 pattern Joey Bada$$ would be proud of ? :P
Here's what I mean:
time(s): 4, 8, 12, 16
servo: [ 0][20][ 0][20]
red: [ 1][ 0][ 0][ 0]
green: [ 0][ 1][ 0][ 0]
You could use a counter for 4 seconds interval and the %(modulo) operator to check which 4 seconds increment you're on (every 4th, 8th, 12th, etc.) second to control the LEDs. This would be more memory efficient, but less flexible/fun if you wanted to easily change patterns.
You could something like this, being aware it wastes more precious memory:
#include <Servo.h>
Servo myservo;
long lastMillis;
const int SECONDS = 4;
const long INTERVAL_MILLIS = SECONDS * 1000;
int pos = 0;
int targetPos = 0;
int intervalIndex;
const int SERVO_PATTERN[4] = {0, 20, 0, 20};
const bool RED_PATTERN[4] = {1, 0, 0, 0};
const bool GREEN_PATTERN[4] = {0, 1, 0, 0};
const int LED_PIN_RED = LED_BUILTIN;
const int LED_PIN_GREEN = 12;
void setup() {
Serial.begin(9600);
lastMillis = millis();
myservo.attach(9);
myservo.write(pos);
pinMode(LED_PIN_RED, OUTPUT);
pinMode(LED_PIN_GREEN, OUTPUT);
}
void loop() {
long millisNow = millis();
long millisDifference = millisNow - lastMillis;
Serial.print("time between setup complete and now:");
Serial.println(millisDifference);
if(millisDifference >= INTERVAL_MILLIS){
Serial.println("4 seconds or more passed");
lastMillis = millis();
intervalIndex++;
if(intervalIndex > 3){
intervalIndex = 0;
}
targetPos = SERVO_PATTERN[intervalIndex];
digitalWrite(LED_PIN_RED, RED_PATTERN[intervalIndex]);
digitalWrite(LED_PIN_GREEN, GREEN_PATTERN[intervalIndex]);
}
updateServo();
}
void updateServo(){
int positionDifference = targetPos - pos;
if(positionDifference > 0){
pos++;
myservo.write(pos);
}else{
pos--;
myservo.write(pos);
}
}
Note As before, I haven't tested the code on an arduino board, you'll need to test/ double check wiring, LED pin numbers, etc. I hope that the comments help explain the concept overall.
The advantage with this approach is the code is somewhat simpler than using 3 timers and you can easily change the patterns (e.g. on, off, off, on, etc.)
The bool
works because HIGH
/LOW
are really boolean values (true
(1), false
(0)).
To further illustrate you can run the logic above using p5.js:
var lastMillis;
const SECONDS = 4;
const INTERVAL_MILLIS = SECONDS * 1000;
var pos = 0;
var targetPos = 0;
var intervalIndex = 0;
const SERVO_PATTERN = [0, 20, 0, 20];
const RED_PATTERN = [1, 0, 0, 0];
const GREEN_PATTERN = [0, 1, 0, 0];
const LED_PIN_RED = 13;
const LED_PIN_GREEN = 12;
function setup() {
createCanvas(300, 300);
stroke(255);
textFont("Courier New",10);
lastMillis = millis();
}
function draw() {
background(0);
var millisNow = millis();
var millisDifference = millisNow - lastMillis;
if (millisDifference >= INTERVAL_MILLIS) {
console.log(SECONDS,"seconds or more passed");
lastMillis = millis();
intervalIndex++;
if (intervalIndex > 3) {
intervalIndex = 0;
}
}
targetPos = SERVO_PATTERN[intervalIndex];
digitalWrite(LED_PIN_RED, RED_PATTERN[intervalIndex]);
digitalWrite(LED_PIN_GREEN, GREEN_PATTERN[intervalIndex]);
updateServo();
showDebug();
}
function updateServo() {
var positionDifference = targetPos - pos;
if (positionDifference > 0) {
pos++;
} else {
pos--;
}
drawServo();
}
function drawServo(){
push();
translate(50, 150);
rotate(radians(targetPos));
triangle(0 ,-25,
50, 0,
0 , 25);
pop();
}
function digitalWrite(pin, value) {
if (pin == LED_PIN_RED) {
fill(value ? color(192, 0, 0) : color(0));
ellipse(150, 150, 50, 50);
}
if (pin == LED_PIN_GREEN) {
fill(value ? color(0, 192, 0) : color(0));
ellipse(250, 150, 50, 50);
}
fill(0);
}
const dr = [112, 130, 148, 167];
function showDebug(){
fill(255);
text("intervalIndex: " + intervalIndex + " relative seconds: " + ((intervalIndex + 1) * 4) +
"\nSERVO_PATTERN = [" + SERVO_PATTERN.map(i => nf(i,2)) + "]" +
"\nRED_PATTERN = [" + RED_PATTERN.map(i => nf(i,2)) + "]" +
"\nGREEN_PATTERN = [" + GREEN_PATTERN.map(i => nf(i,2)) + "]" , 10, 15);
fill(255, 64);
rect(dr[intervalIndex], 21, 12, 35);
fill(0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

In conclusion:
- break your problem down into smaller edible chunks
- write/test/iterate each chunk in isolation until it works and is tidy/clean for integration
- integrate each chunk, one at a time, testing again with each addition
It's great you're dealing with basic LEDs and the Servo library, otherwise if you had more complex LED chipsets to drive (e.g. RGB LEDs like NeoPixel), microsecond timing would be tight and depending on the number of servos and RGB LEDs you would've ran into some nasty interrupt timing situation.
If you have time to try other stuff out, maybe instead of servos and lights you attach a speaker and try either the Tone library or the better (but more resource intensitve) Mozzi library to get some beats going with those 3 patterns.