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 Answers 5
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.
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.
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
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– Igor Stoppa2015年09月17日 03:04:00 +00:00Commented 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)).Mikael Patel– Mikael Patel2015年09月17日 20:47:45 +00:00Commented Sep 17, 2015 at 20:47
-
Ok, thanks for confirming. For my needs, the lack of forced preemption is already disqualifying.Igor Stoppa– Igor Stoppa2015年09月17日 20:53:56 +00:00Commented 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!Mikael Patel– Mikael Patel2015年09月18日 22:59:55 +00:00Commented Sep 18, 2015 at 22:59
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();
}
-
3While 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.sempaiscuba– sempaiscuba2024年02月17日 22:51:54 +00:00Commented Feb 17, 2024 at 22:51
interrupt are not the devil
- see Interrupts