├── .arduino-ci.yml ├── .github ├── FUNDING.yml └── workflows │ ├── arduino-lint.yml │ ├── arduino_test_runner.yml │ └── jsoncheck.yml ├── CHANGELOG.md ├── CountDown.cpp ├── CountDown.h ├── LICENSE ├── README.md ├── examples ├── countdown_DHMS │ └── countdown_DHMS.ino ├── countdown_adaptive_display │ └── countdown_adaptive_display.ino ├── countdown_demo1 │ └── countdown_demo1.ino ├── countdown_demo2 │ └── countdown_demo2.ino ├── countdown_overflow_test │ └── countdown_overflow_test.ino ├── countdown_restart │ └── countdown_restart.ino ├── countdown_resume │ └── countdown_resume.ino └── countdown_setResolution │ └── countdown_setResolution.ino ├── keywords.txt ├── library.json ├── library.properties └── test └── unit_test_001.cpp /.arduino-ci.yml: -------------------------------------------------------------------------------- 1 | platforms: 2 | rpipico: 3 | board: rp2040:rp2040:rpipico 4 | package: rp2040:rp2040 5 | gcc: 6 | features: 7 | defines: 8 | - ARDUINO_ARCH_RP2040 9 | warnings: 10 | flags: 11 | 12 | packages: 13 | rp2040:rp2040: 14 | url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json 15 | 16 | compile: 17 | # Choosing to run compilation tests on 2 different Arduino platforms 18 | platforms: 19 | - uno 20 | # - due 21 | # - zero 22 | # - leonardo 23 | - m4 24 | - esp32 25 | # - esp8266 26 | # - mega2560 27 | - rpipico 28 | 29 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: RobTillaart 4 | custom: "https://www.paypal.me/robtillaart" 5 | -------------------------------------------------------------------------------- /.github/workflows/arduino-lint.yml: -------------------------------------------------------------------------------- 1 | name: Arduino-lint 2 | 3 | on: [push, pull_request] 4 | jobs: 5 | lint: 6 | runs-on: ubuntu-latest 7 | timeout-minutes: 5 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: arduino/arduino-lint-action@v1 11 | with: 12 | library-manager: update 13 | compliance: strict -------------------------------------------------------------------------------- /.github/workflows/arduino_test_runner.yml: -------------------------------------------------------------------------------- 1 | name: Arduino CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | runTest: 7 | runs-on: ubuntu-latest 8 | timeout-minutes: 20 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: ruby/setup-ruby@v1 13 | with: 14 | ruby-version: 2.6 15 | - run: | 16 | gem install arduino_ci 17 | arduino_ci.rb 18 | -------------------------------------------------------------------------------- /.github/workflows/jsoncheck.yml: -------------------------------------------------------------------------------- 1 | name: JSON check 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.json' 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: json-syntax-check 16 | uses: limitusus/json-syntax-check@v2 17 | with: 18 | pattern: "\\.json$" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log CountDown 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | 9 | ## [0.3.4] - 2025-03-20 10 | - add **void resume()** to replace cont() 11 | - update examples 12 | - update readme.md 13 | - minor edits 14 | 15 | 16 | ## [0.3.3] - 2024-04-11 17 | - update GitHub actions 18 | - minor edits 19 | 20 | 21 | ## [0.3.2] - 2023-10-18 22 | - update readme.md (badges) 23 | 24 | ## [0.3.1] - 2023-03-14 25 | - fix #14 add **restart()** function. 26 | - add demo for restart **countdown_restart.ino** 27 | 28 | ## [0.3.0] - 2023-01-10 29 | - fix #12 MINUTES bug 30 | - label enum resolution to be printable u, m, s, M. 31 | - add getUnits() 32 | - add isStopped() for convenience. 33 | - fix initialization of private variables. 34 | - update GitHub actions 35 | - update license 36 | - update readme.md 37 | - update unit tests 38 | 39 | ---- 40 | 41 | ## [0.2.7] - 2022-10-30 42 | - add changelog.md 43 | - add rp2040 to build-CI 44 | - minor edit unit test 45 | 46 | ## [0.2.6] - 2021-12-14 47 | - update library.json, license 48 | 49 | ## [0.2.5] - 2021-10-19 50 | - update Arduino-CI 51 | - add badges 52 | 53 | ## [0.2.4] - 2021-01-15 54 | - start detect overflow now 55 | 56 | ## [0.2.3] - 2020-12-17 57 | - add Arduino-CI + unit test 58 | 59 | ## [0.2.2] - 2020-07-08 60 | - add MINUTES 61 | - refactor 62 | 63 | ## [0.2.1] - 2020-06-05 64 | - fix library.json 65 | 66 | ## [0.2.0] - 2020-03-29 67 | - add #pragma once 68 | - removed pre 1.0 support 69 | 70 | ---- 71 | 72 | ## [0.1.3] - 2017-07-16 73 | - TODO improved seconds - OdoMeter see below ... TODO 74 | 75 | ## [0.1.2] - 2017-07-16 76 | - add start(days, hours, minutes, seconds) + cont() == continue countdown 77 | 78 | ## [0.1.1] - 2015-10-29 79 | - add start(h, m, s) 80 | 81 | ## [0.1.0] - 2015-10-27 82 | - initial version 83 | -------------------------------------------------------------------------------- /CountDown.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: CountDown.cpp 3 | // AUTHOR: Rob Tillaart 4 | // VERSION: 0.3.4 5 | // PURPOSE: CountDown library for Arduino 6 | // URL: https://github.com/RobTillaart/CountDown 7 | 8 | 9 | #include "CountDown.h" 10 | 11 | 12 | CountDown::CountDown(const enum Resolution res) 13 | { 14 | _state = CountDown::STOPPED; 15 | _remaining = 0; 16 | _startTime = 0; 17 | // _res = MILLIS; // set in setResolution 18 | // _ticks = 0; // set in setResolution 19 | setResolution(res); 20 | stop(); 21 | } 22 | 23 | 24 | void CountDown::setResolution(const enum Resolution res) 25 | { 26 | _res = res; 27 | _ticks = 0; 28 | } 29 | 30 | 31 | char CountDown::getUnits() 32 | { 33 | return _res; 34 | } 35 | 36 | 37 | bool CountDown::start(uint32_t ticks) 38 | { 39 | _ticks = ticks; 40 | _state = CountDown::RUNNING; 41 | if (_res == MICROS) 42 | { 43 | _startTime = micros(); 44 | } 45 | else 46 | { 47 | _startTime = millis(); 48 | } 49 | return true; // can not overflow 50 | } 51 | 52 | 53 | bool CountDown::start(uint8_t days, uint16_t hours, uint32_t minutes, uint32_t seconds) 54 | { 55 | float _days = seconds / 86400.0 + minutes / 1440.0 + hours / 24.0 + days; 56 | bool rv = (_days < 49.7102696); 57 | 58 | uint32_t ticks = 86400UL * days + 3600UL * hours + 60UL * minutes + seconds; 59 | // prevent underlying millis() overflow 60 | // 4294967 = number of SECONDS in 2^32 milliseconds 61 | if (ticks > 4294967) ticks = 4294967; 62 | setResolution(SECONDS); 63 | start(ticks); 64 | 65 | return rv; 66 | } 67 | 68 | 69 | bool CountDown::start(uint8_t days, uint16_t hours, uint32_t minutes) 70 | { 71 | float _days = minutes / 1440.0 + hours / 24.0 + days; 72 | bool rv = (_days < 49.7102696); 73 | 74 | uint32_t ticks = 1440UL * days + 60UL * hours + minutes; 75 | // prevent underlying millis() overflow 76 | // 71582 = number of MINUTES in 2^32 milliseconds 77 | if (ticks > 71582) ticks = 71582; 78 | setResolution(MINUTES); 79 | start(ticks); 80 | 81 | return rv; 82 | } 83 | 84 | 85 | void CountDown::stop() 86 | { 87 | calcRemaining(); 88 | _state = CountDown::STOPPED; 89 | } 90 | 91 | 92 | void CountDown::resume() 93 | { 94 | if (_state == CountDown::STOPPED) 95 | { 96 | start(_remaining); 97 | } 98 | } 99 | 100 | 101 | void CountDown::restart() 102 | { 103 | start(_ticks); 104 | } 105 | 106 | 107 | uint32_t CountDown::remaining() 108 | { 109 | calcRemaining(); 110 | if (_remaining == 0) _state = CountDown::STOPPED; 111 | return _remaining; 112 | } 113 | 114 | 115 | bool CountDown::isRunning() 116 | { 117 | calcRemaining(); 118 | return (_state == CountDown::RUNNING); 119 | } 120 | 121 | 122 | bool CountDown::isStopped() 123 | { 124 | calcRemaining(); 125 | return (_state == CountDown::STOPPED); 126 | } 127 | 128 | 129 | ////////////////////////////////////////////////// 130 | // 131 | // PRIVATE 132 | // 133 | void CountDown::calcRemaining() 134 | { 135 | uint32_t t = 0; 136 | if (_state == CountDown::RUNNING) 137 | { 138 | switch(_res) 139 | { 140 | case MINUTES: 141 | t = (millis() - _startTime) / 60000UL; 142 | break; 143 | case SECONDS: 144 | t = (millis() - _startTime) / 1000UL;; 145 | break; 146 | case MICROS: 147 | t = micros() - _startTime; 148 | break; 149 | case MILLIS: 150 | default: 151 | t = millis() - _startTime; 152 | break; 153 | } 154 | _remaining = _ticks > t ? _ticks - t : 0; 155 | if (_remaining == 0) 156 | { 157 | _state = CountDown::STOPPED; 158 | } 159 | return; 160 | } 161 | } 162 | 163 | 164 | // -- END OF FILE -- 165 | 166 | -------------------------------------------------------------------------------- /CountDown.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // 3 | // FILE: CountDown.h 4 | // AUTHOR: Rob Tillaart 5 | // VERSION: 0.3.4 6 | // PURPOSE: CountDown library for Arduino 7 | // URL: https://github.com/RobTillaart/CountDown 8 | 9 | 10 | #include "Arduino.h" 11 | 12 | #define COUNTDOWN_LIB_VERSION (F("0.3.4")) 13 | 14 | 15 | class CountDown 16 | { 17 | public: 18 | enum Resolution { MICROS = 'u', MILLIS = 'm', SECONDS = 's', MINUTES = 'M' }; 19 | 20 | explicit CountDown(const enum Resolution res = MILLIS); 21 | 22 | void setResolution(const enum Resolution res = MILLIS); 23 | enum Resolution resolution() { return _res; }; 24 | char getUnits(); 25 | 26 | // one need to set the resolution before calling start(ticks). 27 | bool start(uint32_t ticks); 28 | // Implicit set resolution to SECONDS. 29 | bool start(uint8_t days, uint16_t hours, uint32_t minutes, uint32_t seconds); 30 | // Implicit set resolution to MINUTES. 31 | bool start(uint8_t days, uint16_t hours, uint32_t minutes); 32 | 33 | void stop(); 34 | void resume(); 35 | void restart(); 36 | 37 | uint32_t remaining(); 38 | bool isRunning(); 39 | bool isStopped(); 40 | 41 | // obsolete in future 42 | void cont() { resume(); }; // replaced by resume() 43 | 44 | private: 45 | enum State { RUNNING, STOPPED }; 46 | 47 | uint32_t _ticks; 48 | uint32_t _remaining; 49 | enum State _state; 50 | enum Resolution _res; 51 | uint32_t _startTime; 52 | void calcRemaining(); 53 | }; 54 | 55 | 56 | // -- END OF FILE -- 57 | 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-2025 Rob Tillaart 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Arduino CI](https://github.com/RobTillaart/CountDown/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci) 3 | [![Arduino-lint](https://github.com/RobTillaart/CountDown/actions/workflows/arduino-lint.yml/badge.svg)](https://github.com/RobTillaart/CountDown/actions/workflows/arduino-lint.yml) 4 | [![JSON check](https://github.com/RobTillaart/CountDown/actions/workflows/jsoncheck.yml/badge.svg)](https://github.com/RobTillaart/CountDown/actions/workflows/jsoncheck.yml) 5 | [![GitHub issues](https://img.shields.io/github/issues/RobTillaart/CountDown.svg)](https://github.com/RobTillaart/CountDown/issues) 6 | 7 | [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/RobTillaart/CountDown/blob/master/LICENSE) 8 | [![GitHub release](https://img.shields.io/github/release/RobTillaart/CountDown.svg?maxAge=3600)](https://github.com/RobTillaart/CountDown/releases) 9 | [![PlatformIO Registry](https://badges.registry.platformio.org/packages/robtillaart/library/CountDown.svg)](https://registry.platformio.org/libraries/robtillaart/CountDown) 10 | 11 | 12 | # CountDown 13 | 14 | Arduino Library to implement a CountDown clock (in software polling, no hardware timer). 15 | 16 | 17 | ## Description 18 | 19 | The CountDown library is a clock that counts down from a given time to zero, like an hour glass. 20 | It does not call any function or so when reaching zero as the user is responsible to check the time remaining. 21 | Typically one checks the remaining time at least once in every **loop()**. 22 | 23 | A CountDown object can be used for 24 | - to detect a timeout 25 | - to show to a user how long to wait e.g. for a traffic light to become green. 26 | - to learn count backwards (educational) 27 | - etc. 28 | 29 | Under the hood the CountDown object uses **micros()** or **millis()** which results in a maximum time 30 | of 4294 seconds in micros (about 1h 10m) or about 49+ days when using milliseconds. 31 | 32 | For longer periods one could cascade CountDown objects, so when one is finished the next one starts. 33 | 34 | Note the CountDown object is as accurate as the underlying **millis()** or **micros()**. 35 | Interrupts etc. might cause deviations. 36 | 37 | 38 | ### Related 39 | 40 | - https://github.com/RobTillaart/printHelpers 41 | - https://github.com/RobTillaart/stopWatch_RT 42 | - https://github.com/RobTillaart/CountDown 43 | - https://github.com/RobTillaart/timing wrappers around millis() and microc() 44 | 45 | 46 | ## Interface 47 | 48 | ```cpp 49 | #include "CountDown.h" 50 | ``` 51 | 52 | The main functions of the CountDown clock are straightforward: 53 | 54 | ### Constructor 55 | 56 | - **CountDown(const enum Resolution res = MILLIS)** constructor, with default resolution of milliseconds. 57 | - **void setResolution(const enum Resolution res = MILLIS)** set the resolution, 58 | default to MILLIS. See table below. 59 | - **enum Resolution resolution()** return the current resolution (integer). 60 | - **char getUnits()** return the current resolution as printable char (u,m,s,M) 61 | 62 | ### Start / Stop functions 63 | 64 | - **bool start(uint32_t ticks)** (re)start in current resolution. 65 | Typical used for MILLIS and MICROS which must be set manually. 66 | - **bool start(uint8_t days, uint16_t hours, uint32_t minutes, uint32_t seconds)** Implicit set resolution to SECONDS. 67 | Returns false if total exceeds 2^32 milliseconds ~49 days. 68 | Note that **remaining()** will report in SECONDS. 69 | - **bool start(uint8_t days, uint16_t hours, uint32_t minutes)** Implicit set resolution to MINUTES. 70 | Returns false if total exceeds 2^32 milliseconds ~49 days. 71 | Note that **remaining()** will report in MINUTES. 72 | - **void stop()** stop the count down. 73 | - **void resume()** resumes / continues the count down. 74 | - **void restart()** restart the CountDown with the same resolution and ticks as before. 75 | resets the \_ticks and starts again. 76 | 77 | Obsolete in future (0.4.0). 78 | - **void cont()** resumes / continue the count down. 79 | *(note continue is a C-Keyword)* 80 | 81 | ### Status 82 | 83 | - **uint32_t remaining()** returns the remaining ticks in current resolution. 84 | - **bool isRunning()** idem. 85 | - **bool isStopped()** idem. 86 | 87 | ## Operation 88 | 89 | The function **start(days, hours, minutes, seconds)** allows all combinations 90 | as long as the total time not exceeds 2^32 milliseconds. 91 | The function will return false if it exceeds this (rounded) maximum. 92 | Example calls are: 93 | - four hundred minutes **start(0, 0, 400, 0)** 94 | - a million seconds **start(0, 0, 0, 1000000)** 95 | - an unusual mix **start(0, 0, 400, 1000)** as parameter. 96 | Note: the resolution is implicitly set to **CountDown::SECONDS**. 97 | 98 | Since 0.2.4 all **start()** functions will check if the parameters cause an overflow 99 | in the underlying math. 100 | - If there is no overflow, **start()** returns true. 101 | - If there is an overflow, **start()** returns false. 102 | 103 | Total amount of time to countdown for **CountDown::MICROS** may not exceed 2\^32 micros 104 | which equals about 1 hour and 10 minutes. 105 | Total amount of time to countdown for **CountDown::MILLIS**, **CountDown::SECONDS** 106 | and **CountDown::MINUTES** may not exceed 2\^32 milliseconds, about 49 days. 107 | 108 | The function **start(days, hours, minutes)** is new since 0.2.2. 109 | It also uses **millis()** under the hood. 110 | The resolution is implicitly set to **CountDown::MINUTES**. 111 | 112 | 113 | | Call to start() | resolution | max time | comments | 114 | |:---------------------------------------|:-------------------|:----------:|:-----------| 115 | | start(days, hours, minutes, seconds) | SECONDS = millis | 49+ days | | 116 | | start(days, hours, minutes) | MINUTES = millis | 49+ days | | 117 | | start(ticks) | MILLIS = millis | 49+ days | default | 118 | | start(ticks) | MICROS = micros | ~70 min | setResolution(CountDown::MICROS) | 119 | | start(ticks) | SECONDS = millis | 49+ days | setResolution(CountDown::SECONDS) | 120 | | start(ticks) | MINUTES = millis | 49+ days | setResolution(CountDown::MINUTES) | 121 | 122 | 123 | The Countdown clock uses by default **millis()** to do the time keeping, 124 | although this can be changed runtime by **setResolution(res)**. 125 | 126 | The parameter **res** can be: 127 | 128 | | unit | uses | getUnits() | Notes | 129 | |:---------------------|:-----------|:------------:|:--------| 130 | | CountDown::MICROS | micros() | u | 131 | | CountDown::MILLIS | millis() | m | default 132 | | CountDown::SECONDS | millis() | s | 133 | | CountDown::MINUTES | millis() | M | 134 | 135 | Although possible one should not change the resolution of the CountDown 136 | clock while it is running as you mix up different timescales. 137 | The user should handle this by selecting the smallest resolution needed. 138 | 139 | Alternative one can get the remaining units, stop the countdown, and start 140 | with another resolution and converted time. 141 | This will probably lead to rounding errors i the total countdown time. 142 | See example **countdown_adaptive_display.ino** 143 | 144 | Finally the user has to check **remaining()** as frequent as needed to meet 145 | the accuracy. E.g checking once a minute while doing milliseconds makes only sense 146 | if the number of milliseconds is still very large. Think of an adaptive strategy. 147 | 148 | 149 | ### Watchdog 150 | 151 | One can call **start(...)** at any time to reset the running clock to a new value. 152 | This allows to implement a sort of watchdog clock in which e.g. 153 | the user must press a button at least once per minute to show he is still awake. 154 | 155 | Since version 0.3.1 the library supports **restart()** to start the countdown with 156 | the last used parameters of **start()**. The user does not need to remember the 157 | number of ticks or hours + minutes + seconds any more. Much easier tom implement 158 | a repeating (timed) function or a watchdog. See examples. 159 | 160 | 161 | ## Future 162 | 163 | #### Must 164 | 165 | - documentation 166 | 167 | #### Should 168 | 169 | 170 | #### Could 171 | 172 | - does **restart()** need to return some information? what? 173 | - add examples 174 | - visualisations - hexadecimal - alphabetical (radix 26) 175 | - depends on sensor 176 | - add resolution::HOURS + **start(days, hours)** 177 | - extend adaptive display example 178 | - or default 00 minutes? 179 | - add **void resume()** instead of **cont()**? 180 | 181 | #### Wont (unless) 182 | 183 | - if counting MINUTES and reaching 1 MINUTES and automatic 184 | change to SECONDS could be NICE 185 | - easy implementable by user, by starting in seconds 186 | and handle the display oneself. 187 | - incorporate a real time clock 188 | - or EEPROM to be reboot proof? cannot be guaranteed. 189 | - Countdown based upon external pulses. 190 | - pulse counter class 191 | - uint64_t version ==> **CountDown64** class? 192 | - would be useful for micros() in theory 193 | but drift / interrupts would make it fail in practice. 194 | - countdown with a big number e.g. billions/ second ==> national deficit counter. 195 | - not time triggered (is just a big variable) 196 | - printable interface (stopwatch_rt) 197 | - add call-back function when **0** is reached 198 | - cannot be guaranteed as interface polls. 199 | 200 | 201 | ## Support 202 | 203 | If you appreciate my libraries, you can support the development and maintenance. 204 | Improve the quality of the libraries by providing issues and Pull Requests, or 205 | donate through PayPal or GitHub sponsors. 206 | 207 | Thank you, 208 | 209 | -------------------------------------------------------------------------------- /examples/countdown_DHMS/countdown_DHMS.ino: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: countdown_DHMS.ino 3 | // AUTHOR: Rob Tillaart 4 | // PURPOSE: demo 5 | // URL: http://forum.arduino.cc/index.php?topic=356253 6 | // https://github.com/RobTillaart/CountDown 7 | 8 | 9 | #include "CountDown.h" 10 | 11 | CountDown CD; 12 | 13 | 14 | void setup() 15 | { 16 | // while(!Serial); // uncomment if needed 17 | Serial.begin(115200); 18 | Serial.println(__FILE__); 19 | Serial.print("COUNTDOWN_LIB_VERSION: "); 20 | Serial.println(COUNTDOWN_LIB_VERSION); 21 | Serial.println(); 22 | 23 | // countdown 1 minute 24 | CD.start(0, 0, 1, 0); 25 | } 26 | 27 | 28 | void loop() 29 | { 30 | static uint32_t last_remaining = 0; 31 | if ((last_remaining != CD.remaining()) || (CD.remaining() == 0) ) 32 | { 33 | Serial.println(); 34 | last_remaining = CD.remaining(); 35 | } 36 | Serial.print('\t'); 37 | Serial.print(CD.remaining()); 38 | delay(250); 39 | } 40 | 41 | 42 | // -- END OF FILE -- 43 | 44 | -------------------------------------------------------------------------------- /examples/countdown_adaptive_display/countdown_adaptive_display.ino: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: countdown_adaptive_display.ino 3 | // AUTHOR: Rob Tillaart 4 | // PURPOSE: demo 5 | // URL: http://forum.arduino.cc/index.php?topic=356253 6 | // https://github.com/RobTillaart/CountDown 7 | 8 | 9 | #include "CountDown.h" 10 | 11 | CountDown CD; 12 | 13 | int wait = 2000; 14 | 15 | 16 | void setup() 17 | { 18 | // while(!Serial); // uncomment if needed 19 | Serial.begin(115200); 20 | Serial.println(__FILE__); 21 | Serial.print("COUNTDOWN_LIB_VERSION: "); 22 | Serial.println(COUNTDOWN_LIB_VERSION); 23 | Serial.println(); 24 | 25 | CD.start(0, 0, 2); // 2 minutes => unit is MINUTES 26 | } 27 | 28 | 29 | void loop() 30 | { 31 | Serial.print("Remaining: "); 32 | Serial.print(CD.remaining()); 33 | Serial.print(" "); 34 | Serial.println(CD.getUnits()); 35 | 36 | // switch units and poll frequency for last minute. 37 | if ((CD.remaining() == 1) && (CD.getUnits() == 'M')) 38 | { 39 | wait = 1000; 40 | CD.stop(); 41 | CD.start(0, 0, 0, 59); 42 | } 43 | delay(wait); 44 | } 45 | 46 | 47 | // -- END OF FILE -- 48 | -------------------------------------------------------------------------------- /examples/countdown_demo1/countdown_demo1.ino: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: countdown_demo.ino 3 | // AUTHOR: Rob Tillaart 4 | // PURPOSE: demo 5 | // URL: http://forum.arduino.cc/index.php?topic=356253 6 | // https://github.com/RobTillaart/CountDown 7 | // 8 | 9 | 10 | #include "CountDown.h" 11 | 12 | CountDown CD(CountDown::MINUTES); 13 | 14 | uint32_t start, stop; 15 | 16 | 17 | void setup() 18 | { 19 | // while(!Serial); // uncomment if needed 20 | Serial.begin(115200); 21 | Serial.println(__FILE__); 22 | Serial.print("COUNTDOWN_LIB_VERSION: "); 23 | Serial.println(COUNTDOWN_LIB_VERSION); 24 | Serial.println(); 25 | 26 | delay(random(2000)); 27 | start = millis(); 28 | CD.start(3); 29 | Serial.println(start); 30 | 31 | while (CD.remaining() > 0 ) 32 | { 33 | // Serial.print(millis()); 34 | // Serial.print("\t"); 35 | // Serial.println(CD.remaining() ); 36 | // delay(100); 37 | } 38 | Serial.println(millis() - start); // SHOULD PRINT 180000 39 | 40 | Serial.println(CD.remaining()); 41 | Serial.println("done..."); 42 | } 43 | 44 | 45 | void loop() 46 | { 47 | } 48 | 49 | 50 | // -- END OF FILE -- 51 | -------------------------------------------------------------------------------- /examples/countdown_demo2/countdown_demo2.ino: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: countdown_demo2.ino 3 | // AUTHOR: Rob Tillaart 4 | // PURPOSE: demo 5 | // URL: http://forum.arduino.cc/index.php?topic=356253 6 | // https://github.com/RobTillaart/CountDown 7 | // 8 | 9 | 10 | #include "CountDown.h" 11 | 12 | CountDown CD[5]; 13 | 14 | uint8_t lines = 0; 15 | 16 | 17 | void setup() 18 | { 19 | // while(!Serial); // uncomment if needed 20 | Serial.begin(115200); 21 | Serial.println(__FILE__); 22 | Serial.print("COUNTDOWN_LIB_VERSION: "); 23 | Serial.println(COUNTDOWN_LIB_VERSION); 24 | Serial.println(); 25 | 26 | for (int i = 0; i < 5; i++) 27 | { 28 | CD[i].start(10000UL * i); 29 | } 30 | delay(1234); 31 | CD[3].stop(); 32 | } 33 | 34 | 35 | void loop() 36 | { 37 | if (lines == 10) 38 | { 39 | lines = 0; 40 | Serial.println(); 41 | } 42 | lines++; 43 | 44 | for (int i = 0; i < 5; i++) 45 | { 46 | Serial.print("\t"); 47 | Serial.print(CD[i].remaining()); 48 | } 49 | for (int i = 0; i < 5; i++) 50 | { 51 | Serial.print("\t"); 52 | Serial.print(CD[i].isRunning()); 53 | } 54 | Serial.println(); 55 | 56 | if (CD[1].isRunning() == false && CD[3].isRunning() == false) 57 | { 58 | uint32_t x = CD[3].remaining(); 59 | CD[3].start(x); 60 | } 61 | 62 | delay(250); 63 | } 64 | 65 | 66 | // -- END OF FILE -- 67 | 68 | -------------------------------------------------------------------------------- /examples/countdown_overflow_test/countdown_overflow_test.ino: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: countdown_overflow_test.ino 3 | // AUTHOR: Rob Tillaart 4 | // PURPOSE: test overflow behavior (micros is fastest to test) 5 | // URL: http://forum.arduino.cc/index.php?topic=356253 6 | // https://github.com/RobTillaart/CountDown 7 | 8 | 9 | #include "CountDown.h" 10 | 11 | CountDown CD(CountDown::MICROS); 12 | 13 | 14 | void setup() 15 | { 16 | // while(!Serial); // uncomment if needed 17 | Serial.begin(115200); 18 | Serial.println(__FILE__); 19 | Serial.print("COUNTDOWN_LIB_VERSION: "); 20 | Serial.println(COUNTDOWN_LIB_VERSION); 21 | Serial.println(); 22 | 23 | // wait for almost overflow ~70 minutes !! 24 | while (micros() < 4290000000UL) 25 | { 26 | Serial.println(micros()); 27 | delay(1000); 28 | } 29 | Serial.println("----------------------"); 30 | 31 | CD.setResolution(CountDown::MICROS); 32 | CD.start(1 * 60 * 1000000UL); // 1 minute = 60 seconds 33 | } 34 | 35 | 36 | void loop() 37 | { 38 | Serial.print("Remaining: "); 39 | Serial.print(CD.remaining()); 40 | Serial.print(" "); 41 | Serial.println(CD.getUnits()); 42 | delay(1000); 43 | } 44 | 45 | 46 | // -- END OF FILE -- 47 | -------------------------------------------------------------------------------- /examples/countdown_restart/countdown_restart.ino: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: countdown_restart.ino 3 | // AUTHOR: Rob Tillaart 4 | // PURPOSE: demo restart 5 | // URL: https://github.com/RobTillaart/CountDown 6 | 7 | 8 | #include "CountDown.h" 9 | 10 | CountDown cdt; // default millis 11 | 12 | uint8_t lines = 0; 13 | 14 | 15 | void setup() 16 | { 17 | // while(!Serial); // uncomment if needed 18 | Serial.begin(115200); 19 | Serial.println(__FILE__); 20 | Serial.print("COUNTDOWN_LIB_VERSION: "); 21 | Serial.println(COUNTDOWN_LIB_VERSION); 22 | Serial.println(); 23 | 24 | cdt.start(10000UL); 25 | } 26 | 27 | 28 | void loop() 29 | { 30 | // time for a new sample 31 | if (cdt.remaining() == 0) 32 | { 33 | // restart countDownTimer same values 34 | cdt.restart(); 35 | // make a sample and print it. 36 | Serial.print(millis()); 37 | Serial.print("\t"); 38 | Serial.println(analogRead(A0)); 39 | } 40 | } 41 | 42 | 43 | // -- END OF FILE -- 44 | -------------------------------------------------------------------------------- /examples/countdown_resume/countdown_resume.ino: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: countdown_resume.ino 3 | // AUTHOR: Rob Tillaart 4 | // PURPOSE: demo 5 | // URL: http://forum.arduino.cc/index.php?topic=356253 6 | // https://github.com/RobTillaart/CountDown 7 | 8 | 9 | #include "CountDown.h" 10 | 11 | CountDown CD[2]; 12 | 13 | 14 | void setup() 15 | { 16 | // while(!Serial); // uncomment if needed 17 | Serial.begin(115200); 18 | Serial.println(__FILE__); 19 | Serial.print("COUNTDOWN_LIB_VERSION: "); 20 | Serial.println(COUNTDOWN_LIB_VERSION); 21 | Serial.println(); 22 | 23 | CD[0].start(10000UL); 24 | CD[1].start(10000UL); 25 | delay(1234); 26 | CD[1].stop(); 27 | } 28 | 29 | 30 | void loop() 31 | { 32 | for (int i = 0; i < 2; i++) 33 | { 34 | Serial.print("\t"); 35 | Serial.print(CD[i].remaining()); 36 | } 37 | for (int i = 0; i < 2; i++) 38 | { 39 | Serial.print("\t"); 40 | Serial.print(CD[i].isRunning()); 41 | } 42 | Serial.println(); 43 | 44 | if (CD[0].isRunning() == false && CD[1].isRunning() == false) 45 | { 46 | CD[1].resume(); 47 | } 48 | 49 | delay(250); 50 | } 51 | 52 | 53 | // -- END OF FILE -- 54 | -------------------------------------------------------------------------------- /examples/countdown_setResolution/countdown_setResolution.ino: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: countdown_setResolution.ino 3 | // AUTHOR: Rob Tillaart 4 | // PURPOSE: demo 5 | // URL: http://forum.arduino.cc/index.php?topic=356253 6 | // https://github.com/RobTillaart/CountDown 7 | // 8 | 9 | 10 | #include "CountDown.h" 11 | 12 | CountDown CD; 13 | 14 | 15 | void setup() 16 | { 17 | // while(!Serial); // uncomment if needed 18 | Serial.begin(115200); 19 | Serial.println(__FILE__); 20 | Serial.print("COUNTDOWN_LIB_VERSION: "); 21 | Serial.println(COUNTDOWN_LIB_VERSION); 22 | Serial.println(); 23 | 24 | CD.setResolution(CountDown::MINUTES); 25 | // countdown 1 minutes 26 | CD.start(1); 27 | } 28 | 29 | 30 | void loop() 31 | { 32 | static uint32_t last_remaining = 0; 33 | if ((last_remaining != CD.remaining()) || (CD.remaining() == 0) ) 34 | { 35 | Serial.println(); 36 | last_remaining = CD.remaining(); 37 | } 38 | Serial.print(' '); 39 | Serial.print(CD.remaining()); 40 | delay(5000); // every 5 seconds 41 | } 42 | 43 | 44 | // -- END OF FILE -- 45 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # Syntax Colouring CountDown 2 | 3 | # Data types (KEYWORD1) 4 | CountDown KEYWORD1 5 | 6 | 7 | # Methods and Functions (KEYWORD2) 8 | setResolution KEYWORD2 9 | resolution KEYWORD2 10 | getUnits KEYWORD2 11 | 12 | start KEYWORD2 13 | stop KEYWORD2 14 | cont KEYWORD2 15 | resume KEYWORD2 16 | restart KEYWORD2 17 | 18 | remaining KEYWORD2 19 | isRunning KEYWORD2 20 | isStopped KEYWORD2 21 | 22 | 23 | # Constants (LITERAL1) 24 | COUNTDOWN_LIB_VERSION LITERAL1 25 | MILLIS LITERAL1 26 | MICROS LITERAL1 27 | SECONDS LITERAL1 28 | MINUTES LITERAL1 29 | 30 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CountDown", 3 | "keywords": "Count,down,stopwatch,timer,millis,micros,seconds,minutes", 4 | "description": "Arduino library to implement a CountDown clock (in SW no HW).", 5 | "authors": 6 | [ 7 | { 8 | "name": "Rob Tillaart", 9 | "email": "Rob.Tillaart@gmail.com", 10 | "maintainer": true 11 | } 12 | ], 13 | "repository": 14 | { 15 | "type": "git", 16 | "url": "https://github.com/RobTillaart/CountDown.git" 17 | }, 18 | "version": "0.3.4", 19 | "license": "MIT", 20 | "frameworks": "*", 21 | "platforms": "*", 22 | "headers": "CountDown.h" 23 | } 24 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=CountDown 2 | version=0.3.4 3 | author=Rob Tillaart 4 | maintainer=Rob Tillaart 5 | sentence=Arduino library to implement a CountDown clock in SW. 6 | paragraph=Polling, no HW timer used. 7 | category=Data Processing 8 | url=https://github.com/RobTillaart/CountDown 9 | architectures=* 10 | includes=CountDown.h 11 | depends= -------------------------------------------------------------------------------- /test/unit_test_001.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // FILE: unit_test_001.cpp 3 | // AUTHOR: Rob Tillaart 4 | // DATE: 2020-12-03 5 | // PURPOSE: unit tests for the CountDown Library 6 | // https://github.com/RobTillaart/CountDown 7 | // https://github.com/Arduino-CI/arduino_ci/blob/master/REFERENCE.md 8 | // 9 | 10 | // supported assertions 11 | // https://github.com/Arduino-CI/arduino_ci/blob/master/cpp/unittest/Assertion.h#L33-L42 12 | // ---------------------------- 13 | // assertEqual(expected, actual) 14 | // assertNotEqual(expected, actual) 15 | // assertLess(expected, actual) 16 | // assertMore(expected, actual) 17 | // assertLessOrEqual(expected, actual) 18 | // assertMoreOrEqual(expected, actual) 19 | // assertTrue(actual) 20 | // assertFalse(actual) 21 | // assertNull(actual) 22 | // assertNotNull(actual) 23 | 24 | 25 | #include 26 | 27 | 28 | #include "Arduino.h" 29 | #include "CountDown.h" 30 | 31 | 32 | 33 | unittest_setup() 34 | { 35 | fprintf(stderr, "COUNTDOWN_LIB_VERSION: %s\n", (char *) COUNTDOWN_LIB_VERSION); 36 | } 37 | 38 | 39 | unittest_teardown() 40 | { 41 | } 42 | 43 | 44 | unittest(test_constants) 45 | { 46 | assertEqual('M', CountDown::MINUTES); 47 | assertEqual('s', CountDown::SECONDS); 48 | assertEqual('m', CountDown::MILLIS); 49 | assertEqual('u', CountDown::MICROS); 50 | } 51 | 52 | 53 | unittest(test_constructor) 54 | { 55 | CountDown a(CountDown::MINUTES); 56 | CountDown b(CountDown::SECONDS); 57 | CountDown c(CountDown::MILLIS); 58 | CountDown d(CountDown::MICROS); 59 | CountDown e; // default MILLIS 60 | 61 | assertEqual(CountDown::MINUTES, a.resolution()); 62 | assertEqual(CountDown::SECONDS, b.resolution()); 63 | assertEqual(CountDown::MILLIS, c.resolution()); 64 | assertEqual(CountDown::MICROS, d.resolution()); 65 | assertEqual(CountDown::MILLIS, e.resolution()); 66 | 67 | assertEqual('M', a.getUnits()); 68 | assertEqual('s', b.getUnits()); 69 | assertEqual('m', c.getUnits()); 70 | assertEqual('u', d.getUnits()); 71 | assertEqual('m', e.getUnits()); 72 | 73 | fprintf(stderr, "\nisRunning\n"); 74 | 75 | assertFalse(a.isRunning()); 76 | assertFalse(b.isRunning()); 77 | assertFalse(c.isRunning()); 78 | assertFalse(d.isRunning()); 79 | assertFalse(e.isRunning()); 80 | 81 | fprintf(stderr, "\nbase\n"); 82 | 83 | a.setResolution(CountDown::SECONDS); 84 | assertTrue(a.start(10)); 85 | assertTrue(a.isRunning()); 86 | 87 | a.stop(); 88 | assertFalse(a.isRunning()); 89 | 90 | a.resume(); 91 | assertTrue(a.isRunning()); 92 | 93 | a.stop(); 94 | assertFalse(a.isRunning()); 95 | 96 | assertTrue(a.start(0)); 97 | assertFalse(a.isRunning()); 98 | } 99 | 100 | 101 | unittest(test_run) 102 | { 103 | CountDown cd(CountDown::MILLIS); 104 | assertEqual(CountDown::MILLIS, cd.resolution()); 105 | 106 | assertFalse(cd.isRunning()); 107 | assertTrue(cd.isStopped()); 108 | cd.start(10); 109 | assertTrue(cd.isRunning()); 110 | assertFalse(cd.isStopped()); 111 | delay(5); 112 | cd.stop(); 113 | assertFalse(cd.isRunning()); 114 | assertTrue(cd.isStopped()); 115 | assertEqual(5, cd.remaining()); 116 | 117 | cd.start(10); 118 | assertTrue(cd.isRunning()); 119 | assertFalse(cd.isStopped()); 120 | delay(15); 121 | assertFalse(cd.isRunning()); 122 | assertTrue(cd.isStopped()); 123 | assertEqual(0, cd.remaining()); 124 | } 125 | 126 | 127 | unittest(test_overflow) 128 | { 129 | CountDown cd; 130 | assertEqual(CountDown::MILLIS, cd.resolution()); 131 | 132 | assertFalse(cd.isRunning()); 133 | assertFalse(cd.start(50, 0, 0)); 134 | assertEqual(CountDown::MINUTES, cd.resolution()); 135 | 136 | assertFalse(cd.start(50, 0, 0, 0)); 137 | assertEqual(CountDown::SECONDS, cd.resolution()); 138 | 139 | assertFalse(cd.start(0, 1200, 0)); 140 | assertFalse(cd.start(0, 1200, 0, 0)); 141 | 142 | assertFalse(cd.start(0, 0, 72000)); 143 | assertFalse(cd.start(0, 0, 72000, 0)); 144 | 145 | assertFalse(cd.start(0, 0, 0, 4320000)); 146 | } 147 | 148 | 149 | unittest_main() 150 | 151 | 152 | // -- END OF FILE -- 153 | 154 | --------------------------------------------------------------------------------