├── img
├── visual.png
├── warnings.png
├── cmpONMARK.png
├── timeGraph.png
├── cmpONDURATION.png
└── cmpTRIGGERED.png
├── .gitattributes
├── examples
├── AdvancedAutoFlashers
│ ├── Left.gif
│ ├── Right.gif
│ ├── Brakes.png
│ ├── Hazard.gif
│ └── AdvancedAutoFlashers.ino
├── BlockNotBlink
│ └── BlockNotBlink.ino
├── BlockNotBlinkParty
│ └── BlockNotBlinkParty.ino
├── ButtonDebounce
│ └── ButtonDebounce.ino
├── ResetAll
│ └── ResetAll.ino
├── DurationTrigger
│ └── DurationTrigger.ino
├── TimersRules
│ └── TimersRules.ino
├── OnWithOffTimers
│ └── OnWithOffTimers.ino
└── MillisRolloverTest
│ └── MillisRolloverTest.ino
├── .idea
└── vcs.xml
├── LICENSE
├── library.properties
├── keywords.txt
├── CHANGELOG.md
├── src
├── BlockNot.h
└── BlockNot.cpp
└── README.md
/img/visual.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EasyG0ing1/BlockNot/HEAD/img/visual.png
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/img/warnings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EasyG0ing1/BlockNot/HEAD/img/warnings.png
--------------------------------------------------------------------------------
/img/cmpONMARK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EasyG0ing1/BlockNot/HEAD/img/cmpONMARK.png
--------------------------------------------------------------------------------
/img/timeGraph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EasyG0ing1/BlockNot/HEAD/img/timeGraph.png
--------------------------------------------------------------------------------
/img/cmpONDURATION.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EasyG0ing1/BlockNot/HEAD/img/cmpONDURATION.png
--------------------------------------------------------------------------------
/img/cmpTRIGGERED.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EasyG0ing1/BlockNot/HEAD/img/cmpTRIGGERED.png
--------------------------------------------------------------------------------
/examples/AdvancedAutoFlashers/Left.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EasyG0ing1/BlockNot/HEAD/examples/AdvancedAutoFlashers/Left.gif
--------------------------------------------------------------------------------
/examples/AdvancedAutoFlashers/Right.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EasyG0ing1/BlockNot/HEAD/examples/AdvancedAutoFlashers/Right.gif
--------------------------------------------------------------------------------
/examples/AdvancedAutoFlashers/Brakes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EasyG0ing1/BlockNot/HEAD/examples/AdvancedAutoFlashers/Brakes.png
--------------------------------------------------------------------------------
/examples/AdvancedAutoFlashers/Hazard.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EasyG0ing1/BlockNot/HEAD/examples/AdvancedAutoFlashers/Hazard.gif
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/BlockNotBlink/BlockNotBlink.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | BlockNot blinkTimer(100);
4 | boolean state = false;
5 |
6 | void setup() {
7 | pinMode(LED_BUILTIN, OUTPUT);
8 | }
9 |
10 | void loop() {
11 |
12 | /*
13 | Here, we can see how BlockNot handles the 100ms
14 | time delay between LED state changes with ease
15 | and simplicity.
16 | */
17 |
18 | if (blinkTimer.TRIGGERED) {
19 | state = !state;
20 | digitalWrite(LED_BUILTIN, (state ? HIGH : LOW));
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Michael Sims
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/library.properties:
--------------------------------------------------------------------------------
1 | name=BlockNot
2 | version=2.4.0
3 | author=Michael Sims
4 | maintainer=Michael Sims
5 | sentence=BlockNot gives you non-blocking timers with simplicity.
6 | paragraph=*** Added feature to compensate for unwanted rapid succession triggers when using a high speed microcontroller such as a Raspberry Pi Pico. Stop using delay() in your code because it stops the execution of your code until the timer has finished. BlockNot's design focus is always SIMPLICITY and COMMON SENSE. It uses common sense terms which simplifies the reading and writing of your code. It offers, among several things, convenient AND SIMPLE timer functionality, but most of all ... it gets you away from blocking methods, like delay() - as a means of managing events in your code. Non-Blocking is the proper way to implement timing events in Arduino code and BlockNot makes it easy while also offering the ability to branch your code using many different references to time. Check out the documentation by clicking on More info. See README for version update notes.
7 | category=Timing
8 | url=http://github.com/EasyG0ing1/BlockNot
9 | architectures=*
10 | includes=BlockNot.h
11 |
--------------------------------------------------------------------------------
/examples/BlockNotBlinkParty/BlockNotBlinkParty.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | BlockNot blinkTimer1(150);
4 | BlockNot blinkTimer2(275);
5 | BlockNot blinkTimer3(400);
6 | BlockNot blinkTimer4(525);
7 |
8 | #define LED1 9
9 | #define LED2 10
10 | #define LED3 11
11 | #define LED4 12
12 |
13 | boolean state1 = false;
14 | boolean state2 = false;
15 | boolean state3 = false;
16 | boolean state4 = false;
17 |
18 | void setup() {
19 | pinMode(LED1, OUTPUT);
20 | pinMode(LED2, OUTPUT);
21 | pinMode(LED3, OUTPUT);
22 | pinMode(LED4, OUTPUT);
23 | }
24 |
25 | void loop() {
26 |
27 | /*
28 | Tru doing this with the delay() method ... not gonna happen!
29 | */
30 |
31 | if (blinkTimer1.TRIGGERED) {
32 | state1 = !state1;
33 | digitalWrite(LED1, (state1 ? HIGH : LOW));
34 | }
35 |
36 | if (blinkTimer2.TRIGGERED) {
37 | state2 = !state2;
38 | digitalWrite(LED2, (state2 ? HIGH : LOW));
39 | }
40 |
41 | if (blinkTimer3.TRIGGERED) {
42 | state3 = !state3;
43 | digitalWrite(LED3, (state3 ? HIGH : LOW));
44 | }
45 |
46 | if (blinkTimer4.TRIGGERED) {
47 | state4 = !state4;
48 | digitalWrite(LED4, (state4 ? HIGH : LOW));
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/examples/ButtonDebounce/ButtonDebounce.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | /**
4 | * BlockNot can be used to debounce a button, and you don't even have to be conservative on your delay time
5 | * because when you press a button, the odds that you'll press a button again within a single second even
6 | * is really low, and since timers trigger AFTER the stated duration has passed, you can have your button
7 | * code run only if the timer has triggered, which means that if it has triggered your code will run immediately
8 | * without needing to debounce it using the delay() method, and after you let go of the button, the timer will
9 | * be primed and ready to go very shortly there after. So then the duration of the timer can be as long as
10 | * half a second or even a full second or longer since the timer is primed after that duration has already
11 | * passed. You're basically debouncing ahead of time so that your button code can run immediately.
12 | *
13 | * Connect one pin of your button to pin 12 of your Arduino, then the other button pin goes to ground, then
14 | * upload this sketch and try it out.
15 | *
16 | * If you want the button to respond faster than one second, change the timers duration from 1, SECONDS to
17 | * something smaller like just 500 for half a second or 250 for 1/4 second.
18 | */
19 |
20 | BlockNot buttonTimer(1, SECONDS);
21 | //BlockNot buttonTimer(500); // 1/2 second
22 | //BlockNot buttonTimer(250); // 1/4 second
23 |
24 | #define BUTTON 12
25 |
26 | #define BUTTON_PRESSED digitalRead(BUTTON) == LOW;
27 |
28 |
29 | void setup() {
30 | Serial.begin(115200);
31 | pinMode(BUTTON, INPUT_PULLUP);
32 | }
33 |
34 | loop() {
35 | if(BUTTON_PRESSED)
36 | if(buttonTimer.TRIGGERED)
37 | Serial.println("Button Pressed!");
38 | }
--------------------------------------------------------------------------------
/examples/ResetAll/ResetAll.ino:
--------------------------------------------------------------------------------
1 | /**
2 | * This library shows how to use the resetAllTimers() method
3 | */
4 |
5 | #include
6 | #include
7 |
8 | BlockNot timer1(32, SECONDS, GLOBAL_RESET);
9 | BlockNot timer2(52, SECONDS);
10 | BlockNot timer3(19000);
11 | BlockNot printRemainingTimer(2, SECONDS);
12 | BlockNot resetTimer(8, SECONDS);
13 |
14 | void printTimers() {
15 | String heading = "Timer\tDuration\tUnits\n-----------------------------";
16 | String t1 = "One\t" + String(timer1.DURATION) + "\t\t" + timer1.GET_UNITS;
17 | String t2 = "Two\t" + String(timer2.DURATION) + "\t\t" + timer2.GET_UNITS;
18 | String t3 = "Three\t" + String(timer3.DURATION) + "\t\t" + timer3.GET_UNITS;
19 | Serial.println(heading);
20 | Serial.println(t1);
21 | Serial.println(t2);
22 | Serial.println(t3);
23 | Serial.print("\n");
24 | }
25 |
26 | void printRemaining() {
27 | String t1 = "Timer One Remaining: " + String(timer1.REMAINING) + " " + timer1.GET_UNITS;
28 | String t2 = "Timer Two Remaining: " + String(timer2.REMAINING) + " " + timer2.GET_UNITS;
29 | String t3 = "Timer Three Remaining: " + String(timer3.REMAINING) + " " + timer3.GET_UNITS;
30 | Serial.println(t1);
31 | Serial.println(t2);
32 | Serial.println(t3);
33 | Serial.print("\n");
34 | }
35 |
36 |
37 | void setup() {
38 | Serial.begin(115200);
39 | delay(2000);
40 | Serial.println(F("\nREADY!\n"));
41 | delay(1000);
42 | printTimers();
43 | int count = 0;
44 | while(true) {
45 | if(printRemainingTimer.TRIGGERED) {
46 | printRemaining();
47 | count++;
48 | }
49 | if (resetTimer.TRIGGERED) {
50 | Serial.println("\nRESET\n");
51 | RESET_TIMERS;
52 | }
53 | if (count >= 12)
54 | break;
55 | }
56 | Serial.println("\nDONE!\n");
57 | }
58 |
59 | void loop() {
60 | }
61 |
--------------------------------------------------------------------------------
/examples/DurationTrigger/DurationTrigger.ino:
--------------------------------------------------------------------------------
1 | /*
2 | * The README file has a thorough explanation of what is going on in this sketch.
3 | *
4 | * Read the section about Triggered On Duration then come back here and play
5 | * with this sketch. It should drive the concept home since you will be able to
6 | * see in real time what this trigger method does.
7 | *
8 | * In Serial Monitor, type p then hit enter to pause the loop, then wait for a
9 | * few durations to pass then enter p again to enable the loop and you will
10 | * see how BlockNot can stack up your trigger events if you need that feature.
11 | */
12 |
13 | #include
14 | #include
15 |
16 | BlockNot mainTimer(10000);
17 | BlockNot checkTimer(1,SECONDS);
18 |
19 | bool run = true;
20 |
21 | void processSerial() {
22 | String input = Serial.readString();
23 | if (input.startsWith("p")) {
24 | run = !run;
25 | Serial.println((run ? "Loop RUNNING" : "Loop PAUSED"));
26 | }
27 | while(Serial.available()) Serial.read();
28 | }
29 |
30 | void setup() {
31 | Serial.begin(115200);
32 | Serial.println(F("\nREADY!\n"));
33 | }
34 |
35 | void loop() {
36 | if (Serial.available()) processSerial();
37 | if (run) {
38 | if(checkTimer.TRIGGERED) {
39 | if (mainTimer.TRIGGERED_ON_DURATION_ALL) {
40 | Serial.println("Timer TRIGGERED");
41 | }
42 | else {
43 | String startTime = String(mainTimer.getStartTime());
44 | String now = String(millis());
45 | String duration = String(mainTimer.getDuration());
46 | String nextTrigger = String(mainTimer.getNextTriggerTime());
47 |
48 | Serial.print("Timer DID NOT TRIGGER.");
49 | Serial.print(" Start time is: " + startTime);
50 | Serial.print(" Now: " + now);
51 | Serial.print(" Duration: " + duration);
52 | Serial.println(" Next Trigger: " + nextTrigger);
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/examples/TimersRules/TimersRules.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | BlockNot myTimer(8000);
5 | BlockNot triggerTimer(10000);
6 | BlockNot stopAfterThreeTimer(3000);
7 | BlockNot liteTimer(15000);
8 | BlockNot shortTimer (500);
9 | BlockNot secondsCounter(1000);
10 |
11 | void setup() {
12 | Serial.begin(57600);
13 | delay(1800);
14 | Serial.println(F("\nREADY!\n"));
15 | }
16 |
17 | void loop()
18 | {
19 |
20 | static int x = 0;
21 | static unsigned long seconds = 0;
22 |
23 | if (secondsCounter.TRIGGERED){
24 | //adds one to variable seconds after the trigger event (1 second)
25 | seconds++;
26 | }
27 | if (myTimer.TRIGGERED) {
28 | Serial.println(("myTimer("+String(seconds)+"): This timer will trigger every 8 seconds!"));
29 | Serial.println(F("---------------------------------------------"));
30 | }
31 |
32 | if (triggerTimer.FIRST_TRIGGER) {
33 | Serial.println(("triggerTimer("+String(seconds)+"): This will trigger at 10 seconds, then it will not run again."));
34 | Serial.println(F("---------------------------------------------"));
35 | }
36 |
37 | if (stopAfterThreeTimer.FIRST_TRIGGER) {
38 | Serial.println(("stopAfterThreeTimer("+String(seconds)+"): This timer runs ONE TIME after 3 seconds then does nothing until it is reset from liteTimer (every 15 seconds). It will then trigger again 3 seconds after it has been reset."));
39 | Serial.println(F("---------------------------------------------"));
40 | }
41 |
42 | if (liteTimer.TRIGGERED) {
43 | Serial.println(("liteTimer("+String(seconds)+"): This is a 15 second timer. Three seconds after it triggers, stopAfterThreeTimer should trigger."));
44 | stopAfterThreeTimer.RESET;
45 | Serial.println(F("---------------------------------------------"));
46 | }
47 |
48 | if (shortTimer.TRIGGERED) {
49 | x++;
50 | if (x > 10) {
51 | x = 0;
52 | Serial.println("shortTimer("+String(seconds)+"): This timer runs code when it has triggered over 10 times. It is set for 1/2 second and should therefore run this code every 5 seconds.");
53 | Serial.println(F("---------------------------------------------"));
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/examples/OnWithOffTimers/OnWithOffTimers.ino:
--------------------------------------------------------------------------------
1 | /*
2 | * This sketch shows the correct way of implementing non-blocking timers
3 | * when you need to have one timer control when an event stays on and
4 | * another timer to control how long the event stays off.
5 | *
6 | * It can be a little tricky, but this sketch has made every effort
7 | * at being as simple and easy to understand as possible.
8 | *
9 | * In this sketch, we use LEDs and then different timers to control
10 | * how long they stay on and how long they stay off. BUT, this could
11 | * easily be a different device such as a stepper motor, a regular motor
12 | * a relay ...
13 | *
14 | * This sketch will start by turning on two LEDs. One of them (blue)
15 | * will only stay on for 500 ms and it will repeat every 2500ms. The
16 | * second LED (red) will turn on and stay on for 5 seconds, then it
17 | * will turn off for one second. every six seconds they will turn on
18 | * at the same time.
19 | */
20 |
21 | #include
22 | #include
23 |
24 | BlockNot blueTimerON(500,GLOBAL_RESET);
25 | BlockNot blueTimerOFF(2500);
26 |
27 | BlockNot redTimerON(5000);
28 | BlockNot redTimerOFF(1000);
29 |
30 | const int BLUE_PIN = 10;
31 | const int RED_PIN = 9;
32 |
33 | bool redON = false;
34 | bool blueON = false;
35 |
36 | bool blueIsOff() { return !blueON; }
37 |
38 | bool blueIsOn() { return blueON; }
39 |
40 | bool redIsOff() { return !redON; }
41 |
42 | bool redIsOn() { return redON; }
43 |
44 | void turnRedOn() {
45 | digitalWrite(RED_PIN, LOW);
46 | redTimerON.RESET;
47 | redON = true;
48 | }
49 |
50 | void turnRedOff() {
51 | digitalWrite(RED_PIN, HIGH);
52 | redTimerOFF.RESET;
53 | redON = false;
54 | }
55 |
56 | void turnBlueOn() {
57 | digitalWrite(BLUE_PIN, LOW);
58 | blueTimerON.RESET;
59 | blueON = true;
60 | }
61 |
62 | void turnBlueOff() {
63 | digitalWrite(BLUE_PIN, HIGH);
64 | blueTimerOFF.RESET;
65 | blueON = false;
66 | }
67 |
68 | void setup() {
69 | Serial.begin(115200);
70 | pinMode(BLUE_PIN, OUTPUT);
71 | pinMode(RED_PIN, OUTPUT);
72 | resetAllTimers();
73 | turnBlueOn();
74 | turnRedOn();
75 | Serial.println(F("\nREADY!\n"));
76 | }
77 |
78 | void loop() {
79 |
80 | if (blueTimerON.TRIGGERED && blueIsOn()) {
81 | turnBlueOff();
82 | }
83 |
84 | if (blueTimerOFF.TRIGGERED && blueIsOff()) {
85 | turnBlueOn();
86 | Serial.println("Blue On");
87 | }
88 |
89 | if (redTimerON.TRIGGERED && redIsOn()) {
90 | turnRedOff();
91 | }
92 |
93 | if (redTimerOFF.TRIGGERED && redIsOff()) {
94 | turnRedOn();
95 | Serial.println("Red On\n");
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/keywords.txt:
--------------------------------------------------------------------------------
1 | #######################################
2 | # Syntax Coloring Map For BlockNot
3 | #######################################
4 |
5 | #######################################
6 | # DataTypes (KEYWORD1)
7 | #######################################
8 |
9 | BlockNotUnit KEYWORD1
10 | BlockNotGlobal KEYWORD1
11 | BlockNotState KEYWORD1
12 | WITH_RESET KEYWORD1
13 | NO_RESET KEYWORD1
14 | ALL KEYWORD1
15 | MICROSECONDS KEYWORD1
16 | MILLISECONDS KEYWORD1
17 | SECONDS KEYWORD1
18 | MINUTES KEYWORD1
19 | NO_GLOBAL_RESET KEYWORD1
20 | GLOBAL_RESET KEYWORD1
21 | RUNNING KEYWORD1
22 | STOPPED KEYWORD1
23 |
24 | #######################################
25 | # Methods and Functions (KEYWORD2)
26 | #######################################
27 |
28 | setDuration KEYWORD2
29 | addTime KEYWORD2
30 | takeTime KEYWORD2
31 | triggered KEYWORD2
32 | triggeredOnDuration KEYWORD2
33 | notTriggered KEYWORD2
34 | firstTrigger KEYWORD2
35 | setFirstTriggerResponse KEYWORD2
36 | getNextTriggerTime KEYWORD2
37 | getTimeUntilTrigger KEYWORD2
38 | triggerNext KEYWORD2
39 | getStartTime KEYWORD2
40 | getDuration KEYWORD2
41 | lastTriggerDuration KEYWORD2
42 | getUnits KEYWORD2
43 | getTimeSinceLastReset KEYWORD2
44 | setStoppedReturnValue KEYWORD2
45 | start KEYWORD2
46 | stop KEYWORD2
47 | isRunning KEYWORD2
48 | isStopped KEYWORD2
49 | toggle KEYWORD2
50 | convert KEYWORD2
51 | switchTo KEYWORD2
52 | reset KEYWORD2
53 | setMillisOffset KEYWORD2
54 | setMicrosOffset KEYWORD2
55 | getMillis KEYWORD2
56 | getFirstTimer KEYWORD2
57 | getNextTimer KEYWORD2
58 | speedComp KEYWORD2
59 | disableSpeedComp KEYWORD2
60 |
61 | ######################################
62 | # Instances (KEYWORD2)
63 | #######################################
64 |
65 | BlockNot KEYWORD2
66 |
67 | #######################################
68 | # Constants (LITERAL1)
69 | #######################################
70 |
71 | TIME_PASSED LITERAL1
72 | TIME_SINCE_RESET LITERAL1
73 | ELAPSED LITERAL1
74 | TIME_TILL_TRIGGER LITERAL1
75 | TIME_REMAINING LITERAL1
76 | REMAINING LITERAL1
77 | DURATION LITERAL1
78 | GET_UNITS LITERAL1
79 | GET_START_TIME LITERAL1
80 | DONE LITERAL1
81 | TRIGGERED LITERAL1
82 | LAST_TRIGGER_DURATION LITERAL1
83 | HAS_TRIGGERED LITERAL1
84 | TRIGGERED_ON_DURATION LITERAL1
85 | TRIGGERED_ON_MARK LITERAL1
86 | TRIGGERED_ON_DURATION_ALL LITERAL1
87 | TRIGGERED_ALL LITERAL1
88 | TRIGGER_NEXT LITERAL1
89 | NOT_DONE LITERAL1
90 | NOT_TRIGGERED LITERAL1
91 | FIRST_TRIGGER LITERAL1
92 | RESET LITERAL1
93 | RESET_TIMERS LITERAL1
94 | START LITERAL1
95 | START_RESET LITERAL1
96 | STOP LITERAL1
97 | ISSTARTED LITERAL1
98 | ISRUNNING LITERAL1
99 | ISSTOPPED LITERAL1
100 | TOGGLE LITERAL1
101 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on **Keep a Changelog**, and this project adheres to **Semantic Versioning**.
5 |
6 | ---
7 |
8 | ## [Unreleased]
9 |
10 |
11 | ## [2.4.0] – 2025-XX-XX
12 | ### Added
13 | - `triggerNext()` method and `TRIGGER_NEXT` macro.
14 | - Ability to define timers in **MINUTES**.
15 | - `getHelp()` method.
16 |
17 | ### Changed
18 | - Restructured internal code for efficiency.
19 | - Rewrote the ResetAll example for clarity.
20 |
21 | ---
22 |
23 | ## [2.3.0] – 2025-XX-XX
24 | ### Added
25 | - `speedComp()` feature for preventing rapid unintended triggers on high-speed MCUs (e.g., Raspberry Pi Pico).
26 |
27 | ---
28 |
29 | ## [2.2.0] – 2025-XX-XX
30 | ### Added
31 | - `setFirstTriggerResponse(bool)` method to control behavior of `firstTrigger()`.
32 |
33 | ---
34 |
35 | ## [2.1.5] – 2025-XX-XX
36 | ### Added
37 | - Advanced Auto Flashers example.
38 | - Updated README documentation.
39 |
40 | ---
41 |
42 | ## [2.1.4] – 2025-XX-XX
43 | ### Changed
44 | - Minor internal updates (per PR #19).
45 |
46 | ---
47 |
48 | ## [2.1.3] – 2025-XX-XX
49 | ### Changed
50 | - `firstTrigger()` no longer performs calculations after triggering, improving efficiency of repeated calls.
51 |
52 | ---
53 |
54 | ## [2.1.2] – 2025-XX-XX
55 | ### Fixed
56 | - Bug fix.
57 |
58 | ---
59 |
60 | ## [2.1.1] – 2025-XX-XX
61 | ### Changed
62 | - Renamed public enums to avoid name conflicts with other libraries.
63 |
64 | ---
65 |
66 | ## [2.1.0] – 2025-XX-XX
67 | ### Added
68 | - Added **Last Trigger Duration** support.
69 |
70 | ---
71 |
72 | ## [2.0.7] – 2025-XX-XX
73 | ### Changed
74 | - Updated variable declarations to improve thread-safety (see Thread Safety notes).
75 |
76 | ---
77 |
78 | ## [2.0.6] – 2025-XX-XX
79 | ### Added
80 | - Constructors allowing creation of timers in a **STOPPED** state.
81 | - `setDuration(unsigned long time, Unit inUnits)` to change duration using alternative units without modifying base units.
82 | - Ability to start a timer *while resetting it*, modifying start/stop behavior to act more like a stopwatch.
83 |
84 | ---
85 |
86 | ## [2.0.5] – 2025-XX-XX
87 | ### Changed
88 | - Adjusted handling of millis offset.
89 |
90 | ### Added
91 | - `setMicrosOffset(unsigned long)` for testing micros() rollover.
92 |
93 | ---
94 |
95 | ## [2.0.3] – 2025-XX-XX
96 | ### Added
97 | - Undocumented methods to aid in testing millis() rollover.
98 | - Example sketch demonstrating rollover behavior.
99 |
100 | ---
101 |
102 | ## [2.0.0] – 2025-XX-XX
103 | ### Added
104 | - Major upgrade enabling timers based on **seconds**, **milliseconds**, and **microseconds**.
105 | - Significant internal redesign to support microsecond precision.
106 |
107 | ---
108 |
109 | ## [1.8.5] – 2024-XX-XX
110 | ### Added
111 | - `TRIGGERED_ALL` and `TRIGGERED_ON_MARK` macros.
112 | - Millis() Rollover section added to documentation.
113 |
114 | ### Changed
115 | - Rewrote the triggeredOnDuration documentation and simplified related graphs.
116 |
117 | ---
118 |
119 | ## [1.8.4] – 2024-XX-XX
120 | ### Changed
121 | - Renamed `STARTED`, `RUNNING`, and `STOPPED` to `ISSTARTED`, `ISRUNNING`, and `ISSTOPPED` to avoid conflicts.
122 |
123 | ---
124 |
125 | ## [1.8.3] – 2024-XX-XX
126 | ### Fixed
127 | - Minor bug fixes.
128 |
129 | ---
130 |
131 | ## [1.8.2] – 2024-XX-XX
132 | ### Changed
133 | - Global Reset option is now default again.
134 | - Updated README to reflect internal public API adjustments.
135 |
136 | ---
137 |
138 | ## [1.8.1] – 2024-XX-XX
139 | ### Added
140 | - `timeTillTrigger()` method and `TIME_TILL_TRIGGER` macro.
141 |
142 | ### Removed
143 | - Removed unnecessary millis() rollover code.
144 |
145 | ### Changed
146 | - Cleaned up redundant macros.
147 | - Renamed internal methods for consistency.
148 |
149 | ---
150 |
151 | ## [1.8.0] – 2024-XX-XX
152 | ### Changed
153 | - Major restructuring into separate header and code files.
154 | - Fixed ODR (One Definition Rule) violation.
155 |
156 | ---
157 |
158 | ## [1.7.4] – 2024-XX-XX
159 | ### Fixed
160 | - Corrected `triggeredOnDuration` calculations; now accounts for rollover.
161 |
162 | ---
163 |
164 | ## [1.7.3] – 2024-XX-XX
165 | ### Changed
166 | - Now compatible with millis rollover at ~49 days.
167 |
168 | ---
169 |
170 | ## [1.7.2] – 2024-XX-XX
171 | ### Added
172 | - `start()` and `stop()` methods.
173 | - `START` and `STOP` macros.
174 |
175 | ### Changed
176 | - Timers can now be modified while stopped.
177 |
178 | ---
179 |
180 | ## [1.7.1] – 2024-XX-XX
181 | ### Changed
182 | - Minor efficiency improvements.
183 |
184 | ---
185 |
186 | ## [1.7.0] – 2024-XX-XX
187 | ### Added
188 | - New `resetAllTimers()` (`RESET_TIMERS`) with bug fixes.
189 | - `triggeredOnDuration()` method and corresponding macro.
190 |
191 | ### Fixed
192 | - Corrected accumulated drift caused by earlier resetAllTimers behavior.
193 |
194 | ---
195 |
196 | ## [1.6.7] – 2024-XX-XX
197 | ### Fixed
198 | - Bug preventing compilation when declaring a timer with duration alone.
199 |
200 | ---
201 |
202 | ## [1.6.6] – 2024-XX-XX
203 | ### Added
204 | - `resetAllTimers()` / `RESET_TIMERS` contributed by @bizprof.
205 |
206 | ---
207 |
208 | ## [1.6.5] – 2024-XX-XX
209 | ### Added
210 | - Added **SECONDS Mode**.
211 |
--------------------------------------------------------------------------------
/examples/MillisRolloverTest/MillisRolloverTest.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | /*
5 | * This sketch is intended to offer you the ability to see that when millis() rolls over,
6 | * A BlockNot timer continues to calculate time duration accurately.
7 | *
8 | * mainTimer is the timer we are clocking in this test, and we record the value of the
9 | * Arduino's actual millis() each time that timer resets, then when it triggers, we
10 | * get millis() again and we take the difference so that we can see whether or not
11 | * the time between triggers is actually equal to the duration set in the timer.
12 | *
13 | * BlockNot has the ability to include an offset for millis() that I put into the library
14 | * specifically to perform this kind of a test. you can assign the offset using a timers
15 | * setMillisOffset(unsigned long) method.
16 | *
17 | * In this test, I set the millis offset to be a value that is 20 seconds before the
18 | * rollover will happen. Then the main timer is set for 15 seconds, that way the rollover
19 | * will happen 5 seconds into the second trigger event.
20 | *
21 | * You can also use a stop watch, and start it when the mainTimer triggers the first time,
22 | * then stop it when it triggers the second time (after the rollover) to verify that it
23 | * did trigger in 15 seconds despite the rollover occurring 5 seconds into the duration
24 | * of the timer.
25 | *
26 | * Once the rollover happens in this test, and you see that the timer triggers properly,
27 | * you will need to reset the Arduino to see the test again, or you'll have to wait
28 | * about two months ;-)
29 | */
30 |
31 | /*
32 | * Once millis() has rolled over, a new rollover time will be set by a random number,
33 | * every seven seconds ... UNLESS ...
34 | */
35 |
36 | /*
37 | * USER INPUT
38 | *
39 | * You can type in the word reset with a number to reset the roll to however many seconds
40 | * you wish. Ex:
41 | *
42 | * reset15
43 | *
44 | * Will set the millis() for the mainTimer to roll over to 0 in 15 seconds. The mainTimer
45 | * will continue counting and displaying its elapsed duration about once ever second, but
46 | * what is important to note, is that even after the rollover, the elapsed duration for
47 | * mainTimer does not skip a beat.
48 | *
49 | * The reason why the elapsed time presented for mainTimer drifts as much as it does is
50 | * because of the blocking nature that is inherent with Serial activity and since it's
51 | * writing text to the Serial port every second.
52 | *
53 | * Since Serial.println is a blocking method, it will inevitably shift the point in time
54 | * where the variable elapsed is calculated under the if then statement for
55 | * reportTimer.TRIGGERED
56 | */
57 |
58 | BlockNot mainTimer(15000);
59 | BlockNot checkTimer(550);
60 | BlockNot reportTimer(1135);
61 | BlockNot randomTimer(7, SECONDS);
62 |
63 | unsigned long mainTimerMillisStart;
64 | unsigned long startTime;
65 | bool millisReset = false;
66 |
67 | unsigned long getRandomSeconds() {
68 | return random(3,25);
69 | }
70 |
71 | void setMillisRolloverTo(unsigned long seconds) {
72 | mainTimer.setMillisOffset(4294967295 - millis() - (seconds * 1000));
73 | mainTimerMillisStart = mainTimer.getMillis();
74 | millisReset = false;
75 | Serial.println("\n\t\tMillis reset to roll in " + String(seconds) + " seconds.\n");
76 | }
77 |
78 | void checkSerial() {
79 | String input = Serial.readString();
80 | if(input.startsWith("reset")) {
81 | unsigned long seconds = input.substring(5).toInt();
82 | setMillisRolloverTo(seconds);
83 | }
84 | Serial.flush();
85 | }
86 |
87 | void setup() {
88 | Serial.begin(115200);
89 | mainTimer.setMillisOffset(4294958296); //9 seconds before rollover
90 | mainTimer.RESET;
91 | startTime = millis();
92 | mainTimerMillisStart = mainTimer.getMillis();
93 | randomSeed(analogRead(0));
94 | Serial.println(F("Test Started"));
95 | }
96 |
97 | void loop() {
98 | static unsigned long currentTimerMillis;
99 | currentTimerMillis = mainTimer.getMillis();
100 | if(checkTimer.TRIGGERED) {
101 | currentTimerMillis = mainTimer.getMillis();
102 | if (currentTimerMillis < mainTimerMillisStart) {
103 | Serial.println(F("\nMillis Rolled"));
104 | Serial.println("Start value: " + String(mainTimerMillisStart));
105 | Serial.println("Current value: " + String(currentTimerMillis) + "\n");
106 | mainTimerMillisStart = currentTimerMillis;
107 | millisReset = true;
108 | randomTimer.RESET;
109 | }
110 | }
111 | if(reportTimer.TRIGGERED) {
112 | unsigned long elapsed = mainTimer.getTimeSinceLastReset();
113 | Serial.println("mainTimer: " + String(elapsed) + " milliseconds");
114 | }
115 | if(mainTimer.TRIGGERED) {
116 | unsigned long endTime = millis();
117 | long delta = endTime - startTime;
118 | auto elapsed = (double) (delta/1000);
119 | Serial.println("\n****************************************************\n* Main Timer Triggered after " + String(elapsed,0) + " seconds elapsed *\n****************************************************\n");
120 | startTime = endTime;
121 | }
122 | if (randomTimer.TRIGGERED && millisReset) {
123 | unsigned long seconds = getRandomSeconds();
124 | setMillisRolloverTo(getRandomSeconds());
125 | }
126 | if (Serial.available())
127 | checkSerial();
128 | }
129 |
--------------------------------------------------------------------------------
/examples/AdvancedAutoFlashers/AdvancedAutoFlashers.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | /*
5 | * This sketch was created for an LED layout that was installed onto the back of a 5th wheel.
6 | * It was written for a Raspberry Pi Pico
7 | *
8 | * It looks for four different states that a driver could be doing with the automobile blinkers.
9 | * It looks for:
10 | * 1) Left flasher flashing
11 | * 2) Right flasher flashing
12 | * 3) Both flashers flashing at the same time (hazard lights)
13 | * 4) Brakes are being applied
14 | *
15 | * When the flashers are engaged, it lights up LEDs that are arranged in a pattern. See the GIF images to see
16 | * how it lights up the various LEDs on the back of the 5th wheel.
17 | *
18 | * BlockNot has been used as the only means of getting the timing right for the various LED animations.
19 | *
20 | * This code shows you how to write a sketch where the execution of the code never stops, yet it provides
21 | * distinct functionality depending on the statae of the flashers.
22 | *
23 | * The input voltage from the blinkers will flash at some unknown frequency. We simply set a boolean high
24 | * when the pin reads at a positive voltage. When it sees that positive voltage, it resets the timeDelay
25 | * timer so that if there are no high values read on those pins for the duration of that timer, it
26 | * then sets the state that the turn signals are not being used, and we use a firstTrigger() for that
27 | * because we only need it to hit one time after a reset.
28 | *
29 | */
30 |
31 | enum State {
32 | LEFT_BLINK,
33 | RIGHT_BLINK,
34 | ALL_OFF,
35 | HAZARD
36 | };
37 |
38 | State state = ALL_OFF;
39 |
40 | u_int leftBlinkIn = 2;
41 | u_int rightBlinkIn = 3;
42 | u_int brakeIn = 4;
43 |
44 | u_int right1 = 9;
45 | u_int right2 = 10;
46 | u_int right3 = 11;
47 | u_int right4 = 12;
48 | u_int right5 = 13;
49 | u_int cLight = 14;
50 |
51 | u_int left1 = 17;
52 | u_int left2 = 18;
53 | u_int left3 = 19;
54 | u_int left4 = 20;
55 | u_int left5 = 21;
56 |
57 | u_int brakeHazard = 16;
58 |
59 | bool brakeOn = false;
60 |
61 | u_int rightArrow[7] = {0, right1, right2, right3, right4, right5, cLight};
62 | u_int leftArrow[7] = {0, left1, left2, left3, left4, left5, cLight};
63 |
64 |
65 | void getState() {
66 | static bool rightOn = false;
67 | static bool leftOn = false;
68 | static BlockNot timeDelay(3, SECONDS);
69 |
70 | if (gpio_get(rightBlinkIn) == HIGH) {
71 | sleep_ms(100);
72 | rightOn = gpio_get(rightBlinkIn) == HIGH;
73 | timeDelay.RESET;
74 | }
75 | if (gpio_get(leftBlinkIn) == HIGH) {
76 | sleep_ms(100);
77 | leftOn = gpio_get(leftBlinkIn) == HIGH;
78 | timeDelay.RESET;
79 | }
80 | if (timeDelay.firstTrigger()) {
81 | rightOn = false;
82 | leftOn = false;
83 | }
84 | if (rightOn && leftOn) {
85 | state = HAZARD;
86 | }
87 | else if (rightOn && !leftOn) {
88 | state = RIGHT_BLINK;
89 | }
90 | else if (leftOn && !rightOn) {
91 | state = LEFT_BLINK;
92 | }
93 | else {
94 | state = ALL_OFF;
95 | }
96 | }
97 |
98 |
99 | void arrowsOff() {
100 | for (int x = 1; x <= 6; x++) {
101 | gpio_put(rightArrow[x], LOW);
102 | gpio_put(leftArrow[x], LOW);
103 | }
104 | if (!brakeOn) {
105 | gpio_put(brakeHazard, LOW);
106 | }
107 | }
108 |
109 | void arrowBlink() {
110 | static BlockNot transition(40);
111 | static BlockNot start(650);
112 | static int index = 0;
113 |
114 | if (state == HAZARD) {
115 | index = 0;
116 | return;
117 | }
118 | if ((state != LEFT_BLINK && state != RIGHT_BLINK) || state == ALL_OFF) {
119 | arrowsOff();
120 | index = 0;
121 | return;
122 | }
123 | if (index == 0) {
124 | if (start.TRIGGERED) {
125 | index = 1;
126 | transition.RESET;
127 | }
128 | }
129 | if (index > 0) {
130 | if (transition.TRIGGERED) {
131 | if (index > 6) {
132 | arrowsOff();
133 | start.RESET;
134 | index = 0;
135 | return;
136 | }
137 |
138 | if (state == RIGHT_BLINK)
139 | gpio_put(rightArrow[index], HIGH);
140 |
141 | if (state == LEFT_BLINK)
142 | gpio_put(leftArrow[index], HIGH);
143 |
144 | index++;
145 | }
146 | }
147 | }
148 |
149 | void hazard() {
150 | static BlockNot transition(40);
151 | static BlockNot start(650);
152 | static int index = 0;
153 | if (state != HAZARD) {
154 | return;
155 | }
156 |
157 | if (index == 0) {
158 | if(start.TRIGGERED) {
159 | index = 1;
160 | }
161 | }
162 |
163 | if(index == 1) {
164 | gpio_put(brakeHazard, HIGH);
165 | transition.RESET;
166 | }
167 |
168 | if(index > 0) {
169 | if(transition.TRIGGERED) {
170 | if(index > 6) {
171 | arrowsOff();
172 | start.RESET;
173 | index = 0;
174 | return;
175 | }
176 | gpio_put(rightArrow[index], HIGH);
177 | gpio_put(leftArrow[index], HIGH);
178 | index++;
179 | }
180 | }
181 | }
182 |
183 | void brake() {
184 | gpio_put(brakeHazard, gpio_get(brakeIn) == LOW ? HIGH : LOW);
185 | brakeOn = gpio_get(brakeIn) == LOW;
186 | }
187 |
188 | void setup() {
189 | for (int x = 9 ; x <= 14 ; x++) {
190 | pinMode(x, OUTPUT);
191 | }
192 | for (int x = 16 ; x <= 21 ; x++) {
193 | pinMode(x, OUTPUT);
194 | }
195 | pinMode(brakeHazard, OUTPUT);
196 | pinMode(rightBlinkIn, INPUT);
197 | pinMode(leftBlinkIn, INPUT);
198 | pinMode(brakeIn, INPUT_PULLUP);
199 | }
200 |
201 | void loop() {
202 | getState();
203 | brake();
204 | switch (state) {
205 | case HAZARD: hazard();
206 | case ALL_OFF: arrowsOff();
207 | default: arrowBlink();
208 | }
209 | sleep_ms(1); // For good measure
210 | }
211 |
--------------------------------------------------------------------------------
/src/BlockNot.h:
--------------------------------------------------------------------------------
1 | /**
2 | * BlockNot is a simple and easy to use Arduino class for the implementation
3 | * of non-blocking timers, as it is far better to use non-blocking timers
4 | * with a micro-controller since they allow you to trigger code at defined
5 | * durations of time, without stopping the execution of your main loop.
6 | *
7 | * Written by - Michael Sims
8 | * Full documentation can be found at: https://github.com/EasyG0ing1/BlockNot
9 | *
10 | * See LICENSE file for acceptable use conditions - this is open source
11 | * and there are no restrictions on its usage, I simply ask for some acknowledgment
12 | * if it is used in your project.
13 | */
14 | #ifndef BlockNot_h
15 | #define BlockNot_h
16 |
17 | #include
18 |
19 | #pragma once
20 |
21 | /**
22 | * Macros - their usage and significance is described in README.md
23 | */
24 |
25 | enum BlockNotUnit {
26 | mic_cTime, mil_cTime, sec_cTime, min_cTime
27 | };
28 | enum BlockNotGlobal {
29 | yes, no
30 | };
31 | enum BlockNotState {
32 | running, stopped
33 | };
34 |
35 | #define WITH_RESET true
36 | #define NO_RESET false
37 | #define ALL true
38 | #define MICROSECONDS BlockNotUnit::mic_cTime
39 | #define MILLISECONDS BlockNotUnit::mil_cTime
40 | #define SECONDS BlockNotUnit::sec_cTime
41 | #define MINUTES BlockNotUnit::min_cTime
42 | #define NO_GLOBAL_RESET BlockNotGlobal::no
43 | #define GLOBAL_RESET BlockNotGlobal::yes
44 | #define RUNNING BlockNotState::running
45 | #define STOPPED BlockNotState::stopped
46 |
47 | #define ELAPSED getTimeSinceLastReset()
48 | #define REMAINING getTimeUntilTrigger()
49 | #define DURATION getDuration()
50 | #define GET_UNITS getUnits()
51 | #define GET_START_TIME getStartTime()
52 |
53 | #ifdef __GNUC__ // GCC or Clang
54 | #define DONE triggered()
55 | #warning "DONE is deprecated and will be removed in a future release. Use TRIGGERED instead."
56 | #define TIME_PASSED getTimeSinceLastReset()
57 | #warning "WARNING: TIME_PASSED is deprecated and will be removed in a future release. Use ELAPSED instead."
58 | #define TIME_SINCE_RESET getTimeSinceLastReset()
59 | #warning "WARNING: TIME_SINCE_RESET is deprecated and will be removed in a future release. Use ELAPSED instead."
60 | #define TIME_TILL_TRIGGER getTimeUntilTrigger()
61 | #warning "WARNING: TIME_TILL_TRIGGER is deprecated and will be removed in a future release. Use REMAINING instead."
62 | #define TRIGGERED_ON_MARK triggeredOnDuration()
63 | #warning "WARNING: TRIGGERED_ON_MARK is deprecated and will be removed in a future release. Use TRIGGERED_ON_DURATION instead."
64 | #define NOT_DONE notTriggered()
65 | #warning "WARNING: NOT_DONE is deprecated and will be removed in a future release. Use NOT_TRIGGERED instead."
66 | #define ISSTARTED isRunning()
67 | #warning "WARNING: ISSTARTED is deprecated and will be removed in a future release. Use ISRUNNING instead."
68 | #define TRIGGERED_ON_DURATION_ALL triggeredOnDuration(ALL)
69 | #warning "WARNING: TRIGGERED_ON_DURATION_ALL is deprecated and will be removed in a future release. Use TRIGGERED_ON_DURATION(ALL) instead."
70 | #define TRIGGERED_ALL triggeredOnDuration(ALL)
71 | #warning "WARNING: TRIGGERED_ALL is deprecated and will be removed in a future release. Use TRIGGERED_ON_DURATION(ALL) instead."
72 | #define START_RESET start(WITH_RESET)
73 | #warning "WARNING: START_RESET is deprecated and will be removed in a future release. Use START(WITH_RESET) instead."
74 | #elif defined(_MSC_VER) // Microsoft Visual Studio
75 | #define DONE (__pragma(message("WARNING: DONE is deprecated and will be removed in a future release. Use TRIGGERED instead.")), triggered())
76 | #define TIME_PASSED (__pragma(message("WARNING: TIME_PASSED is deprecated and will be removed in a future release. Use ELAPSED instead.")), getTimeSinceLastReset())
77 | #define TIME_SINCE_RESET (__pragma(message("WARNING: TIME_SINCE_RESET is deprecated and will be removed in a future release. Use ELAPSED instead.")), getTimeSinceLastReset())
78 | #define TIME_TILL_TRIGGER (__pragma(message("WARNING: TIME_TILL_TRIGGER is deprecated and will be removed in a future release. Use REMAINING instead.")), getTimeUntilTrigger())
79 | #define TRIGGERED_ON_MARK (__pragma(message("WARNING: TRIGGERED_ON_MARK is deprecated and will be removed in a future release. Use TRIGGERED_ON_DURATION instead.")), triggeredOnDuration())
80 | #define TRIGGERED_ON_DURATION_ALL (__pragma(message("WARNING: TRIGGERED_ON_DURATION_ALL is deprecated and will be removed in a future release. Use TRIGGERED_ON_DURATION(ALL) instead.")), triggeredOnDuration(ALL))
81 | #define TRIGGERED_ALL (__pragma(message("WARNING: TRIGGERED_ALL is deprecated and will be removed in a future release. Use TRIGGERED_ON_DURATION(ALL) instead.")), triggeredOnDuration(ALL))
82 | #define NOT_DONE (__pragma(message("WARNING: NOT_DONE is deprecated and will be removed in a future release. Use NOT_TRIGGERED instead.")), notTriggered())
83 | #define START_RESET (__pragma(message("WARNING: START_RESET is deprecated and will be removed in a future release. Use START(WITH_RESET) instead.")), start(WITH_RESET))
84 | #define ISSTARTED (__pragma(message("WARNING: ISSTARTED is deprecated and will be removed in a future release. Use ISRUNNING instead.")), isRunning())
85 | #else
86 | #define DONE triggered()
87 | #define TIME_PASSED getTimeSinceLastReset()
88 | #define TIME_SINCE_RESET getTimeSinceLastReset()
89 | #define TIME_TILL_TRIGGER getTimeUntilTrigger()
90 | #define TRIGGERED_ON_MARK triggeredOnDuration()
91 | #define NOT_DONE notTriggered()
92 | #define ISSTARTED isRunning()
93 | #define TRIGGERED_ON_DURATION_ALL triggeredOnDuration(ALL)
94 | #define TRIGGERED_ALL triggeredOnDuration(ALL)
95 | #define START_RESET start(WITH_RESET)
96 | #endif
97 |
98 | #define TRIGGERED triggered()
99 | #define LAST_TRIGGER_DURATION lastTriggerDuration()
100 | #define HAS_TRIGGERED triggered(NO_RESET)
101 | #define TRIGGER_NEXT triggerNext()
102 | #define TRIGGERED_ON_DURATION(...) triggeredOnDuration(__VA_ARGS__)
103 | #define NOT_TRIGGERED notTriggered()
104 | #define FIRST_TRIGGER firstTrigger()
105 | #define RESET reset()
106 | #define RESET_TIMERS resetAllTimers()
107 | #define START(...) start(__VA_ARGS__)
108 | #define STOP stop()
109 | #define ISRUNNING isRunning()
110 | #define ISSTOPPED isStopped()
111 | #define TOGGLE toggle()
112 |
113 | class BlockNot {
114 | #define TIME_PASSED getTimeSinceLastReset()
115 |
116 | public:
117 | /**
118 | * Constructors
119 | */
120 | BlockNot();
121 |
122 | explicit BlockNot(unsigned long milliseconds);
123 |
124 | BlockNot(unsigned long milliseconds, BlockNotState state);
125 |
126 | BlockNot(unsigned long time, BlockNotUnit units);
127 |
128 | BlockNot(unsigned long time, BlockNotUnit units, BlockNotState state);
129 |
130 | BlockNot(unsigned long milliseconds, BlockNotGlobal globalReset);
131 |
132 | BlockNot(unsigned long milliseconds, BlockNotState state, BlockNotGlobal globalReset);
133 |
134 | BlockNot(unsigned long time, BlockNotUnit units, BlockNotGlobal globalReset);
135 |
136 | BlockNot(unsigned long time, BlockNotUnit units, BlockNotState state, BlockNotGlobal globalReset);
137 |
138 | BlockNot(unsigned long milliseconds, unsigned long stoppedReturnValue);
139 |
140 | BlockNot(unsigned long milliseconds, unsigned long stoppedReturnValue, BlockNotState state);
141 |
142 | BlockNot(unsigned long time, unsigned long stoppedReturnValue, BlockNotUnit units);
143 |
144 | BlockNot(unsigned long time, unsigned long stoppedReturnValue, BlockNotUnit units, BlockNotState state);
145 |
146 | BlockNot(unsigned long milliseconds, unsigned long stoppedReturnValue, BlockNotGlobal globalReset);
147 |
148 | BlockNot(unsigned long milliseconds, unsigned long stoppedReturnValue, BlockNotGlobal globalReset,
149 | BlockNotState state);
150 |
151 | BlockNot(unsigned long time, unsigned long stoppedReturnValue, BlockNotUnit units, BlockNotGlobal globalReset);
152 |
153 | BlockNot(unsigned long time, unsigned long stoppedReturnValue, BlockNotUnit units, BlockNotGlobal globalReset,
154 | BlockNotState state);
155 |
156 | /**
157 | * Public Methods
158 | */
159 |
160 | void setDuration(unsigned long time, bool resetOption = WITH_RESET);
161 |
162 | void setDuration(unsigned long time, BlockNotUnit units, bool resetOption = WITH_RESET);
163 |
164 | void addTime(unsigned long time, bool resetOption = NO_RESET);
165 |
166 | void takeTime(unsigned long time, bool resetOption = NO_RESET);
167 |
168 | bool triggered(bool resetOption = true);
169 |
170 | bool triggeredOnDuration(bool allMissed = false);
171 |
172 | bool notTriggered();
173 |
174 | bool firstTrigger();
175 |
176 | void triggerNext();
177 |
178 | void setFirstTriggerResponse(bool response);
179 |
180 | unsigned long getNextTriggerTime() const;
181 |
182 | unsigned long getTimeUntilTrigger() const;
183 |
184 | unsigned long getStartTime() const;
185 |
186 | unsigned long getStartTime(BlockNotUnit units) const;
187 |
188 | unsigned long getDuration() const;
189 |
190 | unsigned long lastTriggerDuration() const;
191 |
192 | String getUnits() const;
193 |
194 | unsigned long getTimeSinceLastReset() const;
195 |
196 | void setStoppedReturnValue(unsigned long stoppedReturnValue);
197 |
198 | void start(bool resetOption = NO_RESET);
199 |
200 | void stop();
201 |
202 | bool isRunning() const;
203 |
204 | bool isStopped() const;
205 |
206 | void toggle();
207 |
208 | unsigned long convert(unsigned long value, BlockNotUnit units) const;
209 |
210 | void switchTo(BlockNotUnit units);
211 |
212 | void reset(unsigned long newStartTime = 0);
213 |
214 | void setMillisOffset(unsigned long offset = 0);
215 |
216 | void setMicrosOffset(unsigned long offset = 0);
217 |
218 | void speedComp(unsigned long time);
219 |
220 | void disableSpeedComp();
221 |
222 | unsigned long getMillis() const;
223 |
224 | BlockNotUnit getBaseUnits() const;
225 |
226 | static void getHelp(Print &output, bool haltCode = false);
227 |
228 | static void getHelp(bool haltCode = false);
229 |
230 | class cTime {
231 | public:
232 | double seconds = 0.0; // Central storage for time in seconds
233 |
234 | // Class for milliseconds
235 | class milli_t {
236 | double &seconds;
237 |
238 | public:
239 | milli_t(double &s) : seconds(s) {
240 | }
241 |
242 | milli_t &operator=(double ms) {
243 | seconds = ms * 0.001; // Convert milliseconds to seconds
244 | return *this;
245 | }
246 |
247 | operator double() const {
248 | return seconds * 1000.0; // Convert seconds to milliseconds
249 | }
250 | };
251 |
252 | // Class for microseconds
253 | class micro_t {
254 | double &seconds;
255 |
256 | public:
257 | micro_t(double &s) : seconds(s) {
258 | }
259 |
260 | micro_t &operator=(double us) {
261 | seconds = us * 0.000001; // Convert microseconds to seconds
262 | return *this;
263 | }
264 |
265 | operator double() const {
266 | return seconds * 1000000.0; // Convert seconds to microseconds
267 | }
268 | };
269 |
270 | // Class for minutes
271 | class minutes_t {
272 | double &seconds;
273 |
274 | public:
275 | minutes_t(double &s) : seconds(s) {
276 | }
277 |
278 | minutes_t &operator=(double mins) {
279 | seconds = mins * 60.0; // Convert minutes to seconds
280 | return *this;
281 | }
282 |
283 | operator double() const {
284 | return seconds / 60.0; // Convert seconds to minutes
285 | }
286 | };
287 |
288 | // Accessors for helper classes
289 |
290 | milli_t millis;
291 | micro_t micros;
292 | minutes_t minutes;
293 |
294 | // Constructor
295 | cTime() : millis(seconds), micros(seconds), minutes(seconds) {
296 | }
297 |
298 | // Getter for seconds
299 | double getSeconds() const { return seconds; }
300 |
301 | // Setter for seconds
302 | void setSeconds(double s) { seconds = s; }
303 | };
304 |
305 | static BlockNot *firstTimer;
306 | static BlockNot *currentTimer;
307 | BlockNot *nextTimer;
308 |
309 | private:
310 | /**
311 | * Private Variables and Methods
312 | */
313 | unsigned long startTime;
314 | unsigned long millisOffset;
315 | unsigned long microsOffset;
316 | unsigned long timerStoppedReturnValue;
317 | unsigned long lastDuration;
318 | int totalMissedDurations;
319 | bool onceTriggered;
320 | bool triggerOnNext;
321 | bool firstTriggerResponse;
322 | bool speedCompensation;
323 | unsigned long compTime;
324 | unsigned long newStartTimeMillis;
325 | unsigned long newStartTimeMicros;
326 |
327 | static BlockNotGlobal global;
328 | BlockNotUnit baseUnits;
329 | cTime duration;
330 | cTime stopTime;
331 | BlockNotState timerState;
332 |
333 | void resetTimer(unsigned long newStartTime);
334 |
335 | void initDuration(unsigned long time);
336 |
337 | void initDuration(unsigned long time, BlockNotUnit desiredUnits);
338 |
339 | unsigned long timeSinceReset() const;
340 |
341 | bool hasTriggered();
342 |
343 | bool hasNotTriggered() const;
344 |
345 | void addToTimerList();
346 |
347 | unsigned long timeTillTrigger() const;
348 |
349 | unsigned long remaining() const;
350 |
351 | unsigned long getDurationTriggerStartTime() const;
352 |
353 | unsigned long convertUnits(const cTime &timeValue) const;
354 | };
355 |
356 | /**
357 | * Global methods affecting all instances of the BlockNot class.
358 | */
359 | void resetAllTimers(unsigned long newStartTime = 0);
360 |
361 | #endif
362 |
--------------------------------------------------------------------------------
/src/BlockNot.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * BlockNot is a simple and easy to use Arduino class for the implementation
3 | * of non-blocking timers, as it is far better to use non-blocking timers
4 | * with a micro-controller since they allow you to trigger code at defined
5 | * durations of time, without stopping the execution of your main loop.
6 | *
7 | * Written by - Michael Sims
8 | * Full documentation can be found at: https://github.com/EasyG0ing1/BlockNot
9 | *
10 | * See LICENSE file for acceptable use conditions - this is open source
11 | * and there are no restrictions on its usage, I simply ask for some acknowledgment
12 | * if it is used in your project.
13 | */
14 |
15 | #include
16 |
17 | /**
18 | * Global Variables
19 | */
20 |
21 | BlockNot *BlockNot::firstTimer = nullptr;
22 | BlockNot *BlockNot::currentTimer = nullptr;
23 | BlockNotGlobal BlockNot::global = GLOBAL_RESET;
24 |
25 | /**
26 | * Constructors
27 | */
28 |
29 | BlockNot::BlockNot() {
30 | baseUnits = MILLISECONDS;
31 | timerState = RUNNING;
32 | global = (global == NO_GLOBAL_RESET) ? NO_GLOBAL_RESET : GLOBAL_RESET;
33 | if (global == GLOBAL_RESET) addToTimerList();
34 | }
35 |
36 | BlockNot::BlockNot(const unsigned long milliseconds) {
37 | baseUnits = MILLISECONDS;
38 | timerState = RUNNING;
39 | global = (global == NO_GLOBAL_RESET) ? NO_GLOBAL_RESET : GLOBAL_RESET;
40 | initDuration(milliseconds);
41 | reset();
42 | if (global == GLOBAL_RESET) addToTimerList();
43 | }
44 |
45 | BlockNot::BlockNot(const unsigned long milliseconds, const BlockNotState state) {
46 | baseUnits = MILLISECONDS;
47 | timerState = state;
48 | global = (global == NO_GLOBAL_RESET) ? NO_GLOBAL_RESET : GLOBAL_RESET;
49 | if(timerState == STOPPED)
50 | stop();
51 | initDuration(milliseconds);
52 | reset();
53 | if (global == GLOBAL_RESET) addToTimerList();
54 | }
55 |
56 | BlockNot::BlockNot(const unsigned long time, const BlockNotUnit units = MILLISECONDS) {
57 | baseUnits = units;
58 | timerState = RUNNING;
59 | global = (global == NO_GLOBAL_RESET) ? NO_GLOBAL_RESET : GLOBAL_RESET;
60 | initDuration(time);
61 | reset();
62 | if (global == GLOBAL_RESET) addToTimerList();
63 | }
64 |
65 | BlockNot::BlockNot(const unsigned long time, const BlockNotUnit units, const BlockNotState state) {
66 | baseUnits = units;
67 | timerState = state;
68 | global = (global == NO_GLOBAL_RESET) ? NO_GLOBAL_RESET : GLOBAL_RESET;
69 | if(timerState == STOPPED)
70 | stop();
71 | initDuration(time);
72 | reset();
73 | if (global == GLOBAL_RESET) addToTimerList();
74 | }
75 |
76 | BlockNot::BlockNot(const unsigned long milliseconds, const BlockNotGlobal globalReset) {
77 | baseUnits = MILLISECONDS;
78 | timerState = RUNNING;
79 | initDuration(milliseconds);
80 | reset();
81 | global = globalReset;
82 | if (global == GLOBAL_RESET) addToTimerList();
83 | }
84 |
85 | BlockNot::BlockNot(const unsigned long milliseconds, const BlockNotState state, const BlockNotGlobal globalReset) {
86 | baseUnits = MILLISECONDS;
87 | timerState = state;
88 | if(timerState == STOPPED)
89 | stop();
90 | initDuration(milliseconds);
91 | reset();
92 | global = globalReset;
93 | if (global == GLOBAL_RESET) addToTimerList();
94 | }
95 |
96 | BlockNot::BlockNot(const unsigned long time, const BlockNotUnit units, const BlockNotGlobal globalReset) {
97 | baseUnits = units;
98 | timerState = RUNNING;
99 | initDuration(time);
100 | reset();
101 | global = globalReset;
102 | if (global == GLOBAL_RESET) addToTimerList();
103 | }
104 |
105 | BlockNot::BlockNot(const unsigned long time, const BlockNotUnit units, const BlockNotState state, const BlockNotGlobal globalReset) {
106 | baseUnits = units;
107 | timerState = state;
108 | if(timerState == STOPPED)
109 | stop();
110 | initDuration(time);
111 | reset();
112 | global = globalReset;
113 | if (global == GLOBAL_RESET) addToTimerList();
114 | }
115 |
116 | BlockNot::BlockNot(const unsigned long milliseconds, const unsigned long stoppedReturnValue) {
117 | baseUnits = MILLISECONDS;
118 | timerState = RUNNING;
119 | global = (global == NO_GLOBAL_RESET) ? NO_GLOBAL_RESET : GLOBAL_RESET;
120 | initDuration(milliseconds);
121 | timerStoppedReturnValue = stoppedReturnValue;
122 | reset();
123 | if (global == GLOBAL_RESET) addToTimerList();
124 | }
125 |
126 | BlockNot::BlockNot(const unsigned long milliseconds, const unsigned long stoppedReturnValue, const BlockNotState state) {
127 | baseUnits = MILLISECONDS;
128 | timerState = state;
129 | if(timerState == STOPPED)
130 | stop();
131 | global = (global == NO_GLOBAL_RESET) ? NO_GLOBAL_RESET : GLOBAL_RESET;
132 | initDuration(milliseconds);
133 | timerStoppedReturnValue = stoppedReturnValue;
134 | reset();
135 | if (global == GLOBAL_RESET) addToTimerList();
136 |
137 | }
138 |
139 | BlockNot::BlockNot(const unsigned long time, const unsigned long stoppedReturnValue, const BlockNotUnit units) {
140 | baseUnits = units;
141 | timerState = RUNNING;
142 | global = (global == NO_GLOBAL_RESET) ? NO_GLOBAL_RESET : GLOBAL_RESET;
143 | initDuration(time);
144 | timerStoppedReturnValue = stoppedReturnValue;
145 | reset();
146 | if (global == GLOBAL_RESET) addToTimerList();
147 | }
148 |
149 | BlockNot::BlockNot(const unsigned long time, const unsigned long stoppedReturnValue, const BlockNotUnit units, const BlockNotState state) {
150 | baseUnits = units;
151 | timerState = state;
152 | global = (global == NO_GLOBAL_RESET) ? NO_GLOBAL_RESET : GLOBAL_RESET;
153 | if(timerState == STOPPED)
154 | stop();
155 | initDuration(time);
156 | timerStoppedReturnValue = stoppedReturnValue;
157 | reset();
158 | if (global == GLOBAL_RESET) addToTimerList();
159 | }
160 |
161 | BlockNot::BlockNot(const unsigned long milliseconds, const unsigned long stoppedReturnValue, const BlockNotGlobal globalReset) {
162 | baseUnits = MILLISECONDS;
163 | timerState = RUNNING;
164 | initDuration(milliseconds);
165 | timerStoppedReturnValue = stoppedReturnValue;
166 | reset();
167 | global = globalReset;
168 | if (global == GLOBAL_RESET) addToTimerList();
169 | }
170 |
171 | BlockNot::BlockNot(const unsigned long milliseconds, const unsigned long stoppedReturnValue, const BlockNotGlobal globalReset, const BlockNotState state) {
172 | baseUnits = MILLISECONDS;
173 | timerState = state;
174 | if(timerState == STOPPED)
175 | stop();
176 | initDuration(milliseconds);
177 | timerStoppedReturnValue = stoppedReturnValue;
178 | reset();
179 | global = globalReset;
180 | if (global == GLOBAL_RESET) addToTimerList();
181 | }
182 |
183 | BlockNot::BlockNot(const unsigned long time, const unsigned long stoppedReturnValue, const BlockNotUnit units, const BlockNotGlobal globalReset) {
184 | baseUnits = units;
185 | timerState = RUNNING;
186 | initDuration(time);
187 | timerStoppedReturnValue = stoppedReturnValue;
188 | reset();
189 | global = globalReset;
190 | if (global == GLOBAL_RESET) addToTimerList();
191 | }
192 |
193 | BlockNot::BlockNot(const unsigned long time, const unsigned long stoppedReturnValue, const BlockNotUnit units, const BlockNotGlobal globalReset, const BlockNotState state) {
194 | baseUnits = units;
195 | timerState = state;
196 | if(timerState == STOPPED)
197 | stop();
198 | initDuration(time);
199 | timerStoppedReturnValue = stoppedReturnValue;
200 | reset();
201 | global = globalReset;
202 | if (global == GLOBAL_RESET) addToTimerList();
203 | }
204 |
205 | /**
206 | * Public Methods
207 | */
208 |
209 | void BlockNot::setDuration(const unsigned long time, const bool resetOption) {
210 | initDuration(time);
211 | if (resetOption) reset();
212 | }
213 |
214 | void BlockNot::setDuration(const unsigned long time, BlockNotUnit inUnits, const bool resetOption) {
215 | initDuration(time, inUnits);
216 | if (resetOption) reset();
217 | }
218 |
219 | void BlockNot::addTime(const unsigned long time, const bool resetOption) {
220 | unsigned long newDuration;
221 | switch(baseUnits) {
222 | case MICROSECONDS: {
223 | newDuration = duration.micros + time;
224 | if (newDuration < duration.micros) newDuration = 0xFFFFFFFFL;
225 | duration.micros = newDuration;
226 | break;
227 | }
228 | default: {
229 | newDuration = duration.millis + time;
230 | if (newDuration < duration.millis) newDuration = 0xFFFFFFFFL;
231 | duration.millis = newDuration;
232 | break;
233 | }
234 | }
235 | if (resetOption) reset();
236 | }
237 |
238 | void BlockNot::takeTime(const unsigned long time, const bool resetOption) {
239 | long newDuration;
240 | switch(baseUnits) {
241 | case MICROSECONDS: {
242 | newDuration = duration.micros - time;
243 | if (newDuration > duration.micros) newDuration = 0L;
244 | duration.micros = newDuration;
245 | break;
246 | }
247 | default: {
248 | newDuration = duration.millis - time;
249 | if (newDuration > duration.millis) newDuration = 0L;
250 | duration.millis = newDuration;
251 | break;
252 | }
253 | }
254 | if (resetOption) reset();
255 | }
256 |
257 | bool BlockNot::triggered(const bool resetOption) {
258 | const bool triggered = hasTriggered();
259 | if (resetOption && triggered) {
260 | reset();
261 | }
262 | return timerState == RUNNING && triggered;
263 | }
264 |
265 | bool BlockNot::triggeredOnDuration(const bool allMissed) {
266 | const bool triggered = hasTriggered();
267 | if (triggered) {
268 | unsigned long missedDurations;
269 | switch(baseUnits) {
270 | case MICROSECONDS: {
271 | missedDurations = timeSinceReset() / static_cast(duration.micros);
272 | break;
273 | }
274 | default: {
275 | missedDurations = timeSinceReset() / static_cast(duration.millis);
276 | break;
277 | }
278 | }
279 | totalMissedDurations += (allMissed ? missedDurations : 0);
280 | const unsigned long newStartTime = getDurationTriggerStartTime();
281 | reset(newStartTime);
282 | }
283 | if (totalMissedDurations > 0 && allMissed) {
284 | totalMissedDurations--;
285 | return true;
286 | }
287 | return triggered;
288 | }
289 |
290 | bool BlockNot::notTriggered() {
291 | return timerState == RUNNING && !hasTriggered();
292 | }
293 |
294 | bool BlockNot::firstTrigger() {
295 | if(onceTriggered) {
296 | return firstTriggerResponse;
297 | }
298 | if (hasTriggered()) {
299 | onceTriggered = true;
300 | return timerState == RUNNING;
301 | }
302 | return false;
303 | }
304 |
305 | void BlockNot::triggerNext() {
306 | triggerOnNext = true;
307 | }
308 |
309 | void BlockNot::setFirstTriggerResponse(const bool response) {
310 | firstTriggerResponse = response;
311 | }
312 |
313 | unsigned long BlockNot::getNextTriggerTime() const {
314 | cTime nextTrigger;
315 | if (triggerOnNext) {
316 | nextTrigger.micros = micros();
317 | nextTrigger.millis = millis();
318 | }
319 | else {
320 | switch(baseUnits) {
321 | case MICROSECONDS: {
322 | nextTrigger.micros = startTime + duration.micros;
323 | break;
324 | }
325 | default: {
326 | nextTrigger.millis = startTime + duration.millis;
327 | break;
328 | }
329 | }
330 | }
331 | return convertUnits(nextTrigger);
332 | }
333 |
334 | unsigned long BlockNot::getTimeUntilTrigger() const {
335 | return timeTillTrigger();
336 | }
337 |
338 | unsigned long BlockNot::getStartTime() const {
339 | cTime sTime;
340 | switch(baseUnits) {
341 | case MICROSECONDS: {
342 | sTime.micros = startTime;
343 | break;
344 | }
345 | default: {
346 | sTime.millis = startTime;
347 | break;
348 | }
349 | }
350 | return convertUnits(sTime);
351 | }
352 |
353 | unsigned long BlockNot::getStartTime(const BlockNotUnit units) const {
354 | cTime timeValue;
355 | timeValue.micros = startTime;
356 | switch(units) {
357 | case MINUTES:
358 | return timeValue.minutes;
359 | case SECONDS:
360 | return timeValue.seconds;
361 | case MILLISECONDS:
362 | return timeValue.millis;
363 | case MICROSECONDS:
364 | return timeValue.micros;
365 | }
366 | return 0L;
367 | }
368 |
369 | unsigned long BlockNot::getDuration() const {
370 | return timerState == RUNNING ? convertUnits(duration) : timerStoppedReturnValue;
371 | }
372 |
373 | unsigned long BlockNot::lastTriggerDuration() const {
374 | return lastDuration;
375 | }
376 |
377 | String BlockNot::getUnits() const {
378 | return (baseUnits == SECONDS) ? "Seconds" : (baseUnits == MILLISECONDS) ? "Milliseconds" : "Microseconds";
379 | }
380 |
381 | unsigned long BlockNot::getTimeSinceLastReset() const {
382 | cTime timeLapsed;
383 | switch(baseUnits) {
384 | case MICROSECONDS: {
385 | timeLapsed.micros = timeSinceReset();
386 | break;
387 | }
388 | default: {
389 | timeLapsed.millis = timeSinceReset();
390 | break;
391 | }
392 | }
393 | return (timerState == RUNNING) ? convertUnits(timeLapsed) : timerStoppedReturnValue;
394 | }
395 |
396 | void BlockNot::setStoppedReturnValue(const unsigned long stoppedReturnValue) {
397 | timerStoppedReturnValue = stoppedReturnValue;
398 | }
399 |
400 | void BlockNot::start(const bool resetOption) {
401 | if(resetOption)
402 | reset();
403 | else {
404 | switch(baseUnits) {
405 | case MICROSECONDS: {
406 | startTime = micros() - stopTime.micros;
407 | break;
408 | }
409 | default: {
410 | startTime = millis() - stopTime.millis;
411 | break;
412 | }
413 | }
414 | }
415 | timerState = RUNNING;
416 | }
417 |
418 | void BlockNot::stop() {
419 | timerState = STOPPED;
420 | switch(baseUnits) {
421 | case MICROSECONDS: {
422 | stopTime.micros = micros();
423 | break;
424 | }
425 | default: {
426 | stopTime.millis = millis();
427 | break;
428 | }
429 | }
430 | }
431 |
432 | bool BlockNot::isRunning() const {return timerState == RUNNING;}
433 |
434 | bool BlockNot::isStopped() const {return timerState == STOPPED;}
435 |
436 | void BlockNot::toggle() {
437 | if(timerState == RUNNING)
438 | timerState = STOPPED;
439 | else
440 | timerState = RUNNING;
441 | }
442 |
443 | unsigned long BlockNot::convert(const unsigned long value, const BlockNotUnit units) const {
444 | cTime timeValue;
445 | unsigned long result = 0L;
446 | switch(baseUnits) {
447 | case MINUTES: {
448 | timeValue.minutes = value;
449 | break;
450 | }
451 | case SECONDS: {
452 | timeValue.seconds = value;
453 | break;
454 | }
455 | case MILLISECONDS: {
456 | timeValue.millis = value;
457 | break;
458 | }
459 | case MICROSECONDS: {
460 | timeValue.micros = value;
461 | break;
462 | }
463 | }
464 | switch(units) {
465 | case MINUTES: {
466 | result= timeValue.minutes;
467 | break;
468 | }
469 | case SECONDS: {
470 | result = timeValue.seconds;
471 | break;
472 | }
473 | case MILLISECONDS: {
474 | result = timeValue.millis;
475 | break;
476 | }
477 | case MICROSECONDS: {
478 | result = timeValue.micros;
479 | break;
480 | }
481 | }
482 | return result;
483 | }
484 |
485 | void BlockNot::switchTo(const BlockNotUnit units) { baseUnits = units; }
486 |
487 | void BlockNot::reset(const unsigned long newStartTime) {
488 | unsigned long finalStartTime = newStartTime;
489 | if(finalStartTime == 0) {
490 | switch(baseUnits) {
491 | case MICROSECONDS: {
492 | finalStartTime = micros() + microsOffset;
493 | break;
494 | }
495 | default: {
496 | finalStartTime = millis() + millisOffset;
497 | if (speedCompensation)
498 | delay(compTime);
499 | break;
500 | }
501 | }
502 | }
503 | resetTimer(finalStartTime);
504 | }
505 |
506 | void BlockNot::setMillisOffset(const unsigned long offset) {
507 | long delta = offset - millisOffset;
508 | startTime = startTime + delta;
509 | millisOffset = offset;
510 | }
511 |
512 | void BlockNot::setMicrosOffset(const unsigned long offset) {
513 | microsOffset = offset;
514 | }
515 |
516 | void BlockNot::speedComp(const unsigned long time) {
517 | speedCompensation = true;
518 | compTime = time;
519 | }
520 |
521 | void BlockNot::disableSpeedComp() {
522 | speedCompensation = false;
523 | }
524 |
525 | unsigned long BlockNot::getMillis() const {
526 | return millis() + millisOffset;
527 | }
528 |
529 | BlockNotUnit BlockNot::getBaseUnits() const {
530 | return baseUnits;
531 | }
532 |
533 | void BlockNot::getHelp(Print &output, const bool haltCode) {
534 | output.println("\n\nThe following macros can be used for coding simplicity and to produce more readable code:\n");
535 | output.println("Macro\t\t\t\tMethod Called");
536 | output.println("-------------------------------------------------------");
537 | output.println("ELAPSED\t\t\t\tgetTimeSinceLastReset()");
538 | output.println("REMAINING\t\t\tgetTimeUntilTrigger()");
539 | output.println("DURATION\t\t\tgetDuration()");
540 | output.println("GET_UNITS\t\t\tgetUnits()");
541 | output.println("GET_START_TIME\t\t\tgetStartTime()");
542 | output.println("TRIGGERED\t\t\ttriggered()");
543 | output.println("LAST_TRIGGER_DURATION\t\tlastTriggerDuration()");
544 | output.println("HAS_TRIGGERED\t\t\ttriggered(NO_RESET)");
545 | output.println("TRIGGER_NEXT\t\t\ttriggerNext()");
546 | output.println("TRIGGERED_ON_DURATION\t\ttriggeredOnDuration");
547 | output.println("TRIGGERED_ON_DURATION(ALL)\ttriggeredOnDuration(ALL)");
548 | output.println("NOT_TRIGGERED\t\t\tnotTriggered()");
549 | output.println("FIRST_TRIGGER\t\t\tfirstTrigger()");
550 | output.println("RESET\t\t\t\treset()");
551 | output.println("RESET_TIMERS\t\t\tresetAllTimers()");
552 | output.println("START\t\t\t\tstart()");
553 | output.println("START(WITH_RESET)\t\tstart(WITH_RESET)");
554 | output.println("STOP\t\t\t\tstop()");
555 | output.println("ISRUNNING\t\t\tisRunning()");
556 | output.println("ISSTOPPED\t\t\tisStopped()");
557 | output.println("TOGGLE\t\t\t\ttoggle()");
558 | output.println("\nYou use macros like you would a method call only no neeed for passing arguments unless the macro");
559 | output.println("explicitely supports it:\n");
560 | output.println("if (myTimer.TRIGGERED) {");
561 | output.println("\t//My Code");
562 | output.println("}");
563 | output.println(" ");
564 | output.println(" ");
565 | if (haltCode) {
566 | while(true){}
567 | }
568 | }
569 |
570 | void BlockNot::getHelp(const bool haltCode) {
571 | getHelp(Serial, haltCode);
572 | }
573 |
574 | /**
575 | * Private Methods
576 | */
577 |
578 | void BlockNot::initDuration(const unsigned long time) {
579 | switch(baseUnits) {
580 | case MINUTES: {
581 | duration.minutes = time;
582 | break;
583 | }
584 | case SECONDS: {
585 | duration.seconds = time;
586 | break;
587 | }
588 | case MILLISECONDS: {
589 | duration.millis = time;
590 | break;
591 | }
592 | case MICROSECONDS: {
593 | duration.micros = time;
594 | break;
595 | }
596 | }
597 | }
598 |
599 | void BlockNot::initDuration(const unsigned long time, const BlockNotUnit inUnits) {
600 | switch(inUnits) {
601 | case MICROSECONDS:
602 | duration.micros = time;
603 | break;
604 | case MILLISECONDS:
605 | duration.millis = time;
606 | break;
607 | case SECONDS:
608 | duration.seconds = time;
609 | break;
610 | case MINUTES: {
611 | duration.minutes = time;
612 | break;
613 | }
614 | }
615 | }
616 |
617 | void BlockNot::resetTimer(const unsigned long newStartTime) {
618 | startTime = newStartTime;
619 | triggerOnNext = false;
620 | onceTriggered = false;
621 | }
622 |
623 | unsigned long BlockNot::timeSinceReset() const {
624 | unsigned long result;
625 | unsigned long millisBase = millisOffset + millis();
626 | switch(baseUnits) {
627 | case MICROSECONDS: {
628 | result = microsOffset + micros() - startTime;
629 | break;
630 | }
631 | default: {
632 | result = millisBase - startTime;
633 | break;
634 | }
635 | }
636 | return result;
637 | }
638 |
639 | bool BlockNot::hasTriggered() {
640 | if (triggerOnNext) {
641 | triggerOnNext = false;
642 | return true;
643 | }
644 | bool triggered;
645 | const unsigned long sinceReset = timeSinceReset();
646 | switch(baseUnits) {
647 | case MICROSECONDS: {
648 | triggered = sinceReset >= static_cast(duration.micros);
649 | break;
650 | }
651 | default: {
652 | triggered = sinceReset >= static_cast(duration.millis);
653 | break;
654 | }
655 | }
656 | if(triggered)
657 | lastDuration = sinceReset;
658 | return triggered;
659 | }
660 |
661 | bool BlockNot::hasNotTriggered() const {
662 | bool notTriggered;
663 | switch(baseUnits) {
664 | case MICROSECONDS:
665 | notTriggered = timeSinceReset() < static_cast(duration.micros);
666 | break;
667 | default:
668 | notTriggered = timeSinceReset() < static_cast(duration.millis);
669 | break;
670 | }
671 | return notTriggered;
672 | }
673 |
674 | unsigned long BlockNot::timeTillTrigger() const {
675 | const unsigned long sinceReset = timeSinceReset();
676 | unsigned long tillTrigger = 0L;
677 | if (!triggerOnNext) {
678 | cTime triggerTime;
679 | switch(baseUnits) {
680 | case MICROSECONDS: {
681 | triggerTime.micros = (sinceReset < duration.micros) ? static_cast(duration.micros - sinceReset) : 0L;
682 | tillTrigger = (timerState == RUNNING) ? convertUnits(triggerTime) : timerStoppedReturnValue;
683 | break;
684 | }
685 | default: {
686 | triggerTime.millis = (sinceReset < duration.millis) ? static_cast(duration.millis - sinceReset) : 0L;
687 | tillTrigger = (timerState == RUNNING) ? convertUnits(triggerTime) : timerStoppedReturnValue;
688 | break;
689 | }
690 | }
691 | }
692 | return tillTrigger;
693 | }
694 |
695 | unsigned long BlockNot::remaining() const {
696 | const unsigned long timePassed = timeSinceReset();
697 | unsigned long remain = 0L;
698 | if (!triggerOnNext) {
699 | switch(baseUnits) {
700 | case MICROSECONDS: {
701 | remain = (timePassed < duration.micros) ? (static_cast(duration.micros) - timePassed) : 0;
702 | break;
703 | }
704 | default: {
705 | remain = (timePassed < duration.millis) ? (static_cast(duration.millis) - timePassed) : 0;
706 | break;
707 | }
708 | }
709 | }
710 | return remain;
711 | }
712 |
713 | unsigned long BlockNot::getDurationTriggerStartTime() const {
714 | unsigned long durationStartTime;
715 | switch(baseUnits) {
716 | case MICROSECONDS: {
717 | durationStartTime = startTime + ((timeSinceReset() / static_cast(duration.micros)) * static_cast(duration.micros));
718 | break;
719 | }
720 | default: {
721 | durationStartTime = startTime + ((timeSinceReset() / static_cast(duration.millis)) * static_cast(duration.millis));
722 | break;
723 | }
724 | }
725 | return durationStartTime;
726 | }
727 |
728 | unsigned long BlockNot::convertUnits(const cTime &timeValue) const {
729 | return baseUnits == MINUTES ? timeValue.minutes :
730 | baseUnits == SECONDS ? timeValue.seconds :
731 | baseUnits == MILLISECONDS ? timeValue.millis :
732 | timeValue.micros;
733 | }
734 |
735 | void BlockNot::addToTimerList() {
736 | if (firstTimer == nullptr) {
737 | firstTimer = currentTimer = this;
738 | } else {
739 | currentTimer->nextTimer = this;
740 | currentTimer = this;
741 | }
742 | this->nextTimer = nullptr;
743 | }
744 |
745 | /**
746 | * Global Methods affecting all instantiations of the BlockNot class
747 | */
748 |
749 | void resetAllTimers(const unsigned long newStartTime) {
750 | BlockNot *current = BlockNot::firstTimer;
751 | while (current != nullptr) {
752 | current->reset(newStartTime);
753 | current = current->nextTimer;
754 | }
755 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BlockNot Arduino Library
2 |
3 | This library enables you to create non-blocking timers using simple, common sense terms which simplifies the reading and
4 | writing of your code. It offers, among several things, convenient timer functionality, but most of all ... it gets you
5 | away from blocking methods - like delay() - as a means of managing events in your code.
6 |
7 | **Non-Blocking is the proper way to implement timing events in Arduino code and BlockNot makes it easy!**
8 |
9 | ### *** If you are updating to this new version (2.4.0), [READ THIS](#deprecated-code) so that your code doesn't stop working.
10 |
11 | ### *** If you are noticing unwanted rapid succession triggering on high speed microcontrollers, [READ THIS](#Triggering-Too-Fast-With-High-Speed-Microcontrollers)
12 |
13 | # Table of Contents
14 |
15 |
16 |
17 | * [Quick Start](#quick-start)
18 | * [Theory behind BlockNot](#theory-behind-blocknot)
19 | * [How To Use BlockNot](#how-to-use-blocknot)
20 | * [The Trigger](#the-trigger)
21 | * [One Time Trigger](#one-time-trigger)
22 | * [Trigger Next](#trigger-next)
23 | * [Last Trigger Duration](#last-trigger-duration)
24 | * [Triggered OnDuration](#triggered-onduration)
25 | * [Default Behavior](#default-behavior)
26 | * [OnDuration(ALL)](#ondurationall)
27 | * [The Reset](#the-reset)
28 | * [Global Reset](#global-reset)
29 | * [Time Unit Options](#time-unit-options)
30 | * [Default](#default)
31 | * [Other Units](#other-units)
32 | * [BlockNot Assumptions](#blocknot-assumptions)
33 | * [Microseconds](#microseconds)
34 | * [Converting Units](#converting-units)
35 | * [Changing Duration](#changing-duration)
36 | * [Switching Base Units](#switching-base-units)
37 | * [Start / Stop](#start--stop)
38 | * [Return Values on Stopped Timers](#return-values-on-stopped-timers)
39 | * [Summary](#summary)
40 | * [Examples](#examples)
41 | * [BlockNot Blink](#blocknot-blink)
42 | * [BlockNot Blink Party](#blocknot-blink-party)
43 | * [Millis() Rollover Test](#millis-rollover-test)
44 | * [Button Debounce](#button-debounce)
45 | * [Duration Trigger](#duration-trigger)
46 | * [On With Off Timers](#on-with-off-timers)
47 | * [Reset All](#reset-all)
48 | * [Timer's Rules](#timers-rules)
49 | * [Library](#library)
50 | * [Methods](#methods)
51 | * [Macros](#macros)
52 | * [Constants](#constants)
53 | * [Discussion](#discussion)
54 | * [Memory](#memory)
55 | * [Rollover](#rollover)
56 | * [Thread Safety](#thread-safety)
57 | * [Version Update Notes](#version-update-notes)
58 | * [Suggestions](#suggestions)
59 |
60 |
61 |
62 | # Quick Start
63 |
64 | Here is an example of BlockNot's easiest and most common usage:
65 |
66 | First, you crate the timer:
67 |
68 | ```C++
69 | #include
70 | BlockNot helloTimer(1300); //In Milliseconds
71 | ```
72 |
73 | **OR optionally**
74 |
75 | ```C++
76 | #include
77 | BlockNot helloTimer(15, SECONDS); //Whole Seconds timer
78 | BlockNot helloTimer(120000, MICROSECONDS); //Microseconds timer
79 | ```
80 |
81 | Then, you just test it to see if it triggered.
82 |
83 | ```C++
84 | if (helloTimer.TRIGGERED) {
85 | Serial.println("Hello World!");
86 | }
87 | ```
88 |
89 | Every time the TRIGGERED call returns true, the timer is reset and it wont trigger
90 | again until the duration time has elapsed (all behaviors can be changed based on your needs).
91 |
92 | That is all you need to start using BlockNot. Keep reading to learn about other features of the library.
93 |
94 | # Theory behind BlockNot
95 |
96 | This is a traditional non-blockling timer:
97 |
98 | ```C++
99 | long someDuration = 1300;
100 | long startTime = millis();
101 | if (millis() - startTime >= someDuration) {
102 | //Code to run after someDuration has passed.
103 | }
104 | ```
105 |
106 | This does the same thing, only with much simpler code!
107 |
108 | ```C++
109 | if (myTimer.TRIGGERED) {
110 | //Code to run after timer has triggered.
111 | }
112 | ```
113 |
114 | The idea behind BlockNot is very simple. You create the timer, setting its duration when you declare it, then check on
115 | the timer in your looping code to see if it TRIGGERED. Or, you can check for other information such as how long until it
116 | will trigger, or how much time has passed since it last triggered or you can ask it what the current duration is, which
117 | might be useful in scenarios where you change the duration based on dynamic criteria.
118 |
119 | For example, if you wanted to see if the timers duration has come to pass, but you don't want to reset the timer, you
120 | can use this method:
121 |
122 | ```C++
123 | if (myTimer.triggered(NO_RESET)) {}
124 | ```
125 |
126 | OR, you can do it like this:
127 |
128 | ```C++
129 | if (myTimer.HAS_TRIGGERED) {}
130 | ```
131 |
132 | They both do the same thing, but in terms of readability, the second example is the obvious choice. BlockNot has several
133 | easy to understand commands that make it very 'user-friendly' and make your code much more readable.
134 |
135 | Here is a simple graph showing you how BlockNot timers work. What's important here is to realize that your code never
136 | stops executing while the timer is passing time.
137 |
138 | 
139 |
140 | # How To Use BlockNot
141 |
142 | ## The Trigger
143 |
144 | BlockNot is all about the trigger event. When runners line up to start a race, it is a traditional practice
145 | for someone to stand next to the line and hold a gun in the air and pull the trigger when the race starts.
146 | That is the idea behind the TRIGGERED event in BlockNot. If your timer is set, for example, to 1300 milliseconds,
147 | it will return true when you call the TRIGGERED event on or after 1300 milliseconds have passed ... there are
148 | exceptions, however, as you will see which can be useful.
149 |
150 | ```C++
151 | if (voltageReadTimer.TRIGGERED) {
152 | readVoltage();
153 | }
154 | ```
155 |
156 | ### One Time Trigger
157 |
158 | I have personally found it quite handy in some scenarios, to be able to get a boolean true response after the
159 | timer has triggered, but only once, so that the code which executes after getting a true response only executes
160 | once and when the test comes up again in the loop, a response of false will be given until the timer has been
161 | manually reset. The false response can be changed to true if you desire, by running this method:
162 |
163 | ```C++
164 | myTimer.setFirstTriggerResponse(true);
165 | ```
166 |
167 | This kind of trigger is called FIRST_TRIGGER and you use it like this:
168 |
169 | ```C++
170 | if (myTimer.FIRST_TRIGGER) { my code }
171 | ```
172 |
173 | That method will return true ONLY ONE TIME after the timer's duration has passed, but subsequent calls to
174 | that method will return false **until you manually reset the timer** like this:
175 |
176 | ```C++
177 | myTimer.RESET;
178 | ```
179 |
180 | Why would you need to do that? There are countless scenarios where that would be immediately useful.
181 | I have used it with stepper motor projects where I want an idle stepper motor to be completely cut off
182 | from any voltage when it has been idle for a certain length of time ... lets say 25 seconds.
183 |
184 | So first, we define the timer
185 |
186 | ```C++
187 | BlockNot stepperSleepTimer (25, SECONDS);
188 | ```
189 |
190 | Then, we reset the timer every time we use the motor:
191 |
192 | ```C++
193 | stepperSleepTimer.RESET;
194 | ```
195 |
196 | Then, in your loop, you would put something like this:
197 |
198 | ```C++
199 | if (stepperSleepTimer.FIRST_TRIGGER) {
200 | sleepStepper();
201 | }
202 | ```
203 |
204 | So that if the stepper hasn't moved in the last 25 seconds, it will be put to sleep and that sleep routine
205 | won't execute over and over again each time the loop encounters the check. Yet when the stepper is engaged again,
206 | the sleep timer is reset and when it becomes idle again for 25 seconds, it is put to sleep. This helps efficiency
207 | in your program, and it conserves valuable CPU time.
208 |
209 | ### Trigger Next
210 |
211 | This allows you to signal the timer to trigger the next time it is checked regardless of whether or not the duration
212 | time has passed.
213 |
214 | I have found situations where I am using more than one timer to accomplish some purpose where I need a dependent timer
215 | to
216 | trigger the next time it is checked depending on the situation. For example: When using a WiFi module in a project, I
217 | often use a timer to periodically check to make sure WiFi is connected. If not, then I reset the module so that it makes
218 | a clean effort to re-connect to the access point. But lets say that I have another method that will do something like
219 | sync a local Real Time Clock with an Internet Network Time Protocol (NTP) server but I only want to run this method once
220 | the
221 | WiFi has connected. Lets also say that I prefer to re-run the time sync method every few days to mitigate time drift in
222 | the local RealTimeClock. So I have a timer called `resyncTimeTimer` but its duration is set to execute every three days.
223 |
224 | In that case, I want `resyncTimeTimer` to trigger the next time it is checked once the WiFi module has connected to the
225 | local access point, so my code would look something like this:
226 |
227 | ```c++
228 | #include
229 |
230 | enum UpDown {
231 | UP,
232 | DOWN
233 | }
234 |
235 | UpDown wifiState = DOWN;
236 |
237 | void getWifiState() {
238 | wifiState = WiFi.status() == WL_CONNECTED ? UP : DOWN;
239 | }
240 |
241 | void connectWifi() {
242 | BlockNot delay(25, SECONDS);
243 | Serial.println("Attempting to connect to Wifi...");
244 | WiFi.disconnect();
245 | WiFi.mode(WIFI_STA);
246 | WiFi.begin(ssid, password);
247 | getWifiState();
248 | // This while loop will wait for the WiFi to connect for 25 seconds
249 | // If the Wifi connects OR 25 seconds pass, the loop exits.
250 | while (wifiState == DOWN and !delay.TRIGGERED) {
251 | getWifiState();
252 | }
253 | }
254 |
255 | void loop() {
256 | static BlockNot checkWifiTimer(60, SECONDS);
257 | static BlockNot resyncTimeTimer(4320, MINUTES); // 72 hours (60 * 72)
258 |
259 | if (checkWifiTimer.TRIGGERED) {
260 | getWifiState();
261 | if (wifiState == DOWN) {
262 | connectWifi();
263 | resyncTimeTimer.TRIGGER_NEXT;
264 | }
265 | }
266 |
267 | if (resyncTimeTimer.TRIGGERED and wifiState == UP) {
268 | syncRTCTime();
269 | }
270 | }
271 | ```
272 |
273 | Using the TRIGGER_NEXT macro on the re-sync timer lets me use the code that the time holds in the situation where I need
274 | that code to run immediately instead of waiting for the timer to trigger. After it runs, the timer will not trigger
275 | again
276 | until its duration has passed (three days in this example).
277 |
278 | Code like this works especially well on a micro controller like the Pi Pico, where it can be run in a loop in a separate
279 | thread in the second core of the CPU, allowing your main loop to keep running even while this code is holding things up
280 | waiting for the wifi to connect.
281 |
282 | ### Last Trigger Duration
283 |
284 | There can be times when you are collecting information about events using an interrupt pin and it becomes necessary to
285 | know how much time passed in the last data gathering interval.
286 |
287 | ```cpp
288 | myTimer.LAST_TRIGGER_DURATION
289 | ```
290 |
291 | will give you the amount of time that passed when the last trigger was checked. Because it is possible to have checked
292 | for the trigger after the timers duration has passed and so you might need to know how much time actually did elapse and
293 | the `lastTiggerDuration()` method is how you get that information.
294 |
295 | ### Triggered OnDuration
296 |
297 | This topic is a little tricky to comprehend (myself included as I
298 | wrote this method), but I have done my best to explain it as simply as possible.
299 |
300 | There might be times when it becomes necessary to respect a timers trigger in the context of
301 | its **duration**, so that when a timers trigger is checked, it then resets its ```startTime``` to a time that is
302 | relative to its duration rather than simply resetting to the current value of ```micros()``` or ```millis()```.
303 |
304 | ```triggeredOnDuration()``` Does this with two optional results.
305 |
306 | ##### Default Behavior
307 |
308 | Let's use a timer that is set to a duration of 500ms as our example.
309 |
310 | You test it for TRIGGER by using either the macros or the method
311 |
312 | ```CPP
313 | myTimer.TRIGGERED_ON_DURATION
314 | myTimer.TRIGGERED_ON_MARK
315 | myTimer.triggeredOnDuration()
316 | ```
317 |
318 | BlockNot views the timer as having rigid trigger marks that happen exactly 500ms apart. So
319 | if you check the timer - say 700ms after you start it, you will get a TRUE response and
320 | it will trigger again in 300ms instead of 500ms because BlockNot will set the startTime
321 | back to 500ms and not the current time of 700.
322 |
323 | So lets say we are well into the passage of time, and you test for trigger at time index 2830 ...
324 | BlockNot will return ```TRUE``` and then set the startTime back to 2500 so it will trigger again at 3000.
325 |
326 | The idea is that you can have a timer that will provide the ability to run code on a consistent pulse where each pulse
327 | happens exactly at every interval of time based on the timers duration, and you can have your code execute
328 | as close to those 'pulse marks' as possible.
329 |
330 | #### OnDuration(ALL)
331 |
332 | ```triggeredOnDuration(ALL)``` works exactly as ```triggeredOnDuration()``` EXCEPT that BlockNot
333 | will continue to return a ```TRUE``` result until every missed mark is accounted for.
334 |
335 | You can test for TRIGGERED using these macros or the method
336 |
337 | ```CPP
338 | myTimer.TRIGGERED_ON_DURATION_ALL
339 | myTimer.TRIGGERED_ALL
340 | myTimer.triggeredOnDuration(ALL)
341 | ```
342 |
343 | Continuing with our 500ms duration timer, if you test it at time index 1250 then again at
344 | time index 1300, you will get a TRUE response for both tests, since you missed the first
345 | mark at 500, but then tested after the second mark at 1000. If you tested again before
346 | 1500, you would get a ```FALSE``` response.
347 |
348 | These graphs show you what will happen as time advances and you test for TRIGGERED
349 | using this method.
350 |
351 | A green Test means you tested and got TRUE while red means you got FALSE.
352 |
353 | This is how the standard TRIGGERED event responds:
354 |
355 | 
356 |
357 | This visualises how ```TRIGGERED_ON_DURATION``` / ```TRIGGERED_ON_MARK``` works.
358 |
359 | 
360 |
361 | Notice how you can get a TRUE response immediately before and immediately after a trigger
362 | event. BlockNot is giving you a TRUE response for the trigger that happened at time index
363 | 2500, then it gives a TRUE response for the trigger that happened at time index 3000. But
364 | when you test again before 3500, you will get a FALSE response.
365 |
366 | Here is how ```TRIGGERED_ON_DURATION_ALL``` / ```TRIGGERED_ALL``` works.
367 |
368 | 
369 |
370 | Notice you got FOUR consecutive TRUE results in a row. This is because you missed three triggers
371 | , then you tested twice, then at a trigger point, then once again, and the 5th time you tested,
372 | your test happened before the next trigger and all of the missed trigger events had been accounted
373 | for, so you get a FALSE response.
374 |
375 | A possible use case for the ALL trigger test would be when using a timer as a sync counter of sorts. And to explain
376 | that, I
377 | am going to use an extreme example that should illustrate the point... Lets say that you have a project that
378 | must automatically water plants at least six times every hour. It doesn't matter if the plants are watered every 10
379 | minutes, or if they are watered once, then 15 minutes later, then again 5 minutes later etc., as long as they are
380 | watered six times every hour.
381 |
382 | We would define this timer as follows:
383 |
384 | ```CPP
385 | BlockNot waterTimer = BlockNot(600, SECONDS); //10 minute duration
386 | ```
387 |
388 | Let's also assume that your code is so busy doing other things, that it might not
389 | be able to check the trigger on that timer - possibly even after two full durations have passed.
390 | When you check the trigger using ```TRIGGERED_ALL```, that will cause BlockNot to continue giving
391 | you a TRUE response until each missed trigger has been provided to you.
392 |
393 | See the example sketch called **DurationTrigger** to see this method in action.
394 |
395 | ## The Reset
396 |
397 | Resetting a timer is critical to performing repeated events at the right intervals. However, there may be times when you
398 | don't want this behavior.
399 |
400 | Resetting of a timer once it has triggered is the default behavior of BlockNot.
401 |
402 | ```C++
403 | if (myTimer.TRIGGERED) { my code }
404 | ```
405 |
406 | Using TRIGGERED, the timer automatically resets, whereas if you do this:
407 |
408 | ```C++
409 | if (myTimer.HAS_TRIGGERED) { my code }
410 | ```
411 |
412 | The startTime **does not reset** and that test will always come back true every time it is executed in your
413 | code, as long as the timer's duration has passed. The exception of course would be using the FIRST_TRIGGER
414 | method.
415 |
416 | ### Global Reset
417 |
418 | Sometimes, having the ability to reset all of your timers at the exact same time is handy. There are situations,
419 | for example, when you need things to happen in a specific timed order and to do so repeatedly. This is possible
420 | by creating your timers then simply calling one method that resets them all simultaneously.
421 |
422 | You can reset all of your timers simultaneously by simply calling either the method or the macro:
423 |
424 | ```C++
425 | resetAllTimers();
426 | RESET_TIMERS;
427 | ```
428 |
429 | When you call this method, it first captures the value of micros() or millis() then it assigns that
430 | value to the startTime of every instantiated timer so that they all reset at precisely the exact
431 | same time. And don't worry, if you have a peppered mix of timers each with different base units (
432 | milliseconds, microseconds etc.) BlockNot will use either millis() or micros() based on each timers
433 | individual - currently assigned base unit.
434 |
435 | It should be noted that even if you have your project divided into multiple code files, the resetAll
436 | method will reset all timers across all of your code ... It is global to your entire project.
437 |
438 | ## Time Unit Options
439 |
440 | ### Default
441 |
442 | When you declare your timers without specifying the units, they will default to millisecond timers,
443 | because milliseconds are the most commonly used time units in Arduino programming.
444 |
445 | ### Other Units
446 |
447 | You can declare a timer to operate within any time unit you desire, as long as the time unit you desire is either
448 | SECONDS, MILLISECONDS or MICROSECONDS.
449 |
450 | For example, when you only need **second** precision, you can declare your timer as a SECONDS timer. It
451 | can be much easier in terms of writing and reading your code, if you don't need milli or micro second
452 | precision. It's much simpler to use 23 than 23000 when you only need to know about 23 seconds.
453 |
454 | You instantiate your timers with other units like this:
455 |
456 | ```C++
457 | BlockNot myTimer(5, SECONDS);
458 | BlockNot myTimer(14000, MICROSECONDS);
459 | ```
460 |
461 | Under the hood, BlockNot calculates SECONDS and MILLISECONDS using the millis() method, where MICROSECOND
462 | timers use the micros() method.
463 |
464 | #### BlockNot Assumptions
465 |
466 | When a timer is declared as a SECONDS, MILLISECONDS, or MICROSECONDS timer, the unit you choose is referred to as the
467 | timers **base units**.
468 |
469 | You MUST interact with your timer, in the base units you declared it as or in the base units you switch it to (discussed
470 | below).
471 |
472 | When you read values from your timer, by default, you will always get back a value that is in the
473 | base units of the timer.
474 |
475 | For example, these
476 |
477 | ````C++
478 | BloclNot myTimer(50000, MICROSECONDS);
479 |
480 | myTimer.GET_START_TIME;
481 | myTimer.getStartTime();
482 | ````
483 |
484 | will always return a value in MICROSECONDS.
485 |
486 | And
487 |
488 | ```C++
489 | BloclNot myTimer(50000, SECONDS);
490 |
491 | myTimer.GET_START_TIME;
492 | myTimer.getStartTime();
493 | ```
494 |
495 | will always return a value in SECONDS.
496 |
497 | It needs to be noted that in a SECONDS timer, the numbers that are returned when seeking a value,
498 | will ALWAYS be rounded DOWN. So if, for example, you have a SECONDS timer, and you request the value
499 | for the number of seconds remaining until the next trigger, and BlockNot calculates that value to be
500 | 8700 milliseconds. You will get 8 SECONDS back in response.
501 |
502 | This shouldn't be a problem, because if you need fractional second accuracy, then use a millisecond timer.
503 |
504 | #### Microseconds
505 |
506 | MICROSECONDS are almost always used in situations when you need to reference time durations that
507 | are faster than a millisecond. Therefore, NEVER use a MICROSECONDS timer when you need to evaluate
508 | time in durations longer than an hour, because the Arduino rolls the micros() counter after roughly
509 | one hour (read discussion on rollover below) and BlockNot has no way of knowing how often that counter
510 | rolls over. It can and will calculate durations accurately when a rollover happens in between TRIGGERED
511 | events, but when more than one of these events happens in between TRIGGERED events, BlockNot will not
512 | be able to know about those rollovers and your results will be inaccurate.
513 |
514 | Realistically, you should never use a MICROSECONDS timer if your event durations are always longer
515 | than one million microseconds, and certainly you should never use a MICROSECONDS timer for durations
516 | longer than an hour. **If you need to track intervals of time that are longer than an hour,
517 | USE SECONDS OR MILLISECONDS!**
518 |
519 | ### Converting Units
520 |
521 | Because program storage space is extremely valuable with microcontrollers, I decided to offer the
522 | convert method as opposed to writing methods and macros for every option available where values
523 | of interest might be needed. The convert method will convert from the units that your timer is
524 | declared in, to whichever DESIRED units are passed into the method.
525 |
526 | The convert method uses this structure
527 |
528 | ```C++
529 | unsigned long desiredValue = myTimer.convert(valueOfInterest, UNIT_DESIRED);
530 | ```
531 |
532 | - The number returned will always be an unsigned long.
533 |
534 | - ```valueOfInterest``` MUST be an unsigned long, or a long that is not negative
535 | - ```UNIT_DESIRED``` MUST be either ```SECONDS```, ```MILLISECONDS``` or ```MICROSECONDS```
536 |
537 | For example, lets say that we have declared a timer as a MILLISECONDS timer, but we are interested
538 | in knowing how many SECONDS or MICROSECONDS remain until the timer triggers again. We can get those
539 | values in different units like this:
540 |
541 | ```C++
542 | value = myTimer.convert(myTimer.TIME_TILL_TRIGGER, SECONDS);
543 | value = myTimer.convert(myTimer.TIME_TILL_TRIGGER, MICROSECONDS);
544 | ```
545 |
546 | The ```convert()``` method can be used to convert ANY value into whichever units you need, but
547 | realize that the value you pass into the method will be assumed to be in the timers base units.
548 |
549 | Any of these methods can be passed into the convert() method to obtain their values in whichever
550 | unit you desire (desired units in this example were chosen randomly).
551 |
552 | ```C++
553 | myTimer.convert(myTimer.getTimeUntilTrigger(), SECONDS)
554 | myTimer.convert(myTimer.getNextTriggerTime(), MILLISECONDS)
555 | myTimer.convert(myTimer.getStartTime(), SECONDS)
556 | myTimer.convert(myTimer.getDuration(), MICROSECONDS)
557 | myTimer.convert(myTimer.getTimeSinceLastReset(), MILLISECONDS)
558 | ```
559 |
560 | Here is what each of these methods provides:
561 |
562 | - ```getTimeUntilTrigger()``` returns a value that is relative to the timer's duration. So if the duration is set to
563 | 1350ms, and it has been 500ms since it last triggered, the returned value will be 850ms
564 | - ```getNextTriggerTime()``` will return a value that is relative to the microcontrollers internal micros() or millis()
565 | value, which will be the timers current startTime PLUS the timer's duration
566 | - ```getStartTime()``` returns the value of the CPUs micros() or millis() method that was recorded as the timers current
567 | startTime
568 | - ```getDuration()``` Simply returns the duration that is currently set in the timer (the duration you assign when you
569 | create the timer, or the one that you changed it to after the fact)
570 | - ```getTimeSinceLastReset()``` returns a value that represents how much time has elapsed since the timer was last
571 | reset. It does not consider trigger events, but only the current startTime.
572 |
573 | ### Changing Duration
574 |
575 | When you need to change a timers' duration, use the ```setDuration(time)``` method. BlockNot assumes that the number you
576 | pass
577 | into the argument will be in the same units as the current baseUnit of the timer. However, if you wish to change the
578 | duration
579 | by passing in a value that is in different units, you can use ```setDuration(time, Unit)``` and BlockNot will convert
580 | that number
581 | into whatever its current baseUnit is.
582 |
583 | For example, if you have a timer that is declared as a MILLISECONDS timer
584 |
585 | ```C++
586 | BlockNot myTimer(2500);
587 | ```
588 |
589 | And you want to change the duration to three seconds, you could do it in two ways:
590 |
591 | ```c++
592 | myTimer.setDuration(3, SECONDS);
593 | //OR
594 | myTimer.switchTo(SECONDS);
595 | myTimer.setDuration(3);
596 | ```
597 |
598 | Using the first option will not change the baseUnit of the timer, so a MILLISECOND timer will remain as a MILLISECOND
599 | timer even though you changed the duration to 3 SECONDS.
600 |
601 | ### Switching Base Units
602 |
603 | If you need to switch the timers base units, you can do so like this:
604 |
605 | ````C++
606 | myTimer.switchTo(MICROSECONDS);
607 | myTimer.switchTo(MILLISECONDS);
608 | myTimer.switchTo(SECONDS);
609 | ````
610 |
611 | Once you have changed the base units, then obviously, values returned from methods will be returned
612 | in the new base unit, and values given to the timer will be assumed to be in the new base unit that
613 | you switched it to.
614 |
615 | Using ```switchTo()``` is no different from originally declaring the timer in the base unit that you
616 | switch it to.
617 |
618 | ## Start / Stop
619 |
620 | You can stop a timer, then start it again as needed.
621 |
622 | By default, the start() method DOES NOT reset a timer. Calling the method like this is like starting
623 | a stop watch, after it has been stopped. The stop watch does not reset the start time to 00:00, but
624 | rather it merely pauses the timer until you start it again. In like manner, calling stop() then start()
625 | merely pauses the timer, so that any time that passes between a stop() and a start() gets subtracted
626 | out ... and the timers startTime is changed so that the time that passed is added to the startTime so that
627 | you can pick up right where you left off.
628 |
629 | You can override this behavior and have the timer RESET when you start it, by using either of these
630 | options:
631 |
632 | ```C++
633 | myTimer.START_RESET;
634 | myTimer.start(WITH_RESET);
635 | ```
636 |
637 | When a timer is in a stopped state, any call to the timer that would return a boolean value will ALWAYS
638 | return false. And when you query the timer where a numeric value is supposed to be returned,
639 | it will return a ZERO by default, although you can change what number it returns for those
640 | methods, as long as the number you set is a positive number (BlockNot does not ever deal with
641 | negative numbers, since time in our universe always moves forward).
642 |
643 | I've used STOP and START when stepping motors, where the delay between steps is defined in a
644 | MICROSECONDS timer (where the duration is constantly changing based on the value of RPMs) but
645 | when the RPMs are set to 0, then I simply STOP the timer and stepping will not occur. When RPMs
646 | are above 0, then I START the timer and stepping resumes.
647 |
648 | These methods will ALWAYS return false when a timer is stopped (for macro calls see the Macro
649 | section of this document):
650 |
651 | * **triggered()**
652 | * **notTriggered()**
653 | * **firstTrigger()**
654 |
655 | These methods will return a ZERO by default when a timer is stopped (or whichever value you set as the return).
656 |
657 | * **getDuration()**
658 | * **getTimeUntilTrigger()**
659 | * **timeSinceLastReset()**
660 |
661 | ### Return Values on Stopped Timers
662 |
663 | You can declare the return value when you create the timer (see the constructors in the .h file), OR,
664 | once you create your timer, you simply set the value using this method:
665 |
666 | ```C++
667 | setStoppedReturnValue(8675309);
668 | ```
669 |
670 | What matters in this situation is that the default return value for any stopped timer is always ZERO unless you change
671 | it, and the number you assign, once again, CANNOT BE NEGATIVE
672 |
673 | You can start and stop a timer using these methods / macros.
674 |
675 | ```C++
676 | myTimer.START;
677 | myTimer.STOP;
678 | ```
679 |
680 | And you can find out if the timer is running or not using either of these calls:
681 |
682 | ```C++
683 | if (myTimer.ISRUNNING) { my code; }
684 | if (myTimer.ISSTARTED) { my code; }
685 | if (myTimer.ISSTOPPED) { my code; }
686 | ```
687 |
688 | You can also flip the state of the timer (if stopped, it will start; if started, it will stop):
689 |
690 | ```C++
691 | myTimer.TOGGLE;
692 | ```
693 |
694 | Why would you want to just change the state with one line of code? Perhaps you have a toggle button that will toggle a
695 | timer to be started or stopped ... you can assign the one command to the button and everything is handled.
696 |
697 | ```C++
698 | #define BUTTON_PRESSED digitalRead(BUTTON) == LOW
699 |
700 | pinMode(BUTTON, INPUT_PULLUP);
701 |
702 | if (BUTTON_PRESSED) {
703 | myTimer.TOGGLE;
704 | }
705 | ```
706 |
707 | ## Summary
708 |
709 | Well, that's BlockNot in a nutshell.
710 |
711 | Simple, right?
712 |
713 | BlockNot is a library intended to make the employment of non-blocking timers easy,
714 | intuitive, natural and obvious. It can be engaged with simple single word macros
715 | or by calling the methods directly.
716 |
717 | There are more methods that allow you to affect change on your timers after instantiation and also methods to get info
718 | about your timers. You can change the duration of an existing timer in three different ways, you can reset the timer, or
719 | you can even find out how much time is left before the trigger event occurs, or find out how much time has passed since
720 | the timer last triggered.
721 |
722 | # Examples
723 |
724 | There are currently nine examples in the library.
725 |
726 | ### Advanced Auto Flashers
727 |
728 | This sketch was one I wrote recently that was for a friend who wanted to put large LEDs on the back of his 5th wheel so
729 | that people behind him received much better feedback depending on whether he hits his brakes, uses his turn signals or
730 | uses the hazard lights. It's a fairly good example of using BlockNot timers to achieve compartmentalized functions in
731 | code that runs continuously and never stops. The code was written for a Raspberry Pi Pico.
732 |
733 | ### BlockNot Blink
734 |
735 | This sketch does the same thing as the famous blink sketch, only it does it with BlockNot elegance and style.
736 |
737 | ### BlockNot Blink Party
738 |
739 | If you have a nano or an uno or equivalent laying around and four LEDs and some resistors, connect them to pins 9 - 12
740 | and run this sketch. You will immediately see the benefit of non-blocking timers. You could never write a sketch that
741 | could do the same thing using the delay() command. It would be impossible.
742 |
743 | **Non-Blocking MATTERS!**
744 |
745 | ### Millis Rollover Test
746 |
747 | This sketch was added to demonstrate that BlockNot can and does properly calculate
748 | timer durations even when millis() rolls over. The sketch has comments at the top that
749 | fully explain what it does, and how you can adjust the time until millis() rolls using the
750 | terminal. See [the discussion](#rollover) further down on millis() and micros() rollover.
751 |
752 | ### Button Debounce
753 |
754 | Learn how to debounce a button without using delay()
755 |
756 | ### Duration Trigger
757 |
758 | Read the section above to get an idea of what TRIGGERED_ON_DURATION does, then load this example up and play around
759 | with it. You can pause the loop from Terminal monitor by typing in p and hitting enter. Then if you wait for several
760 | durations to pass, then un-pause the loop, you will see hoe BlockNot handles that feature.
761 |
762 | ### On With Off Timers
763 |
764 | This example shows you how to use on and off timers to control anything that you need
765 | to have on for a certain length of time and also off for a certain length of time.
766 |
767 | The example specifically blinks two LEDs such that they will always be in sync every
768 | 6 seconds ... by this pattern:
769 |
770 | ### Reset All
771 |
772 | This sketch shows how all BlockNot timers defined in your sketch can be reset with a
773 | single line of code, rather than having to call reset() for each and every one
774 | separately. This comes in handy when all timers need to be reset at once, e.g. after
775 | the system clock has been adjusted from an external source (NTP or RTC, for example).
776 |
777 | ### Timers Rules
778 |
779 | This sketch has SIX timers created and running at the same time. There are various
780 | things happening at the trigger event of each timer. The expected behavior is explained
781 | in the out Strings to Serial. Read them, then let it run for a minute or so then stop
782 | your Serial monitor and look at the output. You should be able to look at the number of
783 | milliseconds that is given in each output, and compare the differences with the
784 | expected behavior and see that everything runs as it is expected to run.
785 |
786 | For example, when LiteTimer triggers, you should soon after that see the output from
787 | stopAfterThreeTimer. When you look at the number of milliseconds in each of their
788 | outputs, you can see that indeed it does trigger three seconds after being reset,
789 | but then it does not re-trigger until after it is reset again.
790 |
791 | - Thanks to [@SteveRMann](https://github.com/SteveRMann) for kick-starting this example and working with me on
792 | fine-tuning it.
793 |
794 | #### These examples barely scratch the surface of what you can accomplish with BlockNot.
795 |
796 | # Library
797 |
798 | ## Methods
799 |
800 | Below you will find the name of each method in the library and any arguments that it accepts. Below that list, you will
801 | find the names of the macros that are connected to each method along with the arguments that a given macro may or may
802 | not pass to the method. The macros are key to making your code simple.
803 |
804 | ** For any method call that resets a timer by default, the resetting behavior can be overridden by passing **NO_RESET**
805 | into the methods argument, the exception to this is the triggeredOnDuration() method, which exists because of the way it
806 | resets your timer, so overriding reset would make the method useless.
807 |
808 | * **setDuration()** - Override the current timer duration and set it to a new value. This also resets the timer. If you
809 | need the timer to NOT reset, then pass arguments like this (newDuration, NO_RESET);
810 | * **addTime()** - Adds the time you pass into the argument to the current duration value. This does NOT reset the timer.
811 | To also reset the timer, call the method like this **addTime(newTime, WITH_RESET);**
812 | * **takeTime()** - The opposite effect of addTime(), same deal if you want to also reset the timer.
813 | * **triggered()** - Returns true if the duration time has passed. Also resets the timer to the current ```micros()```
814 | or ```millis()``` (override by passing NO_RESET as an argument).
815 | * **triggeredOnDuration()** - See section above entitled **Triggered On Duration** for complete discussion.
816 | * **notTriggered()** - Returns true if the trigger event has not happened yet.
817 | * **firstTrigger()** - Returns true only once and only after the timer has triggered - can be modified with
818 | setFirstTriggerResponse(bool).
819 | * **getNextTriggerTime()** - Returns an unsigned long of the next time that the timer will trigger. If it has triggered,
820 | it will return 0.
821 | * **getTimeUntilTrigger()** - Returns an unsigned long with the number of microseconds remaining until the trigger event
822 | happens, converted to the timers base units.
823 | * **getStartTime()** - Returns an unsigned long, The value of ```micros()``` or ```millis()``` that was recorded at the
824 | last reset of the timer, converted to the timers currently assigned base unit.
825 | * **getDuration()** - Returns an unsigned long, the duration that is currently set in the timer.
826 | * **getUnits()** - Returns a String of the assigned base units of the timer; Seconds, Milliseconds or Microseconds.
827 | * **getTimeSinceLastReset()** - Returns an unsigned long indicating how much time has passed since the timer was last
828 | reset or instantiated. Response will be in the base units of the timer.
829 | * **setStoppedReturnValue()** - Lets you set the value returned for those methods that return numbers, when the timer is
830 | stopped.
831 | * **start()** - starts the timer (timers are started by default when you create them).
832 | * **stop()** - stops the timer.
833 | * **isRunning()** - returns true if the timer is not stopped.
834 | * **isStopped()** - returns true if the timer is stopped.
835 | * **toggle()** - Toggles the start and stopped state so that you only need to call this one method - like in a push
836 | button toggle situation.
837 | * **switchTo()** - Change the timer from whichever base unit it currently is, over to SECONDS, MILLISECONDS or
838 | MICROSECONDS.
839 | * **reset()** - Sets the start time of the timer to the current micros() or millis depending on its currently assigned
840 | base unit.
841 | * **resetAllTimers()** - loops through all timers that you created and resets startTime to ```micros()```
842 | or ```millis()``` depending on the timers currently assigned base unit, which is recorded once and applied to all
843 | timers, so they will all have the exact same startTime. See **Memory** section for further discussion.
844 |
845 | ## Macros
846 |
847 | Here are the macro terms and the methods that they call along with any arguments they pass into the method:
848 |
849 |
850 | | **Macro** | **Method** |
851 | |-------------------------------|--------------------------|
852 | | **TIME_PASSED** | getTimeSinceLastReset() |
853 | | **TIME_SINCE_RESET** | getTimeSinceLastReset() |
854 | | **ELAPSED** | getTimeSinceLastReset() |
855 | | **TIME_TILL_TRIGGER** | getTimeUntilTrigger() |
856 | | **TIME_REMAINING** | getTimeUntilTrigger() |
857 | | **REMAINING** | getTimeUntilTrigger() |
858 | | **DURATION** | getDuration() |
859 | | **GET_UNITS** | getUnits() |
860 | | **GET_START_TIME** | getStartTime() |
861 | | **DONE** | triggered() |
862 | | **TRIGGERED** | triggered() |
863 | | **LAST_TRIGGER_DURATION** | lastTriggerDuration() |
864 | | **HAS_TRIGGERED** | triggered(NO_RESET) |
865 | | **TRIGGER_NEXT** | triggerNext() |
866 | | **TRIGGERED_ON_DURATION** | triggeredOnDuration() |
867 | | **TRIGGERED_ON_MARK** | triggeredOnDuration() |
868 | | **TRIGGERED_ON_DURATION_ALL** | triggeredOnDuration(ALL) |
869 | | **TRIGGERED_ALL** | triggeredOnDuration(ALL) |
870 | | **NOT_DONE** | notTriggered() |
871 | | **NOT_TRIGGERED** | notTriggered() |
872 | | **FIRST_TRIGGER** | firstTrigger() |
873 | | **RESET** | reset() |
874 | | **RESET_TIMERS** | resetAllTimers() |
875 | | **START** | start() |
876 | | **START_RESET** | start(WITH_RESET) |
877 | | **STOP** | stop() |
878 | | **ISSTARTED** | isRunning() |
879 | | **ISRUNNING** | isRunning() |
880 | | **ISSTOPPED** | isStopped() |
881 | | **TOGGLE** | toggle() |
882 |
883 | ## Constants
884 |
885 | * **WITH_RESET** - boolean true
886 | * **NO_RESET** - boolean false
887 | * **ALL** - boolean true
888 | * **SECONDS**
889 | * **MILLISECONDS**
890 | * **MICROSECONDS**
891 | * **NO_GLOBAL_RESET**
892 | * **GLOBAL_RESET**
893 | * **RUNNING**
894 | * **STOPPED** (Pass this into a constructor to create a timer in a STOPPED state)
895 |
896 | If you can think of MACRO names that would make the reading and writing of you code more
897 | natural and you think it would be a benefit to BlockNot, PLEASE either submit a pull
898 | request or shoot me an email so that we can all work together to make this library the
899 | best that it can possibly be.
900 |
901 | Also, you can, of course create your own macros within your code. So, for example, let's
902 | say that you wanted a macro that overrides the default reset behavior in the setDuration()
903 | method, which by default, will change the duration of the timer to your new value and will
904 | also reset the timer. But lets say you want to change the duration WITHOUT resting the
905 | timer and you wanted that to be done with a word that makes more sense to you.
906 |
907 | ```C++
908 | #define QUICK_CHANGE(value) myTimer.setDuration(value, false)
909 |
910 | QUICK_CHANGE(3200);
911 | ```
912 |
913 | The only difference here, is that you cannot make a macro that applies universally to all
914 | of your timers. You would need to make one macro for each timer you have created. This is
915 | why it is better to submit a pull request or contact me with your ideas, so that all of us
916 | who use BlockNot can benefit through continual improvement of the library.
917 |
918 | # Discussion
919 |
920 | ## Memory
921 |
922 | I have compiled BlockNot in a variety of scenarios. The only difference between each specific scenario is that I
923 | would use traditional me7thods of implementing non-blocking timers, vs using BlockNot. In some scenarios, BlockNot
924 | would cause the sketch to compile using less memory and in some scenarios, it would use a little more memory.
925 | Obviously your situation will be different depending on the size of your project, other libraries used etc.
926 |
927 | If you're really struggling for memory space, try creating your timers using the manual method just to see if it
928 | makes a difference or not using BlockNot.
929 |
930 | In the interest of squeezing as much program space as possible, I have added the ability to disable BockNot's
931 | global reset option, because it, by default, maintains an array of all instantiations of BlockNot which consumes
932 | a little extra memory and if you don't need that feature and are hurting for memory space, then you can disable
933 | it by passing NO_GLOBAL_RESET as the last argument into your first timer (You only need to pass that argument ONCE
934 | and it will remain disabled for all timers created after that).
935 |
936 | Examples of how to disable the feature:
937 |
938 | ````C++
939 | BlockNot myTimer(3800, NO_GLOBAL_RESET);
940 | BlockNot myTimer(15, SECONDS, NO_GLOBAL_RESET);
941 | ````
942 |
943 | OR, you can optionally define some timers that are affected by the global reset method, then issue the NO_GLOBAL
944 | argument into the next timer you create and that timer, and every timer created after it will not be included in the
945 | global reset option.
946 |
947 | ````C++
948 | BlockNot timer1(1350);
949 | BlockNot timer2(5,SECONDS);
950 | BlockNot timer3(2670,NO_GLOBAL_RESET)
951 | BlockNot timer4(3460); //not included in global reset
952 | ````
953 |
954 | ## Rollover
955 |
956 | I've been contacted by a few people who have expressed concern with possible problems in timing when the
957 | Arduino ```millis()``` or ```micros()``` counter rolls over (millis() at approximately 50 days and micros() at around 70
958 | minutes) after power up.
959 |
960 | First and foremost, **DON'T USE MICROSECOND TIMERS WHEN YOU CAN USE MILLISECONDS INSTEAD**
961 |
962 | The whole issue about rollover **is not a concern at all**, because
963 | of the way that BlockNot uses ```micros()``` and ```millis()```, your timers will still calculate properly even if the
964 | ```micros()``` or ```millis()``` value rolls over in the duration of a timer. I've tested BlockNot using simulated
965 | values to
966 | artificially create a rollover scenario and I can tell you that it indeed works properly through a
967 | rollover.
968 |
969 | The reason it works has to do with the way CPUs handle binary numbers where there is no possibilty of the number
970 | being negative, and [this article](https://techexplorations.com/guides/arduino/programming/millis-rollover/) can explain
971 | it
972 | in detail if you're interested.
973 |
974 | I have added an example sketch called ```MillisRolloverTest.ino``` that will demonstrate how well
975 | BlockNot calculates timer durations even through millis() rollovers, by artifically inflating the
976 | value of millis() and calculating the time difference between trigger events. There is more
977 | discussion in that sketch.
978 |
979 | ## Thread Safety
980 |
981 | With the introduction of cost effective multi-core microcontrollers, more and more people will be
982 | writing code where they take advantage of having more than one core in the CPU. And currently, the
983 | way that most microcontrollers implement the use of another core is by adding a separate code thread
984 | where the code for that core runs in a different thread.
985 |
986 | One of the major problems with multi-threading applications is when the code from different threads
987 | tries to change the value of a global scoped variable simultaneously.
988 |
989 | And where BlockNot is concerned, that could be an issue if you make references to a single timer from
990 | different threads, because, for example, when you check for TRIGGERED, and the return value is true,
991 | BlockNot updates a variable that resets the startTime of the timer so that TRIGGERED will only return
992 | true after the next duration time has passed.
993 |
994 | I have changed the declaration of the relevant variables to be volatile variables, which can help
995 | situations where a change might be happening at the same instant in time. But the issue is more complex
996 | than that, so simply declaring variables as volatile by no means, makes BlockNot thread safe.
997 |
998 | If you find yourself in a situation where you need to access or engage a timer from two different
999 | threads, what is most important is that only one thread make any calls to the timer that cause
1000 | changes to the timers variables. This includes the TRIGGERED method. There is a way to accomplish
1001 | the modification of a timer from two different threads, by utilizing a global variable where only
1002 | one thread writes to it and the other thread reads from it.
1003 |
1004 | Consider this example. Specifically look at the ```adjustTimer()``` method and the ```core1Entry``` method which is
1005 | what is executing in the other core - or is what is running on the other thread - however you want to
1006 | look at it (6 of one, half-dozen the other)
1007 |
1008 | What is important to see here is that one thread is making changes to ```timerDelay``` while the
1009 | other thread is taking that value and passing it into the timers ```setDuration``` method. That same
1010 | thread is also making calls to TRIGGERED which leaves only that thread as the thread causing changes
1011 | to the timers variables.
1012 |
1013 | ```C++
1014 | #include
1015 |
1016 | BlockNot stepperTimer(1, MICROSECONDS);
1017 | unsigned long timerDelay = 0;
1018 |
1019 |
1020 | void stepStepper() {
1021 | digitalWrite(STEP, HIGH);
1022 | delayMicroseconds(3);
1023 | digitalWrite(STEP, LOW);
1024 | delayMicroseconds(2);
1025 | }
1026 |
1027 |
1028 | [[noreturn]] void core1Entry() {
1029 | static unsigned long lastTimerDelay = 0;
1030 | while (true) {
1031 | if(lastTimerDelay != timerDelay) {
1032 | stepperTimer.setDuration(timerDelay, NO_RESET);
1033 | lastTimerDelay = timerDelay;
1034 | }
1035 | if (stepperTimer.TRIGGERED)
1036 | stepStepper();
1037 | }
1038 | }
1039 |
1040 | void adjustTimer() {
1041 | long potValue = analogRead(POT_PIN);
1042 | timerDelay = map(potValue, 0, 1024, 5000, 25);
1043 | }
1044 |
1045 | void setup() {
1046 | multicore_launch_core1(core1Entry);
1047 | }
1048 |
1049 | void loop() {
1050 | adjustTimer();
1051 | }
1052 | ```
1053 |
1054 | Even though BlockNot is not "thread-safe" you can still use it in multi-threaded environments if you
1055 | simply make sure that only one thread will ever be causing changes to happen in the timer itself.
1056 |
1057 | ## Triggering Too Fast With High Speed Microcontrollers
1058 |
1059 | If you're noticing that some timers seem to trigger immediately after a trigger or a reset and you're running
1060 | on a high speec microcontroller like a Pi Pico, you can enable a feature called `speedComp()` and pass in an
1061 | amount of time for a delay during each reset. When I noticed the problem, I had in my loop, a switch statement
1062 | that would run each time the timer triggered, and I had three cases in the switch. In this instance, each case
1063 | that hit would set the next trigger to run the next case. Each case displayed something different on an OLED
1064 | scree. However, I was noticing that the information was not being shown in the right order but instead I would see a
1065 | quick flash of something then it would go to the next case after the one that was supposed to be next.
1066 |
1067 | What fixed it for me was adding a 5ms delay after each triggering of the timer, so I added this feature, and you
1068 | can use it like this:
1069 |
1070 | ```c++
1071 | myTimer.speedComp(5);
1072 | ```
1073 |
1074 | Put that in your setup() code as it only needs to be executed one time. That will automatically implement a
1075 | delay (of 5 milliseconds in this example) every time the timer is reset. And the timer is automatically reset
1076 | every time it triggers by default.
1077 |
1078 | If you need to disable this feature:
1079 |
1080 | ```c++
1081 | myTimer.disableSpeedComp();
1082 | ```
1083 |
1084 | ### Deprecated Code
1085 |
1086 | I have deprecated some macros from the library for several reasons: code simplicity, compatibility with other libraries and
1087 | to keep readability in line with the methods that the macros run. This means that in a future release of the library, these
1088 | macros will no longer be available, so you should update your projects before that happens.
1089 |
1090 | Here is the list of macros that are deprecated and the macros you should use in their place:
1091 |
1092 | | **Deprecated** | **Replacement** |
1093 | |-------------------------------|--------------------------------|
1094 | | **TIME_PASSED** | **ELAPSED** |
1095 | | **TIME_SINCE_RESET** | **ELAPSED** |
1096 | | **DONE** | **TRIGGERED** |
1097 | | **NOT_DONE** | **NOT_TRIGGERED** |
1098 | | **TRIGGERED_ON_MARK** | **TRIGGERED_ON_DURATION** |
1099 | | **TRIGGERED_ON_DURATION_ALL** | **TRIGGERED_ON_DURATION(ALL)** |
1100 | | **TRIGGERED_ALL** | **TRIGGERED_ON_DURATION(ALL)** |
1101 | | **START_RESET** | **START(WITH_RESET)** |
1102 | | **ISSTARTED** | **ISRUNNING** |
1103 |
1104 | I will wait a few months before I remove these from the code, today being January 7, 2025.
1105 |
1106 | ### Get Help
1107 | I have added a nmethod called `getHelp()` that you can use as a one-off execution in your code. It will print out a list of
1108 | macros that you can call and make your code more readable. If you want it to print out through a Serial port other than
1109 | the default, you can pass your Serial port into the method.
1110 |
1111 | You can also pass in `true` if you want it to stop your code execution so you can read the output. Just remember to remove
1112 | the call from your code after you're done reading it.
1113 |
1114 | ```c++
1115 | BlockNot myTimer(1000);
1116 |
1117 | setup() {
1118 | Serial.begin(115200);
1119 | Serial1.begin(115200);
1120 | myTimer.getHelp();
1121 | myTimer.getHelp(Serial1);
1122 | myTimer.getHelp(true);
1123 | myTimer.getHelp(Serial1, true);
1124 | }
1125 | ```
1126 | All of those will print out the list of available macros and the ones with `true` in them will also halt all further code
1127 | execution.
1128 |
1129 | When you use one of the deprecated macros in your code, the compiler will throw a warning that will look something like
1130 | this:
1131 |
1132 | 
1133 |
1134 | Be on the lookout for those warnings. Your code will continue working, however, until I remove the macros permanently in
1135 | about six months from now (January 7, 2025).
1136 |
1137 | # Version Update Notes
1138 |
1139 | ## Changelog
1140 |
1141 | For the full version history, see the [CHANGELOG.md](CHANGELOG.md) file.
1142 |
1143 | # Suggestions
1144 |
1145 | I welcome any and all suggestions for changes or improvements. You can either open an issue, or code the change yourself
1146 | and create a pull request. This library is for all of us and making it the best it can be is important!
1147 |
1148 | You can also email me
[sims.mike@gmail.com](mailto:sims.mike@gmail.com)
1149 |
1150 | Thank you for your interest in BlockNot. I hope you find it as invaluable in your projects as I have in mine.
1151 |
--------------------------------------------------------------------------------