1

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.

  • 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 Sep 26 '14 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 Sep 26 '14 at 14:14
  • ok I have edited. so.. interrupt are not the devil.. any advice on some middle-level guides/article about it? – nkint Sep 27 '14 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. Jan 29 '15 at 20:41
  • interrupt are not the devil - see Interrupts – Nick Gammon Sep 16 '15 at 20:50
2

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.

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.

  • Does it support forced preemption? Semaphores? I had a cursory look but couldn't find them at glance. – Igor Stoppa Sep 17 '15 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)). – Mikael Patel Sep 17 '15 at 20:47
  • Ok, thanks for confirming. For my needs, the lack of forced preemption is already disqualifying. – Igor Stoppa Sep 17 '15 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! – Mikael Patel Sep 18 '15 at 22:59
0

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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