feat(validation): add HIL validation app (firmware terminal + Python/AD3 test suite) - #120
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Terminal-driven firmware exposing every hal-ti driver with runtime configuration, using the e-foc pinout and peripheral assignment for EK-TM4C123GXL and EK-TM4C1294XL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
✅
|
| Descriptor | Linter | Files | Fixed | Errors | Max errors | Warnings | Elapsed time |
|---|---|---|---|---|---|---|---|
| ✅ ACTION | actionlint | 5 | 0 | 0 | 0.04s | ||
| ✅ CPP | clang-format | 38 | 0 | 0 | 0 | 0.27s | |
| ✅ CPP | cppcheck | 38 | 0 | 0 | 0.6s | ||
| ✅ DOCKERFILE | hadolint | 1 | 0 | 0 | 0.11s | ||
| ✅ JSON | jsonlint | 10 | 0 | 0 | 0.09s | ||
| ✅ JSON | prettier | 10 | 6 | 0 | 0 | 0.98s | |
| markdownlint | 20 | 0 | 12 | 0 | 1.38s | ||
| ✅ MARKDOWN | markdown-table-formatter | 20 | 0 | 0 | 0 | 0.24s | |
| ✅ REPOSITORY | betterleaks | yes | no | no | 1.08s | ||
| ✅ REPOSITORY | checkov | yes | no | no | 22.04s | ||
| ✅ REPOSITORY | git_diff | yes | no | no | 0.06s | ||
| ✅ REPOSITORY | grype | yes | no | no | 79.69s | ||
| ✅ REPOSITORY | ls-lint | yes | no | no | 0.01s | ||
| ✅ REPOSITORY | secretlint | yes | no | no | 0.88s | ||
| ✅ REPOSITORY | syft | yes | no | no | 1.48s | ||
| ✅ REPOSITORY | trivy | yes | no | no | 12.48s | ||
| ✅ REPOSITORY | trivy-sbom | yes | no | no | 0.19s | ||
| ✅ REPOSITORY | trufflehog | yes | no | no | 2.61s | ||
| lychee | 50 | 1 | 0 | 0.88s | |||
| ✅ YAML | prettier | 10 | 2 | 0 | 0 | 0.79s | |
| ✅ YAML | v8r | 10 | 0 | 0 | 5.53s | ||
| ✅ YAML | yamllint | 10 | 0 | 0 | 0.67s |
Detailed Issues
⚠️ SPELL / lychee - 1 error
📝 Summary
---------------------
🔍 Total...........69
🔗 Unique..........48
✅ Successful......61
⏳ Timeouts.........0
🔀 Redirected.......8
👻 Excluded.........7
❓ Unknown..........0
🚫 Errors...........1
⛔ Unsupported......1
Errors in validation/README.md
[403] https://digilent.com/reference/software/waveforms/waveforms-3/start (at 190:12) | Rejected status code: 403 Forbidden
Hint: Followed 8 redirects. You might want to consider replacing redirecting URLs with the resolved URLs. Use verbose mode (`-v`/`-vv`) to see redirection details.
Hint: You can configure accepted/rejected response codes with `-a` or `--accept`
⚠️ MARKDOWN / markdownlint - 12 errors
.claude/agents/executor.md:8 error MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "You are the executor agent for..."]
.claude/agents/orchestrator.md:8 error MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "You are the orchestrator agent..."]
.claude/agents/planner.md:8 error MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "You are the planner agent for ..."]
.claude/agents/reviewer.md:8 error MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "You are the reviewer agent for..."]
.github/agents/executor.agent.md:11 error MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "You are the executor agent for..."]
.github/agents/orchestrator.agent.md:18 error MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "You are the orchestrator agent..."]
.github/agents/planner.agent.md:11 error MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "You are the planner agent for ..."]
.github/agents/reviewer.agent.md:14 error MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "You are the reviewer agent for..."]
.github/prompts/orchestrate.prompt.md:8:401 error MD013/line-length Line length [Expected: 400; Actual: 615]
.github/prompts/orchestrate.prompt.md:8 error MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "Analyze the following task for..."]
AGENTS.md:79:401 error MD013/line-length Line length [Expected: 400; Actual: 712]
AGENTS.md:81:401 error MD013/line-length Line length [Expected: 400; Actual: 557]
Notices
REPOSITORY_GITLEAKS, REPOSITORY_KICS. See Removed linters to find their replacements.
See detailed reports in MegaLinter artifacts
Your project could benefit from a custom flavor, which would allow you to run only the linters you need, and thus improve runtime performances. (Skip this info by defining FLAVOR_SUGGESTIONS: false)
- Documentation: Custom Flavors
- Command:
npx mega-linter-runner@10.1.0 --custom-flavor-setup --custom-flavor-linters ACTION_ACTIONLINT,CPP_CPPCHECK,CPP_CLANG_FORMAT,DOCKERFILE_HADOLINT,JSON_JSONLINT,JSON_PRETTIER,MARKDOWN_MARKDOWNLINT,MARKDOWN_MARKDOWN_TABLE_FORMATTER,REPOSITORY_CHECKOV,REPOSITORY_GIT_DIFF,REPOSITORY_BETTERLEAKS,REPOSITORY_GRYPE,REPOSITORY_LS_LINT,REPOSITORY_SECRETLINT,REPOSITORY_SYFT,REPOSITORY_TRIVY,REPOSITORY_TRIVY_SBOM,REPOSITORY_TRUFFLEHOG,SPELL_LYCHEE,YAML_PRETTIER,YAML_YAMLLINT,YAML_V8R

