Pages: [1] 2 3 ... 7   Go Down
Author Topic: Demonstration code for several things at the same time  (Read 6266 times)
0 Members and 2 Guests are viewing this topic.
UK
Offline Offline
Tesla Member
***
Karma: 101
Posts: 6292
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

There have been a few occasions recently where newcomers seemed to have difficulty applying the "blink without delay" and "state" concepts to a sketch that is intended to manage a number of concurrent actions.

Its very time consuming to respond with suitable code examples - particularly as every case is a little different.

I have prepared the attached example sketch in the hope that it will be a suitable model. I have tested it on an Uno and a Mega. I have called it "SeveralThingsAtTheSameTime.ino". (It seems to be too long to show the code directly in this post)

My sketch uses the concept in "blink without delay" to cause three LEDs to blink at different intervals, a fourth LED is controlled by a button and a servo sweeps back and forth at two different speeds. The idea is to demonstrate how different processes can be accommodated in the same general framework.

It also uses the "state machine" concept to manage the various activities and enable the different functions to determine what to do.

I have deliberately designed the sketch as a series of short functions. Short pieces of code are much easier to understand and debug and it will be an easy matter for a user to delete functions that they don't need or to duplicate and modify functions if they need more of the same (for example to flash 5 LEDS at different intervals).

There is a case for saying there is too much in the sketch but I concluded that the larger number of activities is a better demonstration of the capabilities of this approach.

Comments are welcome.

...R

Edit ... the attached sketch has been revised to take account of comments below about improving the timekeeping ...R

* SeveralThingsAtTheSameTimeRev1.ino (9.37 KB - downloaded 191 times.)
« Last Edit: March 12, 2014, 04:33:27 am by Robin2 » Logged

UK
Offline Offline
Tesla Member
***
Karma: 101
Posts: 6292
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
// SeveralThingsAtTheSameTimeRev1.ino

// 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

//=======

// -----LIBRARIES

#include <Servo.h>

// ----CONSTANTS (won't change)

const int onBoardLedPin =  13;      // the pin numbers for the LEDs
const int led_A_Pin = 12;
const int led_B_Pin = 11;
const int buttonLed_Pin = 10;

const int buttonPin = 7; // the pin number for the button

const int servoPin = 5; // 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

//========

