0.3.0 CountDown

This commit is contained in:
rob tillaart 2023-01-11 11:04:32 +01:00
parent fffe2ad15b
commit 00c9dd1af4
14 changed files with 270 additions and 69 deletions

View File

@ -6,7 +6,7 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: arduino/arduino-lint-action@v1
with:
library-manager: update

View File

@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.6

View File

@ -10,7 +10,7 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: json-syntax-check
uses: limitusus/json-syntax-check@v1
with:

View File

@ -6,12 +6,24 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.3.0] - 2023-01-10
- fix #12 MINUTES bug
- label enum resolution to be printable u, m, s, M.
- add getUnits()
- add isStopped() for convenience.
- fix initialization of private variables.
- update GitHub actions
- update license
- update readme.md
- update unit tests
----
## [0.2.7] - 2022-10-30
- add changelog.md
- add rp2040 to build-CI
- minor edit unit test
## [0.2.6] - 2021-12-14
- update library.json, license

View File

@ -1,7 +1,7 @@
//
// FILE: CountDown.cpp
// AUTHOR: Rob Tillaart
// VERSION: 0.2.7
// VERSION: 0.3.0
// PURPOSE: CountDown library for Arduino
// URL: https://github.com/RobTillaart/CountDown
//
@ -13,6 +13,11 @@
CountDown::CountDown(const enum Resolution res)
{
// _res = MILLIS; // set in setResolution
// _ticks = 0; // set in setResolution
_state = CountDown::STOPPED;
_remaining = 0;
_startTime = 0;
setResolution(res);
stop();
}
@ -25,17 +30,23 @@ void CountDown::setResolution(const enum Resolution res)
}
char CountDown::getUnits()
{
return _res;
}
bool CountDown::start(uint32_t ticks)
{
_ticks = ticks;
_state = CountDown::RUNNING;
if (_res == MICROS)
{
_starttime = micros();
_startTime = micros();
}
else
{
_starttime = millis();
_startTime = millis();
}
return true; // can not overflow
}
@ -47,7 +58,9 @@ bool CountDown::start(uint8_t days, uint16_t hours, uint32_t minutes, uint32_t s
bool rv = (_days < 49.7102696);
uint32_t ticks = 86400UL * days + 3600UL * hours + 60UL * minutes + seconds;
if (ticks > 4294967) ticks = 4294967; // prevent underlying millis() overflow
// prevent underlying millis() overflow
// 4294967 = number of SECONDS in 2^32 milliseconds
if (ticks > 4294967) ticks = 4294967;
setResolution(SECONDS);
start(ticks);
@ -60,8 +73,10 @@ bool CountDown::start(uint8_t days, uint16_t hours, uint32_t minutes)
float _days = minutes / 1440.0 + hours / 24.0 + days;
bool rv = (_days < 49.7102696);
uint32_t ticks = 86400UL * days + 3600UL * hours + 60UL * minutes;
if (ticks > 4294967) ticks = 4294967; // prevent underlying millis() overflow
uint32_t ticks = 1440UL * days + 60UL * hours + minutes;
// prevent underlying millis() overflow
// 71582 = number of MINUTES in 2^32 milliseconds
if (ticks > 71582) ticks = 71582;
setResolution(MINUTES);
start(ticks);
@ -85,13 +100,6 @@ void CountDown::cont()
}
bool CountDown::isRunning()
{
calcRemaining();
return (_state == CountDown::RUNNING);
}
uint32_t CountDown::remaining()
{
calcRemaining();
@ -100,6 +108,24 @@ uint32_t CountDown::remaining()
}
bool CountDown::isRunning()
{
calcRemaining();
return (_state == CountDown::RUNNING);
}
bool CountDown::isStopped()
{
calcRemaining();
return (_state == CountDown::STOPPED);
}
//////////////////////////////////////////////////
//
// PRIVATE
//
void CountDown::calcRemaining()
{
uint32_t t = 0;
@ -108,17 +134,17 @@ void CountDown::calcRemaining()
switch(_res)
{
case MINUTES:
t = (millis() - _starttime) / 60000UL;
t = (millis() - _startTime) / 60000UL;
break;
case SECONDS:
t = (millis() - _starttime) / 1000UL;;
t = (millis() - _startTime) / 1000UL;;
break;
case MICROS:
t = micros() - _starttime;
t = micros() - _startTime;
break;
case MILLIS:
default:
t = millis() - _starttime;
t = millis() - _startTime;
break;
}
_remaining = _ticks > t ? _ticks - t : 0;
@ -128,8 +154,8 @@ void CountDown::calcRemaining()
}
return;
}
// do not change
}
// -- END OF FILE --

