0
votes

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.

However, I am having trouble integrating it into the for/if statement that the servo runs on.

I can write the delay functions for the leds and I can write the servo code, however, the delay function stops all code resulting in either the servo not moving or Led not lighting.

I've learned about millis() is the solution but how would I use it ?

#include <Servo.h>

Servo myservo;  // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;    // variable to store the servo position

void setup() {
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
  pinMode(LED_BUILTIN, OUTPUT);//LedR
}


void loop() {
  
  delay(4000);
    digitalWrite(LED_BUILTIN, HIGH);   // LedR high
  
  for (pos = 0; pos <= 20; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(15);                       // waits 15ms for the servo to reach the position
  }delay(4000);
  digitalWrite(LED_BUILTIN, LOW); //LedR low
  for (pos = 20; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(15);                       // waits 15ms for the servo to reach the position
    
  }
}
1
Your code bears absolutely no relation to the problem from your question.TomServo

1 Answers

1
votes

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:

  1. In setup() would store the current millis() in a variable. For that variable, temporarily, time froze :)
  2. 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>

p5.js sketch preview

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.