On that subject is there a more efficient ( less elaborate ) method for the double beat - I’m Always happy to evolve in perpetual learning.
Set up the required LED pin and blink rate…
This can be changed during program execution to display different conditions…
const int myLED = LED_BUILTIN;
unsigned long blinkrateMs = 500;
In loop() just make sure to call the blinkLED() function.
Of course, no functions should be blocking…
void loop() {
blinkLED();
:
// other stuff
:
}
…and later…
void blinkLED {
// keep the important values for next time around.
static unsigned long lastBlinkMs:
static bool toggle;
// is it time to blink ?
if (millis() - lastBlinkMs > blinkrateMs) {
digitalWrite(myLED, toggle = !toggle); // flip the toggle state
heartbeatMs =millis(); // ready for next blink
}
}
Merci
lastBlinkMs = millis(); ?
For the double-beat, you could use Problems with interrupt not always working fast enough - #42 by lastchancename 's solution with an extra state variable:
// https://wokwi.com/projects/327104226065908307
// for https://forum.arduino.cc/t/problems-with-interrupt-not-always-working-fast-enough/968966/34
const int myLED = LED_BUILTIN;
unsigned long blinkRateMs = 500;
void setup() {
pinMode(myLED, OUTPUT);
Serial.begin(115200);
}
void loop() {
blinkLED();
//:
// other stuff
//:
}
//...
void blinkLED() {
// keep the important values for next time around.
const unsigned long blinkTimeMs = 50; // on time
const unsigned long pauseTimeMs = 200;
const unsigned long longPauseTimeMs = 800;
static unsigned long lastBlinkMs;
static bool toggle = false;
static bool shortPause = true;
// is it time to blink ?
if (millis() - lastBlinkMs >= blinkRateMs) {
digitalWrite(myLED, toggle = !toggle); // flip the toggle state
if (!toggle) {
if (shortPause) {
shortPause = false;
blinkRateMs = longPauseTimeMs;
} else {
shortPause = true;
blinkRateMs = pauseTimeMs;
}
}else{
blinkRateMs = blinkTimeMs;
}
lastBlinkMs = millis(); // ready for next blink
//Serial.print(blinkRateMs); Serial.print(",");
}
}
and
Thank you I feel in terms of elaborate my method is less so and certainly for newbies easier to understand. But I appreciate the answers because I did ask for alternative methods. Once again thank you all
This could skip a beat, if a loop() takes longer than a millisecond and you miss one of the tasks. Also, if the loop is faster than 1ms, you would be doing multiple redundant digital writes in milliseconds 200,400,700,900. Still, you could do that with a switch-case:
switch(elapsedTime){ // heartbeat
case 200:
case 700:
digitalWrite(heartBeat, LOW); break;
case 400:
case 900:
digitalWrite(heartBeat, High); break;
}
Or you could wrap Problems with interrupt not always working fast enough - #35 by paulpaulson 's scheme
static unsigned long last100 = 0;
if(currentTime - last100 >= 100){
last100 += 100 currentTime;
switch(currentTime % 100){
case 2:
case 7: digitalWrite(heartBeat, LOW); break;
case 4:
case 9: digitalWrite(heartBeat, LOW); break;
}
}
...which would normally cost only 1 comparison per loop, and limit the expensive division and writing to only once per 100ms.
I love this perpetual learning. To further that I'm firing questions back (not argumentatively but educationally). In terms of the examples you've mentioned are these
1: Utilizing more SRAM
&
2: Is redundant digitalWrite... a no no ?
3: On a side question what is the minimum frequency once should DigitalRead (if there is one) ?
With the amazing help from multiple participants I'm struggling with how to mark this as solved. Can anyone advise on the correct etiquette please ?
- you can tell the memory usage by turning on the verbose compiler output options. Another way to tell what is really going on is do do a disassembly of the compiled code.
- Redundant digitalWrites are a waste, and they might delay other activities.
- ?? Some folks like coding by reading all the inputs once at one time, doing the processing, and then doing the outputting. In that [Input-Process-Output pattern](Input-Process-Output Model – Programming Fundamentals, you only need to
digitalRead(...)once per each time you do the processing or outputting.
Maybe pick one, but also click "like" on anything that helped or taught you something interesting. I'll often click "like" on well-formed or interesting questions, or when someone does get around to responding to a request to post or reformat their code.
-
No. You can digitalWrite all you want, there will be no effect on the pin unless you change the value you are writing. It will waste a tiny bit of time, but not significant usually.
-
Depends on how old you are. You must read often enough to catch the stimuli you are looking for, and if it is pushbuttons, fast enough to be able to give a very soon thereafter feedback indication to s/he who pressed the button that s/he had done.
Younger people like to see things within 20 ms (50 Hz). Older ppl will claim that 50 ms (20 Hz) is very plenty fast enough. Anyone will find 100 ms (10 Hz) to be slugggish.
a7
Just a hint:
Using
if ((millis() >= (lastOLEDtime + 1000)) )updateOLED();
instead of
if (millis() - lastOLEDtime > 1000) updateOLED();
may cause problems if your sketch runs for about 50 days without reset.
If you are sure it will never, you are save ... but remember Murphy's Law ![]()
The background is described at several places, e.g. here
https://www.norwegiancreations.com/2018/10/arduino-tutorial-avoiding-the-overflow-issue-when-using-millis-and-micros/
Another hint:
You could also move the (if (millis() ...) clause into the updateOLED() function. So you will only write updateOLED() in loop() and the handling of lastOLEDtime is completely inside the function:
void updateOLED() {
if (millis() -lastOLEDtime <= 1000) return; // Just an inverted "if" clause
display.clearDisplay();
display.invertDisplay(false);
display.setTextColor(WHITE);
display.setCursor(0, 1);
display.setTextSize(2);
switch (Command) {
case 0:
display.print(" WAITING");
break;
case 1:
display.print(" OPENING");
display.setCursor(30, 20);
display.setTextSize(6);
if (millis() >= (000 * timeLeft)) {
timeLeft --;
if ((timeLeft < 10)) display.print("0");
display.print(timeLeft);
display.display();
}
if (timeLeft <= 0) {
//statusLock = false;
Command = 2;
timeLeft = holdTime;
}
break;
case 2:
display.print(" HOLDING");
display.setCursor(20, 18);
display.setTextSize(6);
if (millis() >= (000 * timeLeft)) {
timeLeft --;
if ((timeLeft < 100)) display.print("0");
if ((timeLeft < 10)) display.print("0");
display.print(timeLeft);
display.display();
}
if (timeLeft <= 0) {
Command = 3;
timeLeft = closingTime;
}
break;
case 3:
display.print(" CLOSING");
display.setTextSize(1);
display.setCursor(30, 20);
display.setTextSize(6);
if (millis() >= (000 * timeLeft)) {
timeLeft --;
if ((timeLeft < 10)) display.print("0");
display.print(timeLeft);
display.display();
}
if (timeLeft <= 0) {
statusLock = false;
Command = 0;
}
break;
case 4:
display.print("HOLD CLOSED");
display.setCursor(20, 18);
display.setTextSize(6);
break;
// Cases 5 - 7 may be used when necessary
case 8:
display.print(" RESET MODE");
// This option is the only way apart from resetting the contrtoller to remove the Command 9
break;
case 9:
display.print(" EMERGENCY");
display.setCursor(18, 18);
display.setTextSize(6);
display.print("911");
break;
}
display.display();
lastOLEDtime = millis();
}
But that's just due to personal preference ...
I'm learning . So forgive the question. Does your method go to the loop 1000 times more than mine (understood it just leaves again)
Also this is intended to run forever. so I take onboard your comment and will restructure - Out of interest on day 50 would the watchdog recover things anyway ?
sorry, typo as I cleaned up the text. ![]()
No worries ![]()
Does your method go to the loop 1000 times more than mine (understood it just leaves again)
No, how often a function in loop() is called depends on the time that other functions consume. So the less time conuming each function is, the more often loop() starts again (not to say: The more often loop() loops) ...
So it does not make a difference whether the if-clause is in loop() or in the function call itself, it may be just easier to keep track of what happens one after another when you keep loop() as "clean" as possible. As I said: It is a matter of personal preference.
Also this is intended to run forever. so I take onboard your comment and will restructure -
In this case (and for the sake of Murphy's Law) it is always better to use the proper way (which uses a speciality of binary subtraction to avoid an unwanted if condition after an overflow of millis()).
Out of interest on day 50 would the watchdog recover things anyway ?
A watchdog function is not automatically involved in your own time depending functions, unless you integrate one on your own.
In your case what may happen is as follows:
- Let us assume
- we call the maximum value of millis() maxMillis
- and we are just 234 msec before an overflow of millis(), which is (maxMillis - 234) when the last updateOLED() has taken place.
- So lastOLEDtime = maxMillis-234
- Now the value of (lastOLEDtime +1000) will overflow first and be about 766.
- So now for all further loop()s - until millis() also overflows - the condition will always be true. So your OLED will be updated every loop.
It may not be too dramatic in your case (flickering OLED every 50th day) and after the overflow it will stabilize automatically but it can be avoided.
Another possible problem could be if (lastOLEDtime +1000) = maxMillis and the processing happens to delay long enough to roll millis() from maxMillis-1 over to maxMillis+1 between checks. Then millis() would be zero and OLED wouldn't update until another 2^32 milliseconds pass and it gets another chance to precisely hit maxMillis.
If you use the duration form, if(millis() -lastOLEDtime >= 1000) then large differences between the two numbers will trigger the conditional.
Exactly, thanks for mentioning it; it is not a very likely scenario ... and that makes it very likely to happen (again due to Murphy's Law ... )
There are more safe ways to blink every 50th day ....
Thank you everyone for your assistance . Here is what I think is the working version. I appreciate everyone's input. `// Created by Colin Anderson - TLC Innovation 2022
// Thank you to the creators of the utilised libraries and the support from participants
// of the Arduino forum. - This would not be what it is without your input.
//
// Bring in required Libraries
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <avr/wdt.h>
#define CE_PIN 10
#define CSN_PIN 9
const uint8_t safeEdge = 2;
const uint8_t NC_PIN = 3;
const uint8_t NO_PIN = 4;
const uint8_t Relay1 = 5;
const uint8_t Relay2 = 6;
uint8_t Command = 0;
// removed Heartbeat as no longer required
//
//const uint8_t heartBeat = 21;
bool statusLock = false; // Needed to latch the command until selected is complete
// Set up timers for non delay looping
unsigned long int lastOLEDtime;
unsigned long int referenceTime = millis();
unsigned long int elapsedTime;
unsigned long int startTime;
unsigned long int runTime;
unsigned long int seconds;
// Operational timers etc.
uint8_t timeLeft;
uint8_t openingTime;
uint8_t holdTime;
uint8_t closingTime;
uint8_t pauseTime;
uint8_t address = 0;
/*
Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
The pins for I2C are defined by the Wire-library.
On an arduino UNO: A4(SDA), A5(SCL)
*/
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3c // < See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
// Initialise Serial
Serial.begin(9600);
// Assign Digital Pins
pinMode(safeEdge, INPUT_PULLUP);
pinMode(NC_PIN, INPUT_PULLUP);
pinMode(NO_PIN, INPUT_PULLUP);
pinMode(Relay1, OUTPUT);
pinMode(Relay2, OUTPUT);
digitalWrite(Relay1, HIGH);
// initialise OLED
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
}
display.clearDisplay();
// Initialise Watchdog
wdt_enable(WDTO_4S);
/*
This section looks at the digital pins D14 - D16 to find the selected board ID (or D17 for test mode)
A link will be made to ONE of these pins only and as such defines the ID
In its absence the ID is 1 (Actually zero)
NOTE - THIS METHOD WILL BE REPLACED BY STORING AN ADDRESS ON EEPROM ON THE
NEXT RELEASE If I need to free up SRAM
Yes I know I could have varied a resistor value to give me a single pin address
but no resistor required suits me.
*/
const int addressPin1 = 14; // A0 used as digital pin
const int addressPin2 = 15; // A1 used as digital pin
const int addressPin3 = 16; // A2 used as digital pin
const int addressPin4 = 17; // TESTING MODE
pinMode(addressPin1, INPUT_PULLUP);
pinMode(addressPin2, INPUT_PULLUP);
pinMode(addressPin3, INPUT_PULLUP);
pinMode(addressPin4, INPUT_PULLUP);
const int IDcheck1 = digitalRead(addressPin1);
const int IDcheck2 = digitalRead(addressPin2);
const int IDcheck3 = digitalRead(addressPin3);
const int IDcheck4 = digitalRead(addressPin4);
/*
Array structure is ....
Dimension 1 Selects the Gate, dimension 2 Selects the timer values
openingTime, holdTime, closingTime and pauseTime
To know which gate this is will be defined by the pins D14 - D16 (A0 - A2)
*/
const int gateTimers [5] [4] = {
{15, 60, 15, 5}, // Gate A
{15, 60, 17, 5}, // Gate B
{15, 60, 16, 5}, // Gate C
{18, 30, 14, 5}, // Gate D
{10, 15, 10, 5} // TESTING ONLY
};
/*
NOTE the next part reads all three pins relevant to identifying the address
it doesnt matter if more than one reads LOW as in this instance all except
the last one will be overwritten.
in the event that none are LOW then the address will be 1 (Zero really)
So just connect the required pin to GND.
Im using uint8_t to save SRAM as its all I need.
*/
//uint8_t address = 0;
uint8_t IDcheck;
IDcheck = digitalRead(addressPin1);
if (IDcheck == LOW) address = 1;
IDcheck = digitalRead(addressPin2);
if (IDcheck == LOW) address = 2;
IDcheck = digitalRead(addressPin3);
if (IDcheck == LOW) address = 3;
IDcheck = digitalRead(addressPin4);
if (IDcheck == LOW) address = 4;
// Based on the above pull the applicable values from the array for the gate
// as I didnt want to have a different upload for each gate.
openingTime = {gateTimers [address] [0]};
holdTime = {gateTimers [address] [1]};
closingTime = {gateTimers [address] [2]};
pauseTime = {gateTimers [address] [3]};
// Run startup script - Output to SERIAL
Serial.println("");
Serial.println(F("Gate Controller - TLC Innovation 2022"));
Serial.println(F("-------------------------------------"));
Serial.println(F("Watchdog initialised"));
Serial.println();
Serial.print(F("Gate ID found as : "));
Serial.println(address + 1);
Serial.print(F("Open Time : "));
Serial.println(openingTime);
Serial.print(F("Hold Open Time : "));
Serial.println(holdTime);
Serial.print(F("Close Time : "));
Serial.println(closingTime);
Serial.print(F("Pause On Edge : "));
Serial.println(pauseTime);
}
/* How the relays work
The motors are connected to the commons of the relays
whilst the DC supply is connected as follows...
Relay 1 - NC is + volts , NO is GND
Relay 2 - NC IS GND , NO is + volts
Additionally the safedge laser (24volts) is connected to a constant GND and
the NO of Relay 2 as I only need it for the closing of the gate and by doing
this I extend the life of the laser. The output of the laser I connect via a
potentiometer acting as a voltage divider to pin 'safeEdge' which if triggered
will stop closing movement for the duration of 'pauseTime'
Therefore energising neither allows one polarity to close the gate
energising both allows the reverse polarity to open the gate
With either one relay energised whilst the other is de-energised leaves
the gate without power however to ensure the laser gets to power down make
Relay 1 high and Relay 2 low.
*/
void closeGate() { // And arm laser
digitalWrite(Relay1, LOW);
digitalWrite(Relay2, LOW);
}
void openGate() {
digitalWrite(Relay1, HIGH);
digitalWrite(Relay2, HIGH);
}
void holdGate() {
digitalWrite(Relay1, HIGH);
digitalWrite(Relay2, LOW);
}
void pauseOnEdge() {
digitalWrite(Relay1, HIGH);
}
void relayControl() {
switch (Command) {
case 0:
holdGate();
break;
case 1:
openGate();
break;
case 2:
holdGate();
break;
case 3:
closeGate();
break;
case 4:
holdGate();
break;
}
}
void updateOLED() {
display.clearDisplay();
display.invertDisplay(false);
display.setTextColor(WHITE);
display.setCursor(0, 1);
display.setTextSize(2);
switch (Command) {
case 0:
display.print(" WAITING");
display.setCursor(16, 26);
display.setTextSize(1);
display.print("Address:");
display.setTextSize(4);
display.print(address+1);
statusLock = false;
break;
case 1:
display.print(" OPENING");
display.setCursor(37, 20);
display.setTextSize(5);
timeLeft --;
if ((timeLeft < 10)) display.print("0");
display.print(timeLeft);
display.display();
if (timeLeft <= 0) {
//statusLock = false;
Command = 2;
timeLeft = holdTime;
}
break;
case 2:
display.print(" HOLDING");
display.setCursor(21, 18);
display.setTextSize(5);
timeLeft --;
if ((timeLeft < 100)) display.print("0");
if ((timeLeft < 10)) display.print("0");
display.print(timeLeft);
display.display();
if (timeLeft <= 0) {
Command = 3;
timeLeft = closingTime;
}
break;
case 3:
display.print(" CLOSING");
display.setTextSize(1);
display.setCursor(37, 20);
display.setTextSize(5);
timeLeft --;
if ((timeLeft < 10)) display.print("0");
display.print(timeLeft);
display.display();
if (timeLeft <= 0) {
//statusLock = false;
Command = 0;
}
break;
case 4:
display.print("HOLD CLOSED");
display.setCursor(20, 18);
display.setTextSize(6);
break;
case 5:
display.print("PAUSED");
display.setCursor(20, 18);
display.print("Path Blocked");
break;
// Cases 6 & 7 may be used when necessary
case 8:
display.print(" RESET MODE");
// This option is the only way apart from resetting the controller to remove the Command 9
break;
case 9:
display.print(" EMERGENCY");
display.setCursor(18, 18);
display.setTextSize(6);
display.print("911");
break;
}
display.display();
lastOLEDtime = millis();
}
void checkInputs() {
uint8_t buttonState;
buttonState = digitalRead(NC_PIN);
if ((buttonState == HIGH) && (statusLock == false) && (Command == 0)) {
Command = 1;
statusLock = true;
startTime = millis();
timeLeft = openingTime;
}
if ((buttonState == HIGH) && (Command == 2)) timeLeft = holdTime+1;
if ((buttonState == HIGH) && (Command == 3)) {
timeLeft = (openingTime - (closingTime - timeLeft)+1);
Command = 1;
}
buttonState = digitalRead(NO_PIN);
if (buttonState == LOW) {
Command = 9;
}
// Look for safe edge here
/*if (Command = 3) {
buttonState = digitalRead(safeEdge);
if (buttonState == LOW) {
Command = 5;
}
}
*/
}
void loop() {
wdt_reset();
checkInputs();
relayControl();
elapsedTime = (millis() - referenceTime);
display.setCursor(10,56);
display.setTextSize(2);
if (millis() - lastOLEDtime >= 625) display.print("-");
if (millis() - lastOLEDtime >= 650) display.print("-");
if (millis() - lastOLEDtime >= 675) display.print("-");
if (millis() - lastOLEDtime >= 700) display.print("-");
if (millis() - lastOLEDtime >= 725) display.print("-");
if (millis() - lastOLEDtime >= 750) display.print("-");
if (millis() - lastOLEDtime >= 775) display.print("-");
if (millis() - lastOLEDtime >= 800) display.print("-");
if (millis() - lastOLEDtime >= 825) display.print("-");
if (millis() - lastOLEDtime >= 850) display.print("-");
if (millis() - lastOLEDtime >= 875) display.print("-");
if (millis() - lastOLEDtime >= 900) display.print("-");
if (millis() - lastOLEDtime >= 925) display.print("-");
display.display(); display.display(); display.display();
if (millis() - lastOLEDtime >= 1000) updateOLED();
if (elapsedTime >= 2000) {
referenceTime = millis();
}
}
// END OF SKETCH
`
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.