View File

@ -2,26 +2,28 @@
//
// FILE: CountDown.h
// AUTHOR: Rob Tillaart
// VERSION: 0.2.7
// VERSION: 0.3.0
// PURPOSE: CountDown library for Arduino
// URL: https://github.com/RobTillaart/CountDown
//
#include "Arduino.h"
#define COUNTDOWN_LIB_VERSION (F("0.2.7"))
#define COUNTDOWN_LIB_VERSION (F("0.3.0"))
class CountDown
{
public:
enum Resolution { MILLIS, MICROS, SECONDS, MINUTES };
enum Resolution { MICROS = 'u', MILLIS = 'm', SECONDS = 's', MINUTES = 'M' };
explicit CountDown(const enum Resolution res = MILLIS);
void setResolution(const enum Resolution res = MILLIS);
enum Resolution resolution() { return _res; };
char getUnits();
// one need to set the resolution before calling start(ticks).
bool start(uint32_t ticks);
// Implicit set resolution to SECONDS.
bool start(uint8_t days, uint16_t hours, uint32_t minutes, uint32_t seconds);
@ -33,7 +35,8 @@ public:
uint32_t remaining();
bool isRunning();
enum Resolution resolution() const { return _res; };
bool isStopped();
private:
enum State { RUNNING, STOPPED };
@ -42,9 +45,10 @@ private:
uint32_t _remaining;
enum State _state;
enum Resolution _res;
uint32_t _starttime;
uint32_t _startTime;
void calcRemaining();
};
// -- END OF FILE --

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2015-2022 Rob Tillaart
Copyright (c) 2015-2023 Rob Tillaart
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -15,7 +15,7 @@ Arduino Library to implement a CountDown clock (in SW polling, no HW timer).
The countdown library is a clock that counts down from a given time to zero.
It does not call a function or so as the user is responsible to check the time remaining.
Typically one checks the remaining time in every **loop()**.
Typically one checks the remaining time at least once in every **loop()**.
Under the hood the function uses **micros()** or **millis()** which results in a maximum time
of 4294 seconds in micros (1h 10m) or about 49+ days when using milliseconds.
@ -31,32 +31,43 @@ Interrupts etc might cause deviations.
The main functions of the CountDown clock are:
- **CountDown(const enum Resolution res = MILLIS)** constructor, with default resolution of milliseconds.
- **void setResolution(const enum Resolution res = MILLIS)** set the resolution,
default to MILLIS.
- **enum Resolution resolution()** return the current resolution (integer).
- **char getUnits()** return the current resolution as printable char (u,m,s,M)
- **bool start(uint32_t ticks)** (re)start in current resolution.
Typical used for MILLIS and MICROS which must be set manually.
- **bool start(uint8_t days, uint16_t hours, uint32_t minutes, uint32_t seconds)** Implicit set resolution to SECONDS.
Returns false if total exceeds 2^32 milliseconds ~49 days.
Note that **remaining()** will report in SECONDS.
- **bool start(uint8_t days, uint16_t hours, uint32_t minutes)** Implicit set resolution to MINUTES.
- **void stop()** stop the count-down.
- **void cont()** resumes / continue the count-down.
Returns false if total exceeds 2^32 milliseconds ~49 days.
Note that **remaining()** will report in MINUTES.
- **void stop()** stop the count down.
- **void cont()** resumes / continue the count down.
*(note continue is a C-Keyword)*
- **uint32_t remaining()** returns the remaining ticks in current resolution.
- **bool isRunning()** idem.
- **bool isStopped()** idem.
These functions work straightforward.
## Operation
The function **start(days, hours, minutes, seconds)** has changed its
parameters type to minimize them, given that the total time may not exceed 2^32 milliseconds.
This allows the user to call **start()** with e.g.
The function **start(days, hours, minutes, seconds)** allows all combinations
as long as the total time may not exceed 2^32 milliseconds.
The function will return false if it exceeds this (rounded) maximum.
Example calls are:
- four hundred minutes **start(0, 0, 400, 0)**
- a million seconds **start(0, 0, 0, 1000000)**
- a unusual mix **start(0, 0, 400, 1000)** as parameter.
The resolution is implicitly set to **CountDown::SECONDS**.
- an unusual mix **start(0, 0, 400, 1000)** as parameter.
Note: the resolution is implicitly set to **CountDown::SECONDS**.
Since 0.2.4 the function **start()** will check if the parameters cause an overflow
Since 0.2.4 all **start()** functions will check if the parameters cause an overflow
in the underlying math.
If there is no overflow, a call to **start()** returns true.
If there is an overflow, a call to **start()** returns false.
- If there is no overflow, **start()** returns true.
- If there is an overflow, **start()** returns false.
Total amount of time to countdown for **CountDown::MICROS** may not exceed 2\^32 micros
which equals about 1 hour and 10 minutes.
@ -83,13 +94,25 @@ although this can be changed runtime by **setResolution(res)**.
The parameter **res** can be:
- **CountDown::MICROS** // based upon micros()
- **CountDown::MILLIS** // default
- **CountDown::SECONDS** // based upon millis()
- **CountDown::MINUTES** // based upon millis()
| unit | uses | getUnits() | Notes |
|:---------------------|:-----------|:------------:|:--------|
| CountDown::MICROS | micros() | u |
| CountDown::MILLIS | millis() | m | default
| CountDown::SECONDS | millis() | s |
| CountDown::MINUTES | millis() | M |
Although possible one should not change the resolution of the CountDown
clock while it is running as you mix up different timescales.
The user should handle this by selecting the smallest resolution needed.
Alternative one can get the remaining units, stop the countdown, and start
with another resolution and converted time.
This will probably lead to rounding errors i the total countdown time.
See example **countdown_adaptive_display.ino**
Finally the user has to check **remaining()** as frequent as needed to meet
the accuracy. E.g checking once a minute while doing milliseconds makes only sense
if the number of milliseconds is still very large. Think of an adaptive strategy.
#### Watchdog
@ -102,23 +125,37 @@ the user must press a button at least once per minute to show he is still awake.
## Future
#### must
- documentation
#### should
#### could
- incorporate a real time clock
- or EEPROM to be reboot proof?
- examples
- add examples
- visualisations - hexadecimal - alphabetical (radix 26)
- depends on sensor
- uint64_t version ==> **CountDown64** class? (only on request)
- would be useful for micros() in theory but drift / interrupts would make it fail.
- countdown with a big number e.g. billions/ second ==> national deficit coounter.
- (semi)watchdog()
- add resolution::HOURS + **start(days, hours)**
- extend adaptive display example
- add call-back function when **0** is reached
- example
#### wont
#### wont (unless)
- if counting MINUTES and reaching 1 MINUTES and automatic
change to SECONDS could be NICE
- easy implementable by user, by starting in seconds
and handle the display oneself.
- incorporate a real time clock
- or EEPROM to be reboot proof? cannot be guaranteed.
- Countdown based upon external pulses.
- pulse counter
- pulse counter class
- uint64_t version ==> **CountDown64** class?
- would be useful for micros() in theory
but drift / interrupts would make it fail in practice.
- countdown with a big number e.g. billions/ second ==> national deficit counter.
- not time triggered (is just a big variable)

