Skip to content

Commit 07c6e20

Browse files
Copilotpetesramek
andcommitted
feat: auto-register in constructor; zero-setup ISR; three-mode comparison example and tests
Co-authored-by: petesramek <2333452+petesramek@users.noreply.github.com> Agent-Logs-Url: https://github.com/petesramek/tiny-link/sessions/512c208c-f77a-4c3e-b8b4-c67795265687
1 parent 7fc1e70 commit 07c6e20

8 files changed

Lines changed: 570 additions & 10 deletions

File tree

README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,63 @@ void loop() {
148148
**Use Callbacks** if you want to keep your communication logic completely separated from your application logic. This is highly recommended as it makes the `TinyLink` engine more reactive.
149149
150150
151+
## 🔄 Auto-Update Modes
152+
153+
TinyLink's protocol engine must be called periodically to process incoming bytes,
154+
manage timeouts, and fire callbacks. There are **three supported ways** to drive
155+
it — choose the one that fits your hardware and application:
156+
157+
| | Mode 1 — Main Loop | Mode 2 — Timer ISR | Mode 3 — serialEvent() |
158+
|---|---|---|---|
159+
| **How** | `link.update()` in `loop()` | `autoUpdateISR()` on a hardware timer | `autoUpdateISR()` from `serialEvent()` |
160+
| **Hardware interrupt?** | ❌ No | ✅ Timer required | ❌ No |
161+
| **Extra setup call?** | None | Timer init + `attachInterrupt` | None |
162+
| **`enableAutoUpdate()`?** | Not needed | Not needed | Not needed |
163+
| **Works if loop() blocks?** | ❌ No | ✅ Yes | ❌ No |
164+
| **Best for** | Any board, simplest code | Deterministic, busy loops | No-timer boards, reactive RX |
165+
166+
### Mode 1 — Main Loop (default, works everywhere)
167+
168+
```cpp
169+
void loop() {
170+
link.update(); // ← only required call; works on every board
171+
}
172+
```
173+
174+
### Mode 2 — Timer ISR (deterministic, interrupt-driven)
175+
176+
The constructor auto-registers the instance — no `enableAutoUpdate()` needed.
177+
178+
```cpp
179+
#include <TimerOne.h>
180+
181+
void setup() {
182+
Timer1.initialize(1000); // 1 ms
183+
Timer1.attachInterrupt(TinyLink<MyData, TinyArduinoAdapter>::autoUpdateISR);
184+
// No enableAutoUpdate() call required.
185+
}
186+
// loop() does not need to call update() at all.
187+
```
188+
189+
### Mode 3 — serialEvent() (zero-setup, no timer needed)
190+
191+
Arduino calls `serialEvent()` automatically between `loop()` iterations when
192+
Serial bytes are waiting — no library or interrupt configuration required.
193+
194+
```cpp
195+
void serialEvent() {
196+
TinyLink<MyData, TinyArduinoAdapter>::autoUpdateISR();
197+
}
198+
// setup() and loop() have no TinyLink maintenance calls.
199+
```
200+
201+
> **Multi-instance note:** When you have two `TinyLink<T,Adapter>` objects of
202+
> the same type, the last-constructed one is the default ISR target. Call
203+
> `link.enableAutoUpdate()` on the desired instance to override.
204+
205+
See `examples/Auto_Update/` for all three modes with a full side-by-side comparison.
206+
207+
151208
## 📊 Performance & Telemetry
152209

153210
Monitor link health in real-time to diagnose wiring issues:
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Auto_Update — Mode 1: Main-Loop Polling
3+
*
4+
* The simplest, most portable update mode. No hardware interrupts are
5+
* required. Call link.update() once per loop iteration; TinyLink handles
6+
* all framing, checksumming, and dispatching internally.
7+
*
8+
* Works on every Arduino-compatible board, including devices without any
9+
* hardware timer interrupt support (e.g. MH-Tiny88, ATtiny-class MCUs).
10+
*
11+
* Platform: any Arduino-compatible board
12+
* Wiring : Serial TX → peer RX, Serial RX ← peer TX
13+
*/
14+
15+
#include <TinyLink.h>
16+
#include <adapters/TinyArduinoAdapter.h>
17+
#include "SharedData.h"
18+
19+
using namespace tinylink;
20+
21+
TinyArduinoAdapter adapter(Serial);
22+
TinyLink<SensorData, TinyArduinoAdapter> link(adapter);
23+
24+
// ----- Callback (fires inside update()) ----------------------------------
25+
26+
void onReceive(const SensorData& d) {
27+
Serial.print(F("[RX] uptime="));
28+
Serial.print(d.uptime);
29+
Serial.print(F(" temp="));
30+
Serial.println(d.temperature);
31+
}
32+
33+
// -------------------------------------------------------------------------
34+
35+
void setup() {
36+
Serial.begin(9600);
37+
link.onDataReceived(onReceive);
38+
// No extra setup — just start using update() in the loop.
39+
}
40+
41+
void loop() {
42+
// *** The only thing required to drive TinyLink ***
43+
link.update();
44+
45+
// Send a reading every 2 seconds.
46+
static uint32_t lastSend = 0;
47+
static uint8_t appSeq = 0;
48+
if (millis() - lastSend > 2000) {
49+
SensorData msg = { millis(), 24.5f, appSeq++ };
50+
link.sendData(TYPE_DATA, msg);
51+
lastSend = millis();
52+
}
53+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Auto_Update — Mode 2: Hardware Timer ISR
3+
*
4+
* Uses a hardware timer to call TinyLink::autoUpdateISR() at a fixed rate
5+
* (here: every 1 ms via the TimerOne library). The main loop is free of any
6+
* TinyLink maintenance calls; the ISR drives the engine automatically.
7+
*
8+
* autoUpdateISR() is ready to use immediately after construction — no
9+
* enableAutoUpdate() call is needed for the standard single-instance setup.
10+
* Call enableAutoUpdate() only if you need to switch which of several same-type
11+
* instances is driven by the ISR.
12+
*
13+
* When to choose this mode:
14+
* - The main loop has variable, unpredictable timing (busy-wait, delays).
15+
* - You want deterministic, fixed-rate protocol maintenance.
16+
* - A hardware timer is available (most Uno/Mega/ESP32/STM32 boards).
17+
*
18+
* Dependencies: TimerOne library (Arduino IDE Library Manager)
19+
* https://github.com/PaulStoffregen/TimerOne
20+
*
21+
* Platform: Arduino Uno/Mega (AVR) and any board supported by TimerOne
22+
* Wiring : Serial TX → peer RX, Serial RX ← peer TX
23+
*/
24+
25+
#include <TinyLink.h>
26+
#include <adapters/TinyArduinoAdapter.h>
27+
#include <TimerOne.h> // Install via Library Manager
28+
#include "SharedData.h"
29+
30+
using namespace tinylink;
31+
32+
TinyArduinoAdapter adapter(Serial);
33+
TinyLink<SensorData, TinyArduinoAdapter> link(adapter);
34+
35+
// ----- Callback (fires inside the ISR-driven update()) -------------------
36+
37+
void onReceive(const SensorData& d) {
38+
// Keep ISR callbacks short — avoid Serial.print() inside an ISR on AVR.
39+
// Use a flag/buffer and process in loop() for safety on AVR targets.
40+
(void)d;
41+
}
42+
43+
// -------------------------------------------------------------------------
44+
45+
void setup() {
46+
Serial.begin(9600);
47+
link.onDataReceived(onReceive);
48+
49+
// Attach the static ISR function to Timer1 (fires every 1 000 µs = 1 ms).
50+
// No enableAutoUpdate() required — the constructor already registered this
51+
// instance.
52+
Timer1.initialize(1000);
53+
Timer1.attachInterrupt(TinyLink<SensorData, TinyArduinoAdapter>::autoUpdateISR);
54+
}
55+
56+
void loop() {
57+
// update() is NOT called here — the timer ISR handles it automatically.
58+
59+
static uint32_t lastSend = 0;
60+
static uint8_t appSeq = 0;
61+
if (millis() - lastSend > 2000) {
62+
SensorData msg = { millis(), 24.5f, appSeq++ };
63+
link.sendData(TYPE_DATA, msg);
64+
lastSend = millis();
65+
}
66+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Auto_Update — Mode 3: serialEvent() (Zero-Interrupt Fallback)
3+
*
4+
* Arduino calls serialEvent() automatically between loop() iterations
5+
* whenever bytes are waiting in the hardware Serial RX FIFO. This gives
6+
* near-immediate reaction to incoming data without needing a hardware timer
7+
* or UART RX interrupt.
8+
*
9+
* autoUpdateISR() is called from serialEvent() — the function name "ISR" is
10+
* kept for API consistency; serialEvent() is not a true hardware interrupt
11+
* on Arduino, but it provides equivalent zero-extra-step auto-update
12+
* behaviour on every Arduino board regardless of timer availability.
13+
*
14+
* When to choose this mode:
15+
* - No hardware timer is available or all timers are already occupied.
16+
* - You want zero-setup auto-update without any third-party library.
17+
* - Targeting boards where timer interrupt APIs differ across cores
18+
* (e.g., megaTinyCore, ATtinyCore).
19+
*
20+
* Limitation: serialEvent() is not called while the MCU is inside a blocking
21+
* function (delay(), analogRead() >10 ms, etc.). Keep loop() responsive.
22+
*
23+
* Platform: any Arduino-compatible board
24+
* Wiring : Serial TX → peer RX, Serial RX ← peer TX
25+
*/
26+
27+
#include <TinyLink.h>
28+
#include <adapters/TinyArduinoAdapter.h>
29+
#include "SharedData.h"
30+
31+
using namespace tinylink;
32+
33+
TinyArduinoAdapter adapter(Serial);
34+
TinyLink<SensorData, TinyArduinoAdapter> link(adapter);
35+
36+
// ----- Callback -----------------------------------------------------------
37+
38+
static volatile bool g_dataReady = false;
39+
static SensorData g_latest;
40+
41+
void onReceive(const SensorData& d) {
42+
g_latest = d;
43+
g_dataReady = true;
44+
}
45+
46+
// ----- Arduino serialEvent() — called between loop() iterations -----------
47+
//
48+
// No extra setup required; the constructor already registered the instance.
49+
// Simply forward to autoUpdateISR() for API consistency and portability.
50+
//
51+
void serialEvent() {
52+
TinyLink<SensorData, TinyArduinoAdapter>::autoUpdateISR();
53+
}
54+
55+
// -------------------------------------------------------------------------
56+
57+
void setup() {
58+
Serial.begin(9600);
59+
link.onDataReceived(onReceive);
60+
// No enableAutoUpdate(), no timer setup — serialEvent() handles it all.
61+
}
62+
63+
void loop() {
64+
// Process any data that arrived via serialEvent().
65+
if (g_dataReady) {
66+
g_dataReady = false;
67+
Serial.print(F("[RX] uptime="));
68+
Serial.print(g_latest.uptime);
69+
Serial.print(F(" temp="));
70+
Serial.println(g_latest.temperature);
71+
}
72+
73+
static uint32_t lastSend = 0;
74+
static uint8_t appSeq = 0;
75+
if (millis() - lastSend > 2000) {
76+
SensorData msg = { millis(), 24.5f, appSeq++ };
77+
link.sendData(TYPE_DATA, msg);
78+
lastSend = millis();
79+
}
80+
}

0 commit comments

Comments
 (0)