A learned, frame-by-frame compute-scaling framework for edge AI on Arm Cortex-M microcontrollers.
Built for the Arm AI Optimization Challenge 2026 - Physical AI Track
Hardware core: Renesas EK-RA8D1 (Cortex-M85), ESP32, 7SEMI ADS1263, ICM20948.
Submission window: June 10 to August 14, 2026 | Timeline: 6-week build, started July 5, 2026
- Table of Contents
- Overview
- Why This Isn't Just an If/Else Statement
- System Architecture
- Hardware
- Per-Demo Model Zoo Design
- Meta-Controller Design
- Firmware / Software Stack
- Inference Dispatch Architecture
- Repository / Project Structure
- Toolchain, Build System & Environment
- The Build Journey: Every Problem We Hit and How We Fixed It
- Runtime Feature Extraction
- Meta-Controller Training Data Extraction
- Benchmarking Infrastructure
- Dynamic Latency Measurement
- How to Build and Flash
- Demo Script
- Toolchain, Build System and Environment
- Submission Checklist
- License
- Developers
ARIA is not a single model. It is an on-device runtime layer that decides how much inference capacity a physical sensing task should use next, choosing among Fast, Balanced, and Accurate model variants already resident in the same MCU flash image.
Think of it as an automatic transmission for edge inference:
- Model Zoo (the gears): three pre-trained variants of the same task, with different speed/accuracy/compute trade-offs.
- Meta-Controller (the learned transmission logic): a compact neural network that consumes live runtime features and proposes the next model tier.
- Sensor-specific event guard (deployed safety/responsiveness layer): small deterministic logic used only where the physical sensor needs an immediate reaction that the longer inference window may average away. For ADS1263 this guard is triggered only by sudden sample-to-sample spikes or dips; ordinary and gradual behavior remains under the learned controller.
A major implementation advantage is that all six EON-compiled model variants and the trained Meta-Controller engine are linked into one RA8D1 firmware image: Three ADS1263 models, three ICM-20948 models and the trained meta-controller. Runtime switching requires no model download, no filesystem access, no cloud request, and no reboot; the dispatcher simply invokes the already-linked graph selected for that sensor.
The reusable idea is the common skeleton:
sensor -> runtime features -> adaptive controller -> model zoo -> output -> feedback
The sensor-specific feature definitions and protective event logic can differ, while the model-zoo / controller / dispatcher structure remains the same.
ARIA's normal model-selection path is learned from labeled runtime data rather than being a single hand-written mapping from sensor amplitude to model tier. The ADS meta-controller, for example, is a trained 4 -> 16 -> 3 MLP that consumes confidence average, confidence trend, signal delta, and signal RMS and produces learned evidence for Fast, Balanced, and Accurate.
The deployed system is intentionally described accurately as learned routing with sensor-specific event guards, not as a threshold-free system:
- Normal routing is learned. The trained Meta-Controller supplies the model-selection evidence used during ordinary operation.
- The ADS transient override is exceptional, not continuous. It runs on fresh ADC samples but only overrides normal routing when a sudden positive or negative sample-to-sample change is detected. A gradual change does not automatically force Balanced or Accurate.
- The ICM path is physics-guided. Stationary/strong-motion safeguards use motion statistics appropriate to a six-axis IMU, while the trained controller contributes within the adaptive policy.
- The architecture generalizes across domains. Both demos use the same model-zoo + runtime-feature + controller + dispatch + telemetry skeleton even though their sensor-specific features and guards differ.
This distinction is important: ARIA is not claiming that every deployed control decision is neural-network-only. It demonstrates a practical edge-AI architecture in which learned compute selection is combined with minimal, explicit physical event handling when responsiveness or safety requires it.
ARIA's model-zoo-plus-meta-controller pattern isn't tied to any one sensor, it's a template for any Cortex-M deployment where "always run the expensive model" wastes power and "always run the cheap model" misses rare, important events.
1. Industrial predictive maintenance (direct analogue of Demo 1) A sensor node bolted to a motor, pump, or conveyor bearing housing reads vibration or current draw through an analog front-end identical in shape to the ADS1263 pipeline. Fast mode runs during normal operation (near-zero anomaly probability, minimal power draw); the moment RMS energy or waveform shape drifts, ARIA escalates to Balanced or Accurate and flags bearing wear or an impending fault before failure, without an absolute operating-level-to-model table tuned per machine; the deployed sensor may still use a small event guard for exceptional transients.
2. Battery & electrical system anomaly detection The ADS1263's 32-bit resolution is built for exactly this: battery-management-system cell-imbalance detection in EV energy-storage packs, solar inverter output-anomaly monitoring, or panel-level circuit-breaker predictive fault detection, all cases where 99% of readings are boring and 1% matter enormously.
3. Precision lab, test & calibration equipment Bench instrumentation and calibration rigs that log analog reference signals over long unattended runs (24 to 72 hour stability tests, environmental chambers, calibration drift-tracking) can run ARIA's Fast tier for the vast majority of a run and escalate to Accurate only when a reading suggests actual drift or an out-of-spec event worth flagging for a human to review, reducing how much of a multi-day log actually needs close scrutiny.
4. The developer-impact case Because the model-zoo/controller/dispatcher skeleton is reusable across sensors, a Cortex-M85 developer can train three tiers for a new task and reuse the ARIA dispatch structure while adapting sensor-specific runtime features and guards as required. Every use case above is a drop-in replacement for the ADS1263 signal/ ICM20948, not a rewrite of the pipeline.
5. Drone and UAV flight-anomaly detection Flight controllers already carry six-axis IMUs for stabilization. The same accel+gyro feature pipeline used in Demo 2 can run alongside the flight-control loop to flag abnormal motion signatures, a prop strike, an unexpected tumble, an impending stall, escalating compute only when the motion pattern looks abnormal rather than running an expensive anomaly model on every single control loop tick.
Both live demos use the same high-level runtime skeleton:
Sensor
|
v
Runtime / physical feature extraction
|
v
Adaptive controller
|
+--> normal learned Meta-Controller path
|
+--> sensor-specific event guard when required
|
v
Model Zoo: Fast / Balanced / Accurate
|
v
Real inference result + measured latency
|
+--------------------> feedback into the next inference history
The control cadence is sensor-dependent:
- A model inference completes on the window size required by the currently active variant.
- Confidence and signal/motion features are updated.
- The trained Meta-Controller proposes the next tier.
- The dispatcher selects one of the already-linked model graphs for the next inference.
- The result, confidence history, and DWT-measured latency feed the next decision.
For ADS1263 there is one additional fast path: every fresh ADC sample is checked for an abrupt step. Only a sufficiently sudden positive spike or negative dip can temporarily override the normal learned route to Balanced or Accurate. The selected transient tier is latched until one genuine inference from that model completes, then the normal learned path resumes.
For ICM-20948, domain-specific still/strong-motion logic uses gravity-removed accelerometer activity and gyroscope RMS around the same learned-controller/model-zoo skeleton.
The ESP32 and browser dashboard are observation layers only; they do not participate in inference or model selection.
| Demo | Sensor | Task | Status |
|---|---|---|---|
| Demo 1 | ADS1263 (32-bit ADC) | Signal/vibration anomaly detection via analog channel | Hardware bring-up complete, working |
| Demo 2 | ICM-20948 (9-axis IMU) | Motion/vibration anomaly detection (accel + gyro, 6 axes used) | Hardware bring-up complete, working |
ARIA runs on two live demos: ADS1263 (analog signal anomaly detection) as the guaranteed core, and ICM-20948 (motion/vibration anomaly detection, 6 of its 9 axes used) as the second demo proving the framework generalizes across sensor domains.
Demo 1 (ADS1263) is the guaranteed core submission: a single sensor, three model variants (Fast/Balanced/Accurate), and a trained Meta-Controller making the runtime switching decision. This alone demonstrates the full pipeline, switching logic and sensor-agnostic architecture, end to end.
Demo 2 (ICM-20948) reuses the same high-level Meta-Controller/model-zoo architecture and training pattern on a structurally different sensing task (single-channel analog signal vs. multi-axis motion data), which is the direct, hardware-proven demonstration of generality across domains that the judging criteria call out.
| # | Component | Role | Interface |
|---|---|---|---|
| 1 | EK-RA8D1 (Cortex-M85) | Main compute: runs model zoo + meta-controller + RTOS | - |
| 2 | ADS1263 | Demo 1 sensor input | SPI |
| 3 | ICM-20948 (9-axis device; 6 axes used) | Demo 2 sensor input | SPI |
| 4 | ESP32 | Wi-Fi telemetry bridge + Dashboard to view results | UART to RA8D1 |
| ADS1263 Pin | EK-RA8D1 Pin | Notes |
|---|---|---|
| 5V | 5V | |
| GND | GND | |
| DRDY | P010 (MIKROBUS_INT) |
Configured as a plain GPIO input, polled every loop iteration via R_IOPORT_PinRead() — not a hardware ISR (see §11.1 for why). |
| MISO | P410 (SPI1.MISO1) |
Hardware SPI1 peripheral pin |
| MOSI | P411 (SPI1.MOSI1) |
Hardware SPI1 peripheral pin |
| SCK | P412 (SPI1.RSPCK1) |
Hardware SPI1 peripheral pin |
| CS | P413 (SPI1.SSLB0) |
Hardware chip-select, driven by the SPI peripheral itself, not a manual GPIO toggle |
| START | 5V | Strapped high in hardware to keep the ADC in continuous-conversion mode; CMD_START1 in firmware (§8.2) is issued on top of this as the software-side start command |
| RST | P507 (MIKROBUS_RES) |
GPIO output, held low at init; pulsed per the reset sequence in §8.2 |
| AIN0 / A0 | analog divider / test-signal node | Positive signal input used by the demo; do not short this node directly to ground. |
| AINCOM | star GND | Negative input for INPMUX = 0x0A (AIN0 - AINCOM). |
| AGND | star GND | Analog ground reference shared by the divider/test circuit. |
All four SPI signals and the DRDY/RST GPIOs share the RA8D1's mikroBUS/Arduino-compatible header (ARDUINO_D10–D13 silkscreen), which is why DRDY and RST land on the MIKROBUS_INT/MIKROBUS_RES pins rather than a separate PMOD connector.
| ICM-20948 Pin | EK-RA8D1 Pin | Notes |
|---|---|---|
| VIN | 3V3 | Sensor supply |
| GND | GND | Common ground |
| SDO (MISO) | P700 | SPI0 MISO |
| SDA (MOSI) | P701 | SPI0 MOSI |
| SCL (SCK) | P702 | SPI0 RSPCK0 |
| CS | P705 | SPI0 SSLA2, active low |
This is the working final SPI0 mapping. P705 is intentional for chip-select. The final FSP configuration uses SPI mode 0, MSB-first, full-duplex 4-wire operation (SPI_B_SSL_MODE_SPI) at approximately 1 MHz.
The ICM path is fully brought up and live: WHO_AM_I returns 0xEA, six-axis accel+gyro samples are acquired at 100 Hz, and the three ICM EON-compiled models run on the RA8D1.
| ESP32 Pin | EK-RA8D1 Pin | Notes |
|---|---|---|
| GND | GND | |
| VIN | 3V3 | Depends on the specific ESP32 dev board's regulator |
| TX (GPIO17) | J25-2 · PA02 (sci2.rxd2) |
Crossed: ESP32 TX feeds the RA8D1's RX |
| RX (GPIO16) | J25-3 · PA03 (sci2.txd2) |
Crossed: RA8D1 TX feeds the ESP32's RX |
The UART telemetry path is now active and proven with both sensor streams. The RA8D1 sends ADS and ICM telemetry over the shared UART transport; a static binary semaphore serializes transmissions so the two FreeRTOS tasks cannot corrupt each other's packets.
The ESP32 parses the two streams independently and serves the live browser dashboard over Wi-Fi. Telemetry is deliberately outside the inference critical path: disconnecting Wi-Fi or the dashboard does not stop local model selection or inference on the RA8D1.
Each demo has its own 3 model variants, trained in Edge Impulse but designed around what fits that sensor.
| Variant | Processing | Architecture | Quantization |
|---|---|---|---|
| Fast | Flatten (7 statistical features) | 1-layer dense NN (12-20 neurons) | int8 |
| Balanced | Flatten + Spectral Analysis | Dense NN (32→16→8) | int8 |
| Accurate | Flatten + Spectral Analysis (full) | Dense NN (128→64→32, dropout) | int8 |
The ICM-20948 model zoo consumes six interleaved inertial channels:
accX + accY + accZ + gyrX + gyrY + gyrZ
The generated Edge Impulse metadata for all three ICM variants specifies:
- Sensor type: sensor fusion.
- Sampling frequency: 100 Hz (
10 msinterval). - Six input axes.
- Spectral Analysis DSP.
FFT length = 16.- FFT overlap enabled.
- Log spectral output enabled.
78DSP / neural-network input features.- Three classification outputs:
fault,idle,normal. - EON-compiled / TFLite compiled graph.
- Quantized int8 inference.
The deployed variant table is:
| Variant | Raw Input | DSP Block | NN / Deployment Architecture | Quantization |
|---|---|---|---|---|
| Fast | 100 frames × 6 = 600 floats |
Six-axis Spectral Analysis, FFT-16, overlap + log, 78 output features | EON-compiled TFLite classification graph, 78-feature input → 3-class output | int8 |
| Balanced | 100 frames × 6 = 600 floats |
Six-axis Spectral Analysis, FFT-16, overlap + log, 78 output features | EON-compiled TFLite classification graph, 78-feature input → 3-class output | int8 |
| Accurate | 200 frames × 6 = 1200 floats |
Six-axis Spectral Analysis, FFT-16, overlap + log, 78 output features | EON-compiled TFLite classification graph, 78-feature input → 3-class output | int8 |
The generated metadata exposes the deployment graph interface, DSP configuration, feature count and quantization state. The exact layer-by-layer neural-network topology is compiled into the generated EON C++ graph, so this README does not invent an unverified Dense/CNN layer description.
The input layout is:
[ax, ay, az, gx, gy, gz,
ax, ay, az, gx, gy, gz,
...]
The dispatcher returns required floats, not frames:
imu_run_inference_get_required_floats(variant)and inference runs through:
aria_run_inference_imu(
variant,
sample_window,
&result,
false);The ICM classifier exposes:
result.p_fault
result.p_idle
result.p_normalThe final merged implementation locally reproduces the ICM spectral feature path because the ADS-side generated spectral metadata cannot safely be reused for the ICM model. The local DSP reproduces the required six-axis 78-feature spectral input before direct EON graph execution.
ADS model artifacts live under:
src/aria-ads-merged/
Important files include:
src/aria-ads-merged/
├── model-parameters/
│ ├── model_metadata.h
│ └── model_variables.h
├── tflite-model/
│ ├── tflite_model_compiled_fast.cpp
│ ├── tflite_model_compiled_fast.h
│ ├── tflite_model_compiled_balanced.cpp
│ ├── tflite_model_compiled_balanced.h
│ ├── tflite_model_compiled_accurate.cpp
│ ├── tflite_model_compiled_accurate.h
│ └── trained_model_ops_define.h
├── run_inference_dispatcher.cpp
└── run_inference_dispatcher.h
model_variables.h binds the generated model handles, DSP configuration and model-specific runtime metadata used by the ADS dispatcher.
The equivalent IMU-generated artifacts live under:
src/aria-imu-merged/
Important files are:
src/aria-imu-merged/
├── model-parameters/
│ ├── model_metadata_ICM.h
│ └── model_variables_ICM.h
├── tflite-model/
│ ├── tflite_model_compiled_icm_fast.cpp
│ ├── tflite_model_compiled_icm_fast.h
│ ├── tflite_model_compiled_icm_balanced.cpp
│ ├── tflite_model_compiled_icm_balanced.h
│ ├── tflite_model_compiled_icm_accurate.cpp
│ └── tflite_model_compiled_icm_accurate.h
└── run_inference_dispatcher_imu.h
model_variables_ICM.h binds the three ICM EON graph callbacks (init, input, invoke, output, reset), the spectral DSP configuration, quantization metadata, class labels and impulse handles.
The executable implementation remains in the merged dispatcher translation unit on the ADS side because ADS and ICM generated Edge Impulse definitions are intentionally kept together to avoid multiple-definition/linker conflicts.
Same structural template as Demo 1, but consuming 6-axis motion data (accelerometer + gyroscope) instead of a single analog channel. The ICM-20948 is a genuine 9-axis IMU (accelerometer + gyroscope + magnetometer), but ARIA intentionally reads only the 6-axis accel + gyro data. Vibration and motion anomalies show up as changes in acceleration and rotation rate, not heading, so the magnetometer is irrelevant to this task. The richer multi-axis feature set lets Balanced/Accurate variants exploit spectral features across all six channels for improved fault/anomaly classification.
Each sensor owns a three-tier model zoo:
- ADS1263: Fast, Balanced, Accurate
- ICM-20948: Fast, Balanced, Accurate
The generated EON/TFLite artifacts and per-model metadata are compiled into the project rather than loaded dynamically at runtime.
Key deployment advantage — six models, one flash image: all six model graphs are present in the RA8D1 firmware at the same time. Switching a tier does not copy a new model into RAM, reload from external flash, contact the ESP32, or call the cloud. The runtime dispatcher simply selects the appropriate already-linked graph.
This is especially useful for embedded systems because it makes model switching deterministic and removes model-loading latency from the decision loop. The ESP32 is only a telemetry/dashboard bridge; the complete adaptive inference stack remains on the Cortex-M85.
All six deployed model variants have been measured from the actual compiled
object files using arm-none-eabi-size. Runtime inference latency is measured
on-device using the Cortex-M85 DWT cycle counter rather than hardcoded values.
Key deployment result:
- 6 model graphs resident simultaneously in one firmware image
- ~37.11 KiB combined direct compiled model-object flash contribution
- ~288.6 KiB total linked firmware flash content
- No runtime model loading, reflashing, or cloud inference is required when switching between Fast, Balanced, and Accurate.
See RESULTS.md for the complete per-model accuracy, compiled size, runtime benchmark results, and hardware evidence.
The ADS Meta-Controller is a trained MLP exported in meta_controller_weights.h:
4 inputs -> 16 ReLU hidden units -> 3 output scores
Labels are:
0 = FAST
1 = BALANCED
2 = ACCURATE
Its deployed inputs are exactly:
confidence_avg
confidence_trend
signal_delta
signal_rms
latency_us is still logged and measured, but it is intentionally not an input to the current ADS MLP.
The current header was trained on 354 labeled runtime rows and reports a full-data training accuracy of 0.791. The firmware also reproduces the final MLP forward pass so the three raw learned output scores can be exposed in the debugger:
g_debug_meta_score_fast
g_debug_meta_score_balanced
g_debug_meta_score_accurate
g_debug_meta_argmax
This allows the runtime decision to be inspected instead of treating the MLP as a black box.
adc_thread_entry.c maintains:
typedef struct {
float confidence_avg;
float confidence_trend;
float signal_delta;
float latency_us;
float signal_rms;
} meta_controller_features_t;For the current ADS runtime:
confidence_avgis the mean of the last 3p_anomalyvalues.confidence_trendis the newest anomaly confidence minus the previous value.signal_deltais current inference-window RMS minus the previous inference-window RMS.signal_rmsis the absolute RMS of the current inference window.latency_usis the DWT-measured inference duration and is logged/telemetried, but excluded from the four-input MLP.
Normal ADS routing is driven by the existing trained MLP. Instead of discarding all information except the argmax, the deployed firmware also examines the three raw learned output scores.
The current learned-score policy keeps Fast and Accurate argmax decisions directly. When Balanced is the argmax, the two neighboring alternatives are compared; if the learned Accurate score is stronger than the learned Fast score, the requested destination is Accurate, otherwise it remains Balanced.
A hierarchical scheduler then moves through the ordered compute tiers one level at a time:
FAST <-> BALANCED <-> ACCURATE
A learned Fast-to-Accurate request therefore traverses a genuine Balanced inference before reaching Accurate. This scheduler does not inspect ADC voltage or RMS thresholds.
The latest ADS advancement adds a fast, sample-level transient severity guard so a brief spike or dip is not averaged away by a 100-300-sample inference window.
This guard does not override the model on every sample. It only takes control when the newest sample changes abruptly relative to the immediately preceding sample and the recent step-size baseline. If the signal changes gradually or remains ordinary, the trained Meta-Controller continues to control model selection.
The detector uses:
abs_step = abs(current_sample - previous_sample)
and an exponential moving average of recent ordinary step magnitudes. The current bands are:
ADS_BALANCED_STEP_MULTIPLIER = 3.0
ADS_BALANCED_MIN_STEP_V = 0.100 V
ADS_ACCURATE_STEP_MULTIPLIER = 6.0
ADS_ACCURATE_MIN_STEP_V = 0.650 VConceptually:
normal / gradual signal
|
v
trained Meta-Controller
|
+--> FAST / BALANCED / ACCURATE
moderate sudden spike OR dip
|
v
BALANCED immediately
|
v
hold until one real BALANCED inference succeeds
|
v
release latch -> learned controller resumes
strong sudden spike OR dip
|
v
ACCURATE immediately
|
v
hold until one real ACCURATE inference succeeds
|
v
release latch -> learned controller resumes
Accurate has priority: a strong transient can preempt a pending Balanced transient, but a moderate transient cannot demote an active Accurate latch.
This is not an absolute voltage-to-model mapping such as voltage > 0.954 V -> Accurate. The event detector operates on sudden change magnitude, in either direction. A high but slowly changing voltage does not automatically invoke Accurate through this guard.
The IMU uses the same high-level feature/controller/model-zoo skeleton but a sensor-appropriate physics-guided policy. Per-axis acceleration DC/gravity is removed before dynamic accelerometer activity is computed, gyroscope RMS is tracked separately, and physical still/strong-motion regions protect the controller from implausible routing.
This is why the two demos should be described as sharing the same adaptive inference architecture, not literally identical sensor logic.
Demo 1 — ADS1263
- Under ordinary or gradual signal behavior, the trained MLP / learned-score scheduler chooses the next tier.
- A moderate sudden positive spike or negative dip can request Balanced immediately.
- A stronger sudden positive spike or negative dip can request Accurate immediately.
- The transient-selected tier remains active until one real inference from that model completes successfully.
- The transient latch then releases and normal learned routing resumes.
Demo 2 — ICM-20948
- Keep the module physically still -> Fast.
- Apply moderate motion -> Balanced.
- Apply strong acceleration or rotation -> Accurate.
- Stop moving the module -> the controller returns toward lower-compute tiers.
Together these demos prove dynamic three-tier compute selection across a single-channel precision ADC and a six-axis inertial sensor.
| Layer | Technology |
|---|---|
| MCU | Renesas RA8D1 / Arm Cortex-M85 |
| RTOS | FreeRTOS (tasks, static binary semaphores, ISR-safe signalling) |
| HAL / BSP | Renesas FSP v5.9.0 (e² studio, Cortex-M85, arm-none-eabi-gcc 13.2.1) |
| IDE | Renesas e² studio |
| Compiler | Arm GNU Toolchain 13.2.1 |
| ML Runtime | Edge Impulse EON compiled models / TensorFlow Lite Micro |
| DSP / kernels | CMSIS-DSP / CMSIS-NN where used by generated models |
| Application task — ADS1263 | adc_thread_entry.c |
| Application task — ICM-20948 | imu_thread_entry.c |
| ADS + ICM inference | merged dispatcher implementation in run_inference_dispatcher.cpp plus C-facing dispatcher headers |
| ADS Meta-Controller | meta_controller_weights.h |
| ICM Meta-Controller | meta_controller_weights_ICM.h |
| Telemetry | telemetry.c / telemetry.h |
| Remote UI | ESP32 UART bridge + self-contained HTML dashboard |
The ADC thread is the heart of Demo 1 and performs the complete live pipeline:
- DRDY polling: waits for the ADS1263
DRDYpin onBSP_IO_PORT_00_PIN_10. - SPI read: issues
CMD_RDATA1over the establishedg_spi0/ SPI channel-1 hardware path and synchronizes completion with the FreeRTOS semaphore/callback mechanism. - Signed code-to-voltage conversion:
g_adc_voltage = (((double)g_adc_code / 2147483648.0) * 2.5);. - Sample-level transient check: compares the newest sample with the previous sample. Only abrupt changes can invoke the Balanced/Accurate transient latches described in §7.4.
- Ring-buffer capture: stores the continuous signal in the 4000-sample circular buffer.
- Variant-sized inference: waits until the active Fast/Balanced/Accurate model has enough new samples, extracts the required contiguous window, and calls
aria_run_inference(). - Dynamic timing: measures the inference that actually ran with
DWT->CYCCNT. - Meta features: updates confidence average/trend, signal delta, signal RMS, and measured latency.
- Learned routing: runs the trained score-based Meta-Controller policy when no transient latch has temporary priority.
- Live telemetry: sends the latest ADS voltage and current active model about every 100 ms; confidence and latency correspond to the most recent successful real inference.
On inference failure, the ADS path falls back to Fast and clears pending transition/transient state so the controller can recover cleanly.
| Register | Value | Purpose |
|---|---|---|
MODE2 (0x05) |
0x8D |
Sets PGA/data-rate configuration |
MODE1 (0x04) |
0x00 |
Default filter/sinc configuration |
MODE0 (0x03) |
0x00 |
Default conversion mode |
REFMUX (0x0F) |
0x24 |
Selects internal 2.5V reference |
INPMUX (0x06) |
0x0A |
Selects analog input channel routing |
Sequence: hardware reset pulse (RST low 10ms → high) → CMD_RESET → read device ID register (0x00) → write MODE2/MODE1/MODE0/REFMUX/INPMUX → CMD_START1 to begin continuous conversion.
The IMU task performs the equivalent full runtime pipeline:
- SPI / synchronization setup: creates the static SPI completion semaphore, opens the ICM's SPI instance (§8.5), and waits for callback-driven transaction completion.
- Identity and bring-up: probes
WHO_AM_I(§8.6), disables the I²C interface for SPI operation, resets and wakes the sensor, configures accel/gyro registers, and returns to Bank 0. - Six-axis acquisition: reads 12 consecutive data bytes starting at
ACCEL_XOUT_H(§8.7), reconstructing signed 16-bitax/ay/az/gx/gy/gz. - Engineering-unit conversion: converts accelerometer samples to
gand gyroscope samples todeg/s. - Frame buffering: stores six floats per frame in the ICM circular capture buffer at 100 Hz (§8.8).
- Variant-sized inference trigger: calls
imu_run_inference_get_required_floats(variant_used), derives the required frame count, and extracts a contiguous six-axis inference window. - Inference + timing: runs
aria_run_inference_imu()and measures its real DWT/CYCCNT inference duration. - Runtime feature extraction: updates fault-confidence history, confidence trend/average, legacy motion features, gravity-removed accelerometer AC RMS and gyroscope RMS (§8.9).
- Adaptive motion routing: still → Fast, moderate motion → Balanced, strong motion → Accurate; the trained ICM meta-controller may refine only the intermediate region (§7.5's ADS-side equivalent describes the same confirmation/hold-window pattern applied here to motion).
- Telemetry: reports the model that actually produced the current fault confidence and measured latency while the newly selected variant is reserved for the next inference window.
The ICM-20948 uses register banks selected through REG_BANK_SEL.
| Register | Address | Value / Use | Purpose |
|---|---|---|---|
WHO_AM_I |
0x00 |
expected 0xEA |
Sensor identity verification |
USER_CTRL |
0x03 |
set I2C_IF_DIS = 0x10 |
Disable I²C interface for SPI operation |
PWR_MGMT_1 |
0x06 |
0x80 then 0x01 |
Device reset, then wake / clock selection |
PWR_MGMT_2 |
0x07 |
0x00 |
Enable accelerometer and gyroscope |
ACCEL_XOUT_H |
0x2D |
read start address | First byte of 12-byte accel + gyro sample block |
REG_BANK_SEL |
0x7F |
bank << 4 |
Select register bank |
| Register | Address | Value Used | Purpose |
|---|---|---|---|
GYRO_SMPLRT_DIV |
0x00 |
0x00 |
Gyroscope sample-rate divider |
GYRO_CONFIG_1 |
0x01 |
0x00 |
Gyroscope full-scale / filter configuration used by firmware |
ACCEL_SMPLRT_DIV_1 |
0x10 |
0x00 |
Accelerometer divider MSB |
ACCEL_SMPLRT_DIV_2 |
0x11 |
0x00 |
Accelerometer divider LSB |
ACCEL_CONFIG |
0x14 |
0x00 |
Accelerometer full-scale / filter configuration used by firmware |
Initialization sequence:
WHO_AM_I == 0xEA
↓
Bank 0
↓
USER_CTRL.I2C_IF_DIS = 1
↓
PWR_MGMT_1 = 0x80
↓
250 ms reset delay
↓
PWR_MGMT_1 = 0x01
PWR_MGMT_2 = 0x00
↓
re-assert USER_CTRL.I2C_IF_DIS
↓
Bank 2
↓
configure gyro + accel divider/config registers
↓
Bank 0
↓
continuous 12-byte accel + gyro reads
The I²C-disable bit is deliberately re-applied after reset because reset clears interface-control state.
The ICM path runs on its own dedicated FSP SPI instance, confirmed working with the following configuration:
channel = 0
mode = master
SPI mode = mode 0
bit order = MSB first
communication= full duplex
SSL = SSL2 / P705
SSL polarity = active low
interface = SPI_B_SSL_MODE_SPI (4-wire)
bitrate ≈ 1 MHz
A critical integration detail is that the SPI-B operation must be configured as 4-wire SPI:
.spi_clksyn = SPI_B_SSL_MODE_SPIand not the clock-synchronous/3-wire mode. Getting this wrong is what originally caused the WHO_AM_I = 0x00 symptom documented in §12.6.
The SPI read convention sets bit 7 of the register address:
tx[0] = REG_WHO_AM_I | 0x80;
tx[1] = 0x00;Expected response:
RX[1] = 0xEA
The firmware retries the identity probe up to 10 times with a short delay between attempts.
Debugger-visible diagnostics include:
g_imu_who_am_i
g_imu_who_am_i_attempts
g_imu_last_tx[]
g_imu_last_rx[]
g_imu_spi_cb_fire_count
g_imu_last_spi_event
g_imu_stage
These proved useful for distinguishing a completed SPI transaction from a physical wiring / MISO / CS problem.
ARIA reads 12 consecutive bytes beginning at:
ACCEL_XOUT_H = 0x2D
and reconstructs:
accel X
accel Y
accel Z
gyro X
gyro Y
gyro Z
The raw signed 16-bit values are converted using the configured full-scale conversion constants:
accelerometer → g
gyroscope → degrees/second
The six converted floats are placed into the ICM frame ring buffer.
The task maintains:
1400 frames × 6 floats/frame
of circular capture capacity.
Each model requests the amount of history it needs. The latest frames are copied into a contiguous inference window before dispatch.
The IMU inference call is:
aria_run_inference_imu(
variant_used,
g_imu_inference_window,
&result,
false);The physical motion controller does not use the gravity-contaminated absolute acceleration vector directly.
Instead, the accelerometer window mean is removed per axis before RMS calculation. The gyroscope retains absolute RMS because constant angular rotation is itself real motion.
Debugger-visible motion signals:
g_imu_debug_accel_ac_rms_g
g_imu_debug_gyro_rms_dps
g_imu_debug_motion_band
Motion-band meaning:
0 = still
1 = moderate
2 = strong
The final project intentionally keeps the ADS and IMU inference implementations inside the merged C++ inference translation unit.
This is important because generated Edge Impulse SDK headers contain function bodies and global/generated model content. Splitting the combined generated environment across multiple independent C++ translation units produced duplicate-definition/linker conflicts, the same class of problem documented in §12.10.
The public C-facing headers remain small:
run_inference_dispatcher.h
run_inference_dispatcher_imu.h
and deliberately avoid pulling the full Edge Impulse SDK into the FreeRTOS C application threads.
ADS uses the generated Edge Impulse runtime path and per-variant model handles.
ADC inference window
↓
aria_run_inference()
↓
selected Fast / Balanced / Accurate model
↓
p_anomaly / p_normal
The IMU integration required extra isolation because the generated spectral metadata from the two model sets is not interchangeable (§12.11). The final IMU path uses:
- local spectral feature generation matching the ICM model configuration;
- six-axis features;
- direct compiled EON graph invocation;
- quantization / dequantization using generated tensor scale and zero point;
- three output probabilities: fault, idle and normal.
This is the same inference path affected by the task-stack-exhaustion hard fault documented in §12.9; increasing the IMU thread's stack resolved it while retaining Cortex-M85 MVE/Helium acceleration for the model's kernels.
aria_v2/
├── ra/
│ └── aws/
│ └── FreeRTOS/
│ └── FreeRTOS/
│ └── Source/
│ ├── include/ ← FreeRTOS kernel headers
│ └── portable/
│ ├── MemMang/heap_4.c ← heap implementation (fetched from FreeRTOS-Kernel repo)
│ └── GCC/ARM_CM85/... (or ARM_CM33_NTZ, depending on core config)
├── src/
│ ├── adc_thread_entry.c ← ADS1263 sampling + inference dispatch task
│ ├── aria-ads-merged/
│ │ ├── edge-impulse-sdk/ ← vendored Edge Impulse C++ SDK
│ │ │ ├── tensorflow/lite/micro/... ← TensorFlow Lite Micro kernels
│ │ │ ├── CMSIS-NN/ , CMSIS-DSP/ ← ARM CMSIS optimized kernels
│ │ │ ├── classifier/ ← ei_run_classifier, ei_aligned_malloc, ei_data_normalization
│ │ │ ├── dsp/ ← ei_alloc, numpy, spectral filters, flatten
│ │ │ └── porting/renesas-ra/ ← MCU-specific porting shim (ei_*, malloc/printf bridge)
│ │ ├── model-parameters/model_variables.h ← trained weights + scaler params
│ │ ├── tflite-model/
│ │ │ ├── tflite_model_compiled_fast.cpp/.h
│ │ │ ├── tflite_model_compiled_balanced.cpp/.h
│ │ │ ├── tflite_model_compiled_accurate.cpp/.h
│ │ │ └── trained_model_ops_define_{fast,balanced,accurate}.h
│ │ └── run_inference_dispatcher.{cpp,h} ← aria_run_inference(), variant dispatch
├── script/fsp.ld ← linker script
The exact shell transcript above is preserved as captured. Expanded logically, the relevant source tree is:
src/
├── adc_thread_entry.c
├── adc_thread.h
├── imu_thread_entry.c
├── imu_thread.h
├── meta_controller_weights.h
├── meta_controller_weights_ICM.h
├── telemetry.c
├── telemetry.h
├── aria-ads-merged/
│ ├── edge-impulse-sdk/
│ ├── ei_classifier_porting_ra.cpp
│ ├── model-parameters/
│ │ ├── model_metadata.h
│ │ └── model_variables.h
│ ├── tflite-model/
│ │ ├── tflite_model_compiled_fast.cpp
│ │ ├── tflite_model_compiled_fast.h
│ │ ├── tflite_model_compiled_balanced.cpp
│ │ ├── tflite_model_compiled_balanced.h
│ │ ├── tflite_model_compiled_accurate.cpp
│ │ ├── tflite_model_compiled_accurate.h
│ │ └── trained_model_ops_define.h
│ ├── run_inference_dispatcher.cpp
│ └── run_inference_dispatcher.h
└── aria-imu-merged/
├── model-parameters/
│ ├── model_metadata_ICM.h
│ └── model_variables_ICM.h
├── tflite-model/
│ ├── tflite_model_compiled_icm_fast.cpp
│ ├── tflite_model_compiled_icm_fast.h
│ ├── tflite_model_compiled_icm_balanced.cpp
│ ├── tflite_model_compiled_icm_balanced.h
│ ├── tflite_model_compiled_icm_accurate.cpp
│ └── tflite_model_compiled_icm_accurate.h
└── run_inference_dispatcher_imu.h
| File | Purpose |
|---|---|
src/adc_thread_entry.c |
ADS1263 acquisition, buffering, inference, runtime features and model routing |
src/imu_thread_entry.c |
ICM-20948 bring-up, six-axis acquisition, inference, motion features and routing |
src/meta_controller_weights.h |
ADS trained meta-controller weights |
src/meta_controller_weights_ICM.h |
ICM trained meta-controller weights |
src/telemetry.c |
Shared UART telemetry transport |
src/telemetry.h |
ADS / IMU telemetry interface |
src/aria-ads-merged/run_inference_dispatcher.cpp |
Combined inference implementation required by the merged Edge Impulse build |
src/aria-ads-merged/run_inference_dispatcher.h |
ADS dispatcher API |
src/aria-imu-merged/run_inference_dispatcher_imu.h |
ICM dispatcher API |
src/aria-imu-merged/model-parameters/model_metadata_ICM.h |
ICM generated model/DSP metadata |
src/aria-imu-merged/model-parameters/model_variables_ICM.h |
ICM model variables / generated parameters |
| Tool | Version / Detail |
|---|---|
| IDE | Renesas e² studio 2025-04 |
| FSP | v5.9.0 |
| Compiler | arm-none-eabi-g++/gcc 13.2.1 (arm-gnu-toolchain-13.2.Rel1) |
| Target flags | -mthumb -mfloat-abi=hard -mcpu=cortex-m85+nopacbti -O2 |
| Warnings enabled | -Wall -Wextra -Wunused -Wuninitialized -Wmissing-declarations -Wconversion -Wpointer-arith -Wshadow -Wlogical-op -Waggregate-return -Wfloat-equal |
| Linker | arm-none-eabi-g++ with -T ../script/fsp.ld --gc-sections --specs=nano.specs |
| ML export path | Edge Impulse Studio → EON Compiler (C++ library export, int8 quantized) |
It's easy to claim "runs on Arm." Here's exactly what makes that true, sourced directly from the FSP project configuration rather than asserted:
Cross-compiled, not cross-run. The entire firmware is cross-compiled on a development host using arm-none-eabi-gcc/g++ 13.2.1, targeting -mcpu=cortex-m85+nopacbti -mthumb -mfloat-abi=hard, and flashed as a FreeRTOS-based ELF image directly onto the EK-RA8D1 (R7FA8D1BHECBD) via J-Link/E2 Lite. FreeRTOS handles task scheduling and ISR-to-task signaling. The adc_thread runs at priority 1 with a statically-allocated 8192-word stack, on a 1000 Hz tick, backed by a heap_4 allocator with a 20480-byte static heap, but there is no general-purpose OS beneath it. This is a real embedded cross-toolchain build, not a Python script or container on an Arm64 cloud VM.
Helium (MVE) confirmed active, three independent ways. Rather than assert MVE usage, we verified it:
(1) the compiler's resolved target architecture is armv8.1-m.main+fp.dp+mve.fp
(2) the preprocessor defines __ARM_FEATURE_MVE 3 at build time
(3) the linked ELF's build attributes show Tag_MVE_arch: MVE Integer and FP.
CMSIS-NN's quantized int8 kernels (for example fully-connected, softmax, and pooling kernels used by the generated models) execute through this vector unit where applicable. The scalar-only instructions visible in the top-level EON wrapper are expected and by design; the MVE instructions live inside the CMSIS-NN kernel objects themselves.
SPI: real register-level sensor bring-up, with DMA offload. The ADS1263 is driven over SPI channel 1 (g_spi0, on pins P410-P413) configured as master, full-duplex, MSB-first, at a 1 MHz bitrate, CPOL=low / CPHA=edge-even, with chip-select handled directly by the peripheral's hardware SSLB0 line rather than a manually toggled GPIO. Both the TX and RX paths are offloaded to the RA8D1's DTC (Data Transfer Controller), g_transfer0 / g_transfer1, so SPI bytes move without the CPU babysitting every transfer, and the SPI RXI/TXI/TEI/ERI interrupts run at priority level 2. On top of this, the ADS1263 gets full register-level configuration (MODE0-2, REFMUX, INPMUX) and DRDY-pin monitoring, production-style sensor integration, not a vendor demo API call.
DRDY is intentionally polled, not interrupt-driven, and that's documented, not hidden. DRDY connects to P010(MIKROBUS_INT), configured as a bare GPIO input. The RA8D1 does have a true external-interrupt-capable pin available on this board (P508/PMOD2_7_INT, wired to IRQ channel 14 with a pull-up), but it isn't the one this sensor uses. The ADC thread polls DRDY via R_IOPORT_PinRead() on its own schedule (§8.1). We're calling this out explicitly rather than letting the README imply interrupt-driven acquisition it doesn't have.
UART: telemetry, never a dependency. The ESP32 bridges live telemetry (active model, confidence, latency) to a browser dashboard over UART + Wi-Fi. This path is a convenience layer for visualization. It is never in the critical path of an actual inference decision, and it lives entirely in the ESP32-side Arduino sketch rather than the RA8D1's FSP project.
Inference runs entirely in the microcontroller's own memory. All six quantized model variants — ADS Fast/Balanced/Accurate plus ICM Fast/Balanced/Accurate — are statically compiled into the firmware image and linked directly into flash. Nothing is loaded from external storage at runtime. Model switching is a dispatcher choice between already-linked graphs, so there is no model reload delay, no cloud call, no external inference server, and no network dependency anywhere in the decision loop. Wi-Fi/UART can be disconnected without stopping local inference or model selection; they are used only for telemetry.
This section documents, in order, the real build failures encountered while integrating a vendored Edge Impulse / TensorFlow Lite Micro SDK into a bare-metal Renesas FSP + FreeRTOS project, and exactly what was done to resolve each one.
Symptom: Build failed because no heap manager (heap_1-heap_5) was present under Source/portable/MemMang/, so pvPortMalloc/vPortFree were undefined.
Fix: Fetched the canonical heap_4.c (first-fit with coalescing, suitable for long-running systems with mixed alloc/free sizes) directly from the official FreeRTOS-Kernel GitHub repository into Source/portable/MemMang/heap_4.c:
mkdir -p ".../FreeRTOS/Source/portable/MemMang"
wget -O ".../MemMang/heap_4.c" \
"https://raw.githubusercontent.com/FreeRTOS/FreeRTOS-Kernel/main/portable/MemMang/heap_4.c"Confirmed via HTTP 200 / 24,498 bytes downloaded successfully.
Symptom: After fixing the heap, find searches confirmed the entire Cortex-M portable layer (port.c, portmacro.h. The files implementing context switching, xPortStartScheduler, pxPortInitialiseStack, vPortSetupTimerInterrupt) was also absent from both the project tree and the cached Renesas FSP installation. This meant heap_4.c alone would not be sufficient; the next build pass would fail on undefined scheduler symbols.
Fix approach: Identify the exact Cortex-M variant needed based on the EK-RA8D1's Cortex-M85 core (the correct upstream FreeRTOS-Kernel port directory, e.g. GCC/ARM_CM85 or the closest compatible ARMv8.1-M variant depending on TrustZone/PACBTI configuration) and fetch port.c + portmacro.h from the same FreeRTOS-Kernel repository, mirroring the heap_4.c fetch pattern.
Symptom: Full firmware link failed with dozens of undefined reference errors originating from the vendored Edge Impulse SDK object files (tflite_model_compiled_{fast,balanced,accurate}.o, run_inference_dispatcher.o, and deep inside dsp/ei_alloc.h, dsp/spectral/filters.hpp, dsp/numpy.hpp, classifier/ei_aligned_malloc.h, classifier/ei_data_normalization.h, classifier/inferencing_engines/tflite_helper.h):
undefined reference to `ei_printf(char const*, ...)'
undefined reference to `ei_calloc(unsigned int, unsigned int)'
undefined reference to `ei_malloc(unsigned int)'
undefined reference to `ei_free(void*)'
undefined reference to `ei_read_timer_us()'
Root cause: The Edge Impulse SDK is platform-agnostic by design, which is, it calls a small set of abstracted ei_* primitives for memory allocation, debug printing, and timing, and expects the target platform's porting layer to supply the concrete implementations. The SDK ships with pre-written porting shims for dozens of platforms (porting/zephyr, porting/mbed, porting/arduino, porting/stm32-cubeai, porting/silabs, porting/posix, porting/renesas-ra, etc.) which are visible directly in the linker's object list. However, the renesas-ra porting object (porting/renesas-ra/ei_classifier_porting.o, porting/renesas-ra/debug_log.o) either was not being compiled/linked into the final image, or its implementations were incomplete/stubbed for this FSP+FreeRTOS combination.
Fix: Ensured the porting/renesas-ra/ei_classifier_porting.cpp translation unit is included in the build (added to the subdir.mk source list / include path so its object file participates in the final --start-group ... --end-group link), providing real implementations of:
ei_printf()--> routed to the project's existing debug UART/SEGGER RTT output.ei_malloc()/ei_calloc()/ei_free()--> routed either to the FreeRTOS heap (pvPortMalloc/vPortFree, now functional after the heap_4.c fix in §12.1) or to a dedicated static tensor arena, depending on call site.ei_read_timer_us()--> routed to the Cortex-M85 DWT cycle counter (the samebenchmark_dwt_get()/benchmark_cycles_to_us()infrastructure already built for the ADC task's benchmarking, see §15), converting cycles to microseconds usingSystemCoreClock.
This resolved all eiprintf, eicalloc, eimalloc, eifree, and eireadtimerus undefined-reference errors across tflite_model_compiled_{fast,balanced,accurate}.o, the DSP layer (spectral::filters::butterworth_lowpass/butterworth_highpass), flatten_class, ei_data_normalization's standard_scaler, and run_inference_dispatcher.o.
Symptom: Builds produced 1,494+ warnings under the strict -Wall -Wextra -Wconversion -Wshadow -Wfloat-equal flag set, spanning: unused parameters in TFLite Micro's MicroMemoryPlanner/MicroContext/OpResolver virtual interfaces, float<-->double narrowing conversions when the trained StandardScaler weights (stored as double from Python/Edge Impulse export) are assigned into const float arrays in model_variables.h, a -Wshadow warning from a local block variable in ei_run_classifier.h shadowing an outer declaration, and floating-point equality comparisons (scale == 0.0f) used as "is this quantized?" checks in tflite_helper.h.
Assessment: These are all benign and expected consequences of vendoring a third-party, multi-platform SDK unmodified. They do not block linking and were left as-is (fixing them would mean patching upstream Edge Impulse/TFLite Micro source, which is avoided to keep the vendored SDK easily re-exportable/updatable from Edge Impulse Studio).
Once 12.1-12.3 were resolved, the final arm-none-eabi-g++ ... -o ariav2.elf -Wl,--start-group ... link step successfully pulled in and resolved: all three compiled TFLite model graphs, the full TensorFlow Lite Micro kernel library (100+ op kernels: conv, depthwise_conv, fully_connected, softmax, pooling, LSTM, SVDF, quantize/dequantize, etc.), CMSIS-NN quantized kernel implementations (s8/s16/q7/q15 convolution, matmul, softmax, pooling variants), CMSIS-DSP transform/support/statistics functions (FFT, MFCC, DCT, sorting, copy/fill utilities used by the DSP feature-extraction pipeline), the renesas-ra porting shim, and the application-level adc_thread_entry.o / run_inference_dispatcher.o.
The IMU initially failed the identity probe:
WHO_AM_I = 0x00
while the SPI callback itself was firing.
The final solution required the complete physical + peripheral configuration to match:
P700 = MISO0
P701 = MOSI0
P702 = RSPCK0
P705 = SSLA2
with:
SPI_B_SSL_MODE_SPI
full duplex
Mode 0
MSB first
SSL2 active low
The final expected identity is:
WHO_AM_I = 0xEA
USER_CTRL.I2C_IF_DIS must be re-asserted after reset. The working bring-up therefore disables I2C both before and after the reset/wake sequence.
The SPI completion state must be armed before starting R_SPI_B_WriteRead(). Otherwise a very fast completion callback can set the success flag before the task clears it, making a successful transfer appear to fail.
Direct EON graph invocation initially faulted inside inference.
Fault inspection showed the IMU FreeRTOS task PSP had reached PSPLIM, proving task stack exhaustion. Increasing the IMU task stack fixed the issue while retaining Cortex-M85 MVE/Helium acceleration.
The ADS and ICM generated model sets could not be treated as ordinary independent SDK translation units because generated headers provide definitions that conflict when included multiple times.
The final architecture keeps the model implementations within the merged dispatcher strategy and exposes minimal C APIs to the FreeRTOS tasks.
The ICM model expects its own spectral configuration while the merged ADS environment carries different generated metadata.
The final dispatcher reproduces the required ICM spectral feature extraction locally rather than forcing incompatible global Edge Impulse spectral macros.
Using absolute acceleration magnitude as "motion RMS" included gravity, so a stationary module could appear highly active.
The deployed controller now removes per-axis acceleration DC/gravity before computing dynamic accelerometer RMS and also observes gyroscope RMS.
ADS and IMU are independent FreeRTOS tasks but share one UART transport.
A static binary semaphore TX gate serializes access to the UART buffer and peripheral, preventing concurrent telemetry writes from corrupting each other.
The application layer maintains lightweight controller features in addition to each Edge Impulse model's own DSP pipeline.
For the current ADS controller:
- Confidence history: last 3
p_anomalyoutputs. - Confidence average: mean of those three values.
- Confidence trend: newest
p_anomalyminus the previous value. - Signal delta: current inference-window RMS minus previous inference-window RMS.
- Signal RMS: absolute RMS of the current inference window.
- Inference latency: real DWT-measured duration of the model that actually ran.
The trained ADS MLP consumes the first four learned inputs listed in §7.1 (confidence_avg, confidence_trend, signal_delta, signal_rms). latency_us remains a measured/logged runtime result but is intentionally excluded from the current MLP.
Separately, the ADS transient guard tracks sample-to-sample absolute step magnitude plus an exponential moving average of recent ordinary steps. These transient values are not fed into the frozen MLP; they are used only for the exceptional sudden-spike/dip override described in §7.4.
The ADS binary training/runtime log records five little-endian floats per row:
[0] confidence_avg
[1] confidence_trend
[2] signal_delta
[3] latency_us
[4] signal_rms
The binary row size is therefore:
5 × 4 bytes = 20 bytes
The current meta_controller_weights.h was trained using columns 0, 1, 2, and 4; latency was excluded from the deployed MLP.
The current ADS firmware exposes both learned and transient routing state:
g_debug_meta_score_fast
g_debug_meta_score_balanced
g_debug_meta_score_accurate
g_debug_meta_argmax
g_debug_controller_candidate
g_debug_controller_target
g_debug_target_pending
g_debug_active_variant
g_debug_transient_abs_step_v
g_debug_transient_balanced_threshold_v
g_debug_transient_accurate_threshold_v
g_debug_transient_balanced_latched
g_debug_transient_accurate_latched
This makes it possible to distinguish a learned MLP request from a sudden-event override while debugging.
The ICM training-compatible log keeps the same five-float shape for consistency:
fault confidence average
fault confidence trend
legacy signal/motion delta
inference latency
legacy signal/motion RMS
The deployed motion policy additionally tracks gravity-removed accelerometer AC RMS and gyroscope RMS so a truly stationary module is not confused with a fixed-orientation ~1 g reading.
During a labeled training/data-collection run, place the firmware in the corresponding meta-training mode and let the desired condition run until the meta log has accumulated samples (the log's binary structure is documented in §13.1).
The binary log can then be extracted directly from the GDB debugger console.
Use the following command exactly in the "Debugger Console", after doing a test run for training the meta-controller, when "suspend" is clicked. This command is used to retrieve the log:
dump binary memory "YOUR_DESIRED_PATH/YOUR_DESIRED_NAME.bin" &g_meta_log ((char *)&g_meta_log + sizeof(g_meta_log))Example:
dump binary memory "/home/rishi/ARIA_DATA/meta_log_still.bin" &g_meta_log ((char *)&g_meta_log + sizeof(g_meta_log))The IMU task uses the corresponding symbol:
dump binary memory "YOUR_DESIRED_PATH/YOUR_DESIRED_NAME.bin" &g_imu_meta_log ((char *)&g_imu_meta_log + sizeof(g_imu_meta_log))Example:
dump binary memory "/home/rishi/ARIA_DATA/meta_log_gentle.bin" &g_imu_meta_log ((char *)&g_imu_meta_log + sizeof(g_imu_meta_log))- Set the appropriate meta-training-mode macro to
1. - Rebuild and flash the RA8D1.
- Run one clearly labeled physical condition.
- Allow the log buffer to accumulate.
- Halt the target in the debugger.
- Use
dump binary memory ...from the GDB debugger console. - Convert the
.binfile to CSV with the project conversion script. - Label the session.
- Repeat for the next physical condition.
- Train / validate the meta-controller.
- Export the generated weights header.
- Set the corresponding training-mode macro back to
0for deployment.
For repeatable validation, sessions should remain identifiable instead of randomly mixing every row across train/test splits.
A DWT (Data Watchpoint and Trace)-based cycle-accurate benchmarking layer was built directly into adc_thread_entry.c, since the Cortex-M85 exposes a free-running 32-bit cycle counter (DWT->CYCCNT) once trace is enabled via CoreDebug->DEMCR:
benchmark_dwt_init(): enablesTRCENA, zeroes and startsCYCCNT.benchmark_cycles_to_us(): converts a cycle delta (handling 32-bit wraparound) to microseconds usingSystemCoreClock.bench_stats_t: a lightweight running-statistics struct (count, last, min, max, sum) updated viabenchmark_record(), withbenchmark_avg_us()for the mean.
Four independent timing channels are tracked and exposed as debugger-visible volatile uint32_t globals (refreshed every 20 samples via benchmark_refresh_globals()):
| Channel | What it measures |
|---|---|
g_bench_spi_txn_* |
Pure SPI transaction cost (R_SPI_B_WriteRead + semaphore wait) |
g_bench_sample_* |
Full sample acquisition + inference dispatch cost |
g_bench_loop_* |
Full main-loop iteration cost |
g_bench_infer_* |
Pure aria_run_inference() call cost |
This same infrastructure was reused to implement ei_read_timer_us() in the Renesas-RA porting shim (§12.3), keeping timing consistent across the application and SDK layers. §16 covers how this same DWT counter feeds the live telemetry latency figures shown on the dashboard.
Latency displayed by ARIA is not hardcoded.
Both sensor inference paths use the Cortex-M85 DWT cycle counter (the same infrastructure described in §15).
Initialization:
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;Inference timing:
uint32_t t0 = DWT->CYCCNT;
/* run Fast / Balanced / Accurate inference */
uint32_t t1 = DWT->CYCCNT;Cycle count is converted to microseconds using:
SystemCoreClock
The telemetry protocol preserves both exact microseconds and decimal milliseconds:
{
"latency_us": 842,
"latency_ms": 0.842
}This avoids the earlier integer truncation problem where any inference below 1 ms appeared as 0 ms.
- Open the project in Renesas e² studio 2025-04 with FSP 5.9.0 configured for the EK-RA8D1 (Cortex-M85) board.
- Ensure the FreeRTOS portable layer is complete:
Source/portable/MemMang/heap_4.cpresent (§12.1) and the matching Cortex-M port directory (port.c+portmacro.h, §12.2) present underSource/portable/GCC/<variant>/. - Ensure
porting/renesas-ra/ei_classifier_porting.cppis included in the build's source list soei_printf/ei_malloc/ei_calloc/ei_free/ei_read_timer_usresolve (§12.3). - Build (
make -r -j8 allunder the hood via e² studio); Target output isariav2.elf. - Flash via the on-board debugger (J-Link/E2 Lite) to the EK-RA8D1.
- Open a debugger Expressions view and add
g_adc_voltage,g_active_variant,g_meta_features, theg_bench_*globals, and theg_debug_meta_*/g_debug_transient_*variables to observe learned routing, sudden-event overrides, and real inference timing without needing UART.
- Start with the ADS1263 signal in a stable condition and show that the system remains under normal learned Meta-Controller routing.
- Create a moderate sudden voltage step/dip and show Balanced being selected immediately; keep the dashboard visible until a real Balanced inference completes and the transient latch releases.
- Create a strong sudden voltage step/dip and show Accurate being selected immediately; again show that the tier is held until a real Accurate inference completes.
- Point out that a gradual voltage change does not automatically trigger the transient override — the new advancement is specifically for sudden spikes/dips.
- Show the ICM-20948 stream: still -> Fast, moderate motion -> Balanced, strong motion -> Accurate.
- Highlight that both sensor model zoos are already present in the same RA8D1 flash image and every inference decision is local; the ESP32 only visualizes telemetry.
- Close with: "Same adaptive inference skeleton, two very different physical sensors, six resident models, one Cortex-M85."
| Tool | Version / Detail |
|---|---|
| Target | Renesas EK-RA8D1 |
| CPU | Arm Cortex-M85 |
| IDE | Renesas e² studio |
| FSP | 5.9.0 |
| FreeRTOS | 11.1.0 |
| Compiler | GCC / G++ 13.2.1 |
| ML tooling | Edge Impulse / EON Compiler |
| Debug probe | J-Link-compatible RA8D1 debug configuration |
| Telemetry bridge | ESP32 UART at 115200 baud |
The project is built with Cortex-M85/Helium support enabled project-wide.
- Public GitHub repo, MIT/Apache 2.0 license visible
- Written project overview + setup instructions (this README)
- Track declared: Physical AI
- All source code included: model zoo training, meta-controller training, RA8D1 firmware, ESP32 firmware
- Benchmark table: latency/accuracy/power per model variant, per demo
- 3-minute demo video showing full demo script on real hardware
- Written case for scalability, referencing the final demo set as proof of generality
To be finalized as MIT or Apache 2.0 prior to submission, per the Arm AI Optimization Challenge 2026 submission requirements.
Sachi Patwardhan linkedIn
Rishiraj Rakesh Kumar GitHub