View File

@ -0,0 +1,48 @@
//
// FILE: countdown_adaptive_display.ino
// AUTHOR: Rob Tillaart
// PURPOSE: demo
// URL: http://forum.arduino.cc/index.php?topic=356253
// https://github.com/RobTillaart/CountDown
//
#include "CountDown.h"
CountDown CD;
int wait = 2000;
void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("COUNTDOWN_LIB_VERSION: ");
Serial.println(COUNTDOWN_LIB_VERSION);
Serial.println();
CD.start(0, 0, 2); // 2 minutes => unit is MINUTES
}
void loop()
{
Serial.print("Remaining: ");
Serial.print(CD.remaining());
Serial.print(" ");
Serial.println(CD.getUnits());
// switch units and poll frequency for last minute.
if ((CD.remaining() == 1) && (CD.getUnits() == 'M'))
{
wait = 1000;
CD.stop();
CD.start(0, 0, 0, 59);
}
delay(wait);
}
// -- END OF FILE --

View File

@ -0,0 +1,44 @@
//
// FILE: countdown_overflow_test.ino
// AUTHOR: Rob Tillaart
// PURPOSE: test overflow behavior (micros is fastest to test)
// URL: http://forum.arduino.cc/index.php?topic=356253
// https://github.com/RobTillaart/CountDown
#include "CountDown.h"
CountDown CD(CountDown::MICROS);
void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("COUNTDOWN_LIB_VERSION: ");
Serial.println(COUNTDOWN_LIB_VERSION);
// wait for almost overflow ~70 minutes !!
while (micros() < 4290000000UL)
{
Serial.println(micros());
delay(1000);
}
Serial.println("----------------------");
CD.setResolution(CountDown::MICROS);
CD.start(1 * 60 * 1000000UL); // 1 minute = 60 seconds
}
void loop()
{
Serial.print("Remaining: ");
Serial.print(CD.remaining());
Serial.print(" ");
Serial.println(CD.getUnits());
delay(1000);
}
// -- END OF FILE --

