├── 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 | ![](./img/visual.png) 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 | ![](./img/cmpTRIGGERED.png) 356 | 357 | This visualises how ```TRIGGERED_ON_DURATION``` / ```TRIGGERED_ON_MARK``` works. 358 | 359 | ![](./img/cmpONMARK.png) 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 | ![](./img/cmpONDURATION.png) 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 | ![](./img/warnings.png) 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 | --------------------------------------------------------------------------------