Skip to content

Commit

Permalink
Merge pull request #10 from bxparks/develop
Browse files Browse the repository at this point in the history
v0.5.2 - support ATmega2560; pack zoneinfo files to save 2.5 - 6 kB of flash
  • Loading branch information
bxparks authored Jul 30, 2019
2 parents 8bc521e + 6d63bd7 commit 8650754
Show file tree
Hide file tree
Showing 302 changed files with 5,596 additions and 5,413 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

* Unreleased
* 0.5.2
* Create `HelloZoneManager` and add it to the `README.md`.
* Recommend using "Arduino MKR ZERO" board or "SparkFun SAMD21 Mini
Breakout" board for the "SAMD21 M0 Mini" boards.
* Pack `basic::ZoneInfo`, `extended:ZoneInfo` and related structs tighter on
32-bit processors, saving 2.5kB on the Basic zoneinfo files and 5.6kB on
Extended zoneinfo files.
* Pack `basic::Transition` and `extended::Transition` tighter on 32-bit
processors, saving 20-32 bytes on `BasicZoneProcessor` and
`ExtendedZoneProcessor`.
* Test and support ATmega2560 AVR processor.
* Replace all uses of `Serial` with `SERIAL_PORT_MONITOR` for compatibility
with boards (e.g. SAMD boards) which use `SerialUSB` as the serial
monitor.
* 0.5.1 (2019-07-24, TZ DB version 2019a, beta)
* Add documentation about the ZoneManager into `USER_GUIDE.md`.
* Move `DateStrings` string pointers into PROGMEM, saving 42 bytes of RAM.
Expand Down
89 changes: 75 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,20 +160,21 @@ time.

The zoneinfo files are stored in flash memory (using the `PROGMEM` compiler
directive) if the microcontroller allows it (e.g. AVR, ESP8266) so that they do
not consume static RAM:
not consume static RAM. The [MemoryBenchmark](examples/MemoryBenchmark/)
program shows that:

* 270 timezones supported by `BasicZoneProcessor`consume:
* 14 kB of flash on an 8-bit processor (AVR)
* 21 kB of flash on a 32-bit processor (ESP8266)
* 270 timezones supported by `BasicZoneProcessor`consume about:
* 15 kB of flash on an 8-bit processor (AVR)
* 20 kB of flash on a 32-bit processor (ESP8266)
* 387 timezones supported by `ExtendedZoneProcessor` consume:
* 23 kB of flash on an 8-bit processor (AVR)
* 37 kB of flash on a 32-bit processor (ESP8266)
* 24 kB of flash on an 8-bit processor (AVR)
* 33 kB of flash on a 32-bit processor (ESP8266)

Normally a small application will use only a small number of timezones. The
AceTime library with one timezone using the `BasicZoneProcessor` and the
`SystemClock` consumes:
* 8.5 kB of flash and 350 bytes of RAM on an 8-bit Arduino Nano (AVR),
* 11 kB of flash and 850 bytes of RAM on an ESP8266 processor (32-bit).
* 9-10 kB of flash and 350 bytes of RAM on an 8-bit AVR processors,
* 6-22 kB of flash and 900-1800 bytes of RAM on an 32-bit processors.

An example of more complex application is the [WorldClock](examples/WorldClock)
which has 3 OLED displays over SPI, 3 timezones using `BasicZoneProcessor`, a
Expand All @@ -185,23 +186,27 @@ inside the 30 kB flash size of an Arduino Pro Micro controller.
Conversion from date-time components (year, month, day, etc) to epochSeconds
(`ZonedDateTime::toEpochSeconds()`) takes about:
* 90 microseconds on an 8-bit AVR processor,
* 14 microseconds on an SAMD21,
* 7 microseconds on an ESP8266,
* 1.4 microseconds on an ESP32,
* 0.5 microseconds on a Teensy 3.2.

Conversion from an epochSeconds to date-time components including timezone
(`ZonedDateTime::forEpochSeconds()`) takes (assuming cache hits):
* 600 microseconds on an 8-bit AVR,
* 25 microseconds on an ESP8266,
* 2.5 microseconds on an ESP32,
* 50 microseconds on an SAMD21,
* 27 microseconds on an ESP8266,
* 2.8 microseconds on an ESP32,
* 6 microseconds on a Teensy 3.2.

**Version**: 0.5.1 (2019-07-24, TZ DB version 2019a, beta)
**Version**: 0.5.2 (2019-07-29, TZ DB version 2019a, beta)