View File

@ -6,12 +6,16 @@ CountDown KEYWORD1
# Methods and Functions (KEYWORD2)
setResolution KEYWORD2
resolution KEYWORD2
getUnits KEYWORD2
start KEYWORD2
stop KEYWORD2
cont KEYWORD2
remaining KEYWORD2
isRunning KEYWORD2
resolution KEYWORD2
isStopped KEYWORD2
# Constants (LITERAL1)
@ -20,3 +24,4 @@ MILLIS LITERAL1
MICROS LITERAL1
SECONDS LITERAL1
MINUTES LITERAL1

View File

@ -15,7 +15,7 @@
"type": "git",
"url": "https://github.com/RobTillaart/CountDown.git"
},
"version": "0.2.7",
"version": "0.3.0",
"license": "MIT",
"frameworks": "arduino",
"platforms": "*",

View File

@ -1,5 +1,5 @@
name=CountDown
version=0.2.7
version=0.3.0
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Arduino library to implement a CountDown clock in SW.

View File

@ -32,7 +32,7 @@
unittest_setup()
{
fprintf(stderr, "VERSION: %s\n", (char *) COUNTDOWN_LIB_VERSION);
fprintf(stderr, "COUNTDOWN_LIB_VERSION: %s\n", (char *) COUNTDOWN_LIB_VERSION);
}
@ -41,6 +41,15 @@ unittest_teardown()
}
unittest(test_constants)
{
assertEqual('M', CountDown::MINUTES);
assertEqual('s', CountDown::SECONDS);
assertEqual('m', CountDown::MILLIS);
assertEqual('u', CountDown::MICROS);
}
unittest(test_constructor)
{
CountDown a(CountDown::MINUTES);
@ -55,6 +64,12 @@ unittest(test_constructor)
assertEqual(CountDown::MICROS, d.resolution());
assertEqual(CountDown::MILLIS, e.resolution());
assertEqual('M', a.getUnits());
assertEqual('s', b.getUnits());
assertEqual('m', c.getUnits());
assertEqual('u', d.getUnits());
assertEqual('m', e.getUnits());
fprintf(stderr, "\nisRunning\n");
assertFalse(a.isRunning());
@ -89,19 +104,23 @@ unittest(test_run)
assertEqual(CountDown::MILLIS, cd.resolution());
assertFalse(cd.isRunning());
assertTrue(cd.isStopped());
cd.start(10);
assertTrue(cd.isRunning());
assertFalse(cd.isStopped());
delay(5);
cd.stop();
assertFalse(cd.isRunning());
assertTrue(cd.isStopped());
assertEqual(5, cd.remaining());
cd.start(10);
assertTrue(cd.isRunning());
assertFalse(cd.isStopped());
delay(15);
assertFalse(cd.isRunning());
assertTrue(cd.isStopped());
assertEqual(0, cd.remaining());
}
@ -112,7 +131,10 @@ unittest(test_overflow)
assertFalse(cd.isRunning());
assertFalse(cd.start(50, 0, 0));
assertEqual(CountDown::MINUTES, cd.resolution());
assertFalse(cd.start(50, 0, 0, 0));
assertEqual(CountDown::SECONDS, cd.resolution());
assertFalse(cd.start(0, 1200, 0));
assertFalse(cd.start(0, 1200, 0, 0));
@ -123,6 +145,9 @@ unittest(test_overflow)
assertFalse(cd.start(0, 0, 0, 4320000));
}
unittest_main()
// --------
// -- END OF FILE --