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:
// a variable to store millis at a set time
long lastMillis;
// 5 seconds in millis
const long fiveSeconds = 5 * 1000;
void setup() {
Serial.begin(9600);
// remember the millis right now
lastMillis = millis();
}
void loop() {
// get current millis - ever increasing
long millisNow = millis();
// calculate the difference
long millisDifference = millisNow - lastMillis;
// print debug text: open Serial Monitor to view
Serial.print("time between setup complete and now:");
Serial.println(millisDifference);
// test after 5 seconds
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:
// a variable to store millis at a set time
long lastMillis;
// 5 seconds in millis
const long fiveSeconds = 5 * 1000;
void setup() {
Serial.begin(9600);
// remember the millis right now
lastMillis = millis();
}
void loop() {
// get current millis - ever increasing
long millisNow = millis();
// calculate the difference
long millisDifference = millisNow - lastMillis;
// print debug text: open Serial Monitor to view
Serial.print("time between setup complete and now:");
Serial.println(millisDifference);
// test after 5 seconds
if(millisDifference >= fiveSeconds){
Serial.println("5 seconds or more passed");
// update millis snaphot so this happens every 5 seconds
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; // create servo object to control a servo
// a variable to store millis at a set time
long lastMillis;
// seconds to millis
const long INTERVAL = 4 * 1000;
int pos = 0; // variable to store the servo position
int targetPos = 0;// the target position rotate towards
void setup() {
Serial.begin(9600);
// remember the millis right now
lastMillis = millis();
myservo.attach(9); // attaches the servo on pin 9 to the servo object
myservo.write(pos);
pinMode(LED_BUILTIN, OUTPUT);//LedR
}
void loop() {
// get current millis - ever increasing
long millisNow = millis();
// calculate the difference
long millisDifference = millisNow - lastMillis;
// print debug text: open Serial Monitor to view
Serial.print("time between setup complete and now:");
Serial.println(millisDifference);
// test after 4 seconds
if(millisDifference >= INTERVAL){
Serial.println("4 seconds or more passed");
// update millis snaphot so this happens every 5 seconds
lastMillis = millis();
// flip between 0 and 20 servo target position by subtracting the current position from the maximum position
// e.g. 20 - 0 = 20, otherwise, 20 - 20 = 0
targetPos = 20 - targetPos;
}
// update servo position
updateServo();
}
// update servo without a blocking delay
// you could use an extra millis() based system
void updateServo(){
// difference between the current servo position and the next (target) servo position
int positionDifference = targetPos - pos;
// check the sign of the difference to tell if the servo should increment or decrement positions
// otherwise ignore
if(positionDifference > 0){
// the target position is greater than the current therefore increase
// feel free to change the increment to something nicer
pos++;
myservo.write(pos);
}else{
// the target position is smaller than the current therefore decrea
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; // create servo object to control a servo
// a variable to store millis at a set time
long lastMillis;
// seconds to millis
const int SECONDS = 4;
const long INTERVAL_MILLIS = SECONDS * 1000;
int pos = 0; // variable to store the servo position
int targetPos = 0;// the target position rotate towards
/*
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.
time(s): 4, 8, 12, 16
servo: [ 0][20][ 0][20]
red: [ 1][ 0][ 0][ 0]
green: [ 0][ 1][ 0][ 0]
*/
int intervalIndex;
// rotates 20 degrees and back every 4 seconds.
const int SERVO_PATTERN[4] = {0, 20, 0, 20};
// on for the first 4 seconds of code then low for the next 12 seconds.
const bool RED_PATTERN[4] = {1, 0, 0, 0};
// on from the 8th second of code until the 12th then low for the next 12 seconds.
const bool GREEN_PATTERN[4] = {0, 1, 0, 0};
const int LED_PIN_RED = LED_BUILTIN;
//LedG, maybe pin 12: TODO update to what you've got on your breadboard
const int LED_PIN_GREEN = 12;
void setup() {
Serial.begin(9600);
// remember the millis right now
lastMillis = millis();
myservo.attach(9); // attaches the servo on pin 9 to the servo object
myservo.write(pos);
pinMode(LED_PIN_RED, OUTPUT);//LedR
pinMode(LED_PIN_GREEN, OUTPUT);//
}
void loop() {
// get current millis - ever increasing
long millisNow = millis();
// calculate the difference
long millisDifference = millisNow - lastMillis;
// print debug text: open Serial Monitor to view
Serial.print("time between setup complete and now:");
Serial.println(millisDifference);
// test after 4 seconds
if(millisDifference >= INTERVAL_MILLIS){
Serial.println("4 seconds or more passed");
// update millis snaphot so this happens every 4 seconds
lastMillis = millis();
// update interval counter
intervalIndex++;
// reset every 4 => 0, 1, 2, 3, reset (perfect as array index)
if(intervalIndex > 3){
intervalIndex = 0;
}
// update servo target position
targetPos = SERVO_PATTERN[intervalIndex];
// update the red LED
digitalWrite(LED_PIN_RED, RED_PATTERN[intervalIndex]);
// update the green LED
digitalWrite(LED_PIN_GREEN, GREEN_PATTERN[intervalIndex]);
}
// update servo position
updateServo();
}
// update servo without a blocking delay
// you could use an extra millis() based system
void updateServo(){
// difference between the current servo position and the next (target) servo position
int positionDifference = targetPos - pos;
// check the sign of the difference to tell if the servo should increment or decrement positions
// otherwise ignore
if(positionDifference > 0){
// the target position is greater than the current therefore increase
// feel free to change the increment to something nicer
pos++;
myservo.write(pos);
}else{
// the target position is smaller than the current therefore decrea
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;
// seconds to millis
const SECONDS = 4;
const INTERVAL_MILLIS = SECONDS * 1000;
var pos = 0; // variable to store the servo position
var targetPos = 0; // the target position rotate towards
/*
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.
time(s): 4, 8, 12, 16
servo: [ 0][20][ 0][20]
red: [ 1][ 0][ 0][ 0]
green: [ 0][ 1][ 0][ 0]
*/
var intervalIndex = 0;
// rotates 20 degrees and back every 4 seconds.
const SERVO_PATTERN = [0, 20, 0, 20];
// on for the first 4 seconds of code then low for the next 12 seconds.
const RED_PATTERN = [1, 0, 0, 0];
// on from the 8th second of code until the 12th then low for the next 12 seconds.
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() {
// clear drawing
background(0);
// get current millis - ever increasing
var millisNow = millis();
// calculate the difference
var millisDifference = millisNow - lastMillis;
// print debug text: open Serial Monitor to view
// test after x seconds
if (millisDifference >= INTERVAL_MILLIS) {
console.log(SECONDS,"seconds or more passed");
// update millis snaphot so this happens every 5 seconds
lastMillis = millis();
// update interval counter
intervalIndex++;
// reset every 4 => 0, 1, 2, 3, reset (perfect as array index)
if (intervalIndex > 3) {
intervalIndex = 0;
}
}
// update servo target position
targetPos = SERVO_PATTERN[intervalIndex];
// update the red LED
digitalWrite(LED_PIN_RED, RED_PATTERN[intervalIndex]);
// update the green LED
digitalWrite(LED_PIN_GREEN, GREEN_PATTERN[intervalIndex]);
// update servo position
updateServo();
// debug text
showDebug();
}
function updateServo() {
// difference between the current servo position and the next (target) servo position
var positionDifference = targetPos - pos;
// check the sign of the difference to tell if the servo should increment or decrement positions
// otherwise ignore
if (positionDifference > 0) {
// the target position is greater than the current therefore increase
// feel free to change the increment to something nicer
pos++;
} else {
// the target position is smaller than the current therefore decrea
pos--;
}
drawServo();
}
function drawServo(){
push();
translate(50, 150);
rotate(radians(targetPos));
triangle(0 ,-25, // top
50, 0, // right
0 , 25); // bottom
pop();
}
// hacky LED visualisation
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);
// 111
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.