├── .github ├── FUNDING.yml └── workflows │ ├── aunit_tests.yml │ └── githubci.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── examples ├── blink │ └── blink.ino ├── blink_micros │ └── blink_micros.ino ├── blink_print │ └── blink_print.ino └── full │ └── full.ino ├── extras └── tests │ ├── Makefile │ ├── rollover-generic │ └── rollover-generic.ino │ ├── rollover │ └── rollover.ino │ ├── timerMultiCancelTest │ ├── Makefile │ └── timerMultiCancelTest.ino │ ├── timerTest │ ├── Makefile │ └── timerTest.ino │ └── unitTest │ ├── Makefile │ └── unitTest.ino ├── keywords.txt ├── library.properties └── src ├── arduino-timer.h └── timer.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: contrem 2 | -------------------------------------------------------------------------------- /.github/workflows/aunit_tests.yml: -------------------------------------------------------------------------------- 1 | name: arduino-timer unit tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Build 14 | run: | 15 | make -C extras/tests 16 | - name: Run 17 | run: | 18 | make -C extras/tests runtests 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/githubci.yml: -------------------------------------------------------------------------------- 1 | name: arduino-timer platform builds 2 | 3 | on: [pull_request, push, repository_dispatch] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/setup-python@v4 11 | with: 12 | python-version: '3.x' 13 | - uses: actions/checkout@v3 14 | - uses: actions/checkout@v3 15 | with: 16 | repository: adafruit/ci-arduino 17 | path: ci 18 | 19 | - name: pre-install 20 | run: bash ci/actions_install.sh 21 | 22 | - name: test platforms 23 | run: python3 ci/build_platform.py main_platforms 24 | #run: python3 ci/build_platform.py main_platforms esp32 esp8266 25 | #run: python3 ci/build_platform.py main_platforms esp32 esp8266 nrf52840 26 | 27 | # disabled for now. 28 | #- name: doxygen 29 | #env: 30 | # GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }} 31 | # PRETTYNAME : "arduino-timer Library" 32 | #run: bash ci/doxy_gen_and_deploy.sh 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # (borrowed from Adafruit_NeoPixel lib) 2 | # 3 | # Our handy .gitignore for automation ease 4 | Doxyfile* 5 | doxygen_sqlite3.db 6 | html 7 | *.out 8 | *.o 9 | *.swp 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Coding Style 4 | 5 | Try and adhere to the [kernel coding style](https://www.kernel.org/doc/html/latest/process/coding-style.html) with the exception of using 4 spaces instead of 8 for indentation. 6 | 7 | ## Reporting Bugs 8 | 9 | Open an issue. Please include as much code / logging / output / information as 10 | possible to help diagnose your issue. 11 | 12 | ## Proposing Changes 13 | 14 | Open a pull request using a branch based on **master**. Read the [branches 15 | section](#branches) for an overview of the workflow. 16 | 17 | If it's a major change, open an issue to gauge interest before putting in too 18 | much effort. 19 | 20 | The following subsections inspired by the [git patch guidelines](https://github.com/git/git/blob/master/Documentation/SubmittingPatches). 21 | 22 | ### Commits 23 | 24 | Make separate commits for logically separate changes 25 | 26 | The title for the commit should start with the file or subsection it is 27 | changing. 28 | 29 | Give an explanation for the change(s) that is detailed enough so that people 30 | can judge if it is good thing to do, without reading the actual patch text to 31 | determine how well the code does what the explanation promises to do. 32 | 33 | If your description starts to get too long, that's a sign that you probably 34 | need to split up your commit to finer grained pieces. That being said, patches 35 | which plainly describe the things that help reviewers check the patch, and 36 | future maintainers understand the code, are the most beautiful patches. 37 | Descriptions that summarize the point in the subject well, and describe the 38 | motivation for the change, the approach taken by the change, and if relevant 39 | how this differs substantially from the prior version, are all good things to 40 | have. 41 | 42 | ### Basing your branch 43 | 44 | In general, always base your work on the oldest branch that your change is 45 | relevant to. 46 | 47 | - A bugfix should be based on **master** in general. For a bug that's not yet 48 | in **master**, find the topic that introduces the regression, and base your 49 | work on the tip of the topic. 50 | 51 | - A new feature should be based on **master** in general. If the new feature 52 | depends on a topic that is in **test**, but not in **master**, base your 53 | work on the tip of that topic. 54 | 55 | * Corrections and enhancements to a topic not yet in **master** should be based 56 | on the tip of that topic. If the topic has not been merged to **next**, it's 57 | alright to add a note to squash minor corrections into the series. 58 | 59 | * In the exceptional case that a new feature depends on several topics not in 60 | **master**, start working on **next** or **test** privately and send out 61 | patches for discussion. Before the final merge, you may have to wait until 62 | some of the dependent topics graduate to **master**, and rebase your work. 63 | 64 | To find the tip of a topic branch, run `git log --first-parent master..test` 65 | and look for the merge commit. The second parent of this commit is the tip of 66 | the topic branch. 67 | 68 | ## Branches 69 | 70 | Usage inspired by the [git branch workflow](https://github.com/git/git/blob/master/Documentation/howto/maintain-git.txt). 71 | 72 | ### master 73 | 74 | - All new features and changes should be based on this branch 75 | - Is meant to be more stable than any tagged releases, and users are encouraged 76 | to follow it 77 | - Tagged for releases 78 | 79 | ### next 80 | 81 | - Used to publish changes (both enhancements and fixes) that (1) have 82 | worthwhile goal, (2) are in a fairly good shape suitable for everyday use, 83 | (3) but have not yet demonstrated to be regression free. 84 | - Contains all new proposed changes for the next release 85 | - Users are encouraged to use it to test for regressions and bugs before they 86 | get merged for release 87 | - Periodically rebased on to **master**, usually after a tagged release 88 | 89 | ### test 90 | 91 | - Branch is used to publish other proposed changes that do not yet pass the 92 | criteria set for **next**. 93 | - Contains all seen proposed feature / enhancement requests 94 | - Most unstable branch 95 | - Regularly rebased on to **master** 96 | 97 | ### [initials]/[branch-name] 98 | 99 | - Name based on the author's initials and topic of the branch. For example 100 | *mc/fix-bug-in-foo* 101 | - Based on **master**, unless it requires code from another feature, in which 102 | case base it on that feature branch 103 | - Contains a proposed feature / enhancement / bug fix wanting to be merged 104 | - It will first get merged into **test**, then **next**, and finally **master** 105 | when it's ready 106 | 107 | ## Versioning 108 | 109 | Version numbers follow [Semantic Versioning](https://semver.org/). 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Michael Contreras 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 22 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME := arduino-timer 2 | VERSION := $(shell git describe --tags --always --dirty) 3 | 4 | $(NAME)-$(VERSION).zip: 5 | git archive HEAD --prefix=$(@:.zip=)/ --format=zip -o $@ 6 | 7 | tag: 8 | git tag $(VERSION) 9 | 10 | test: 11 | $(MAKE) -C extras/tests/ test 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arduino-timer - library for delaying function calls 2 | 3 | Simple *non-blocking* timer library for calling functions **in / at / every** specified units of time. Supports millis, micros, time rollover, and compile time configurable number of tasks. 4 | 5 | ### Use It 6 | 7 | Include the library and create a *Timer* instance. 8 | ```cpp 9 | #include 10 | 11 | auto timer = timer_create_default(); 12 | ``` 13 | 14 | Or using the *Timer* constructors for different task limits / time resolution 15 | ```cpp 16 | Timer<10> timer; // 10 concurrent tasks, using millis as resolution 17 | Timer<10, micros> timer; // 10 concurrent tasks, using micros as resolution 18 | Timer<10, micros, int> timer; // 10 concurrent tasks, using micros as resolution, with handler argument of type int 19 | ``` 20 | 21 | Call *timer*.**tick()** in the loop function 22 | ```cpp 23 | void loop() { 24 | timer.tick(); 25 | } 26 | ``` 27 | 28 | Make a function to call when the *Timer* expires 29 | ```cpp 30 | bool function_to_call(void *argument /* optional argument given to in/at/every */) { 31 | return true; // to repeat the action - false to stop 32 | } 33 | ``` 34 | 35 | Call *function\_to\_call* **in** *delay* units of time *(unit of time defaults to milliseconds)*. 36 | ```cpp 37 | timer.in(delay, function_to_call); 38 | timer.in(delay, function_to_call, argument); // or with an optional argument for function_to_call 39 | ``` 40 | 41 | Call *function\_to\_call* **at** a specific *time*. 42 | ```cpp 43 | timer.at(time, function_to_call); 44 | timer.at(time, function_to_call, argument); // with argument 45 | ``` 46 | 47 | Call *function\_to\_call* **every** *interval* units of time. 48 | ```cpp 49 | timer.every(interval, function_to_call); 50 | timer.every(interval, function_to_call, argument); // with argument 51 | ``` 52 | 53 | To **cancel** a *Task* 54 | ```cpp 55 | auto task = timer.in(delay, function_to_call); 56 | timer.cancel(task); 57 | ``` 58 | 59 | To **cancel** all *Task*s 60 | ```cpp 61 | timer.cancel(); 62 | ``` 63 | 64 | Check if a timer is **empty** - no active *Task*s 65 | ```cpp 66 | if (timer.empty()) { /* no active tasks */ } 67 | ``` 68 | 69 | Get the number of active *Task*s 70 | ```cpp 71 | auto active_tasks = timer.size(); 72 | ``` 73 | 74 | Be fancy with **lambdas** 75 | ```cpp 76 | timer.in(1000, [](void*) -> bool { return false; }); 77 | timer.in(1000, [](void *argument) -> bool { return argument; }, argument); 78 | ``` 79 | 80 | Getting the number of **ticks** until the next *Task* 81 | ```cpp 82 | auto ticks = timer.ticks(); // usefull for sleeping until the next task 83 | ``` 84 | ```cpp 85 | void loop { 86 | auto ticks = timer.tick(); // returns the number of ticks 87 | } 88 | ``` 89 | 90 | Avoiding **ticks** calculation inside of **tick** 91 | ```cpp 92 | void loop { 93 | timer.tick(); // avoids ticks() calculation 94 | } 95 | ``` 96 | 97 | ### API 98 | 99 | ```cpp 100 | /* Constructors */ 101 | /* Create a timer object with default settings: 102 | millis resolution, TIMER_MAX_TASKS (=16) task slots, T = void * 103 | */ 104 | Timer<> timer_create_default(); // auto timer = timer_create_default(); 105 | 106 | /* Create a timer with max_tasks slots and time_func resolution */ 107 | Timer timer; 108 | Timer<> timer; // Equivalent to: auto timer = timer_create_default() 109 | Timer<10> timer; // Timer with 10 task slots 110 | Timer<10, micros> timer; // timer with 10 task slots and microsecond resolution 111 | Timer<10, micros, int> timer; // timer with 10 task slots, microsecond resolution, and handler argument type int 112 | 113 | /* Signature for handler functions - T = void * by default */ 114 | bool handler(T argument); 115 | 116 | /* Timer Methods */ 117 | /* Ticks the timer forward, returns the ticks until next event, or 0 if none */ 118 | unsigned long tick(); // call this function in loop() 119 | 120 | /* Calls handler with opaque as argument in delay units of time */ 121 | Timer<>::Task 122 | in(unsigned long delay, handler_t handler, T opaque = T()); 123 | 124 | /* Calls handler with opaque as argument at time */ 125 | Timer<>::Task 126 | at(unsigned long time, handler_t handler, T opaque = T()); 127 | 128 | /* Calls handler with opaque as argument every interval units of time */ 129 | Timer<>::Task 130 | every(unsigned long interval, handler_t handler, T opaque = T()); 131 | 132 | /* Cancel a timer task */ 133 | bool cancel(Timer<>::Task &task); 134 | /* Cancel all tasks */ 135 | void cancel(); 136 | 137 | /* Returns the ticks until next event, or 0 if none */ 138 | unsigned long ticks(); 139 | 140 | /* Number of active tasks in the timer */ 141 | size_t size() const; 142 | 143 | /* True if there are no active tasks */ 144 | bool empty() const; 145 | ``` 146 | 147 | ### Installation 148 | 149 | [Check out the instructions](https://www.arduino.cc/en/Guide/Libraries) from Arduino. 150 | 151 | **OR** copy **src/arduino-timer.h** into your project folder *(you won't get managed updates this way)*. 152 | 153 | ### Examples 154 | 155 | Found in the **examples/** folder. 156 | 157 | The simplest example, blinking an LED every second *(from examples/blink)*: 158 | 159 | ```cpp 160 | #include 161 | 162 | auto timer = timer_create_default(); // create a timer with default settings 163 | 164 | bool toggle_led(void *) { 165 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED 166 | return true; // keep timer active? true 167 | } 168 | 169 | void setup() { 170 | pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT 171 | 172 | // call the toggle_led function every 1000 millis (1 second) 173 | timer.every(1000, toggle_led); 174 | } 175 | 176 | void loop() { 177 | timer.tick(); // tick the timer 178 | } 179 | ``` 180 | 181 | ### LICENSE 182 | 183 | Check the LICENSE file - 3-Clause BSD License 184 | 185 | ### Notes 186 | 187 | Currently only a software timer. Any blocking code delaying *timer*.**tick()** will prevent the timer from moving forward and calling any functions. 188 | 189 | The library does not do any dynamic memory allocation. 190 | 191 | The number of concurrent tasks is a compile time constant, meaning there is a limit to the number of concurrent tasks. The **in / at / every** functions return **NULL** if the *Timer* is full. 192 | 193 | A *Task* value is valid only for the timer that created it, and only for the lifetime of that timer. 194 | 195 | Change the number of concurrent tasks using the *Timer* constructors. Save memory by reducing the number, increase memory use by having more. The default is **TIMER_MAX_TASKS** which is currently 16. 196 | 197 | If you find this project useful, [consider becoming a sponsor.](https://github.com/sponsors/contrem) 198 | -------------------------------------------------------------------------------- /examples/blink/blink.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * timer_blink 3 | * 4 | * Blinks the built-in LED every second using the arduino-timer library. 5 | * 6 | */ 7 | 8 | #include 9 | 10 | auto timer = timer_create_default(); // create a timer with default settings 11 | 12 | bool toggle_led(void *) { 13 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED 14 | return true; // repeat? true 15 | } 16 | 17 | void setup() { 18 | pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT 19 | 20 | // call the toggle_led function every 1000 millis (1 second) 21 | timer.every(1000, toggle_led); 22 | } 23 | 24 | void loop() { 25 | timer.tick(); // tick the timer 26 | } 27 | -------------------------------------------------------------------------------- /examples/blink_micros/blink_micros.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * timer_blink_micros 3 | * 4 | * Blinks the built-in LED every second using the arduino-timer library. 5 | * 6 | */ 7 | 8 | #include 9 | 10 | Timer<1, micros> timer; // create a timer with 1 task and microsecond resolution 11 | 12 | bool toggle_led(void *) { 13 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED 14 | return true; // repeat? true 15 | } 16 | 17 | void setup() { 18 | pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT 19 | 20 | // call the toggle_led function every 1000000 micros (1 second) 21 | timer.every(1000000, toggle_led); 22 | } 23 | 24 | void loop() { 25 | timer.tick(); // tick the timer 26 | } 27 | -------------------------------------------------------------------------------- /examples/blink_print/blink_print.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * timer_blink_print 3 | * 4 | * Blinks the built-in LED every half second, and prints a messages every 5 | * second using the arduino-timer library. 6 | * 7 | */ 8 | 9 | #include 10 | 11 | auto timer = timer_create_default(); // create a timer with default settings 12 | 13 | bool toggle_led(void *) { 14 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED 15 | return true; // repeat? true 16 | } 17 | 18 | bool print_message(void *) { 19 | Serial.print("print_message: Called at: "); 20 | Serial.println(millis()); 21 | return true; // repeat? true 22 | } 23 | 24 | void setup() { 25 | Serial.begin(9600); 26 | pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT 27 | 28 | // call the toggle_led function every 500 millis (half second) 29 | timer.every(500, toggle_led); 30 | 31 | // call the print_message function every 1000 millis (1 second) 32 | timer.every(1000, print_message); 33 | } 34 | 35 | void loop() { 36 | timer.tick(); // tick the timer 37 | } 38 | -------------------------------------------------------------------------------- /examples/full/full.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * timer_full 3 | * 4 | * Full example using the arduino-timer library. 5 | * Shows: 6 | * - Setting a different number of tasks with microsecond resolution 7 | * - disabling a repeated function 8 | * - running a function after a delay 9 | * - cancelling a task 10 | * 11 | */ 12 | 13 | #include 14 | 15 | auto timer = timer_create_default(); // create a timer with default settings 16 | Timer<> default_timer; // save as above 17 | 18 | // create a timer that can hold 1 concurrent task, with microsecond resolution 19 | // and a custom handler type of 'const char * 20 | Timer<1, micros, const char *> u_timer; 21 | 22 | 23 | // create a timer that holds 16 tasks, with millisecond resolution, 24 | // and a custom handler type of 'const char * 25 | Timer<16, millis, const char *> t_timer; 26 | 27 | bool toggle_led(void *) { 28 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED 29 | return true; // repeat? true 30 | } 31 | 32 | bool print_message(const char *m) { 33 | Serial.print("print_message: "); 34 | Serial.println(m); 35 | return true; // repeat? true 36 | } 37 | 38 | size_t repeat_count = 1; 39 | bool repeat_x_times(void *opaque) { 40 | size_t limit = (size_t)opaque; 41 | 42 | Serial.print("repeat_x_times: "); 43 | Serial.print(repeat_count); 44 | Serial.print("/"); 45 | Serial.println(limit); 46 | 47 | return ++repeat_count <= limit; // remove this task after limit reached 48 | } 49 | 50 | void setup() { 51 | Serial.begin(9600); 52 | pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT 53 | 54 | // call the toggle_led function every 500 millis (half second) 55 | timer.every(500, toggle_led); 56 | 57 | // call the repeat_x_times function every 1000 millis (1 second) 58 | timer.every(1000, repeat_x_times, (void *)10); 59 | 60 | // call the print_message function every 1000 millis (1 second), 61 | // passing it an argument string 62 | t_timer.every(1000, print_message, "called every second"); 63 | 64 | // call the print_message function in five seconds 65 | t_timer.in(5000, print_message, "delayed five seconds"); 66 | 67 | // call the print_message function at time + 10 seconds 68 | t_timer.at(millis() + 10000, print_message, "call at millis() + 10 seconds"); 69 | 70 | // call the toggle_led function every 500 millis (half second) 71 | auto task = timer.every(500, toggle_led); 72 | timer.cancel(task); // this task is now cancelled, and will not run 73 | 74 | // call print_message in 2 seconds, but with microsecond resolution 75 | u_timer.in(2000000, print_message, "delayed two seconds using microseconds"); 76 | 77 | if (!u_timer.in(5000, print_message, "never printed")) { 78 | /* this fails because we created u_timer with only 1 concurrent task slot */ 79 | Serial.println("Failed to add microsecond event - timer full"); 80 | } 81 | } 82 | 83 | void loop() { 84 | timer.tick(); // tick the timer 85 | t_timer.tick(); 86 | u_timer.tick(); 87 | } 88 | -------------------------------------------------------------------------------- /extras/tests/Makefile: -------------------------------------------------------------------------------- 1 | TESTS := $(wildcard *Test) 2 | 3 | UNIXHOSTDUINO := ../../../UnixHostDuino/UnixHostDuino.mk 4 | UNIXHOSTDUINO_URL := https://github.com/bxparks/UnixHostDuino 5 | UNIXHOSTDUINO_TAG := 1936444e0097a37ad268e5e3d6e6cca8d957a86b 6 | AUNIT := ../../../AUnit/ 7 | AUNIT_URL := https://github.com/bxparks/AUnit 8 | AUNIT_TAG := e0aa8dfb635101d170cdb9fd9669e1d8047e2db1 9 | 10 | ### ### ### 11 | 12 | UHD_DIR := $(dir $(UNIXHOSTDUINO)) 13 | AU_DIR := $(dir $(AUNIT)) 14 | SUB_DIRS := $(addsuffix /, $(TESTS)) 15 | TEST_DIRS := $(join $(SUB_DIRS), $(TESTS)) 16 | 17 | INOS := $(addsuffix .ino, $(join $(SUB_DIRS), $(TESTS))) 18 | BINS := $(addsuffix .out, $(TEST_DIRS)) 19 | RUNS := $(addsuffix .run, $(BINS)) 20 | CLEANS := $(addsuffix .clean, $(SUB_DIRS)) 21 | 22 | all: $(BINS) 23 | 24 | $(UHD_DIR): 25 | git clone $(UNIXHOSTDUINO_URL) $@ 26 | cd $@; git checkout $(UNIXHOSTDUINO_TAG) 27 | 28 | $(AU_DIR): 29 | git clone $(AUNIT_URL) $@ 30 | cd $@; git checkout $(AUNIT_TAG) 31 | 32 | $(BINS): $(INOS) $(UHD_DIR) $(AU_DIR) 33 | # ==== Building: $(dir $@) 34 | $(MAKE) UNIXHOSTDUINO=../$(UNIXHOSTDUINO) -C $(dir $@) 35 | 36 | $(RUNS): $(BINS) 37 | # ==== Running: $(@:.run=) 38 | $(@:.run=) 39 | 40 | $(CLEANS): 41 | # ==== Cleaning: $(@:.clean=) 42 | $(MAKE) UNIXHOSTDUINO=../$(UNIXHOSTDUINO) -C $(@:.clean=) clean 43 | 44 | tests: $(BINS) 45 | 46 | runtests: $(RUNS) 47 | 48 | clean: $(CLEANS) 49 | 50 | test: runtests clean 51 | -------------------------------------------------------------------------------- /extras/tests/rollover-generic/rollover-generic.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Test timer rollover handling 3 | */ 4 | 5 | #include 6 | 7 | unsigned long wrapping_millis(); 8 | 9 | Timer<1, wrapping_millis> timer; // this timer will wrap 10 | auto _timer = timer_create_default(); // to count milliseconds 11 | 12 | unsigned long _millis = 0L; 13 | unsigned long wrapping_millis() 14 | { 15 | // uses _millis controled by _timer 16 | // 6-second time loop starting at rollover - 3 seconds 17 | if (_millis - (-3000) >= 6000) 18 | _millis = -3000; 19 | return _millis; 20 | } 21 | 22 | void setup() { 23 | pinMode(LED_BUILTIN, OUTPUT); 24 | _timer.every(1, [](void *) -> bool { 25 | ++_millis; // increase _millis every millisecond 26 | return true; 27 | }); 28 | 29 | // should blink the led every second, regardless of wrapping 30 | timer.every(1000, [](void *) -> bool { 31 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); 32 | return true; 33 | }); 34 | } 35 | 36 | void loop() { 37 | _timer.tick(); 38 | timer.tick(); 39 | } 40 | -------------------------------------------------------------------------------- /extras/tests/rollover/rollover.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Test timer rollover handling 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | auto timer = timer_create_default(); 9 | 10 | // https://arduino.stackexchange.com/questions/12587/how-can-i-handle-the-millis-rollover 11 | void set_millis(unsigned long ms) 12 | { 13 | extern unsigned long timer0_millis; 14 | ATOMIC_BLOCK (ATOMIC_RESTORESTATE) { 15 | timer0_millis = ms; 16 | } 17 | } 18 | 19 | void setup() { 20 | pinMode(LED_BUILTIN, OUTPUT); 21 | timer.every(1000, [](void *) -> bool { 22 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); 23 | return true; 24 | }); 25 | } 26 | 27 | void loop() { 28 | // 6-second time loop starting at rollover - 3 seconds 29 | if (millis() - (-3000) >= 6000) 30 | set_millis(-3000); 31 | timer.tick(); 32 | } 33 | -------------------------------------------------------------------------------- /extras/tests/timerMultiCancelTest/Makefile: -------------------------------------------------------------------------------- 1 | # See https://github.com/bxparks/UnixHostDuino for documentation about this 2 | # Makefile to compile and run Arduino programs natively on Linux or MacOS. 3 | # 4 | 5 | APP_NAME := timerMultiCancelTest 6 | ARDUINO_LIBS := AUnit arduino-timer 7 | CPPFLAGS += -Werror 8 | 9 | include $(UNIXHOSTDUINO) 10 | -------------------------------------------------------------------------------- /extras/tests/timerMultiCancelTest/timerMultiCancelTest.ino: -------------------------------------------------------------------------------- 1 | // arduino-timer unit tests 2 | // timerMultiCancelTest.ino unit test 3 | // find regressions when: 4 | // timer.cancel(...) is called twice and cancels an extra task 5 | // timer.in/at/every(...) return task id 0 (should mean "not created") 6 | // timer.in/at/every(...) return an in-use task id (1 id with 2 tasks) 7 | 8 | // Arduino "AUnit" library required 9 | 10 | // Required for UnixHostDuino emulation 11 | #include 12 | 13 | #if defined(UNIX_HOST_DUINO) 14 | #ifndef ARDUINO 15 | #define ARDUINO 100 16 | #endif 17 | #endif 18 | 19 | #include 20 | #include 21 | 22 | //#define DEBUG 23 | #ifdef DEBUG 24 | #define DEBUG_PRINT(x) Serial.print(x) 25 | #define DEBUG_PRINTLN(x) Serial.println(x) 26 | #else 27 | #define DEBUG_PRINT(x) 28 | #define DEBUG_PRINTLN(x) 29 | #endif 30 | 31 | auto timer = timer_create_default(); // create a timer with default settings 32 | 33 | // a generic task 34 | bool dummyTask(void*) { 35 | //digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED 36 | return true; // repeat? true 37 | } 38 | 39 | struct TaskInfo { 40 | Timer<>::Task id; // id to use to cancel 41 | unsigned long instanceNumber; 42 | }; 43 | 44 | static const int numTaskIds = 20; 45 | 46 | TaskInfo tasksToCancel[numTaskIds] = { { 0, 0 } }; 47 | 48 | unsigned long creationAttempts = 0; 49 | 50 | bool createTask(int index) { 51 | bool successful = true; // OK so far 52 | TaskInfo& taskInfo = tasksToCancel[index]; 53 | 54 | if (taskInfo.id != (Timer<>::Task)NULL) { 55 | // cancel task in slot 56 | auto staleId = taskInfo.id; 57 | int beforeSize = (int)timer.size(); 58 | successful &= timer.cancel(taskInfo.id); 59 | int afterSize = (int)timer.size(); 60 | successful &= (afterSize == beforeSize - 1); 61 | if (!successful) { 62 | Serial.println(F("could not cancel a task")); 63 | } else { 64 | 65 | successful &= !timer.cancel(staleId); // double cancel should not hit another task 66 | int afterSize2 = (int)timer.size(); 67 | successful &= (afterSize2 == beforeSize - 1); 68 | if (!successful) { 69 | Serial.println(F("second cancel removed another task")); 70 | } 71 | } 72 | } 73 | 74 | auto newId = timer.every(250, dummyTask); 75 | ++creationAttempts; 76 | 77 | static size_t timerSize = 0; 78 | auto newSize = timer.size(); 79 | if (timerSize != newSize) { 80 | timerSize = newSize; 81 | DEBUG_PRINT(F("Timer now has ")); 82 | DEBUG_PRINT(timerSize); 83 | DEBUG_PRINTLN(F(" tasks")); 84 | } 85 | 86 | if (newId == 0) { 87 | Serial.print(F("timer task creation failure on creation number ")); 88 | Serial.println(creationAttempts); 89 | successful = false; 90 | 91 | } else { 92 | 93 | // check for collisions before saving taskInfo 94 | for (int i = 0; i < numTaskIds; i++) { 95 | const TaskInfo& ti = tasksToCancel[i]; 96 | if (ti.id == newId) { 97 | successful = false; 98 | Serial.print(F("COLLISION FOUND! instance number: ")); 99 | Serial.print(creationAttempts); 100 | Serial.print(F(" hash ")); 101 | Serial.print(F("0x")); 102 | Serial.print((size_t)newId, HEX); 103 | Serial.print(F(" ")); 104 | Serial.print((size_t)newId, BIN); 105 | 106 | Serial.print(F(" matches hash for instance number: ")); 107 | Serial.println(ti.instanceNumber); 108 | } 109 | } 110 | taskInfo.id = newId; 111 | taskInfo.instanceNumber = creationAttempts; 112 | 113 | static const unsigned long reportCountTime = 10000; 114 | if (creationAttempts % reportCountTime == 0) { 115 | DEBUG_PRINT(creationAttempts / 1000); 116 | DEBUG_PRINTLN(F("k tasks created.")); 117 | } 118 | } 119 | return successful; 120 | } 121 | 122 | test(timerMultiCancel) { 123 | timer.cancel(); // ensure timer starts empty 124 | assertEqual((int)timer.size(), 0); 125 | creationAttempts = 0; 126 | 127 | // timer capacity is 0x10 -- stay below 128 | // load up some static tasks 129 | for (int i = 0; i < 6; i++) { 130 | assertTrue(createTask(i)); 131 | } 132 | 133 | assertEqual((int)timer.size(), 6); 134 | 135 | // cancel/recreate tasks 136 | //for (unsigned long groups = 0; groups < 30000UL; groups++) { 137 | unsigned long groups = 0; 138 | do { 139 | //for (unsigned long groups = 0; creationAttempts < 0x10010; groups++) { // trouble over 64k tasks? 140 | for (int i = 9; i < 0x10; i++) { 141 | assertTrue(createTask(i)); 142 | if (groups > 0) { 143 | // should be steady-state task size now 144 | assertEqual((int)timer.size(), 13); 145 | } 146 | } 147 | groups++; 148 | //} 149 | } while (creationAttempts < 0x10010); // no trouble over 64K tasks? 150 | 151 | Serial.print(creationAttempts); 152 | Serial.println(F(" tasks created.")); 153 | } 154 | 155 | void sketch(void) { 156 | Serial.println(); 157 | Serial.println(F("Running " __FILE__ ", Built " __DATE__)); 158 | } 159 | 160 | void setup() { 161 | ::delay(1000UL); // wait for stability on some boards to prevent garbage Serial 162 | Serial.begin(115200UL); // ESP8266 default of 74880 not supported on Linux 163 | while (!Serial) 164 | ; // for the Arduino Leonardo/Micro only 165 | sketch(); 166 | } 167 | 168 | void loop() { 169 | // Should get: 170 | // TestRunner summary: 171 | // passed, failed, skipped, timed out, out of test(s). 172 | aunit::TestRunner::run(); 173 | } 174 | -------------------------------------------------------------------------------- /extras/tests/timerTest/Makefile: -------------------------------------------------------------------------------- 1 | # See https://github.com/bxparks/UnixHostDuino for documentation about this 2 | # Makefile to compile and run Arduino programs natively on Linux or MacOS. 3 | # 4 | 5 | APP_NAME := timerTest 6 | ARDUINO_LIBS := AUnit arduino-timer 7 | CPPFLAGS += -Werror 8 | 9 | include $(UNIXHOSTDUINO) 10 | -------------------------------------------------------------------------------- /extras/tests/timerTest/timerTest.ino: -------------------------------------------------------------------------------- 1 | // 2 | // timerTest.ino 3 | // 4 | // Confirm arduino-timer behaves as expected. 5 | 6 | // UnixHostDuino emulation needs this include 7 | // (it's not picked up "for free" by Arduino IDE) 8 | // 9 | #include 10 | 11 | // fake it for UnixHostDuino emulation 12 | #if defined(UNIX_HOST_DUINO) 13 | # ifndef ARDUINO 14 | # define ARDUINO 100 15 | # endif 16 | #endif 17 | 18 | // 19 | // also, you need to provide your own forward references 20 | 21 | // These tests depend on the Arduino "AUnit" library 22 | #include 23 | #include 24 | 25 | //////////////////////////////////////////////////////// 26 | // You really want to be simulating time, rather than 27 | // forcing tests to slow down. 28 | // This simulation works across different processor families. 29 | // 30 | // BE CAREFUL to use delay() and millis() ONLY WHEN YOU MEAN IT! 31 | // 32 | // this namespace collision should help you make it more clear what you get 33 | // 34 | namespace simulateTime { 35 | 36 | //////////////////////////////////////////////////////// 37 | // Simulate delays and elapsed time 38 | // 39 | unsigned long simTime = 0; 40 | unsigned long simMillis(void) { 41 | return simTime; 42 | } 43 | 44 | void simDelay(unsigned long delayTime) { 45 | simTime += delayTime; 46 | } 47 | 48 | // TRAP ambiguous calls to delay() and millis() that are NOT simulated 49 | void delay(unsigned long delayTime) { 50 | simDelay(delayTime); 51 | } 52 | unsigned long millis(void) { 53 | return simMillis(); 54 | } 55 | 56 | }; // namespace simulateTime 57 | 58 | using namespace simulateTime; // and detect ambiguous function calls 59 | 60 | 61 | //////////////////////////////////////////////////////// 62 | // instrumented dummy tasks 63 | class DummyTask { 64 | public: 65 | DummyTask(unsigned long runTime, bool repeats = true) : 66 | busyTime(runTime), repeat(repeats) 67 | { 68 | reset(); 69 | }; 70 | 71 | unsigned long busyTime; 72 | unsigned long numRuns; 73 | unsigned long timeOfLastRun; 74 | bool repeat; 75 | 76 | bool run(void) { 77 | timeOfLastRun = simMillis(); 78 | simDelay(busyTime); 79 | numRuns++; 80 | return repeat; 81 | }; 82 | 83 | // run a task as an object method 84 | static bool runATask(void * aDummy) 85 | { 86 | DummyTask * myDummy = static_cast(aDummy); 87 | return myDummy->run(); 88 | }; 89 | 90 | void reset(void) { 91 | timeOfLastRun = 0; 92 | numRuns = 0; 93 | }; 94 | }; 95 | 96 | // trivial dummy task to perform 97 | bool no_op(void *) { 98 | return false; 99 | } 100 | 101 | // timer doesn't work as a local var; too big for the stack? 102 | // Since it's not on the stack it's harder to guarantee it starts empty 103 | // after the first test() 104 | // 105 | #define MAXTASKS 5 106 | Timer timer; 107 | 108 | void prepForTests(void) { 109 | timer.cancel(); 110 | simTime = 0ul; 111 | } 112 | 113 | ////////////////////////////////////////////////////////////////////////////// 114 | // confirm tasks can be cancelled. 115 | test(timer_cancelTasks) { 116 | prepForTests(); 117 | 118 | unsigned long aWait = timer.ticks(); // time to next active task 119 | assertEqual(aWait, 0ul); // no tasks! 120 | 121 | DummyTask dt_3millisec(3ul); 122 | DummyTask dt_5millisec(5ul); 123 | DummyTask dt_7millisec(7ul); 124 | 125 | auto inTask = timer.in(13ul, DummyTask::runATask, &dt_3millisec); 126 | auto atTask = timer.at(17ul, DummyTask::runATask, &dt_5millisec); 127 | auto everyTask = timer.every(19ul, DummyTask::runATask, &dt_7millisec); 128 | 129 | aWait = timer.ticks(); 130 | assertEqual(aWait, 13ul); // inTask delay 131 | 132 | timer.cancel(inTask); 133 | aWait = timer.ticks(); 134 | assertEqual(aWait, 17ul); // atTask delay 135 | 136 | timer.cancel(atTask); 137 | aWait = timer.ticks(); 138 | assertEqual(aWait, 19ul); // everyTask delay 139 | 140 | timer.cancel(everyTask); 141 | aWait = timer.ticks(); 142 | assertEqual(aWait, 0ul); // no tasks! all canceled 143 | }; 144 | 145 | ////////////////////////////////////////////////////////////////////////////// 146 | // confirm timer.at() behaviors 147 | // 148 | test(timer_at) { 149 | prepForTests(); 150 | 151 | unsigned long aWait = timer.ticks(); // time to next active task 152 | assertEqual(aWait, 0ul); // no tasks! 153 | assertEqual(simMillis(), 0ul); 154 | 155 | DummyTask waste_3ms(3ul); 156 | 157 | const unsigned long atTime = 17ul; 158 | const unsigned long lateStart = 4ul; 159 | simDelay(lateStart); 160 | 161 | // Note timer.at() returns the task ID. 162 | // Keep it to modify the task later. 163 | // 164 | timer.at(atTime, DummyTask::runATask, &waste_3ms); 165 | 166 | aWait = timer.tick(); 167 | assertEqual(aWait, atTime - lateStart); 168 | assertEqual(waste_3ms.numRuns, 0ul); 169 | 170 | for (unsigned long i = lateStart + 1ul; i < atTime; i++ ) { 171 | simDelay(1ul); 172 | aWait = timer.tick(); 173 | assertEqual(waste_3ms.numRuns, 0ul); // still waiting 174 | } 175 | 176 | simDelay(1ul); 177 | aWait = timer.tick(); 178 | assertEqual(waste_3ms.numRuns, 1ul); // triggered 179 | assertEqual(aWait, 0ul); 180 | 181 | simDelay(1ul); 182 | aWait = timer.tick(); 183 | assertEqual(waste_3ms.numRuns, 1ul); // not repeating 184 | 185 | aWait = timer.tick(); 186 | assertEqual(aWait, 0ul); // no tasks! all canceled 187 | }; 188 | 189 | ////////////////////////////////////////////////////////////////////////////// 190 | // confirm timer.in() behaviors 191 | // 192 | test(timer_in) { 193 | prepForTests(); 194 | 195 | unsigned long aWait = timer.ticks(); // time to next active task 196 | assertEqual(aWait, 0ul); // no tasks! 197 | assertEqual(simMillis(), 0ul); 198 | 199 | DummyTask waste_3ms(3); 200 | 201 | const unsigned long lateStart = 7; 202 | simDelay(lateStart); 203 | 204 | const unsigned long delayTime = 17; 205 | timer.in(delayTime, DummyTask::runATask, &waste_3ms); 206 | 207 | aWait = timer.tick(); 208 | assertEqual(aWait, delayTime); 209 | assertEqual(waste_3ms.numRuns, 0ul); 210 | 211 | for (unsigned long i = 1ul; i < delayTime; i++ ) { 212 | simDelay(1ul); 213 | aWait = timer.tick(); 214 | assertEqual(waste_3ms.numRuns, 0ul); // still waiting 215 | } 216 | 217 | simDelay(1ul); 218 | aWait = timer.tick(); 219 | assertEqual(waste_3ms.numRuns, 1ul); // triggered 220 | assertEqual(aWait, 0ul); 221 | 222 | simDelay(1ul); 223 | aWait = timer.tick(); 224 | assertEqual(waste_3ms.numRuns, 1ul); // not repeating 225 | 226 | aWait = timer.tick(); 227 | assertEqual(aWait, 0ul); // no tasks! all canceled 228 | }; 229 | 230 | ////////////////////////////////////////////////////////////////////////////// 231 | // confirm timer.every() behaviors 232 | // 233 | test(timer_every) { 234 | prepForTests(); 235 | 236 | unsigned long aWait = timer.ticks(); // time to next active task 237 | assertEqual(aWait, 0ul); // no tasks! 238 | assertEqual(simMillis(), 0ul); 239 | 240 | DummyTask waste_3ms(3ul); 241 | DummyTask waste_100ms_once(100ul, false); 242 | 243 | const unsigned long lateStart = 7ul; 244 | simDelay(lateStart); 245 | 246 | //const unsigned long delayTime = 17; 247 | timer.every(50ul, DummyTask::runATask, &waste_3ms); 248 | timer.every(200ul, DummyTask::runATask, &waste_100ms_once); 249 | 250 | aWait = timer.tick(); 251 | assertEqual(aWait, 50ul); 252 | assertEqual(waste_3ms.numRuns, 0ul); 253 | 254 | for (unsigned long i = 1ul; i < 1000ul; i++ ) { 255 | simDelay(1ul); 256 | aWait = timer.tick(); 257 | } 258 | 259 | assertEqual(waste_3ms.numRuns, 22ul); // triggered 260 | assertEqual(waste_100ms_once.numRuns, 1ul); // triggered 261 | 262 | aWait = timer.tick(); 263 | assertEqual(aWait, 39ul); // still a repeating task 264 | }; 265 | 266 | ////////////////////////////////////////////////////////////////////////////// 267 | // confirm calculated delays to next event in the timer are "reasonable" 268 | // reported by timer.tick() and timer.ticks(). 269 | // 270 | test(timer_delayToNextEvent) { 271 | prepForTests(); 272 | 273 | unsigned long aWait = timer.ticks(); // time to next active task 274 | assertEqual(aWait, 0ul); // no tasks! 275 | 276 | DummyTask dt_3millisec(3ul); 277 | DummyTask dt_5millisec(5ul); 278 | timer.every( 7ul, DummyTask::runATask, &dt_3millisec); 279 | timer.every(11ul, DummyTask::runATask, &dt_5millisec); 280 | 281 | assertEqual(dt_3millisec.numRuns, 0ul); 282 | 283 | unsigned long start = simMillis(); 284 | assertEqual(start, 0ul); // earliest task 285 | 286 | aWait = timer.ticks(); // time to next active task 287 | assertEqual(aWait, 7ul); // earliest task 288 | 289 | aWait = timer.tick(); // no tasks ran? 290 | unsigned long firstRunTime = simMillis() - start; 291 | assertEqual(firstRunTime, 0ul); 292 | 293 | simDelay(aWait); 294 | unsigned long firstActiveRunStart = simMillis(); 295 | aWait = timer.tick(); 296 | unsigned long firstTaskRunTime = simMillis() - firstActiveRunStart; 297 | assertEqual(firstTaskRunTime, 3ul); 298 | assertEqual(aWait, (unsigned long) (11 - 7 - 3)); // other pending task 299 | 300 | // run some tasks; count them. 301 | while (simMillis() < start + 1000ul) { 302 | aWait = timer.tick(); 303 | if(aWait == 0ul) { 304 | aWait = 1ul; // at least one millisec 305 | } 306 | simDelay(aWait); 307 | } 308 | 309 | // expect the other task causes some missed deadlines 310 | assertNear(dt_3millisec.numRuns, 100ul, 9ul); // 7+ millisecs apart 311 | // (ideally 142 runs) 312 | 313 | assertNear(dt_5millisec.numRuns, 90ul, 4ul); // 11+ millisecs apart 314 | // (ideally 90 runs) 315 | }; 316 | 317 | 318 | ////////////// ... so which sketch is this? 319 | void showID(void) 320 | { 321 | Serial.println(); 322 | Serial.println(F( "Running " __FILE__ ", Built " __DATE__)); 323 | }; 324 | 325 | ////////////////////////////////////////////////////////////////////////////// 326 | void setup() { 327 | ::delay(1000ul); // wait for stability on some boards to prevent garbage Serial 328 | Serial.begin(115200ul); // ESP8266 default of 74880 not supported on Linux 329 | while (!Serial); // for the Arduino Leonardo/Micro only 330 | showID(); 331 | } 332 | 333 | ////////////////////////////////////////////////////////////////////////////// 334 | void loop() { 335 | // Should get: 336 | // TestRunner summary: 337 | // passed, failed, skipped, timed out, out of test(s). 338 | aunit::TestRunner::run(); 339 | } 340 | -------------------------------------------------------------------------------- /extras/tests/unitTest/Makefile: -------------------------------------------------------------------------------- 1 | # See https://github.com/bxparks/UnixHostDuino for documentation about this 2 | # Makefile to compile and run Arduino programs natively on Linux or MacOS. 3 | # 4 | 5 | APP_NAME := unitTest 6 | ARDUINO_LIBS := AUnit arduino-timer 7 | CPPFLAGS += -Werror 8 | 9 | include $(UNIXHOSTDUINO) 10 | -------------------------------------------------------------------------------- /extras/tests/unitTest/unitTest.ino: -------------------------------------------------------------------------------- 1 | // arduino-timer unit tests 2 | // Arduino "AUnit" library required 3 | 4 | // Required for UnixHostDuino emulation 5 | #include 6 | 7 | #if defined(UNIX_HOST_DUINO) 8 | # ifndef ARDUINO 9 | # define ARDUINO 100 10 | # endif 11 | #endif 12 | 13 | #include 14 | #include 15 | 16 | class Clock { 17 | public: 18 | Clock(unsigned long start = 0UL) : _time(start) {} 19 | 20 | unsigned long tick(unsigned long amount = 1) { return inc(amount); } 21 | unsigned long inc(unsigned long amount = 1) { return _time += amount; } 22 | unsigned long dec(unsigned long amount = 1) { return _time -= amount; } 23 | unsigned long set(unsigned long time) { return _time = time; } 24 | 25 | void reset() { _time = 0UL; } 26 | 27 | unsigned long millis() { return _time; } 28 | unsigned long micros() { return _time; } 29 | unsigned long time() { return _time; } 30 | 31 | void delay(unsigned long amount) { inc(amount); } 32 | private: 33 | unsigned long _time; 34 | }; 35 | 36 | namespace CLOCK { 37 | Clock clock; 38 | 39 | unsigned long 40 | tick(unsigned long amount = 1) 41 | { 42 | return clock.tick(amount); 43 | } 44 | 45 | unsigned long 46 | set(unsigned long t) 47 | { 48 | return clock.set(t); 49 | } 50 | 51 | unsigned long 52 | millis() 53 | { 54 | return clock.millis(); 55 | } 56 | 57 | void 58 | delay(unsigned long amount) 59 | { 60 | return clock.delay(amount); 61 | } 62 | 63 | void 64 | reset() 65 | { 66 | clock.reset(); 67 | } 68 | }; 69 | 70 | using namespace CLOCK; 71 | 72 | struct Task { 73 | Clock *clock; 74 | 75 | unsigned long duration, 76 | runs, 77 | last_run; 78 | bool repeat; 79 | 80 | Task( 81 | Clock *clock, 82 | unsigned long duration, 83 | bool repeat = true 84 | ) : clock(clock), 85 | duration(duration), 86 | runs(0UL), 87 | last_run(0UL), 88 | repeat(repeat) 89 | {} 90 | 91 | bool 92 | run(Task *opaque) 93 | { 94 | last_run = this->clock->millis(); 95 | this->clock->inc(duration); 96 | ++runs; 97 | 98 | if (this != opaque) { 99 | ; 100 | } 101 | 102 | return repeat; 103 | } 104 | }; 105 | 106 | bool handler(Task *t) 107 | { 108 | return t->run(t); 109 | } 110 | 111 | Task 112 | make_task(unsigned long duration = 0UL, bool repeat = true) 113 | { 114 | return Task(&CLOCK::clock, duration, repeat); 115 | } 116 | 117 | void 118 | pre_test() 119 | { 120 | CLOCK::reset(); 121 | } 122 | 123 | test(timer) { 124 | auto timer = timer_create_default(); 125 | assertEqual(timer.ticks(), 0UL); 126 | } 127 | 128 | test(timer_in) { 129 | pre_test(); 130 | 131 | Timer<0x1, CLOCK::millis, Task *> timer; 132 | const unsigned long in = 3UL; 133 | 134 | auto task = make_task(); 135 | const auto r = timer.in(in, handler, &task); 136 | 137 | // assert task added 138 | assertNotEqual((unsigned long)r, 0UL); 139 | // assert pending task time 140 | assertEqual(in, timer.ticks()); 141 | 142 | // assert task has not run 143 | assertEqual(task.runs, 0UL); 144 | 145 | // tick forward in - 1 times 146 | for (unsigned long i = 1UL; i < in; ++i) { 147 | CLOCK::tick(); 148 | 149 | assertEqual(in - i, timer.ticks()); 150 | assertEqual(in - i, timer.tick()); 151 | 152 | // assert task has not run 153 | assertEqual(task.runs, 0UL); 154 | } 155 | 156 | CLOCK::tick(); 157 | 158 | // assert task is about to run 159 | assertEqual(0UL, timer.ticks()); 160 | assertEqual(0UL, timer.tick()); 161 | 162 | // assert task ran one time 163 | assertEqual(task.runs, 1UL); 164 | 165 | CLOCK::tick(); 166 | assertEqual(0UL, timer.ticks()); 167 | assertEqual(0UL, timer.tick()); 168 | 169 | // assert task did not run again 170 | assertEqual(task.runs, 1UL); 171 | } 172 | 173 | test(timer_at) { 174 | pre_test(); 175 | 176 | Timer<0x1, CLOCK::millis, Task *> timer; 177 | const unsigned long in = 3UL; 178 | 179 | auto task = make_task(); 180 | const auto r = timer.at(in, handler, &task); 181 | 182 | // assert task added 183 | assertNotEqual((unsigned long)r, 0UL); 184 | // assert pending task time 185 | assertEqual(in, timer.ticks()); 186 | 187 | // assert task has not run 188 | assertEqual(task.runs, 0UL); 189 | 190 | // tick forward in - 1 times 191 | for (unsigned long i = 1UL; i < in; ++i) { 192 | CLOCK::tick(); 193 | 194 | assertEqual(in - i, timer.ticks()); 195 | assertEqual(in - i, timer.tick()); 196 | 197 | // assert task has not run 198 | assertEqual(task.runs, 0UL); 199 | } 200 | 201 | CLOCK::tick(); 202 | 203 | // assert task is about to run 204 | assertEqual(0UL, timer.ticks()); 205 | assertEqual(0UL, timer.tick()); 206 | 207 | // assert task ran one time 208 | assertEqual(task.runs, 1UL); 209 | 210 | CLOCK::tick(); 211 | assertEqual(0UL, timer.ticks()); 212 | assertEqual(0UL, timer.tick()); 213 | 214 | // assert task did not run again 215 | assertEqual(task.runs, 1UL); 216 | } 217 | 218 | test(timer_every) { 219 | pre_test(); 220 | 221 | Timer<0x1, CLOCK::millis, Task *> timer; 222 | const unsigned long every = 2UL, until = 9UL;; 223 | auto task = make_task(); 224 | const auto r = timer.every(every, handler, &task); 225 | 226 | // assert task added 227 | assertNotEqual((unsigned long)r, 0UL); 228 | // assert pending task time 229 | assertEqual(every, timer.ticks()); 230 | 231 | // assert task has not run 232 | assertEqual(task.runs, 0UL); 233 | 234 | for (unsigned long i = 0UL; i < until; ++i) { 235 | unsigned long period = i ? i % every : every; 236 | 237 | assertEqual(period, timer.ticks()); 238 | const auto ticks = timer.tick(); 239 | assertEqual(ticks, timer.ticks()); 240 | 241 | CLOCK::tick(); 242 | } 243 | 244 | // assert task ran correct number of times 245 | assertEqual(task.runs, until / every); 246 | } 247 | 248 | test(timer_cancel) { 249 | pre_test(); 250 | 251 | Timer<0x1, CLOCK::millis, Task *> timer; 252 | const unsigned long in = 3UL; 253 | 254 | auto task = make_task(); 255 | auto r = timer.in(in, handler, &task); 256 | 257 | // assert task added 258 | assertNotEqual((unsigned long)r, 0UL); 259 | // assert pending task time 260 | assertEqual(in, timer.ticks()); 261 | 262 | // assert task has not run 263 | assertEqual(task.runs, 0UL); 264 | 265 | auto stale_r = r; 266 | const bool success = timer.cancel(r); 267 | 268 | // assert task found 269 | assertEqual(success, true); 270 | 271 | // assert task cleared 272 | assertEqual((unsigned long)r, 0UL); 273 | 274 | // assert task will not run 275 | assertEqual(timer.ticks(), 0UL); 276 | 277 | CLOCK::tick(in); 278 | 279 | // assert task did not run 280 | assertEqual(timer.tick(), 0UL); 281 | assertEqual(task.runs, 0UL); 282 | 283 | const bool fail = timer.cancel(r); 284 | 285 | // assert task not found 286 | assertEqual(fail, false); 287 | 288 | // stale task pointer has nothing to cancel 289 | const bool stale_cancel_fail = timer.cancel(stale_r); 290 | assertEqual(stale_cancel_fail, false); 291 | } 292 | 293 | test(timer_cancel_all) { 294 | pre_test(); 295 | 296 | Timer<0x2, CLOCK::millis, Task *> timer; 297 | const unsigned long in = 3UL; 298 | 299 | auto task = make_task(); 300 | auto r = timer.in(in, handler, &task); 301 | auto r2 = timer.in(in, handler, &task); 302 | 303 | // assert task added 304 | assertNotEqual((unsigned long)r, 0UL); 305 | assertNotEqual((unsigned long)r2, 0UL); 306 | assertNotEqual((unsigned long)r, (unsigned long)r2); 307 | 308 | // assert pending task time 309 | assertEqual(in, timer.ticks()); 310 | 311 | // assert task has not run 312 | assertEqual(task.runs, 0UL); 313 | 314 | timer.cancel(); 315 | 316 | // assert task will not run 317 | assertEqual(timer.ticks(), 0UL); 318 | 319 | CLOCK::tick(in); 320 | 321 | // assert task did not run 322 | assertEqual(timer.tick(), 0UL); 323 | assertEqual(task.runs, 0UL); 324 | } 325 | 326 | test(timer_ticks) { 327 | pre_test(); 328 | 329 | Timer<0x2, CLOCK::millis, Task *> timer; 330 | const unsigned long small = 3UL, big = 13UL; 331 | unsigned long delta = 0; 332 | 333 | auto small_task = make_task(); 334 | auto big_task = make_task(); 335 | 336 | auto r = timer.in(small, handler, &small_task); 337 | auto r2 = timer.every(big, handler, &big_task); 338 | 339 | // assert task added 340 | assertNotEqual((unsigned long)r, 0UL); 341 | assertNotEqual((unsigned long)r2, 0UL); 342 | 343 | assertEqual(small, timer.ticks()); // small pending 344 | 345 | // assert tasks have not run 346 | assertEqual(small_task.runs, 0UL); 347 | assertEqual(big_task.runs, 0UL); 348 | 349 | CLOCK::tick(small); 350 | 351 | assertEqual(0UL, timer.ticks()); // small ready to run 352 | 353 | timer.tick(); // run small 354 | 355 | assertEqual(small_task.runs, 1UL); // small ran once 356 | assertEqual(big_task.runs, 0UL); // big has not run 357 | 358 | delta = big - small; 359 | assertEqual(delta, timer.ticks()); // big pending 360 | 361 | CLOCK::tick(); // tick one forward 362 | --delta; 363 | assertEqual(delta, timer.ticks()); // check the reduction 364 | 365 | CLOCK::tick(delta); 366 | assertEqual(0UL, timer.ticks()); // big ready to run 367 | 368 | timer.tick(); // run big 369 | 370 | assertEqual(small_task.runs, 1UL); // small ran once only 371 | assertEqual(big_task.runs, 1UL); // big ran once 372 | 373 | assertEqual(big, timer.ticks()); // big pending again 374 | } 375 | 376 | test(timer_size) { 377 | pre_test(); 378 | 379 | Timer<0x2, CLOCK::millis, Task *> timer; 380 | 381 | assertEqual((unsigned long) timer.size(), 0UL); 382 | 383 | auto t = make_task(); 384 | 385 | auto r = timer.in(0UL, handler, &t); 386 | 387 | assertNotEqual((unsigned long)r, 0UL); 388 | assertEqual((unsigned long) timer.size(), 1UL); 389 | 390 | r = timer.in(0UL, handler, &t); 391 | 392 | assertNotEqual((unsigned long)r, 0UL); 393 | assertEqual((unsigned long) timer.size(), 2UL); 394 | 395 | timer.cancel(); 396 | 397 | assertEqual((unsigned long) timer.size(), 0UL); 398 | } 399 | 400 | test(timer_empty) { 401 | pre_test(); 402 | 403 | Timer<0x1, CLOCK::millis, Task *> timer; 404 | 405 | assertEqual(timer.empty(), true); 406 | 407 | auto t = make_task(); 408 | 409 | auto r = timer.in(0UL, handler, &t); 410 | 411 | assertNotEqual((unsigned long)r, 0UL); 412 | assertEqual(timer.empty(), false); 413 | 414 | timer.cancel(); 415 | 416 | assertEqual(timer.empty(), true); 417 | } 418 | 419 | test(timer_rollover_every) { 420 | pre_test(); 421 | 422 | Timer<0x1, CLOCK::millis, Task *> timer; 423 | const unsigned long every = 2UL, until = 257UL, rollover = 32UL; 424 | auto task = make_task(); 425 | const auto r = timer.every(every, handler, &task); 426 | 427 | // assert task added 428 | assertNotEqual((unsigned long)r, 0UL); 429 | // assert pending task time 430 | assertEqual(every, timer.ticks()); 431 | 432 | // assert task has not run 433 | assertEqual(task.runs, 0UL); 434 | 435 | for (unsigned long i = 0UL; i < until; ++i) { 436 | unsigned long period = i ? i % every : every; 437 | 438 | assertEqual(period, timer.ticks()); 439 | const auto ticks = timer.tick(); 440 | assertEqual(ticks, timer.ticks()); 441 | 442 | CLOCK::tick(); 443 | 444 | // rollover / overflow clock back to 0 445 | CLOCK::set(CLOCK::millis() % rollover); 446 | } 447 | 448 | // assert task ran correct number of times 449 | assertEqual(task.runs, until / every); 450 | } 451 | 452 | test(timer_rollover) { 453 | pre_test(); 454 | 455 | Timer<0x1, CLOCK::millis, Task *> timer; 456 | const unsigned long in = 2UL; 457 | 458 | CLOCK::set((unsigned long)-1); // start clock at max value 459 | 460 | auto task = make_task(); 461 | const auto r = timer.in(in, handler, &task); 462 | 463 | // assert task added 464 | assertNotEqual((unsigned long)r, 0UL); 465 | // assert pending task time 466 | assertEqual(in, timer.ticks()); 467 | 468 | // assert task has not run 469 | assertEqual(task.runs, 0UL); 470 | 471 | // roll clock over - one tick 472 | CLOCK::tick(); 473 | assertEqual(in - 1UL, timer.ticks()); 474 | 475 | timer.tick(); 476 | 477 | // assert task has not run 478 | assertEqual(task.runs, 0UL); 479 | 480 | CLOCK::tick(); 481 | assertEqual(0UL, timer.ticks()); // ready to run 482 | timer.tick(); 483 | 484 | // assert task ran 485 | assertEqual(task.runs, 1UL); 486 | } 487 | 488 | void 489 | sketch(void) 490 | { 491 | Serial.println(); 492 | Serial.println(F("Running " __FILE__ ", Built " __DATE__)); 493 | } 494 | 495 | void 496 | setup() 497 | { 498 | ::delay(1000UL); // wait for stability on some boards to prevent garbage Serial 499 | Serial.begin(115200UL); // ESP8266 default of 74880 not supported on Linux 500 | while (!Serial); // for the Arduino Leonardo/Micro only 501 | sketch(); 502 | } 503 | 504 | void 505 | loop() 506 | { 507 | // Should get: 508 | // TestRunner summary: 509 | // passed, failed, skipped, timed out, out of test(s). 510 | aunit::TestRunner::run(); 511 | } 512 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For Timer 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | Timer KEYWORD1 10 | Task KEYWORD1 11 | handler_t KEYWORD1 12 | 13 | ####################################### 14 | # Methods and Functions (KEYWORD2) 15 | ####################################### 16 | 17 | in KEYWORD2 18 | at KEYWORD2 19 | every KEYWORD2 20 | tick KEYWORD2 21 | ticks KEYWORD2 22 | cancel KEYWORD2 23 | size KEYWORD2 24 | empty KEYWORD2 25 | 26 | ####################################### 27 | # Instances (KEYWORD2) 28 | ####################################### 29 | 30 | ####################################### 31 | # Constants (LITERAL1) 32 | ####################################### 33 | 34 | TIMER_MAX_TASKS LITERAL1 35 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=arduino-timer 2 | version=3.0.1 3 | 4 | author=Michael Contreras 5 | maintainer=Michael Contreras 6 | sentence=Timer library for delaying function calls 7 | paragraph=Simple non-blocking timer library for calling functions in / at / every specified units of time. Supports millis, micros, time rollover, and compile time configurable number of tasks. 8 | category=Timing 9 | url=https://github.com/contrem/arduino-timer 10 | architectures=* 11 | includes=arduino-timer.h 12 | -------------------------------------------------------------------------------- /src/arduino-timer.h: -------------------------------------------------------------------------------- 1 | /** 2 | arduino-timer - library for delaying function calls 3 | 4 | Copyright (c) 2018, Michael Contreras 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | 1. Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 14 | 2. Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 18 | 3. Neither the name of the copyright holder nor the names of its 19 | contributors may be used to endorse or promote products derived from 20 | this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 23 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 24 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 25 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | #ifndef _CM_ARDUINO_TIMER_H__ 36 | #define _CM_ARDUINO_TIMER_H__ 37 | 38 | #include 39 | 40 | #include 41 | 42 | #ifndef TIMER_MAX_TASKS 43 | #define TIMER_MAX_TASKS 0x10 44 | #endif 45 | 46 | #define _timer_foreach_task(T, task) \ 47 | for (T task = tasks; task < tasks + max_tasks; ++task) 48 | 49 | #define timer_foreach_task(T) \ 50 | _timer_foreach_task(struct task *, T) 51 | 52 | #define timer_foreach_const_task(T) \ 53 | _timer_foreach_task(const struct task *, T) 54 | 55 | template < 56 | size_t max_tasks = TIMER_MAX_TASKS, /* max allocated tasks */ 57 | unsigned long (*time_func)() = millis, /* time function for timer */ 58 | typename T = void * /* handler argument type */ 59 | > 60 | class Timer { 61 | public: 62 | 63 | typedef void * Task; /* public task handle */ 64 | typedef bool (*handler_t)(T opaque); /* task handler func signature */ 65 | 66 | /* Calls handler with opaque as argument in delay units of time */ 67 | Task 68 | in(unsigned long delay, handler_t h, T opaque = T()) 69 | { 70 | return add_task(time_func(), delay, h, opaque); 71 | } 72 | 73 | /* Calls handler with opaque as argument at time */ 74 | Task 75 | at(unsigned long time, handler_t h, T opaque = T()) 76 | { 77 | const unsigned long now = time_func(); 78 | return add_task(now, time - now, h, opaque); 79 | } 80 | 81 | /* Calls handler with opaque as argument every interval units of time */ 82 | Task 83 | every(unsigned long interval, handler_t h, T opaque = T()) 84 | { 85 | return add_task(time_func(), interval, h, opaque, interval); 86 | } 87 | 88 | /* Cancel the timer task */ 89 | bool 90 | cancel(Task &task) 91 | { 92 | struct task * const t = static_cast(task); 93 | 94 | if (t && (tasks <= t) && (t < tasks + max_tasks) && t->handler) { 95 | remove(t); 96 | task = static_cast(NULL); 97 | return true; 98 | } 99 | 100 | return false; 101 | } 102 | 103 | /* Cancel all timer tasks */ 104 | void 105 | cancel() 106 | { 107 | timer_foreach_task(t) { 108 | remove(t); 109 | } 110 | } 111 | 112 | /* Ticks the timer forward - call this function in loop() */ 113 | unsigned long 114 | tick() 115 | { 116 | tick(); 117 | return ticks(); 118 | } 119 | 120 | template void 121 | tick() 122 | { 123 | timer_foreach_task(task) { 124 | if (task->handler) { 125 | const unsigned long t = time_func(); 126 | const unsigned long duration = t - task->start; 127 | 128 | if (duration >= task->expires) { 129 | task->repeat = task->handler(task->opaque) && task->repeat; 130 | 131 | if (task->repeat) task->start = t; 132 | else remove(task); 133 | } 134 | } 135 | } 136 | } 137 | 138 | /* Ticks until the next event */ 139 | unsigned long 140 | ticks() const 141 | { 142 | unsigned long ticks = ULONG_MAX, elapsed; 143 | const unsigned long start = time_func(); 144 | 145 | timer_foreach_const_task(task) { 146 | if (task->handler) { 147 | const unsigned long t = time_func(); 148 | const unsigned long duration = t - task->start; 149 | 150 | if (duration >= task->expires) { 151 | ticks = 0; 152 | break; 153 | } else { 154 | const unsigned long remaining = task->expires - duration; 155 | ticks = remaining < ticks ? remaining : ticks; 156 | } 157 | } 158 | } 159 | 160 | elapsed = time_func() - start; 161 | 162 | if (elapsed >= ticks || ticks == ULONG_MAX) ticks = 0; 163 | else ticks -= elapsed; 164 | 165 | return ticks; 166 | } 167 | 168 | /* Number of active tasks in the timer */ 169 | size_t 170 | size() const 171 | { 172 | size_t s = 0; 173 | 174 | timer_foreach_const_task(task) { 175 | if (task->handler) ++s; 176 | } 177 | 178 | return s; 179 | } 180 | 181 | /* True if there are no active tasks */ 182 | bool 183 | empty() const 184 | { 185 | timer_foreach_const_task(task) { 186 | if (task->handler) return false; 187 | } 188 | 189 | return true; 190 | } 191 | 192 | Timer() : tasks{} {} 193 | 194 | private: 195 | 196 | struct task { 197 | handler_t handler; /* task handler callback func */ 198 | T opaque; /* argument given to the callback handler */ 199 | unsigned long start, 200 | expires, /* when the task expires */ 201 | repeat; /* repeat task */ 202 | } tasks[max_tasks]; 203 | 204 | inline 205 | void 206 | remove(struct task *task) 207 | { 208 | task->handler = NULL; 209 | task->opaque = T(); 210 | task->start = 0; 211 | task->expires = 0; 212 | task->repeat = 0; 213 | } 214 | 215 | inline 216 | struct task * 217 | next_task_slot() 218 | { 219 | timer_foreach_task(slot) { 220 | if (slot->handler == NULL) return slot; 221 | } 222 | 223 | return NULL; 224 | } 225 | 226 | inline 227 | struct task * 228 | add_task(unsigned long start, unsigned long expires, 229 | handler_t h, T opaque, bool repeat = 0) 230 | { 231 | struct task * const slot = next_task_slot(); 232 | 233 | if (!slot) return NULL; 234 | 235 | slot->handler = h; 236 | slot->opaque = opaque; 237 | slot->start = start; 238 | slot->expires = expires; 239 | slot->repeat = repeat; 240 | 241 | return slot; 242 | } 243 | }; 244 | 245 | /* create a timer with the default settings */ 246 | inline Timer<> 247 | timer_create_default() 248 | { 249 | return Timer<>(); 250 | } 251 | 252 | #undef _timer_foreach_task 253 | #undef timer_foreach_task 254 | #undef timer_foreach_const_task 255 | 256 | #endif 257 | -------------------------------------------------------------------------------- /src/timer.h: -------------------------------------------------------------------------------- 1 | #error "Including this file is deprecated. Please #include instead." 2 | 3 | #include 4 | --------------------------------------------------------------------------------