Show us your support by starring ⭐ the repository
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…g work - can.close masks the CAN interrupt and destroys the driver behind the events it already queued, so none runs on a destroyed driver - adc.close answers a pending adc.measure with ERR failed - reset uses its own timer so it no longer cancels a pending delay Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…l layers The WaveForms binding, AD3 wrapper, fakes, signal analysis, terminal client and pytest AD3 options now live in github.com/embedded-pro/ad3-waveforms-bench; the host package keeps the hal-ti command API, board wiring, fake firmware and HIL tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
The protocol plumbing, pin pool and per-peripheral command groups now come from EMIL services.hil / services.hil.commands; the firmware keeps the board profiles, the Tiva pin factory and board info, one factory per peripheral with the Tiva Config mapping, and the Tiva-only pwm.fault/pwm.count commands. EMIL is pinned to the embedded-infra-lib#152 head until it merges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…ted hil services TivaPwmFactory hands out HilPwmAdapter instances instead of its own PWM start dispatch, and the fault report uses infra::AtomicTriggerScheduler. EMIL is pinned to the embedded-infra-lib#152 head. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Aliases name peripheral functions (m0pwm*, qei0*, ain*, can0*, led*, gpio*) on the same pins; pwm.open and adc.open require explicit generators/pins and use neutral defaults; separate rise/fall dead time, per-generator trigger and irq sources, a configurable pwm.fault, adc dcmp bands/modes and reference, can bit timing and filter match, comparator trigger sense and a wdt early-warning toggle pin are exposed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Board files use the generic aliases on the same wiring; every driver is exercised through parameter matrices (sync and async where available) with a --depth quick|full option (pairwise subset vs full product). Known driver gaps are marked xfail. The README documents the generic wiring sets and what the Analog Discovery 3 can emulate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
… pin is busy pwm.fault now claims a new fault pin before rebuilding the PWM module, so ERR pin/busy leaves the running configuration untouched; repeating the held pin keeps it. The fake firmware follows the same order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
PC7 is C0-; C0+ is PC6 on the TM4C1294, as on the TM4C123. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…d on a Windows host The README describes bridge mode: ad3-bench-server and port-bridge on Windows share the AD3, the terminal COM port and an OpenOCD GDB server over TCP; the devcontainer builds, flashes with gdb-multiarch and runs pytest with --ad3-remote and a socket:// port. The devcontainer maps host.docker.internal to the host, VS Code gains launch configurations that attach to the host's OpenOCD, and the host package pins ad3-waveforms-bench to v0.2.0, the first release with remote support. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…ck selectable The AD3 is no longer a CAN node. CAN0 goes through a 3.3 V transceiver to a CANable, and `--can-mode loopback|bus|both` selects the link the frame, bit-timing and acceptance-filter tests run over (loopback stays the default and needs no wiring). - hal_ti_validation.can_peer: the CANable through python-can (`slcan:COM7`, `slcan:socket://host:5002`, `gs_usb:0`, `candle:0`, `socketcan:can0`) or port-bridge's CAN bridge (`port-bridge:host:5001`, fixed bit rate). - Bus-only tests: ackError with a listen-only CANable, receive errors and bus off with the CANable at a wrong bit rate, recover=0/1. - --fake: the fake firmware joins a python-can virtual bus that stands in for the CANable. - Board files and README: transceiver wiring, CANable options, bridge mode with a second port-bridge for the CANable's COM port. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The fake GPIO model omits valid TM4C129 interrupt ports, and the CAN socket test has a nondeterministic short-read failure.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Adds a complete HIL validation application for TI LaunchPads, combining terminal firmware with an AD3-driven Python test suite.
Changes:
- Adds peripheral factories, board profiles, and terminal firmware.
- Adds host tooling, fakes, pairwise matrices, and HIL/unit tests.
- Adds build, container, debugger, protocol, and usage configuration.
| File | Description |
|---|---|
CMakeLists.txt |
Updates EMIL and enables validation builds. |
.devcontainer/devcontainer.json |
Adds host bridge resolution. |
.vscode/launch.json |
Adds external OpenOCD launch profiles. |
validation/CMakeLists.txt |
Includes validation firmware. |
validation/README.md |
Documents setup and operation. |
validation/PROTOCOL.md |
Defines the terminal protocol. |
validation/firmware/CMakeLists.txt |
Defines the firmware target. |
validation/firmware/AdcFactory.cpp |
Implements ADC command mapping. |
validation/firmware/AdcFactory.hpp |
Declares the ADC factory. |
validation/firmware/BoardTypes.hpp |
Defines shared board types. |
validation/firmware/CanFactory.cpp |
Implements CAN command mapping. |
validation/firmware/CanFactory.hpp |
Declares the CAN factory. |
validation/firmware/ComparatorFactory.cpp |
Implements comparator mapping. |
validation/firmware/ComparatorFactory.hpp |
Declares the comparator factory. |
validation/firmware/Console.cpp |
Configures the terminal UART. |
validation/firmware/Console.hpp |
Defines console storage. |
validation/firmware/EepromFactory.cpp |
Lazily constructs EEPROM. |
validation/firmware/EepromFactory.hpp |
Declares the EEPROM factory. |
validation/firmware/EthernetGroup.cpp |
Implements TM4C129 Ethernet commands. |
validation/firmware/EthernetGroup.hpp |
Declares Ethernet registration. |
validation/firmware/EthernetGroupUnsupported.cpp |
Provides unsupported Ethernet commands. |
validation/firmware/Main.cpp |
Assembles validation services. |
validation/firmware/PwmFactory.cpp |
Implements PWM extensions and mapping. |
validation/firmware/PwmFactory.hpp |
Declares the PWM factory. |
validation/firmware/QeiFactory.cpp |
Implements QEI command mapping. |
validation/firmware/QeiFactory.hpp |
Declares the QEI factory. |
validation/firmware/SpiFactory.cpp |
Implements SPI command mapping. |
validation/firmware/SpiFactory.hpp |
Declares the SPI factory. |
validation/firmware/TivaBoardInfo.cpp |
Reports board and reset information. |
validation/firmware/TivaBoardInfo.hpp |
Declares board information. |
validation/firmware/TivaPinFactory.cpp |
Implements managed Tiva pins. |
validation/firmware/TivaPinFactory.hpp |
Declares the pin factory. |
validation/firmware/UartFactory.cpp |
Implements UART variants. |
validation/firmware/UartFactory.hpp |
Declares the UART factory. |
validation/firmware/WatchDogFactory.cpp |
Implements watchdog validation. |
validation/firmware/WatchDogFactory.hpp |
Declares the watchdog factory. |
validation/firmware/boards/tm4c123/BoardProfile.hpp |
Defines TM4C123 hardware. |
validation/firmware/boards/tm4c129/BoardProfile.hpp |
Defines TM4C129 hardware. |
validation/host/.gitignore |
Ignores Python artifacts. |
validation/host/pyproject.toml |
Defines host package and pytest settings. |
validation/host/boards/ek_tm4c123gxl.yaml |
Defines TM4C123 test wiring. |
validation/host/boards/ek_tm4c1294xl.yaml |
Defines TM4C129 test wiring. |
validation/host/hal_ti_validation/__init__.py |
Exports the host API. |
validation/host/hal_ti_validation/can_peer.py |
Implements CAN peers. |
validation/host/hal_ti_validation/config.py |
Loads board configuration. |
validation/host/hal_ti_validation/console.py |
Provides the validation console. |
validation/host/hal_ti_validation/expect.py |
Calculates expected hardware values. |
validation/host/hal_ti_validation/fake_firmware.py |
Implements offline firmware behavior. |
validation/host/hal_ti_validation/firmware.py |
Provides typed firmware commands. |
validation/host/hal_ti_validation/pairwise.py |
Generates parameter combinations. |
validation/host/hal_ti_validation/protocol.py |
Handles pins and aliases. |
validation/host/tests/conftest.py |
Implements pytest integration. |
validation/host/tests/hil/test_adc.py |
Validates ADC behavior. |
validation/host/tests/hil/test_can.py |
Validates CAN behavior. |
validation/host/tests/hil/test_comparator.py |
Validates comparators. |
validation/host/tests/hil/test_eeprom.py |
Validates EEPROM behavior. |
validation/host/tests/hil/test_ethernet.py |
Validates Ethernet behavior. |
validation/host/tests/hil/test_gpio.py |
Validates GPIO behavior. |
validation/host/tests/hil/test_pwm.py |
Validates PWM behavior. |
validation/host/tests/hil/test_qei.py |
Validates QEI behavior. |
validation/host/tests/hil/test_spi.py |
Validates SPI behavior. |
validation/host/tests/hil/test_system.py |
Validates system commands. |
validation/host/tests/hil/test_uart.py |
Validates UART behavior. |
validation/host/tests/hil/test_watchdog.py |
Validates watchdog behavior. |
validation/host/tests/unit/test_can_peer.py |
Tests CAN peer framing. |
validation/host/tests/unit/test_config.py |
Tests board configuration. |
validation/host/tests/unit/test_expect.py |
Tests expected-value calculations. |
validation/host/tests/unit/test_fake_firmware.py |
Tests fake firmware. |
validation/host/tests/unit/test_firmware.py |
Tests the typed firmware API. |
validation/host/tests/unit/test_pairwise.py |
Tests pairwise generation. |
validation/host/tests/unit/test_protocol.py |
Tests protocol helpers. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…it test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…ared roles The wiring sets reused DIO0-7 for every peripheral and left DIO8-15 almost unused, so each set meant rewiring. The new `harness` set wires all 16 DIOs once per board and each pin serves several tests: the PWM outputs are also the SPI pins (SSI2 on PB4-PB7, SSI3 on PF0-PF3) and GPIO test pins, QEI0 phase A is also the TM4C123 fault input, and UART flow control shares the TM4C123 PWM generator 3 pins. The analog sets only add W1/W2 and the scope. - The CAN transceiver stays on its pins: on the TM4C123 comparator 0's output (PF0) is read through the firmware only and PF0 is no longer a locked-pin test pin. - On the TM4C1294 the comparator sets move DIO15 to PD1 and the new `locked` set moves DIO14 to PD7. - Tests look up the fault, flow-control and watchdog pins by pin instead of by role; the pwm4/faultpin/qei1/flow/dcmp tags are gone. - Unit test: the harness uses every DIO once and every parameter pin is wired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…eaders PF0 is not on X6-X9 (it only drives LED D4 and reaches the unpopulated X11 pad 66), so the harness can no longer use it for M0PWM0 and SSI3. - SPI moves to SSI2 on PD0-PD3; PD1 is also C1o, so the comparator sets no longer move a DIO and combine with the harness. - PWM tests use generators 1-3 on this board. - UART3 flow control (PP4/PP5) becomes the `flow` set, which moves DIO14/15. - No user LED is on the headers, so the board has no LED output test. - The notes of both boards give the header pin of every connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
The harness plus the small adc/comparator/comparator_c0/flow/locked sets become two complete bundles, each selected with one --wiring-set: - bundle1: the 16-DIO harness with W1/W2 on the ADC inputs; runs nearly the whole suite. - bundle2: the comparator tests (and on the EK-TM4C1294XL UART flow control and the locked pin). A jumper ties the second comparator's input to the same wavegen channel (PC7-PC4 on TM4C123, PC5-PC6 on TM4C1294); the TM4C1294 moves DIO0/1/14 to PP4/PP5/PD7. On the TM4C123, DIO6/DIO8 are left out of bundle2 because PC4/PC6 are comparator inputs there. A connection can list `jumpered` pins: the channel reaches them as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…feature A manual install lands in the container's home directory and is lost on every rebuild, and the image has no curl for the installer one-liner. The feature installs the CLI on rebuild, so `claude remote-control` can run a session against the HIL hardware from the devcontainer. The README's bridge-mode section says how. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
On the real EK-TM4C1294XL through port-bridge, test_system got replies meant for earlier commands (`info` -> ERR usage, timeouts). FirmwareTerminal takes any reply that arrives after a command was written as that command's reply, so one late reply shifts every later command. - sync with the full command timeout instead of 0.5 s; - quiesce() reads until the line is silent, after sync and before every HIL test, so a late reply is dropped instead of taken by the next command; - --command-timeout / HAL_TI_COMMAND_TIMEOUT override the board's timeout. A unit test reproduces the misaligned reply and checks the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
EMIL's services::DebugLed blinks the blue LED PF2 on the EK-TM4C123GXL (its green LED PF3 is the CAN0 transmit pin) and LED D1 PN1 on the EK-TM4C1294XL, so a glance at the board shows that the image is flashed and the event loop runs. The pin is reserved like the terminal pins; test_system checks it answers ERR busy, and the fake firmware reserves it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
… bench - Size the synchronous UART ring buffer for a full uart.recv (256 bytes plus the empty slot); 64 bytes dropped data in the large-payload and full-duplex stream tests. - QEI clock/direction mode counts both edges of PhA on the TM4C129, so the EK-TM4C1294XL board file expects 2 counts per pulse. - test_index_resets_position: the AD3 pattern puts an index at the start of every `every` cycles, so the last index can fall inside `extra`; expect the counts since that last index. - test_warning_period: the capture triggers on the first toggle, one timeout after the start, so wait for that timeout as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SSI2XDAT0 (TX) is PD1 and SSI2XDAT1 (RX) is PD0. hal-ti#119 fixes the TM4C129 pinout table accordingly, so the board file follows it: the SPI instance, the DIO notes and the loopback note (the AD3 now only listens on DIO14). Against main, whose table still swaps the pins, `spi.open` with these pins fails with `ERR pin` until #119 is merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… interrupt window - apply_level reads the scope on a 5 V range centred on mid-supply; the default 10 V range read ~25 mV high, most of the 40-code ADC tolerance. - The raw-sample spread limit is a board parameter (`spread_codes`). The EK-TM4C1294XL analog supply carries a ~100 kHz ripple of about 1 % of the reading (measured; gone with hardware averaging), so its single conversions spread up to ~110 codes; the 40-code mean tolerance stays. - The PWM interrupt counter is cleared and read somewhere within host round trips, so the counted window is bounded by host timestamps instead of assumed equal to the delay; a slow round trip through the serial bridge no longer fails test_interrupt_count. EK-TM4C1294XL, bundle1, quick depth: 555 passed, 78 skipped, 1 xfailed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ADC spread - test_waveform: the driver rounds the duty to whole counts and drives a static level once it reaches LOAD; at 200 kHz with pwmclk 1.875 MHz a period has 9 counts, so 90 % becomes full-on and the capture never sees an edge. Skip duties that round to 0 or LOAD (expect.pwm_duty_resolvable). - ek_tm4c1294xl: the bench measured up to 131 codes peak-to-peak on raw samples at 3.1 V from the analog supply ripple; spread_codes 120 -> 150. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
… EK-TM4C1294XL The bench measured all 16 steps of both ranges at VDDA 3.30 V: RNG=0 is 0.778 V + 0.1136 V/step and RNG=1 is 0.1473 V/step (residuals <= 26 mV), 6-14 % above the constants the board file used, which failed every test_internal_reference case. The driver writes ACREFCTL as specified; only the expectation changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Keeps the services.hil terminal (dd77048) and adds the ISB after NVIC_DisableIRQ in DisableIrq (#154). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…inactive Follows the hal-ti Pwm fault rework in #119: - PROTOCOL: a fault drives the faulted generators' outputs to their inactive level and reports one EVT pwm; the next report needs pwm.stop. - test_fault_forces_outputs is no longer xfail. - New test_fault_reported_once_until_stop: five fault pulses give one event, pwm.stop re-arms the report for the next fault. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
embedded-infra-lib#155 refreshes the watchdog in the early-warning callback instead of the scheduled report, so a 1 ms timeout no longer stretches to 1.117 ms between warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Measured on the EK-TM4C123GXL bench, as on the TM4C1294: 50 pulses give 100 counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
The HIL runs use ad3-waveforms-bench main, which adds the WaveForms UART parity code and the digital-in reset before the logic analyzer is armed. Neither fix is released yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Raw conversions on the EK-TM4C123GXL spread 83-165 codes peak-to-peak, and the spread grows with the level, while the means stay within tolerance. Allow 180 codes, as the TM4C1294 board file does with its own measurement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
… bench The 16 steps of both ranges, measured on the EK-TM4C123GXL, fit to 0.773 + 0.1133*n V (low) and 0.1481*n V (high), with residuals of at most 6 and 16 mV. The datasheet values were 50-300 mV low and failed 7 ladder points. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
On the EK-TM4C1294XL, the scope mean that sets the expected ADC code scattered ±20-25 mV between back-to-back acquisitions, while the ADC read the programmed level within 8 mV at 64x averaging. That intermittently pushed DC-level cases past the 40-code tolerance. The 10 ms window covered half a 50 Hz period; 100 ms spans whole 50 and 60 Hz periods. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX

Summary
Adds a hardware-in-the-loop validation app under
validation/. It exercises every hal-ti driver on EK-TM4C123GXL and EK-TM4C1294XL, using a Digilent Analog Discovery 3 to drive and measure the pins and a CANable as the CAN bus peer. Build, flash, wiring and usage are described invalidation/README.md.The peripheral pins follow LaunchPads modified for the e-foc project, which is the hardware baseline. Nothing else of e-foc is assumed: there are no e-foc defaults in the firmware and no e-foc configuration in the tests.
validation/PROTOCOL.md: the command contractOK,ERR <reason>orEVT ....docs/Hil.md.validation/firmware: C++ firmwarehal_ti.validation_firmwareservices.hilandservices.hil.commands(embedded-infra-lib#152). The EMILGIT_TAGis pinned to0059646. That commit also brings in the ISB afterNVIC_DisableIRQ(embedded-infra-lib#154) and the watchdog auto-feed in the early-warning callback (embedded-infra-lib#155).*.openarguments to the Tiva driverConfig;pwm.faultandpwm.countcommands.m0pwm*,qei0*,ain*,can0*,led*,gpio*.pwm.openneedsgensorpins, and runs at 10 kHz, edge-aligned, no dead time;adc.openneedspins, and an async sequencer needs an explicit PWM trigger.pwm.faultconfigurable for comparators, fault pins, latch and minimum period;wdt.start pin=, which toggles a pin on every early warning so its period can be measured.HilTerminaloverUartWithDmaat 921600 8N1: UART0 on TM4C123, UART2 PD4/PD5 on TM4C129.-RelWithDebInfobuild presets. At -O0 the TM4C123 can't keep up with interrupt-driven UART at 921600 Bd. It builds only whenHAL_TI_BUILD_EXAMPLESis on, so CI's embedded builds compile it.validation/host: Python package and pytest suiteIt depends on ad3-waveforms-bench, pinned at
dcb3754. That is v0.2.0 plus the UART parity and logic-analyzer reset fixes; the Windowsad3-bench-serverneeds the same commit. It provides the Analog Discovery 3 layer, the fakes, signal analysis and the serial terminal client. python-can drives the CANable.Tests are generic parameter matrices per driver, running both the synchronous and asynchronous drivers wherever both exist.
--depth quickruns a deterministic pairwise subset;--depth fullruns the full product.Coverage per driver:
pwm.stop)--can-mode): standard and extended IDs, DLC 0–8, explicit timing, filters; on the bus also ackError, receive errors, bus off and recoveryc0source and reference ladder, interruptsKnown driver gaps are marked
xfail(strict=False)(see below).The board files hold the constants measured on each board: ADC sample spread, the comparator reference ladder, and QEI clock/direction counts.
Wiring: two fixed AD3 bundles per board, all on the BoosterPack headers
bundle1:bundle2:jumperedpins.bundle2, because PC4 and PC6 are comparator inputs there. Onlytest_comparator.pyruns in that bundle.CAN bus: transceiver + CANable
--can-peertakesslcan:COM7,slcan:socket://host:5002,gs_usb:0,candle:0,socketcan:can0, orport-bridge:host:5001(fixed bit rate).Windows host + Docker (bridge mode)
This setup is described in
validation/README.md. The Analog Discovery 3, the LaunchPad UART, the ICDI debugger and the CANable stay on Windows; the build, flashing and pytest run in the devcontainer.ad3-bench-server--ad3-remote host.docker.internal:5025/AD3_REMOTEport-bridge --serial-port COMx --serial-baudrate 921600--port socket://host.docker.internal:5000/HAL_TI_PORTport-bridge --probe openocd --openocd-board ek-tm4c1294xlgdb-multiarch(target extended-remote)port-bridgeon the CANable's slcan COM port--can-peer slcan:socket://host.docker.internal:5002/HAL_TI_CAN_PEER.devcontainer/devcontainer.jsonadds--add-host=host.docker.internal:host-gateway, so the name also resolves under Docker Engine in WSL2..vscode/launch.jsonadds one "OpenOCD on host" cortex-debug configuration per board (servertype: external).What the Analog Discovery 3 can emulate
FDwfDigitalUart*)T1/T2 (trigger pins) and V+/V- (supplies) are not used. The trigger pins can't be driven or read as levels, and a supply can't replace a wavegen for the comparator sweeps and square waves.
Driver issues found
Found by this suite and fixed in #119. The bench confirmed each fix.
channelAInverted/channelBInvertedwere ignored;PWMFAULTVALwas never programmed, so a fault did not force the outputs.reset=0(erratum WDT#03).Resolution()was off by one.Still open (not fixed here):
hal::tiva::Uartcannot be constructed:using UartBase::UartBaseinherits protected constructors.GpioPin::ResetConfigleaves the pin as a driven output.GetPeripheralPinConfigignores the peripheral index (TM4C123 UART4 on PC4/PC5, SSI3 on PD0/PD1).[this]lambdas from its ISR with no way to cancel them. The app works around this incan.close.Gpio::ReservePinasserts when a pin is reserved twice.Eepromcalls its completion callback from a different context for write, read and erase.Adc::Config::externalReferenceis never written to the hardware.hal::InterruptTrigger.ERR usageon a well-formed ~140-char line). It happens during 921600 full duplex on UART1, with or without the latest UART fixes. Not classified yet.Test plan
tm4c123gh6pm,tm4c1294ncpdtandhost-single-Debug.pytest validation/host/tests/unit: 154 passed.--fakewithbundle1andbundle2on both boards; bridge mode againstad3-bench-server --fake; CAN against the fakes.bundle1full depth, CAN loopback, nothing deselected: 2652 passed, 1 failed, 123 skipped. The failure was the UART DMA full-duplex duplicate, fixed by fix!: second assessment round — driver bugs, family #ifdef refactor, host tests, docs #119's 016afa5: 0/240 rounds and 40/40 test runs afterwards.bundle2full depth: 82 passed, 0 failed, 2 skipped (reference 0 V is outside the wavegen range), with the reference ladder measured on this board.bundle1full depth, CAN loopback, nothing deselected: 2418 passed, 3 failed, 132 skipped, no driver failures.test_adc.pyfull depth then passed 338/0 twice.bundle2full (-O0 build): 78 passed, 0 failed.from_c0cases on the 1294.🤖 Generated with Claude Code
https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Generated by Claude Code