**Status**: Fully functional. Added `ZoneManager` for dynamic binding of
zoneName or zoneId to the TimeZone.

## HelloDateTime
## Examples

### HelloDateTime

Here is a simple program (see [examples/HelloDateTime](examples/HelloDateTime))
which demonstrates how to create and manipulate date and times in different time
Expand Down Expand Up @@ -313,7 +318,61 @@ pacificTime.compareTo(londonTime): 0
pacificTime == londonTime: false
```

## HelloSystemClock
### HelloZoneManager

The [HelloZoneManager](examples/HelloZoneManager) example shows how to load the
entire TZ Database into a `BasicZoneManager`, then create 3 time zones using 3
different ways: `createForZoneInfo()`, `createForZoneName()`, and
`createForZoneId()`.

```C++
#include <AceTime.h>

using namespace ace_time;

// Create a BasicZoneManager with the entire TZ Database.
static const int CACHE_SIZE = 3;
static BasicZoneManager<CACHE_SIZE> manager(
zonedb::kZoneRegistrySize, zonedb::kZoneRegistry);

void setup() {
Serial.begin(115200);
while (!Serial); // Wait Serial is ready - Leonardo/Micro

// Create Los Angeles by ZoneInfo
auto pacificTz = manager.createForZoneInfo(&zonedb::kZoneAmerica_Los_Angeles);
auto pacificTime = ZonedDateTime::forComponents(
2019, 3, 10, 3, 0, 0, pacificTz);
pacificTime.printTo(Serial);
Serial.println();

// Create London by ZoneName
auto londonTz = manager.createForZoneName("Europe/London");
auto londonTime = pacificTime.convertToTimeZone(londonTz);
londonTime.printTo(Serial);
Serial.println();

// Create Sydney by ZoneId
uint32_t syndeyId = BasicZone(&zonedb::kZoneAustralia_Sydney).zoneId();
auto sydneyTz = manager.createForZoneId(syndeyId);
auto sydneyTime = pacificTime.convertToTimeZone(sydneyTz);
sydneyTime.printTo(Serial);
Serial.println();
}

void loop() {
}
```
This consumes about 25kB of flash, which means that it can run on an
Arduino Nano or Micro . It produces the following output:
```
2019-03-10T03:00:00-07:00[America/Los_Angeles]
2019-03-10T10:00:00+00:00[Europe/London]
2019-03-10T21:00:00+11:00[Australia/Sydney]
```
### HelloSystemClock
This is the example code for using the `SystemClock` taken from
[examples/HelloSystemClock](examples/HelloSystemClock).
Expand Down Expand Up @@ -374,9 +433,10 @@ then printing the system time every 2 seconds:
2019-06-17T19:50:00-07:00[America/Los_Angeles]
2019-06-17T19:50:02-07:00[America/Los_Angeles]
2019-06-17T19:50:04-07:00[America/Los_Angeles]
...
```

## Example: WorldClock
### WorldClock

Here is a photo of the [WorldClock](examples/WorldClock) that supports 3
OLED displays with 3 timezones, and automatically adjusts the DST transitions
Expand Down Expand Up @@ -433,6 +493,7 @@ The library is extensively tested on the following boards:
I will occasionally test on the following hardware as a sanity check:

* Teensy 3.2 (72 MHz ARM Cortex-M4)
* Mini Mega 2560 (Arduino Mega 2560 compatible, 16 MHz ATmega2560)

## Changelog

Expand Down
85 changes: 48 additions & 37 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

See the [README.md](README.md) for introductory background.

Version: 0.5.1 (2019-07-24, TZ DB version 2019a, beta)
Version: 0.5.2 (2019-07-29, TZ DB version 2019a, beta)

## Installation

