mirror of
https://github.com/newdigate/teensy-x86-stubs.git
synced 2026-07-04 14:46:42 +00:00
Merge pull request #16 from newdigate/claude/init-k08rmx
Support multiple concurrent IntervalTimers (Teensy parity)
This commit is contained in:
@@ -56,8 +56,10 @@ simply runs the three CMake builds above on every push.
|
||||
by `initialize_mock_arduino()` (`Arduino.cpp`), which captures the start epoch so timers read
|
||||
as "since program start". **Sketches must call `initialize_mock_arduino()` at the top of
|
||||
`main()`** or timing values are absolute epoch values.
|
||||
- **`IntervalTimer`** (`IntervalTimer.cpp`) is implemented with POSIX `setitimer(ITIMER_REAL)` +
|
||||
a `SIGALRM` handler — a single static handler, so it effectively supports one active timer.
|
||||
- **`IntervalTimer`** (`IntervalTimer.cpp`) runs each active timer on its own `std::thread`,
|
||||
with a static slot counter capped at 4 (matching the Teensy 4.x PIT channels) — `begin()`
|
||||
returns false once all 4 are in use. Because it uses `std::thread`, `src/CMakeLists.txt` links
|
||||
`Threads::Threads` as a PUBLIC dependency so consumers link pthread transitively.
|
||||
- **Concurrency / interrupts.** There is no ISR model. `__disable_irq`/`__enable_irq`
|
||||
(`Arduino.cpp`) are a `std::mutex` critical section guarded by the global
|
||||
`arduino_should_exit` flag. `yield()` sleeps and optionally drives the `EventResponder` (via
|
||||
|
||||
+6
-1
@@ -46,4 +46,9 @@ if (BUILD_DYNAMIC_LIBRARY)
|
||||
add_library(teensy_x86_stubs SHARED ${HEADER_FILES} ${SOURCE_FILES})
|
||||
else ()
|
||||
add_library(teensy_x86_stubs SHARED STATIC ${HEADER_FILES} ${SOURCE_FILES})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# IntervalTimer runs each timer on its own std::thread, so consumers must link
|
||||
# a threads library. Propagate it publicly so anything linking this lib gets it.
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(teensy_x86_stubs PUBLIC Threads::Threads)
|
||||
+49
-15
@@ -30,24 +30,58 @@
|
||||
|
||||
#include "IntervalTimer.h"
|
||||
|
||||
std::function<void()> IntervalTimer::handler = NULL;
|
||||
std::atomic<int> IntervalTimer::s_activeCount{0};
|
||||
|
||||
void timer_handler (int signum){
|
||||
IntervalTimer::callhandler();
|
||||
//printf("dfsdfsdf\n");
|
||||
bool IntervalTimer::beginMicros(callback_t funct, uint32_t microseconds)
|
||||
{
|
||||
if (funct == nullptr) return false;
|
||||
if (microseconds == 0 || microseconds > MAX_PERIOD) return false;
|
||||
|
||||
// Restart cleanly if this instance was already running (frees its slot).
|
||||
end();
|
||||
|
||||
// Claim one of the limited timer slots, matching the Teensy's 4 PIT
|
||||
// channels: once all are in use, begin() fails just like on hardware.
|
||||
int cur = s_activeCount.load();
|
||||
do {
|
||||
if (cur >= MAX_TIMERS) return false;
|
||||
} while (!s_activeCount.compare_exchange_weak(cur, cur + 1));
|
||||
|
||||
_func = funct;
|
||||
_period_us.store(microseconds);
|
||||
_active.store(true);
|
||||
_thread = std::thread(&IntervalTimer::run, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IntervalTimer::beginCycles(callback_t funct, uint32_t cycles)
|
||||
void IntervalTimer::run()
|
||||
{
|
||||
IntervalTimer::handler = funct;
|
||||
s_action.sa_handler = &timer_handler;
|
||||
sigaction (SIGALRM, &s_action, NULL);
|
||||
using namespace std::chrono;
|
||||
auto next = steady_clock::now();
|
||||
while (_active.load()) {
|
||||
// Schedule the next tick before sleeping so update() takes effect on
|
||||
// the following interval (the in-progress interval completes as set).
|
||||
next += microseconds(_period_us.load());
|
||||
std::this_thread::sleep_until(next);
|
||||
if (!_active.load()) break;
|
||||
callback_t f = _func;
|
||||
if (f) f();
|
||||
}
|
||||
}
|
||||
|
||||
timer.it_value.tv_sec = 0;
|
||||
timer.it_value.tv_usec = cycles;
|
||||
timer.it_interval.tv_sec = 0;
|
||||
timer.it_interval.tv_usec = cycles;
|
||||
setitimer (ITIMER_REAL, &timer, NULL);
|
||||
|
||||
return true;
|
||||
void IntervalTimer::end()
|
||||
{
|
||||
bool was_active = _active.exchange(false);
|
||||
if (_thread.joinable()) {
|
||||
if (std::this_thread::get_id() == _thread.get_id()) {
|
||||
// Called from within the callback itself: can't join our own
|
||||
// thread, so let it finish and detach.
|
||||
_thread.detach();
|
||||
} else {
|
||||
_thread.join();
|
||||
}
|
||||
}
|
||||
if (was_active) {
|
||||
s_activeCount.fetch_sub(1);
|
||||
}
|
||||
}
|
||||
+31
-35
@@ -33,28 +33,35 @@
|
||||
#define __INTERVALTIMER_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include "Arduino.h"
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <functional>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
// IntervalTimer provides access to hardware timers which can run an
|
||||
// interrupt function a precise timing intervals.
|
||||
// https://www.pjrc.com/teensy/td_timing_IntervalTimer.html
|
||||
// Up to 4 IntervalTimers may be in use simultaneously. Many
|
||||
// libraries use IntervalTimer, so some of these 4 possible
|
||||
// instances may be in use by libraries.
|
||||
//
|
||||
// On the host this is emulated with one std::thread per active timer: the
|
||||
// callback runs on its own thread at the requested period. Up to MAX_TIMERS
|
||||
// (4, matching the Teensy 4.x PIT channels) may run at once; begin() returns
|
||||
// false once they are all in use.
|
||||
class IntervalTimer {
|
||||
private:
|
||||
static const int32_t MAX_PERIOD = UINT32_MAX / (24000000 / 1000000); // need to change to int32_t to avoid warnings
|
||||
static const uint32_t MAX_PERIOD = UINT32_MAX / (24000000 / 1000000);
|
||||
static const int MAX_TIMERS = 4; // Teensy 4.x exposes 4 PIT channels
|
||||
static std::atomic<int> s_activeCount; // number of timers currently running
|
||||
public:
|
||||
IntervalTimer() {
|
||||
memset (&s_action, 0, sizeof (s_action));
|
||||
}
|
||||
IntervalTimer() {}
|
||||
~IntervalTimer() {
|
||||
end();
|
||||
}
|
||||
// IntervalTimer owns a thread and atomics, so it is not copyable/movable.
|
||||
IntervalTimer(const IntervalTimer&) = delete;
|
||||
IntervalTimer& operator=(const IntervalTimer&) = delete;
|
||||
|
||||
using callback_t = void (*)(void);
|
||||
// Start the hardware timer and begin calling the function. The
|
||||
// interval is specified in microseconds, using integer or float
|
||||
@@ -62,36 +69,27 @@ public:
|
||||
// all hardware timers are already in use.
|
||||
template <typename period_t>
|
||||
bool begin(callback_t funct, period_t period) {
|
||||
uint32_t cycles = period;
|
||||
return beginCycles(funct, cycles);
|
||||
uint32_t microseconds = (uint32_t)period;
|
||||
return beginMicros(funct, microseconds);
|
||||
}
|
||||
// Change the timer's interval. The current interval is completed
|
||||
// as previously configured, and then the next interval begins with
|
||||
// with this new setting.
|
||||
void update(unsigned int microseconds) {
|
||||
if (microseconds == 0 || microseconds > MAX_PERIOD) return;
|
||||
timer.it_interval.tv_usec = microseconds;
|
||||
setitimer (ITIMER_REAL, &timer, NULL);
|
||||
_period_us.store(microseconds);
|
||||
}
|
||||
// Change the timer's interval. The current interval is completed
|
||||
// as previously configured, and then the next interval begins with
|
||||
// with this new setting.
|
||||
template <typename period_t>
|
||||
void update(period_t period){
|
||||
uint32_t micros = microsFromPeriod(period);
|
||||
timer.it_value.tv_sec = 0;
|
||||
timer.it_value.tv_usec = micros;
|
||||
timer.it_interval.tv_sec = 0;
|
||||
timer.it_interval.tv_usec = micros;
|
||||
setitimer (ITIMER_REAL, &timer, NULL);
|
||||
}
|
||||
void update(period_t period) {
|
||||
update((unsigned int)period);
|
||||
}
|
||||
|
||||
// Stop calling the function. The hardware timer resource becomes available
|
||||
// for use by other IntervalTimer instances.
|
||||
void end() {
|
||||
timer.it_interval.tv_usec = 0;
|
||||
setitimer (ITIMER_REAL, &timer, NULL);
|
||||
}
|
||||
void end();
|
||||
// Set the interrupt priority level, controlling which other interrupts this
|
||||
// timer is allowed to interrupt. Lower numbers are higher priority, with 0
|
||||
// the highest and 255 the lowest. Most other interrupts default to 128. As
|
||||
@@ -100,16 +98,14 @@ public:
|
||||
void priority(uint8_t n) {
|
||||
}
|
||||
|
||||
static std::function<void()> handler;
|
||||
static void callhandler() {
|
||||
if (handler != nullptr)
|
||||
handler();
|
||||
}
|
||||
|
||||
private:
|
||||
struct sigaction s_action;
|
||||
struct itimerval timer;
|
||||
bool beginCycles(callback_t funct, uint32_t cycles);
|
||||
bool beginMicros(callback_t funct, uint32_t microseconds);
|
||||
void run();
|
||||
|
||||
callback_t _func = nullptr;
|
||||
std::atomic<uint32_t> _period_us{0};
|
||||
std::atomic<bool> _active{false};
|
||||
std::thread _thread;
|
||||
};
|
||||
|
||||
#endif //__INTERVALTIMER_H__
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
|
||||
static int g_failures = 0;
|
||||
static int g_total = 0;
|
||||
@@ -106,6 +107,45 @@ static void test_available_count() {
|
||||
drain();
|
||||
}
|
||||
|
||||
// IntervalTimer: the host emulation must support multiple concurrent timers
|
||||
// (Teensy 4.x exposes 4 PIT channels), not just a single shared one.
|
||||
static std::atomic<int> g_tick1{0}, g_tick2{0}, g_tick3{0}, g_tick4{0};
|
||||
static void cb1() { ++g_tick1; }
|
||||
static void cb2() { ++g_tick2; }
|
||||
static void cb3() { ++g_tick3; }
|
||||
static void cb4() { ++g_tick4; }
|
||||
|
||||
static void test_interval_timer_multiple() {
|
||||
std::printf("IntervalTimer multiple concurrent timers (Teensy parity)\n");
|
||||
g_tick1 = g_tick2 = g_tick3 = g_tick4 = 0;
|
||||
|
||||
IntervalTimer t1, t2, t3, t4, t5;
|
||||
bool b1 = t1.begin(cb1, 10000); // 10 ms period
|
||||
bool b2 = t2.begin(cb2, 10000);
|
||||
bool b3 = t3.begin(cb3, 10000);
|
||||
bool b4 = t4.begin(cb4, 10000);
|
||||
bool b5 = t5.begin(cb1, 10000); // 5th must fail: only 4 channels
|
||||
CHECK(b1 && b2 && b3 && b4, "four IntervalTimers start concurrently");
|
||||
CHECK(!b5, "a fifth IntervalTimer is refused (max 4, like Teensy)");
|
||||
|
||||
delay(120); // ~12 ticks per timer
|
||||
int s1 = g_tick1.load();
|
||||
t1.end();
|
||||
t2.end();
|
||||
t3.end();
|
||||
t4.end();
|
||||
CHECK(g_tick1 > 0 && g_tick2 > 0 && g_tick3 > 0 && g_tick4 > 0,
|
||||
"all four timer callbacks fired (timers ran in parallel)");
|
||||
|
||||
delay(50);
|
||||
CHECK(g_tick1.load() == s1 || g_tick1.load() == s1 + 1,
|
||||
"end() stops the timer (no further ticks beyond the in-flight one)");
|
||||
|
||||
bool b6 = t5.begin(cb2, 10000);
|
||||
CHECK(b6, "a timer slot frees up after end()");
|
||||
t5.end();
|
||||
}
|
||||
|
||||
int main() {
|
||||
initialize_mock_arduino();
|
||||
|
||||
@@ -115,6 +155,7 @@ int main() {
|
||||
test_readstringuntil_max_cap();
|
||||
test_write_return_value();
|
||||
test_available_count();
|
||||
test_interval_timer_multiple();
|
||||
|
||||
std::printf("\n%d/%d checks passed, %d failed\n",
|
||||
g_total - g_failures, g_total, g_failures);
|
||||
|
||||
Reference in New Issue
Block a user