The previous implementation used a single static SIGALRM handler and one ITIMER_REAL, so a second IntervalTimer silently clobbered the first despite the API documenting up to 4 simultaneous timers. Reimplement with one std::thread per active timer, each invoking its callback at the requested period via sleep_until. A static slot counter caps concurrent timers at 4 (matching the Teensy 4.x PIT channels); begin() returns false once all 4 are in use, and end() frees the slot. update() retimes the next interval, and a self-end() from within a callback detaches rather than joining to avoid deadlock. Because the library now uses std::thread, src/CMakeLists.txt links Threads::Threads PUBLIC so consumers (e.g. test/blink) link pthread transitively. Adds a test/bugs regression test covering four concurrent timers, the max-4 limit, and slot reuse after end(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FAsvzgsaayj2nbZnnQGAav
4.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
A library of stub/mock implementations of the Arduino + Teensyduino core APIs so that Teensy (Teensy 4.x / IMXRT1062) sketches and libraries can be compiled, run, and debugged natively on x86/x64 (Linux, macOS, Windows) instead of on the microcontroller. It is the base of an ecosystem of companion stub libraries (sd, audio, st7735, midi, bounce2, encoder) that depend on it.
The implementations are deliberately minimal: most hardware functions (GPIO, ADC, PWM, interrupts, RTC, tempmon) are empty no-ops. The parts that have real behavior are timing, serial I/O, string handling, and timers — emulated using the host OS.
Build
mkdir cmake-build-debug && cd cmake-build-debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build .
The root build (CMakeLists.txt → src/CMakeLists.txt) only produces the teensy_x86_stubs
library; it has no main. To actually run something you build one of the test apps, which are
standalone consumer projects, not part of the root build:
cd test/blink # or test/first
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build .
./blink # test/first produces ./basic
Each test/*/CMakeLists.txt pulls the library fresh from GitHub (main branch) via the
DeclareAndFetch macro in test/cmake_declare_and_fetch.cmake.in — it does not use the
local working copy. So when changing library code, verify against the root build, or temporarily
point DeclareAndFetch at your branch/local path to test end-to-end.
There is no unit-test framework and no lint step. CI (.github/workflows/{root,blink,first}.yml)
simply runs the three CMake builds above on every push.
Architecture notes
- Target emulation.
src/CMakeLists.txtsets__IMXRT1062__,ARDUINO_TEENSY41, andARDUINO=158as compile definitions, and C++17. Code that branches on these macros is pretending to be a Teensy 4.1. - Headers mirror Teensyduino. Files like
Arduino.h,Print.h,Stream.h,WString.h,core_pins.h,IntervalTimer.h,AudioStream.h,EventResponder.h,imxrt.h,arm_math.hkeep the same names/signatures as the real Teensy core so unmodified sketches compile. When stubbing a new API, match the upstream Teensyduino signature exactly. - Timing is real, hardware is fake.
millis()/micros()/nanos()(core_pins.cpp) readstd::chrono::system_clock;delay*callstd::this_thread::sleep_for. The baseline is set byinitialize_mock_arduino()(Arduino.cpp), which captures the start epoch so timers read as "since program start". Sketches must callinitialize_mock_arduino()at the top ofmain()or timing values are absolute epoch values. IntervalTimer(IntervalTimer.cpp) runs each active timer on its ownstd::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 usesstd::thread,src/CMakeLists.txtlinksThreads::Threadsas a PUBLIC dependency so consumers link pthread transitively.- Concurrency / interrupts. There is no ISR model.
__disable_irq/__enable_irq(Arduino.cpp) are astd::mutexcritical section guarded by the globalarduino_should_exitflag.yield()sleeps and optionally drives theEventResponder(via theYIELD_CHECK_EVENT_RESPONDERflag andyield_active_check_flags). setup/loopare declared withasm("_setup")/asm("_loop")aliases — test apps typically provide their ownmain()instead of relying on the Arduino setup/loop runtime.- MSVC.
ctz_clz.cppis compiled only under MSVC (count-trailing/leading-zero intrinsics GCC/Clang already provide). Several files have#ifdef _MSC_VERbranches; preserve them when editing.
Conventions
- Many source files carry the PJRC Teensyduino MIT license header verbatim because they are derived from / kept signature-compatible with the upstream core. Keep these headers when editing such files.
- Adding a new stub means editing
src/CMakeLists.txt'sSOURCE_FILES/HEADER_FILESlists — there is no glob.