Expand Down Expand Up @@ -1396,8 +1396,8 @@ database is about 23kB. On 32-bit processors, the `zonedb::` data set is about
#include <AceTime.h>
using namespace ace_time;
...
const uint16_t SIZE = 2;
BasicZoneManager<SIZE> zoneManager(
static const uint16_t SIZE = 2;
static BasicZoneManager<SIZE> zoneManager(
zonedb::kZoneRegistrySize, zonedb::kZoneRegistry);
void someFunction(const char* zoneName) {
Expand Down Expand Up @@ -1430,7 +1430,8 @@ static const basic::ZoneInfo* const kBasicZoneRegistry[] ACE_TIME_PROGMEM = {
static const uint16_t kZoneRegistrySize =
sizeof(Controller::kZoneRegistry) / sizeof(basic::ZoneInfo*);

static BasicZoneManager<2> zoneManager(kZoneRegistrySize, kZoneRegistry);
static const uint16_t NUM_ZONES = 2;
static BasicZoneManager<NUM_ZONES> zoneManager(kZoneRegistrySize, kZoneRegistry);
```
Here is the equivalent `ExtendedZoneManager` with 4 zones from the `zonedbx::`
Expand All @@ -1450,7 +1451,8 @@ static const extended::ZoneInfo* const kZoneRegistry[] ACE_TIME_PROGMEM = {
static const uint16_t kZoneRegistrySize =
sizeof(Controller::kZoneRegistry) / sizeof(extended::ZoneInfo*);
static ExtendedZoneManager<2> zoneManager(kZoneRegistrySize, kZoneRegistry);
static const uint16_t NUM_ZONES = 2;
static ExtendedZoneManager<NUM_ZONES> zoneManager(kZoneRegistrySize, kZoneRegistry);
```

The `ACE_TIME_PROGMEM` macro is defined in
Expand Down Expand Up @@ -1556,7 +1558,7 @@ fully qualified zone name was used.
#### createForZoneIndex

The `ZoneManager::createForZoneIndex()` creates a `TimeZone` from its integer
index into the Zone registry, from 0 to `registrySize-1`. This is useful when
index into the Zone registry, from 0 to `registrySize - 1`. This is useful when
you want to show the user with a menu of zones from the `ZoneManager` and allow
the user to select one of the options.

Expand Down Expand Up @@ -2295,35 +2297,35 @@ sizeof(LocalTime): 3
sizeof(LocalDateTime): 6
sizeof(TimeOffset): 1
sizeof(OffsetDateTime): 7
sizeof(BasicZoneProcessor): 156
sizeof(ExtendedZoneProcessor): 500
sizeof(BasicZoneProcessor): 136
sizeof(ExtendedZoneProcessor): 468
sizeof(TimeZone): 8
sizeof(ZonedDateTime): 16
sizeof(TimePeriod): 4
sizeof(SystemClock): 24
sizeof(NtpTimeProvider): 88 (ESP8266), 116 (ESP32)
sizeof(SystemClockSyncLoop): 20
sizeof(SystemClockSyncCoroutine): 52
sizeof(SystemClockSyncLoop): 16
sizeof(SystemClockSyncCoroutine): 48
```

The [MemoryBenchmark](examples/MemoryBenchmark) program gives a more
comprehensive answer to the amount of memory taken by this library.
Here is a short summary for an 8-bit microcontroller (e.g. Arduino Nano):

* Using the `TimeZone` class with a `BasicZoneProcessor` for one timezone takes
about 6140 bytes of flash memory and 193 bytes of static RAM.
* Using 2 timezones with `BasiCZoneProcessorincreases the consumption to 6628
bytes of flash and 231 bytes of RAM.
* Loading the entire `zonedb::` zoneinfo database consumes 20354 bytes of flash
and 601 bytes of RAM.
about 6 kB of flash memory and 193 bytes of static RAM.
* Using 2 timezones with `BasiCZoneProcessor increases the consumption to
about 7 kB of flash and 207 bytes of RAM.
* Loading the entire `zonedb::` zoneinfo database consumes 21 kB bytes of flash
and 597 bytes of RAM.
* Adding the `SystemClock` to the `TimeZone` and `BasicZoneProcessor` with one
timezone consumes 8436 bytes of flash and 344 bytes of RAM.
timezone consumes 8.5 kB bytes of flash and 352 bytes of RAM.

These numbers indicate that the AceTime library is useful even on a limited
8-bit controller with only 30-32kB of flash and 2kB of RAM. As a concrete
8-bit controller with only 30-32 kB of flash and 2 kB of RAM. As a concrete
example, the [WorldClock](examples/WorldClock) program contains 3 OLED displays
over SPI, 2 buttons, one DS3231 chip, and 3 timezones using AceTime, and these
all fit inside a Arduino Pro Micro limit of 30kB flash and 2.5kB of RAM.
all fit inside a Arduino Pro Micro limit of 30 kB flash and 2.5 kB of RAM.

## Comparisons to Other Time Libraries

Expand Down Expand Up @@ -2549,19 +2551,19 @@ did not think it would fit inside an Arduino controller.
emulator.
* `zonedb/` and `zonedbx/` zoneinfo files
* These statically defined data structures are loaded into flash memory
then copied to RAM when the application starts. Fortunately, most
`ZoneInfo` entries are only 40-60 bytes each and the corresponding
`ZonePolicy` entries are 50-100 bytes each.
* It may be possible to use the `PROGMEM` keyword to store them only on
flash memory, but that will increase the flash memory size due to the code
needed to read these data structures from flash. In some applications,
flash memory may be more precious than RAM so it is not clear that using
`PROGMEM` for these data structures is the appropriate solution.
using the `PROGMEM` keyword. The vast majority of the data structure
fields will stay in flash memory and not copied into RAM.
* The zoneinfo files have *not* been compressed using bit-fields or any
other compression techniques. It may be possible to decrease the size of
the full database using these compression techniques. However, compression
will increase the size of the program file, so for applications that use
only a small number of zones, it is not clear if the zoneinfo file
compression will provide a reduction in the size of the overall program.
* The TZ database files `backzone`, `systemv` and `factory` are
not processed by the `tzcompiler.py` tool. They don't seem to contain
anything worthwhile.
* The datasets and `*ZoneProcessor` classes have been *not* been tested or
validated for years prior to 2000.
* The datasets, `BasicZoneProcessor` and `ExtendedZoneProcessor` classes
have been *not* been tested or validated for years prior to 2000.
* TZ Database version 2019b contains the first use of the
`{onDayOfWeek<=onDayOfMonth}` syntax that I have seen (specifically `Rule
Zion, FROM 2005, TO 2012, IN Apr, ON Fri<=1`). The actual transition date
Expand All @@ -2580,7 +2582,7 @@ did not think it would fit inside an Arduino controller.
"[US/Pacific]".
* Arduino Zero and SAMD21 Boards
* SAMD21 boards (which all identify themselves as `ARDUINO_SAMD_ZERO`) are
fully supported, but there are some tricky points.
supported, but there are some tricky points.
* If you are using an original Arduino Zero and using the "Native USB Port",
you may encounter problems with nothing showing up on the Serial Monitor.
* The original Arduino Zero has [2 USB
Expand All @@ -2595,16 +2597,25 @@ did not think it would fit inside an Arduino controller.
* You may be able to fix this by setting
`ACE_TIME_CLOBBER_SERIAL_PORT_MONITOR` to `1` in
`src/ace_time/common/compat.h`. (I do not test this option often, so
it may be broke.)
it may be broken.)
* If you are using a SAMD21 development or breakout board, or one of the
many clones called something like "Ardunio SAMD21 M0 Mini" (this is what I
have), I have found things working better using the SparkFun
Boards instead of the Arduino Zero board. Download "SparkFun SAMD Boards"
using the Board Manager by following the [SparkFun Boards
Installation](https://github.com/sparkfun/Arduino_Boards), then select the
board labeled "SparkFun SAMD Mini Breakout". These boards have only a
single USB connector, and the `SERIAL_PORT_MONITOR` will be properly
defined to be `SerialUSB`.
have), I have been unable to find a board configuration that is an exact
match. You have a few choices:
* If you are running the [AceTime unit tests](tests/), you need to have
a working `SERIAL_PORT_MONITOR`, so the "Arduino MKR ZERO" board
might work better, instead of the "Arduino Zero (Native USB Port)"
board.
* If you are running an app that requires proper pin configuration,
it seems that the `Arduino MKR ZERO" configuration is not correct for
this clone board. You need to go back to the "Arduino/Genuino Zero
(Native USB Port)" board configuration.
* You may also try installing the [SparkFun
Boards](https://github.com/sparkfun/Arduino_Boards) and select
the "SparkFun SAMD21 Mini Breakout" board. The advantage of using
this configuration is that the `SERIAL_PORT_MONITOR` is configured
properly as well as the port pin numbers. However, I have found that
the USB connection can be a bit flaky.
* The SAMD21 microcontroller does *not* provide any EEPROM. Therefore,
this feature is disabled in the apps under `examples` (e.g.
`CommandLineClock`, `OledClock`, and `WorldClock`) which use this feature.
2 changes: 1 addition & 1 deletion docs/doxygen.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ PROJECT_NAME = "AceTime"
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER = 0.5.1
PROJECT_NUMBER = 0.5.2

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
Expand Down
Loading

0 comments on commit 8650754

Please sign in to comment.