0

I'm beginning to do kind of sophisticated things (well.. for my entry level skill) and I have to control a display with 3 LED and 4/5 types of events and status.

I'm using the BlinkWithoutDelay example duplicating all the variables for different status/events but the code is really messy, full of puzzling nested if statements.

How to simplify the things? I don't know what to choose, because OOP could be a choice but I think it will hurt the low-memory thing inside the Arduino Uno/Leonardo. Interrupts could be another choice but I think they are like threads, and following the KISS principle I'd like to stay away from them.

So, can someone advice me some article/tutorial/code-to-read/whatever to understand how to use a lot of LEDs and keep a lot internal status without screw my brain?

EDIT:

It is a little bit hard to reproduce a simple skelton of my messy code, but it looks like this (in pseudocode maybe)

void loop() {
    if(state==0) handleState0();
    if(state==1) handleState1();
    if(state==2) handleState2();
    if(state==3) handleState3();
    readButtons();
    readSensors();
    …
}

// this is a block of variables that I have to copy-and-paste 
// every time I have to blink a led (hoping that there is 
// nothing blocking that can skrew up everything)
unsigned long previousMillis_1 = 0;
const long interval_1 = 1000;
int ledState_1 = LOW;

void handleState1() {
  // Green led blinking, yellow is fixed
  // this is a copy-and-paste code hard to maintain, 
  // easily broken from any blocking call (like also I2C)
  // and hard to refactor or isolate
  unsigned long currentMillis_1 = millis();
  if(currentMillis_1 - previousMillis_1 >= interval_1) {
    previousMillis_1 = currentMillis_1;   
    if (ledState_1 == LOW) ledState_1 = HIGH;
    else ledState_1 = LOW;
    digitalWrite(pinGreen, ledState_1);
    digitalWrite(pinYellow, LOW);
}

unsigned long previousMillis_2 = 0;
const long interval_2 = 1000;
int ledState_2 = LOW;

void handleState2() {
  // Yellow led blinking, green is fixed
  // same as before
  unsigned long currentMillis_2 = millis();
  if(currentMillis_2 - previousMillis_2 >= interval_2) {
    previousMillis_2 = currentMillis_2;   
    if (ledState_2 == LOW) ledState_2 = HIGH;
    else ledState_2 = LOW;
    digitalWrite(pinYellow, ledState_2);
    digitalWrite(pinGreen, LOW);
}

void handleButtonPressed() {
  // this skrew everything and because is blocking 
  // and should be inserted in the main loop with 
  // some global variables: active/disable, counter, 
  // and non-blocking vars like previousMills, interval, etc etc
  Serial.println("\ndisplayAccessDenied");
  for(int i=0; i<3; i++) {
    digitalWrite(pinRed, LOW);
    delay(100);
    digitalWrite(pinRed, HIGH);
    delay(100);
  } digitalWrite(pinRed, LOW);
}

I feel a little bit lost but all the Arduino example arounds are super simple and to make a middle-level LED interface I have to screw my minds with something that is not working as 100% and other than Arduino I find only super-hard articles on microprocessors with low-level C code. But the pasted example is not working at 100% AND super hard to read, maintain, etc.

5
  • I'd suggest you to take a look at Cosa framework for Arduino (github.com/mikaelpatel/Cosa): powerful and quite easy to use, fully OOP but still has a small memory footprint and excellent performance.
    – jfpoilpret
    Commented Sep 26, 2014 at 11:31
  • 2
    Could you post your code? Different problems have different (optimal) solutions. PS. interrupts are nothing like threads. Maybe not the best place to start as a beginner, but they can be extremely powerful. I don't think they are against the KISS principle. You could e.g. have the main loop check all the events, and have an interrupt set the led in accordance with some variables set in the loop.
    – Gerben
    Commented Sep 26, 2014 at 14:14
  • ok I have edited. so.. interrupt are not the devil.. any advice on some middle-level guides/article about it?
    – nkint
    Commented Sep 27, 2014 at 9:06
  • You can try: bleaklow.com/2010/07/20/a_very_simple_arduino_task_manager.html It's simple but powerful.
    – Damian G.
    Commented Jan 29, 2015 at 20:41
  • interrupt are not the devil - see Interrupts
    – Nick Gammon
    Commented Sep 16, 2015 at 20:50

5 Answers 5

3

It is not really clear what you want here...

If you want to execute things in a certain time, then take a look at http://playground.arduino.cc/Code/Scheduler

Or, if you want more than just that (multiple tasks, preemptive multitasking) https://launchpad.net/arduos (arduOS can also just serve to run things in a certain time, but without having to use a function all the time)

While OOP MIGHT take more program memory, an object without virtual functions only takes the RAM the variables in it take (if you have a byte (1 byte) and an int (2 bytes) in an object it would take 3 bytes)

On the AVR-architecture (the one most Arduinos use) the program is directly executed from a flash-memory and only the variables are in the RAM

The if-state-handling you are doing there can be done easier using arrays like:

typedef void(*handler)(void)
handler stateHandlers[STATES] = {&handle1, &handle2, &handle3 ...
// handle? are functions without the () !
void handleState(int state)
{
    if(state >= STATES)
        return;
    stateHandlers[state]();
}

Obviously, you can also use arrays to store the states of the LEDs.

Also note that HIGH is just 1 and LOW is 0, so technically a bit would be enough to store the states.

2

Following the KISS principle, I have recently published the Instructable "Simple Multi-tasking in Arduino on any board"

It covers non-blocking delays, non-blocking serial output, non-blocking user input, removing delays from third party libraries, and loop timers, so you can see and adjust the response/latency of your tasks.

Here is some example code

void loop() {
 callTask_1(); // do something
 callTask_2(); // do something else
 callTask_1(); // check the first task again as it needs to be more responsive than the others.
 callTask_3(); // do something else
}

The trick is that each callTask..() method must return quickly so that the other tasks in the loop get called promptly and often. The rest of the instructable covers how to keep your tasks running quickly and not holding everything up, using a temperature controlled, stepper motor driven damper with a user interface as a concrete example.

Finally the instructable moves the code, unchanged, to an ESP32 to add remote control via Wifi, Bluetooth or BLE, to the temperature controlled, stepper motor driven damper.

Drop me a message/email via Instructables or my website, if you are unclear about anything in the instructable.

1

Here's an example of blinking two LEDs independently:

// Which pins are connected to which LED
const byte greenLED = 12;
const byte redLED = 13;

// Time periods of blinks in milliseconds (1000 to a second).
const unsigned long greenLEDinterval = 500;
const unsigned long redLEDinterval = 1000;

// Variable holding the timer value so far. One for each "Timer"
unsigned long greenLEDtimer;
unsigned long redLEDtimer;

void setup () 
  {
  pinMode (greenLED, OUTPUT);
  pinMode (redLED, OUTPUT);
  }  // end of setup

void toggleLED (const byte which, unsigned long & whenToggled)
  {
   if (digitalRead (which) == LOW)
      digitalWrite (which, HIGH);
   else
      digitalWrite (which, LOW);
  // remember when we toggled it
  whenToggled = millis ();  
  }  // end of toggleLED

void loop ()
  {
  // Handling the blink of one LED.
  if ( (millis () - greenLEDtimer) >= greenLEDinterval)
     toggleLED (greenLED, greenLEDtimer);

  // The other LED is controlled the same way. Repeat for more LEDs
  if ( (millis () - redLEDtimer) >= redLEDinterval) 
    toggleLED (redLED, redLEDtimer);

/* Other code that needs to execute goes here.
   It will be called many thousand times per second because the above code
   does not wait for the LED blink interval to finish. */

  }  // end of loop

Note that instead of copying and pasting the blinking code, I made a generic function. That way any bugs are easily fixed.


For more LEDs you could use an array:

const int NUMBER_OF_LEDS = 5;
// Which pins are connected to which LED
const byte LED_PINS [NUMBER_OF_LEDS] = { 8, 9, 10, 11, 12 };
// Time periods of blinks in milliseconds (1000 to a second).
const unsigned long LED_INTERVALS [NUMBER_OF_LEDS] = { 200, 400, 100, 1000, 2000};
// Variable holding the timer value so far. One for each "Timer"
unsigned long timeLEDtoggled [NUMBER_OF_LEDS];

void setup () 
  {
  for (int i = 0; i < NUMBER_OF_LEDS; i++)
    pinMode (LED_PINS [i], OUTPUT);
  }  // end of setup

void toggleLED (const byte which, unsigned long & whenToggled)
  {
   if (digitalRead (which) == LOW)
      digitalWrite (which, HIGH);
   else
      digitalWrite (which, LOW);
  // remember when we toggled it
  whenToggled = millis ();  
  }  // end of toggleLED

void loop ()
  {
  // see if it is time to toggle the LEDs    
  for (int i = 0; i < NUMBER_OF_LEDS; i++)
    if ( (millis () - timeLEDtoggled [i]) >= LED_INTERVALS [i])
       toggleLED (LED_PINS [i], timeLEDtoggled [i]);

  /* Other code that needs to execute goes here. */
  }  // end of loop

If anything, the above code is simpler, but it blinks 5 LEDs at different rates. Now you can have more sophisticated tests than that, but the general concept is the same.


References

1

Cosa supports a large number of mechanisms for multi-tasking. The latest addition is an abstract class for Jobs (delayed or periodic functions) and Job Schedulers. This abstraction allows functions to be scheduled on micro-, milli- and seconds level.

To get started please see the Cosa example sketches. If you have an Logic Analyzer (Salea Logic for instance) there are a number of example sketches that show pulse generation.

There is also a binding of the Job Scheduler to a seconds tick level. This is abstracted to Clock and Alarms.

For higher abstraction of multi-tasking Cosa also implements FSM, Proto-threads, Threads (multi-stacks), UML Capsules, and more.

4
  • Does it support forced preemption? Semaphores? I had a cursory look but couldn't find them at glance. Commented Sep 17, 2015 at 3:04
  • The Threads (see Cosa/libraries/Nucleo) are not forced preemption. It is collaborative, i.e. need to call yield(), delay(), etc. There are semaphores and some basic message passing support. The Job and Job::Scheduler can be used in a forced preemption fashion as the dispatch is from ISR and can be modified to run() within the ISR context. The simplest of all the multi-tasking abstractions is the periodic block (See Cosa/Periodic.hh and the macro periodic(timer, ms)). Commented Sep 17, 2015 at 20:47
  • Ok, thanks for confirming. For my needs, the lack of forced preemption is already disqualifying. Commented Sep 17, 2015 at 20:53
  • It is a question about resources. Cosa supports AVR MPU from ATtiny to ATmega. With only 0.5-1 Kbyte SRAM it is a challenge to allow thread stacks and ISR frames and still have memory over for the application. It is very much a trade off. The mechanism in Cosa can be used with forced preemption but that is not in the open-source version. Cheers! Commented Sep 18, 2015 at 22:59
0

Use the NonBlockingSequence library. It has clean examples for applying blinking sequences without delay. (Example Link)

    #include <NonBlockingSequence.h>


class Blinking_Led{

  private:
  // Declare Sequence
  ClassNonBlockingSequence<Blinking_Led> Sequence;

  // led pin
  byte _pin; 

  // Define step-functions
  bool led_on(){
    digitalWrite(_pin,1);
    return true;
  }
  bool led_off(){
      digitalWrite(_pin,0);
      return true;
  }

  public:
  Blinking_Led(){};  
  void init(byte pin,unsigned long pause){
    
    // define the led pin
    _pin=pin;                    
    pinMode(_pin,OUTPUT);    

    // This line explain important fact for the Sequence
    Sequence.AttachedObj(this);
    // Sequence should use steps defined inside this class only

    // Add steps
    Sequence.AddNewStep(&led_on);
    Sequence.AddDelayInMillis(pause);
    Sequence.AddNewStep(&led_off);
    Sequence.AddDelayInMillis(pause);
    Sequence.Repeat();
    // repeat the sequence infinite number of times
    
  }
  
  Blink(){ // use familiar name for led
    Sequence.DoSequence();
  }
  
};

// Declare new objects
Blinking_Led Led1;
Blinking_Led Led2;

void setup() {

  //  Led.init( pin , blinking time )
  Led1.init(13,200);
  Led2.init(12,1500);
 
}

void loop() {
  // put your main code here, to run repeatedly:
  Led1.Blink();
  Led2.Blink();
}
1
  • 3
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. Commented Feb 17 at 22:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.