Ultra-fast, zero-allocation, bitmask-based cron for Deep Sleep
Traditional C cron libraries are cloud ports squeezed into silicon. They parse strings at runtime (<string.h>), allocate dynamic memory (malloc), and iterate arrays (O(N)) just to answer: "is it time yet?".
FastCron is an Embedded-First architecture. We strictly decouple parsing from execution:
- Payload Generation: The schedule is converted into a densely packed 24-byte bitmask (natively aligned for O(1) fetch speed). This can be done remotely by your backend (PHP, Node, Python) and sent via MQTT/CoAP, or locally using our C helpers.
- Firmware Time: The MCU evaluates this raw binary payload using pure integer math and hardware bit-scans (
__builtin_ctz), skipping any string parsing on-chip.
The result: Deterministic, bounded execution (< 5 Β΅s). The MCU calculates the exact microsecond sleep duration, configures the RTC alarm, and enters deep sleep. No wasted cycles. No heap fragmentation.
-
Ultra-Low Power Deep Sleep (ESP32/STM32/nRF52):
Instead of waking up every minute to poll a schedule, FastCron calculates the exact
$\Delta t$ until the next event. Feed this directly toesp_deep_sleep()or Zephyr'sk_sleep(). -
LoRaWAN / NB-IoT Over-The-Air Updates:
Sending a cron string like
"*/15 8-18 * 1-5 1-5"over LoRa takes ~20 bytes + requires parsing code on the chip. FastCron'sFastCron_tstruct is exactly 24 bytes (natively aligned for O(1) fetch speed). Send the raw binary payload, inject it straight into RAM, and save massive airtime and Flash memory. - 100% Offline RTC Automation: Perfect for agricultural timers or industrial relays that never connect to the internet. Store hundreds of 24-byte schedules in tiny EEPROMs and evaluate them against a DS3231 RTC module using FastCron's pure Julian Day math.
FastCron is a pure math engine β zero OS dependencies, zero string parsing, zero dynamic memory. The FastCron_t bitmask is populated via:
- Over-The-Air (OTA) Payloads: Generated by your backend server and sent via MQTT/CoAP/LoRaWAN directly into the struct's memory.
- Runtime initialization: Using our intuitive C setter functions directly on the microcontroller.
- Static initialization: At compile time for immutable, fixed schedules.
This decoupling keeps the C library at ~200 lines with no #ifdef portability hacks.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β External: User Application / Static Initializer β
β β β
β ββββββββββββΌβββββββββββ β
β β FastCron_t Mask β β
β β β β
β β minutes: uint64_t β 60 bits β
β β hours: uint32_t β 24 bits β
β β dom: uint32_t β 31 bits β
β β months: uint16_t β 12 bits β
β β dow: uint8_t β 7 bits β
β ββββββββββββ¬βββββββββββ = 24 bytes β
β β β
β βββββββΌβββββββ β
β β Resolver β O(1) bit-scan per β
β β β field, Julian Day β
β β β epoch math (pure) β
β βββββββ¬βββββββ β
β β β
β βββββββββΌβββββββββ β
β β Sleep Helper β β
β β (s / ms / Β΅s) β β
β βββββββββ¬βββββββββ β
β β β
β esp_deep_sleep(Β΅s) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
add_subdirectory(fastcron)
target_link_libraries(your_app PRIVATE fastcron)#include "fastcron.h"
#include <sys/time.h>
#include "esp_sleep.h"
void app_main(void)
{
// "*/15 8-18 * * 1-5" β every 15 min, MonβFri, 8 AM to 6 PM
FastCron_t schedule = {0};
fastcron_set_minute(&schedule, 0);
fastcron_set_minute(&schedule, 15);
fastcron_set_minute(&schedule, 30);
fastcron_set_minute(&schedule, 45);
for (int i = 8; i <= 18; i++)
{
fastcron_set_hour(&schedule, i);
}
fastcron_set_all_days_of_month(&schedule);
fastcron_set_all_months(&schedule);
fastcron_set_day_of_week(&schedule, FASTCRON_MONDAY);
fastcron_set_day_of_week(&schedule, FASTCRON_TUESDAY);
fastcron_set_day_of_week(&schedule, FASTCRON_WEDNESDAY);
fastcron_set_day_of_week(&schedule, FASTCRON_THURSDAY);
fastcron_set_day_of_week(&schedule, FASTCRON_FRIDAY);
struct timeval now;
gettimeofday(&now, NULL);
uint64_t sleep_us = fastcron_sleep_us(&schedule, now.tv_sec, (uint32_t)now.tv_usec);
printf("Deep sleep for %llu Β΅s (%.1f min)\n",
sleep_us, (double)sleep_us / 60000000.0);
esp_deep_sleep(sleep_us);
}For maximum performance, compute the bitmask offline and initialize it statically:
// "*/15 8-18 * * 1-5"
static const FastCron_t static_schedule =
{
.minutes = (1ULL << 0) | (1ULL << 15) | (1ULL << 30) | (1ULL << 45),
.hours = 0x0007FF00U, // bits 8..18
.days_of_month = 0xFFFFFFFEU, // all days (1-31)
.months = 0x1FFE, // all months (1-12)
.days_of_week = 0x3E, // Mon(1)βFri(5)
};time_t next = fastcron_get_next_wakeup(&schedule, time(NULL));| Function | Description |
|---|---|
fastcron_get_next_wakeup(mask, epoch) |
Next matching UTC epoch via O(1) bit-scan |
fastcron_sleep_s(mask, epoch) |
Whole seconds until next event |
fastcron_sleep_ms(mask, tv_sec, tv_usec) |
Milliseconds (sub-second aware) |
fastcron_sleep_us(mask, tv_sec, tv_usec) |
Microseconds (for esp_deep_sleep) |
| Field | Type | Bits Used | Encoding |
|---|---|---|---|
minutes |
uint64_t |
0β59 | bit N = minute N |
hours |
uint32_t |
0β23 | bit N = hour N |
days_of_month |
uint32_t |
1β31 | bit N = day N (bit 0 unused) |
months |
uint16_t |
1β12 | bit N = month N (bit 0 unused) |
days_of_week |
uint8_t |
0β6 | bit 0 = Sunday, bit 6 = Saturday |
| Metric | FastCron | ccronexpr |
|---|---|---|
| Algorithm | Bounded O(k) bitmask via O(1) bit-scan | O(N) array iteration |
| RAM per schedule | 24 bytes (stack) | ~120+ bytes (parsed) |
| Flash footprint (.text) | ~1.5 KB | ~7.4 KB |
| malloc calls | 0 (Static) | Multiple malloc()s |
| Avg resolution time | 142 ns | ~100.7 Β΅s (100,743 ns) |
| OS dependencies | None (pure math) | libc (mktime, malloc) |
| MISRA compliant | β | β |
| Deep-sleep ready | β Β΅s precision | β |
24 bytes total (natively aligned for O(1) fetch speed) β the
FastCron_tstruct fits in a single cache line and requires zero heap allocation. The engine uses Julian Day math for epoch conversion, making it fully portable to any C11 target with no system headers beyond<stdint.h>.
- No dynamic memory allocation β
malloc,calloc,realloc,freenever called. - No recursion β all functions use bounded iteration (hard cap: 800).
- No floating-point arithmetic in the engine.
- No platform
#ifdefchains β pure math, same code path everywhere. - No string operations β zero
<string.h>dependency. - All casts are explicit β no implicit type conversions.
cmake -B build -DFASTCRON_BUILD_TESTS=ON
cmake --build build
ctest --test-dir build --output-on-failurefastcron/
βββ include/
β βββ fastcron.h # Public API (24-byte bitmask + 4 functions)
βββ src/
β βββ fastcron.c # Pure-math engine (~200 LOC)
βββ tests/
β βββ test_fastcron.c # Unity test suite
βββ docs/
β βββ Doxyfile # Doxygen β XML
β βββ conf.py # Sphinx + Breathe
β βββ index.rst # Documentation index
β βββ api.rst # API reference
βββ .github/
β βββ workflows/
β βββ main.yml # CI: Build + Cppcheck + Docs deploy
βββ CMakeLists.txt
βββ LICENSE # MIT
βββ README.md
MIT β use it anywhere, including commercial embedded products.