void setup() {

  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 loop() {

      // 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
« Last Edit: March 12, 2014, 04:42:21 am by Robin2 » Logged

Offline Offline
Jr. Member
**
Karma: 0
Posts: 92
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Darn good idea :-)
Logged

Offline Offline
Sr. Member
****
Karma: 10
Posts: 319
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I prefer using switch statements for may state variables rather than ifs, even if there's only two states something can be in. I find it easier to follow than trying to match an else with the right if to find out what's going on, especially if they're nested.

So instead of:
Code:
void updateLed_A_State() {

  if (led_A_State == LOW) {
    if (currentMillis - previousLed_A_Millis >= led_A_Interval) {
       led_A_State = HIGH;
       previousLed_A_Millis = currentMillis;
    }
  }
  else {
    if (currentMillis - previousLed_A_Millis >= blinkDuration) {
       led_A_State = LOW;
       previousLed_A_Millis = currentMillis;
    }
  }   
}
I prefer:
Code:
void updateLed_A_State()
{
  switch( led_A_State )
  {
    case LOW:
      if (currentMillis - previousLed_A_Millis >= led_A_Interval)
      {
        led_A_State = HIGH;
        previousLed_A_Millis = currentMillis;
      }
   
    case HIGH:
      if (currentMillis - previousLed_A_Millis >= blinkDuration)
      {
        led_A_State = LOW;
        previousLed_A_Millis = currentMillis;
      }
    default:
      ;
  }
}
I also prefer a different bracketing style to yours, but that's neither here nor there.
Logged

NSW Australia
Offline Offline
Faraday Member
**
Karma: 80
Posts: 3232
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I also prefer a different bracketing style to yours, but that's neither here nor there.

Ah, but if you use "Auto Format", it arranges things in a certain way for you.
Logged

UK
Offline Offline
Tesla Member
***
Karma: 101
Posts: 6292
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I debated using switch statements (even just using them in one function to illustrate how they are an alternative to if statements). In the end I decided that it would just add to the learning burden for new users. It would be an unusual program that could avoid if statements but switch statements are always optional - even if convenient when you know them.


I like my bracketing system. The formatting was done manually in Gedit. If I have not been consistent everywhere I apologize.

...R
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 199
Posts: 12768
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
...
    if (currentMillis - previousOnBoardLedMillis >= onBoardLedInterval) {
...
       previousOnBoardLedMillis = currentMillis;
    }

The average interval will be slightly to significantly more than onBoardLedInterval.  For most uses (like blinking an LED) the longer interval is insignificant.  For some uses the longer interval is a failure.

Code:
       previousOnBoardLedMillis += onBoardLedInterval;

...produces a bit more code but keeps the average interval at exactly onBoardLedInterval.
Logged

UK
Offline Offline
Tesla Member
***
Karma: 101
Posts: 6292
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@CodingBadly, I'm interested but I don't understand exactly what you mean by "The average interval will be slightly to significantly more than onBoardLedInterval"

My plan was/is to use a single instant in time (as captured in currentMillis) as the reference point for the flashing of all of the leds so that their timing can't drift apart.

I can't figure out what effect your code would have.

...R
Logged

Copenhagen, Denmark
Offline Offline
Edison Member
*
Karma: 32
Posts: 1204
Have you testrun your INO file today?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Nice long example (not all examples can be 1<10 lines).

I wondered what the difference was between the attached file in the first and reply#1 with the code inline. My editor only shows comments lines are shorter  smiley-mr-green

I would do it slightly different (which means neither better nor worse)  smiley-kitty

I do not quite get the educational value of having update_LED_state() and switchLEDs() as seperate functions - it means you have to carry LED-desired-state as global variables, when they could be nicely encapsulated. Place most variables as static inside the function where they are exclusivly used - this way you can not inadvertently modify a timer variable elsewhere.

CodingBadly's comment is that if your loop takes - say - 5 millisec to execute (because of some not-yet-written heavy calculation code), then the LEDs will blink at the interval+5ms. By adding "interval" to the  "timer" (not the measured millis() value) you get it to run at the "interval" rate. For a "real" project it may be desirable to include the loop overhead/jitter, for some you want a steady rate.
Logged

UK
Offline Offline
Shannon Member
****
Karma: 222
Posts: 12551
-
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
       previousOnBoardLedMillis += onBoardLedInterval;

...produces a bit more code but keeps the average interval at exactly onBoardLedInterval.

That is my preferred approach too.

When your things are each implemented in separate self-contained functions s in the original example, I also like to define the state data as local static variables rather than globals. Reducing the scope is beneficial in its own right but in particular this makes copy/paste much safer, and that's something that I would expect novice coders to be doing a lot when starting from examples like this. For example:

Code:
void blinkLed()
{
static const int LED_PIN = 13;
static const unsigned long BLINK_INTERVAL = 500; // duration of each blink phase
static unsigned long lastBlinkTime = 0;

if(millis() - lastBlinkTime >= BLINK_INTERVAL)
{
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
lastBlinkTime += BLINK_INTERVAL
}
}
Logged

I only provide help via the forum - please do not contact me for private consultancy.

UK
Offline Offline
Tesla Member
***
Karma: 101
Posts: 6292
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


That is my preferred approach too.


I'm hoping you or @GodingBadly will explain why.

I did consider static local variables but personally I prefer the simplicity and visibility of globals when I'm working with such a small memory space. (I wouldn't do that with PC code). Also I was trying to limit the amount of new stuff that a newbie has to take in.

I understand what you say about copy/paste, but I guess I'm old fashioned enough to prefer users to understand what's going on.

There is an opportunity for someone to use my code as a starting point for a separate lesson on the use of static local variables smiley

And thanks very much for all the comments.

...R
« Last Edit: March 10, 2014, 12:22:46 pm by Robin2 » Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 199
Posts: 12768
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


Two hints...
1. You assume the delta is always 1.  It is not.
2. You assume the time to execute loop is <= 1 millisecond.  In your example that may be true but that will not be true for every application.

Assume millis returns values evenly divisible by seven (0, 7, 14, etcetera) and work through the code by hand.
Logged

Offline Offline
Edison Member
*
Karma: 43
Posts: 1556
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@jiggy-ninja:
I also prefer the switch statement but you need break statements in there to make it equivalent to the original code. I would write it like this:
Code:
void updateLed_A_State()
{
  switch( led_A_State )
  {
    case LOW:
      if (currentMillis - previousLed_A_Millis >= led_A_Interval)
      {
        led_A_State = HIGH;
        previousLed_A_Millis = currentMillis;
      }
      break;
     
    case HIGH:
      if (currentMillis - previousLed_A_Millis >= blinkDuration)
      {
        led_A_State = LOW;
        previousLed_A_Millis = currentMillis;
      }
      break;
     
    default:
      break;
  }
}

Pete
Logged

Where are the Nick Gammons of yesteryear?

UK
Offline Offline
Tesla Member
***
Karma: 101
Posts: 6292
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I wondered what the difference was between the attached file in the first and reply#1 with the code inline. My editor only shows comments lines are shorter  smiley-mr-green


Sorry, I meant to mention this earlier ...

You are quite right, I was just trying to save enough bytes to get within the upload limit. I actually haven't tested the visible version smiley-sad

...R
Logged

Offline Offline
Sr. Member
****
Karma: 10
Posts: 319
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@jiggy-ninja:
I also prefer the switch statement but you need break statements in there to make it equivalent to the original code. I would write it like this:

Pete
>.<    Doh!

And that, ladies and gentlemen, is one argument against using switches.
Logged

Pages: [1] 2 3 ... 7   Go Up
Jump to: