Portable C11
The project is developing an independent portable C implementation for embedded and cross-language use.
The C side is intentionally not a Python extension module. Python and C are separate implementations that can converge on the same language-neutral behavioral contracts.
Current 0.2 foundation
The first portable C contracts are small capability-specific HAL interfaces. They require no dynamic allocation and no Arduino-specific headers, so they can be compiled with an ordinary C11 host compiler before platform adapters are added.
SPI HAL
Introduced in: rtd-acquire 0.2.0
rtd_acquire_spi_t, declared in c/include/rtd_acquire/spi.h, carries:
- caller-owned opaque context;
- effective SPI settings; and
- a full-duplex transfer callback for one complete transaction.
The callback owns chip-select handling for the transaction, so portable device code does not need a separate GPIO/chip-select dependency.
Blocking-delay HAL
Introduced in: rtd-acquire 0.2.0
rtd_acquire_delay_t, declared in c/include/rtd_acquire/delay.h, carries:
- caller-owned opaque context; and
- a blocking
delay_uscallback using whole microseconds.
A successful delay must last at least the requested interval. Oversleep is allowed. The contract deliberately does not introduce a general clock, scheduler, timer object, or Arduino-specific timing API.
Platform delay failures remain execution/platform failures rather than becoming normalized device diagnostics.
See the Portable C HAL API for the exact public types.
Caller-owned measurement and diagnostic storage
Introduced in: rtd-acquire 0.2.0
rtd_acquire_measurement_t, declared in c/include/rtd_acquire/core.h, binds
caller-supplied diagnostic and native-evidence arrays instead of allocating
memory internally. There is no project-wide fixed diagnostic maximum: the
caller selects capacities appropriate for the target.
The C result uses explicit presence flags for optional resistance and
uncertainty values, preserves composite native evidence, derives status from
diagnostic severity, and validates the same core trust invariants as Python.
The portable scalar rtd_acquire_real_t is currently C float. The frozen
Python-binary64/C-binary32 profile defines cross-language numeric acceptance
when that C float is binary32; targets with another floating representation
need an explicit target profile before making the same numeric conformance
claim.
See the Portable C core API for the exact public types.
MAX31865 configuration and threshold encoding
Introduced in: rtd-acquire 0.2.0
rtd_acquire_max31865_config_t, declared in
c/include/rtd_acquire/max31865.h, is the first device-specific portable C
layer. It mirrors the Python electrical configuration contract, validates the
same 2-/3-/4-wire and 50/60 Hz choices, and encodes the static MAX31865
configuration bits and threshold registers.
Low thresholds round downward and high thresholds round upward. The shared threshold-encoding JSON vectors execute against both Python and C.
MAX31865 native register decoding
Introduced in: rtd-acquire 0.2.0
The portable C MAX31865 layer also decodes the native RTD and fault-status
registers into rtd_acquire_measurement_t. Threshold bits remain warnings with
a usable resistance; D5-through-D2 native faults produce a FAULT result without
a resistance. The decoder preserves each documented native bit identifier and
message in caller-owned evidence storage and ignores reserved fault-status bits
rather than inferring a condition.
The MAX31865-specific worst case is six diagnostics plus six native-evidence records. Public constants expose those device-specific maxima so callers can reserve enough fixed storage without creating a project-wide diagnostic cap. The measurement-decode vectors execute against both Python and C using the frozen binary64/binary32 profile, including a non-binary32-exact reference case that exercises the permitted numeric difference.
The public device operations use a discriminated
rtd_acquire_max31865_result_t rather than a single success Boolean. Invalid
arguments, invalid configuration, insufficient caller storage, SPI I/O, delay,
and internal invariant failures remain distinguishable from device-reported
Measurement diagnostics. The pure config_is_valid() query remains Boolean.
MAX31865 one-shot acquisition
Introduced in: rtd-acquire 0.2.0
The portable MAX31865 driver now consumes the SPI and blocking-delay HALs for the same fault-checked one-shot sequence as Python. Its timing policy stores the external input-filter time constant as whole microseconds and requests conservative integer delays for bias settling, automatic fault detection, post-fault settling, and 50/60 Hz conversion. Effective SPI settings are validated before I/O, and execution failures trigger a best-effort VBIAS-off write once the biased sequence has started.
See the Portable C MAX31865 API for the exact public types and helpers.
Arduino AVR / HERO platform adapter
Introduced in: rtd-acquire 0.2.0
The first real embedded adapter now binds the portable HALs to Arduino AVR / UNO-class boards, including the inventr.io HERO. The adapter is C++ only at the platform edge because Arduino's SPI API is C++; all result, diagnostic, MAX31865, SPI-HAL, and delay-HAL contracts remain portable C11.
The SPI binding owns a caller-selected chip-select GPIO around each Arduino SPI
transaction and reports the effective AVR divider-selected clock. The delay
binding preserves the HAL's uint32_t duration range by splitting long waits
into millisecond and sub-millisecond calls instead of passing a long value
directly to AVR delayMicroseconds().
A strict host C++11 test validates adapter semantics, and CI additionally
compiles the included MAX31865 example with the real arduino:avr:uno core.
Neither software check substitutes for the still-pending physical HERO hardware
comparison.
See the Arduino AVR / HERO adapter API.
Why start with the HALs and result contract?
Freezing the platform boundaries first lets future HERO/Arduino, STM32, ESP32, RP2040, Linux, and other adapters implement the same capabilities without baking one board family into the acquisition core.
With the HAL and result boundaries frozen, the MAX31865 implementation was added in independently reviewable layers: configuration/threshold encoding, native register decoding, and finally HAL-driven one-shot acquisition.