Skip to content

fix!: second assessment round — driver bugs, family #ifdef refactor, host tests, docs - #119

Open
gabrielfrasantos wants to merge 37 commits into
mainfrom
claude/driver-bug-assessment-vt76wf
Open

gabrielfrasantos wants to merge 37 commits into
mainfrom
claude/driver-bug-assessment-vt76wf

Conversation

@gabrielfrasantos

@gabrielfrasantos gabrielfrasantos commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

This fixes the findings from a second, independent review of main after #117. Every finding was checked against the code before being fixed. I also re-checked the three High findings myself.

Since then, the HIL bench (#120) has run these drivers on an EK-TM4C1294XL and an EK-TM4C123GXL. The hardware fixes it led to are listed under Found on hardware, several of them TM4C123 errata.

High

  • PWM: generator interrupts were never enabled at the module level (PWMINTEN.INTPWMn), so onNormalInterrupt never fired.
  • SPI on TM4C129: MOSI and MISO were swapped on every SSI instance. In normal SPI mode SSInXDAT0 is TX and SSInXDAT1 is RX.
  • UartWithDma: on an RX timeout, the half-buffer currently being filled was detected wrongly once the primary half had been re-armed, so received bytes were dropped. It is now read from the uDMA ALTSET state.

Medium

  • GPIO pin release: releasing a pin (and analog setup) left it as a push-pull output driving the stale data latch. It now returns to its reset state (input, digital function off).
  • Pwm/SynchronousPwm: Config is kept by value, not by a reference that dangles for temporaries. 0% and 100% duty now drive the output constant low/high; before, 0% could come out as roughly 100%.
  • Interrupt table: sized 155 so TM4C123 PWM module 1 interrupts (IRQ 134–138) fit.
  • ADC: channels AIN16–19 are encoded through SSEMUX. Before, they sampled AIN0–3 in differential mode.
  • Comparator: ACCTL.TOEN is bit 11 and is set when the ADC trigger is enabled.
  • TM4C129 clock: SystemCoreClock reports the frequency actually produced, not the requested one. The oscillator source is no longer forced to PIOSC after setup.
  • CAN: the accept-all filter no longer rejects 29-bit frames.
  • GPIO interrupts: they now work on every port, including TM4C129 ports G–N and the per-pin P/Q vectors. InterruptType::immediate runs the action directly in the ISR.
  • FreeRTOS example: the syscall priority ceiling now lets EMIL's normal/high interrupt priorities call FreeRTOS APIs from an ISR.

Low

  • Startup SystemCoreClock is 16 MHz (the reset clock).
  • EEPROM callbacks always run from the event dispatcher.
  • SynchronousSpiMaster drains stale RX data correctly in its constructor.
  • MAC address filter: no more out-of-bounds read.
  • SPI clock-divider search checks its inputs and never picks a rate above the one requested.
  • SpiMaster's interrupt handler is no longer destroyed from inside its own interrupt handler.
  • The two watchdog timers share one vector through a single handler.
  • DmaChannel stores its channel by value.
  • GpioPin::Set writes through the masked data address.
  • Pin reservation checks use really_assert, so they stay in release builds.
  • QEI is reset before configuration, and Resolution() returns the configured value.
  • ADC completion polling only checks the sequencer's own bit.
  • CAN: the ISR only uses IF2 (the thread uses IF1), and the TX request is cleared on bus-off.
  • Unused config fields are now applied (PWM output inversion, LaunchPad clock divider and PLL). The UART word-length field only supports 8 bits and now asserts on anything else.
  • The instantiations namespace typo is fixed.
  • Ethernet uses DEVICE_HEADER and reports the selected PHY address.

Found on hardware (HIL, #120)

  • UartWithDma:
    • RX with burst arbitration keeps a residue so the receive time-out fires. USEBURST is restored on every arm, and the whole FIFO is drained at time-out.
    • RX now moves one item per request. At a ping-pong half switch, the uDMA could serve a burst request the UART had not yet withdrawn. That read the empty FIFO and inserted stale characters at 921600 Bd.
    • TX is burst-only, so a single request can no longer push 4 items into a nearly full FIFO. This dropped bytes on the TM4C123.
  • Uart (interrupt): the receive interrupt fires at half full instead of 7/8, giving more latency headroom.
  • SpiMaster: batches complete from the receive FIFO. The TM4C123 does not latch TXRIS in EOT mode (erratum SSI#07), so asynchronous transfers timed out and then overran into an assert.
  • PWM fault: PWMFAULT/PWMFAULTVAL force the outputs inactive on a fault. The ISR reports one event, and Stop() clears the latch and re-arms.
  • GPIO: dispatched interrupts are coalesced per pin with a pending-edge counter, so fast edges can't flood the event queue.
  • TM4C129 comparator: outputs C0o–C2o on PD0–PD2 use PCTL function 5.
  • Watchdog: without reset, the ISR clears the time-out itself. Watchdog 1 otherwise resets on the second time-out even with RESEN clear (erratum WDT#03).
  • ADC:
    • The module is reset through SRADC when its clock is first enabled (erratum ADC#14).
    • A sequence whose END step goes to a digital comparator ends on a dummy FIFO step instead, and that sample is discarded. On the TM4C123 the comparator END step lost one FIFO sample per run (erratum ADC#03).
  • Build: the Tiva configure presets set CMAKE_BUILD_TYPE=RelWithDebInfo. Before, the single-config generator built firmware at -O0, CI included, and the build presets' configuration had no effect.
  • EMIL: pinned to 0059646, which includes the HIL watchdog auto-feed fix (embedded-infra-lib#155).

Repo rules and consistency

  • No family #ifdefs in C++ (AGENTS.md rule). Family-specific constants, types and register helpers moved to hal_tiva/tiva/family/<family>/<Driver>Family.hpp, and the BSP to hal_tiva/instantiations/family/<family>/ (<family> is tm4c123 or tm4c129). CMake adds only family/$<LOWER_CASE:${TARGET_MCU_FAMILY}> to the include path.
  • Interrupt handler order: ADC, CAN, EEPROM, watchdog, Ethernet and SpiMaster now register their EMIL interrupt handler at the end of the constructor, after clearing the pending interrupt. Destructors release it first instead of calling NVIC_* directly.
  • Host tests: the placeholder test is replaced by 47 real tests. CAN bit timing and the SPI clock-divider search moved into device-independent headers (CanBitTiming.hpp, SpiClockDivisor.hpp); the two SPI drivers now share one implementation.
  • Examples: terminal_uart_with_dma now actually uses Dma + UartWithDma. The new HAL_TI_INCLUDE_LWIP option lets the lwIP instantiation build standalone.
  • Docs (README, AGENTS.md, CLAUDE.md, Copilot/Claude agent instructions): interrupt dispatch goes through EMIL's InterruptTable, so there are no per-interrupt startup-file edits. Also corrected: the handler registration order, CTest preset name, hal::cortex namespace, CAN status write-back, CMake 3.24, the hal_tiva.tiva target name and the broken CODEOWNERS links. Added doc/EK-TM4C1294XL.md.

Breaking changes

  • AnalogComparator::Config::routeToPwmFault is removed; it had no effect.
  • DmaChannel::IsCompletionPending() / ClearCompletion() (TM4C123 only) are replaced by DmaChannel::ChannelNumber().
  • Can::BitTiming is now an alias of hal::tiva::CanBitTiming, with the same fields.
  • UART NumberOfBytes::_16_bytes now asserts; it was silently ignored before.
  • Tiva builds configured from the presets are now RelWithDebInfo; pass -DCMAKE_BUILD_TYPE=Debug for an unoptimised build.

Verification

  • tm4c123gh6pm and tm4c1294ncpdt builds pass, examples included.
  • The FreeRTOS example builds for both chips. With HAL_TI_INCLUDE_LWIP=ON, the TM4C129 build passes.
  • Host build passes, and 47/47 host tests pass.
  • No new clang-format deviations; ls-lint passes. The original line endings (CRLF/LF) are kept.
  • HIL with feat(validation): add HIL validation app (firmware terminal + Python/AD3 test suite) #120's validation firmware:
    • EK-TM4C123GXL (optimised build, bundle1, full depth, async SPI included): 2652 passed and 1 failed. The failure was the UART 921600 DMA full-duplex duplicate. It is fixed by 016afa5 and confirmed clean afterwards (0/240 rounds, 40/40 runs), with no change in the full UART suite (160 passed).
    • EK-TM4C1294XL (bundle1 full, then bundle2 full, on an -O0 build): 2416 passed and 5 failed. Four were the watchdog failures fixed in EMIL; one was an SPI decode that passed on rerun. bundle2: 78 passed. The 1294 has not been re-run since the later UART, SPI, ADC and build-type changes.

🤖 Generated with Claude Code

https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX

- ClockTm4c129: report the frequency actually produced by the PLL instead
  of the requested one; stop forcing RSCLKCFG.OSCSRC to PIOSC after setup
- system files: SystemCoreClock starts at the 16 MHz PIOSC reset clock
  (SystemInit is not called by the startup code)
- LaunchPad (TM4C123): pass the configured divider and PLL choice to
  ConfigureClock
- TracingReset: fix instantiations namespace typo
- FreeRTOS example: allow EMIL normal/high IRQ priorities to call FreeRTOS
  FromISR APIs (max syscall priority 1)
- CMake: HAL_TI_INCLUDE_LWIP option instead of forcing lwIP off

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…, IRQ table size

- set the module-level PWMINTEN.INTPWMn bit so generator interrupts reach
  the NVIC (only the per-generator source was enabled)
- 0% and 100% duty drive the output constant low/high via the generator
  action register; coinciding LOAD/CMP events otherwise made 0% ~100%
- apply channelAInverted/channelBInverted through PWMINVERT
- keep Config by value (was a reference that dangles for temporaries)
- interrupt table sized for every TM4C123/129 vector (PWM1 IRQs 134-138)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
- TM4C129 pin table mapped MISO to SSInXDAT0 and MOSI to SSInXDAT1; in
  legacy SPI mode XDAT0 is TX and XDAT1 is RX, so data lines were crossed
  on every SSI instance
- clock divider search: validate the requested rate, keep CPSDVSR in
  2..254 and SCR in 0..255, pick the fastest rate not above the request,
  mask CPSR
- SynchronousSpiMaster: actually drain stale RX data in the constructor
  (the loop never read DR and could hang)
- SpiMaster: register the interrupt handler once after configuration
  instead of creating and destroying it per transfer (it was destroyed
  from inside its own Invoke)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…gth, DMA example

- UartWithDma: decide which ping-pong half is filling from the uDMA ALTSET
  state; the old check misread it after the primary half was re-armed and
  dropped received bytes
- RX DMA: single requests with arbitration size 2 to match the 1/8 FIFO
  trigger, so the uDMA never reads from an empty FIFO
- DmaChannel keeps its Channel by value
- UART word length: only 8-bit words exist on this API; assert instead of
  silently ignoring _16_bytes
- terminal_uart_with_dma example now actually uses Dma + UartWithDma

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…t, QEI reset

- ADC: encode AIN16-19 through SSEMUX instead of leaking bit 4 into the
  SSCTL nibble (they sampled AIN0-3 in differential mode); poll only the
  sequencer's own RIS bit; clamp the returned sample range
- Adc registers its interrupt handler after the peripheral is clocked and
  configured, and releases it first on destruction
- comparator: ACCTL.TOEN is bit 11, set when the ADC trigger is enabled;
  drop the unimplemented routeToPwmFault option
- QuadratureEncoder: software-reset the QEI before configuring it (ENABLE
  cannot be cleared otherwise), Resolution() returns the configured value,
  remove unused interrupt members

BREAKING CHANGE: AnalogComparator::Config::routeToPwmFault is removed (it
had no effect).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…t, shared WDT vector, IRQ ordering

- Can: the accept-all filter required IDE=0 and rejected every 29-bit
  frame; the ISR now uses only IF2 (thread uses IF1) and clears the TX
  request on bus-off before reporting the failed send
- Eeprom: completion callbacks always run from the event dispatcher
  (were ISR / synchronous / timer depending on the operation)
- WatchDog: WDT0 and WDT1 share one vector through a single handler, so
  two instances no longer collide; masking/unmasking uses EMIL
  Register/Unregister instead of raw NVIC calls
- Ethernet: build the MAC filter words from the six address bytes (was an
  out-of-bounds read), use DEVICE_HEADER, keep Delay file-local, report
  the selected PHY address
- Can, Eeprom, WatchDog, Ethernet register their interrupt handler after
  the peripheral is clocked and configured, and release it first on
  destruction

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…dlers, atomic Set

- releasing a pin (and analog config) left it as a push-pull output
  driving the stale DATA latch; restore input with the digital function
  disabled, and configure analog pins as the datasheet describes
- GPIO interrupts now work on every port with a vector, including the
  TM4C129 ports G..N and the per-pin P/Q vectors; InterruptType::immediate
  runs the action in the ISR instead of going through the event loop
- GpioPin::Set writes through the masked DATA alias so concurrent writers
  to other pins of the same port can't lose updates
- pin reservation checks use really_assert so they stay in release builds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
The only host test was a placeholder. Move the two pure calculations out
of the drivers into device-independent headers (CanBitTiming.hpp,
SpiClockDivisor.hpp) so they can be unit tested on the host, and share the
SPI divisor search between SpiMaster and SynchronousSpiMaster instead of
duplicating it. Can::BitTiming is now an alias of hal::tiva::CanBitTiming.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
- interrupt dispatch goes through EMIL's InterruptTable via Default_Handler;
  drivers register an EMIL handler last in the constructor (after clearing
  the pending IRQ) and release it first in the destructor; no per-ISR
  startup-file edits are needed
- hal::cortex::InterruptTable namespace, CAN status write-back (LEC = 7),
  ctest --preset host, QuadratureEncoder class name, TimeKeeper is not
  part of EMIL's cortex_m
- README: CMake 3.24, hal_tiva.tiva target, build options incl.
  HAL_TI_INCLUDE_LWIP, host unit tests; drop broken CODEOWNERS links
- add doc/EK-TM4C1294XL.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…ily headers

AGENTS.md forbids #ifdef TM4C123/TM4C129 in C++, but about a dozen files
used them. Family-specific constants, types and small register helpers now
live in hal_tiva/tiva/family/<FAMILY>/<Driver>Family.hpp (same API for both
families) and the BSP in hal_tiva/instantiations/family/<FAMILY>/; CMake
adds only family/${TARGET_MCU_FAMILY} to the include path and lists
family-only sources with generator expressions.

- Gpio: port table (incl. IRQs and per-pin P/Q vectors), AHB aliases and
  commit-locked pins per family
- Pwm/SynchronousPwm: module count, IRQs, clock divider access
- AnalogComparator, QuadratureEncoder, UniqueDeviceId, UartWithDma DMA
  completion (TM4C123 uses DMACHIS, which TM4C129 does not have)
- family-neutral PinoutTableDefault.hpp for bringup; LaunchPad gains
  SecondLed() so the FreeRTOS example needs no conditional
- DmaChannel exposes ChannelNumber() instead of a TM4C123-only API

RAM use is unchanged; code is 84-208 bytes smaller per example because the
commit-lock check is no longer inlined into every GpioPin constructor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
@gabrielfrasantos

gabrielfrasantos commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor Author

✅⚠️MegaLinter analysis: Success with warnings

Descriptor Linter Files Fixed Errors Max errors Warnings Elapsed time
✅ ACTION actionlint 5 0 0 0.05s
✅ CPP clang-format 7 0 0 0 0.06s
✅ CPP cppcheck 7 0 0 0.14s
✅ DOCKERFILE hadolint 1 0 0 0.12s
✅ JSON jsonlint 10 0 0 0.05s
✅ JSON prettier 10 7 0 0 0.84s
⚠️ MARKDOWN markdownlint 18 0 12 0 0.72s
✅ MARKDOWN markdown-table-formatter 18 0 0 0 0.15s
✅ REPOSITORY betterleaks yes no no 1.03s
✅ REPOSITORY checkov yes no no 19.24s
✅ REPOSITORY git_diff yes no no 0.03s
✅ REPOSITORY grype yes no no 62.79s
✅ REPOSITORY ls-lint yes no no 0.0s
✅ REPOSITORY secretlint yes no no 0.61s
✅ REPOSITORY syft yes no no 1.11s
✅ REPOSITORY trivy yes no no 12.32s
✅ REPOSITORY trivy-sbom yes no no 0.09s
✅ REPOSITORY trufflehog yes no no 2.27s
✅ SPELL lychee 44 0 0 1.22s
✅ YAML prettier 8 2 0 0 0.59s
✅ YAML v8r 8 0 0 3.87s
✅ YAML yamllint 8 0 0 1.27s

Detailed Issues

⚠️ 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

⚠️ Your configuration references items that have been removed from MegaLinter and are ignored: 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

MegaLinter is provided by OX Security
Show us your support by starring ⭐ the repository

ls-lint requires snake_case directory names; rename family/TM4C123 and
family/TM4C129 to family/tm4c123 and family/tm4c129 and select them with
$<LOWER_CASE:${TARGET_MCU_FAMILY}>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…e-out

The receive time-out only fires while the RX FIFO holds a character. Single DMA
requests (USEBURST clear) and a 2-item burst at the 2-character trigger level
both empty the FIFO, so a short command stayed in the DMA half-buffer until 64
more bytes arrived. On an EK-TM4C1294XL the validation terminal answered each
command only after later input.

RX now uses bursts only, 4 items at an 8-character trigger level, so 1 to 7
characters always stay in the FIFO and the time-out hands them over. The halves
of the RX buffer must be a multiple of the burst size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Copilot AI balanced review requested due to automatic review settings October 1, 2026 14:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

PWM channel updates can overwrite companion outputs, while several new shared IRQ registrations mishandle pending state or priority.

Review effort: Balanced
Findings: 5 Medium severity

Open (5)
What changed in this PR

Broad correctness sweep across TI peripheral drivers, family-specific code, host tests, examples, and documentation.

Changes:

  • Fixes PWM, SPI, UART DMA, GPIO, ADC, CAN, watchdog, clock, and interrupt handling.
  • Moves family-specific logic into CMake-selected headers.
  • Adds 47 host tests and updates examples/documentation.
File Description
tiva/​CMSIS/​Device/​TI/​TM4C129/​Source/​Templates/​system_TM4C129.c Sets reset clock to 16 MHz.
tiva/​CMSIS/​Device/​TI/​TM4C123/​Source/​Templates/​system_TM4C123.c Sets reset clock to 16 MHz.
README.md Updates integration and testing docs.
integration_test/​test/​Test.cpp Removes placeholder test.
integration_test/​test/​SpiClockDivisorTest.cpp Tests SPI divisor selection.
integration_test/​test/​CMakeLists.txt Registers new host tests.
integration_test/​test/​CanBitTimingTest.cpp Tests CAN timing calculation.
hal_tiva/​tiva/​WatchDog.hpp Adds shared-handler state.
hal_tiva/​tiva/​WatchDog.cpp Shares the watchdog IRQ.
hal_tiva/​tiva/​UniqueDeviceId.cpp Delegates family-specific behavior.
hal_tiva/​tiva/​UartWithDma.cpp Fixes DMA completion and timeout handling.
hal_tiva/​tiva/​UartBase.cpp Rejects unsupported word lengths.
hal_tiva/​tiva/​SpiMaster.cpp Uses shared divisor logic and persistent IRQ registration.
hal_tiva/​tiva/​SpiClockDivisor.hpp Adds device-independent divisor search.
hal_tiva/​tiva/​Pwm.hpp Owns configuration and adds inversion setup.
hal_tiva/​tiva/​Pwm.cpp Fixes interrupts, inversion, and endpoint duty cycles.
hal_tiva/​tiva/​PinoutTableDefaultTm4c129.cpp Corrects TM4C129 SPI pins.
hal_tiva/​tiva/​PinoutTableDefault.hpp Adds family-neutral declarations.
hal_tiva/​tiva/​Gpio.hpp Expands interrupt routing support.
hal_tiva/​tiva/​Gpio.cpp Fixes release, analog, masked writes, and IRQ dispatch.
hal_tiva/​tiva/​family/​tm4c129/​UniqueDeviceIdFamily.hpp Provides TM4C129 unique ID access.
hal_tiva/​tiva/​family/​tm4c129/​UartWithDmaFamily.hpp Defines TM4C129 DMA status handling.
hal_tiva/​tiva/​family/​tm4c129/​QuadratureEncoderFamily.hpp Defines TM4C129 QEI resources.
hal_tiva/​tiva/​family/​tm4c129/​PwmFamily.hpp Defines TM4C129 PWM resources.
hal_tiva/​tiva/​family/​tm4c129/​GpioFamily.hpp Defines TM4C129 GPIO resources.
hal_tiva/​tiva/​family/​tm4c129/​AnalogComparatorFamily.hpp Defines comparator registers and IRQs.
hal_tiva/​tiva/​family/​tm4c129/​AnalogComparatorFamily.cpp Adds comparator 2 forwarding.
hal_tiva/​tiva/​family/​tm4c123/​UniqueDeviceIdFamily.hpp Defines empty TM4C123 unique ID.
hal_tiva/​tiva/​family/​tm4c123/​UartWithDmaFamily.hpp Defines TM4C123 DMA completion handling.
hal_tiva/​tiva/​family/​tm4c123/​QuadratureEncoderFamily.hpp Defines TM4C123 QEI resources.
hal_tiva/​tiva/​family/​tm4c123/​PwmFamily.hpp Defines TM4C123 PWM resources.
hal_tiva/​tiva/​family/​tm4c123/​PwmFamily.cpp Adds PWM1 interrupt forwarding.
hal_tiva/​tiva/​family/​tm4c123/​GpioFamily.hpp Defines TM4C123 GPIO resources.
hal_tiva/​tiva/​family/​tm4c123/​AnalogComparatorFamily.hpp Defines TM4C123 comparator resources.
hal_tiva/​tiva/​Ethernet.hpp Makes IRQ registration optional.
hal_tiva/​tiva/​Ethernet.cpp Fixes lifecycle, MAC packing, and PHY reporting.
hal_tiva/​tiva/​Eeprom.hpp Stores optional IRQ handler.
hal_tiva/​tiva/​Eeprom.cpp Defers callbacks and fixes handler lifecycle.
hal_tiva/​tiva/​Dma.hpp Owns channel data and exposes channel state.
hal_tiva/​tiva/​Dma.cpp Implements alternate-state and channel queries.
hal_tiva/​tiva/​CMakeLists.txt Selects family-specific implementation files.
hal_tiva/​tiva/​ClockTm4c129.cpp Reports actual configured frequency.
hal_tiva/​tiva/​CanBitTiming.hpp Extracts CAN timing calculation.
hal_tiva/​tiva/​Can.hpp Uses shared timing type and optional handler.
hal_tiva/​tiva/​Can.cpp Fixes filtering, IF usage, and lifecycle.
hal_tiva/​tiva/​AnalogComparator.hpp Removes ineffective PWM routing API.
hal_tiva/​tiva/​AnalogComparator.cpp Fixes trigger output configuration.
hal_tiva/​synchronous_tiva/​SynchronousUart.cpp Rejects unsupported word lengths.
hal_tiva/​synchronous_tiva/​SynchronousSpiMaster.cpp Shares divisor logic and drains RX data.
hal_tiva/​synchronous_tiva/​SynchronousQuadratureEncoder.hpp Stores configured resolution.
hal_tiva/​synchronous_tiva/​SynchronousQuadratureEncoder.cpp Resets QEI and fixes resolution reporting.
hal_tiva/​synchronous_tiva/​SynchronousPwm.hpp Owns configuration and adds inversion setup.
hal_tiva/​synchronous_tiva/​SynchronousPwm.cpp Fixes divisors, inversion, and endpoint duty cycles.
hal_tiva/​synchronous_tiva/​SynchronousAnalogComparator.hpp Removes obsolete routing alias.
hal_tiva/​synchronous_tiva/​SynchronousAnalogComparator.cpp Fixes trigger output configuration.
hal_tiva/​synchronous_tiva/​SynchronousAdc.cpp Fixes channel encoding and result sizing.
hal_tiva/​instantiations/​TracingReset.hpp Corrects namespace spelling.
hal_tiva/​instantiations/​TracingReset.cpp Corrects namespace spelling.
hal_tiva/​instantiations/​LaunchPadBsp.hpp Selects family-specific BSP.
hal_tiva/​instantiations/​family/​tm4c129/​LaunchPadFamily.hpp Defines TM4C129 LaunchPad setup.
hal_tiva/​instantiations/​family/​tm4c123/​LaunchPadFamily.hpp Defines TM4C123 LaunchPad setup.
hal_tiva/​instantiations/​CMakeLists.txt Adds family BSP include paths.
hal_tiva/​bringup/​Bringup.cpp Enlarges the interrupt table.
examples/​terminal_uart_with_dma/​Main.cpp Demonstrates actual DMA UART usage.
examples/​freertos/​Main.cpp Uses family-neutral second LED.
examples/​freertos/​config/​FreeRTOSConfig.h Adjusts syscall interrupt priority.
doc/​EK-TM4C1294XL.md Documents the TM4C129 LaunchPad.
CONTRIBUTING.md Corrects project and reporting guidance.
CMakeLists.txt Adds optional lwIP integration.
CLAUDE.md Updates interrupt lifecycle guidance.
AGENTS.md Updates architecture and workflow rules.
.github/​prompts/​orchestrate.prompt.md Updates orchestration guidance.
.github/​instructions/​hal-ti-cpp.instructions.md Updates C++ interrupt rules.
.github/​copilot-instructions.md Updates repository patterns and commands.
.github/​agents/​reviewer.agent.md Updates review checklist.
.github/​agents/​planner.agent.md Updates planning guidance.
.github/​agents/​orchestrator.agent.md Updates routing guidance.
.github/​agents/​executor.agent.md Updates implementation guidance.
.claude/​agents/​reviewer.md Updates review checklist.
.claude/​agents/​planner.md Updates planning guidance.
.claude/​agents/​orchestrator.md Updates routing guidance.
.claude/​agents/​executor.md Updates implementation guidance.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread hal_tiva/synchronous_tiva/SynchronousPwm.cpp
Comment thread hal_tiva/tiva/Gpio.cpp
Comment thread hal_tiva/tiva/Pwm.cpp
Comment thread hal_tiva/tiva/WatchDog.cpp
Comment thread hal_tiva/tiva/WatchDog.cpp Outdated
claude and others added 9 commits October 1, 2026 15:07
…ing handlers

- Pwm, SynchronousPwm: two PinChannels on one generator (channel A and channel
  B) are separate Generators on the same registers; each now writes only its
  own GENA/GENB action, so a duty update no longer overwrites the companion's
  0 %/100 % or compare action.
- Gpio: clear the pending port and per-pin IRQs before their handlers enable
  them.
- Pwm: clear stale generator and fault status and the pending NVIC bit before
  registering the generator and fault handlers.
- WatchDog: the shared vector runs at the most urgent priority of the active
  watchdogs, recomputed on add and remove, and every registration clears a
  pending IRQ first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…s TX DMA completions

- At the receive time-out the FIFO residue was copied only into the space left
  in the active half; with 60 DMA'd bytes and 5-7 residue bytes the tail of a
  command stayed in the FIFO until more input arrived, so replies drifted out of
  step under load. The residue is now drained completely, reusing the half once
  it has been delivered, and a half that completed just before the time-out is
  delivered first so data stays in order.
- A TX DMA done flag without a transfer in flight ran ProcessDmaTx again, which
  scheduled the already-cleared completion callback: the event dispatcher then
  aborted on an empty infra::Function. The completion is now only processed
  while sending and once the channel's transfer has actually stopped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
… after a DMA half

The uDMA controller clears USEBURST at the end of a cycle, so the channel is armed again with burst mode restored on every start and re-arm. When a half completes and single requests have already emptied the FIFO, no receive time-out follows: the RX path is flushed right away, at most once per interrupt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The transmit interrupt fires when the FIFO drains through its trigger level, never for a FIFO that is already empty, so the first send after reset never started. SendData pends the UART interrupt and the handler fills the FIFO until it is full.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…FIFO fed

SpiMaster scheduled a [this] lambda calling onDone; a close before it ran left an empty AutoResetFunction (abort). It now schedules onDone.Clone(), like UartBase. SynchronousSpiMaster sent one frame and waited for its reply, stopping the clock between bytes; it now keeps up to a FIFO of frames queued so they are clocked back to back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…keep comparator sequences converting

Closing one sequencer gated the clock of the whole ADC, so closing another sequencer on it bus-faulted; Adc and SynchronousAdc now reference-count the clock per ADC. The digital comparator high band is CTC 0x3 (0xC00), not the reserved 0x2. A sequence with digital comparator steps feeds the PWM fault logic, so it starts converting when opened and keeps converting after Stop; Measure discards stale FIFO data first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… event per episode, safe teardown

Start resets the counters of newly started generators together through PWMSYNC, so their edges line up. SetBaseFrequency recomputes the comparators of running generators so the duty cycle survives a frequency change. A fault pulse pends the NVIC line while the raw status follows the live fault, so a fault flooded the event loop; the line now stays disabled for the fault episode and is re-enabled after 1 ms without new pulses, and the event names the configured sources when the status is already clear. The destructor masks the PWM interrupts before releasing the handlers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GPIODATA returns the output latch for an output pin, so a released open-drain pin always read 1. Set(true) releases an open-drain pin by making it an input, Set(false) drives it low.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
QEIPOS ignores writes while the module is disabled, so the configured offset was lost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gabrielfrasantos added a commit that referenced this pull request Oct 1, 2026
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>
claude added 2 commits October 2, 2026 07:26
PD0/PD1/PD2 carry C0o/C1o/C2o on port-control function 5; the table used 8,
which left the pin unconnected. Found on the EK-TM4C1294XL bench: with C1o on
PD1 the pin stayed low while the comparator toggled, and writing 5 into PD1's
PCTL nibble made the pin follow the comparator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Brings in the ISB after NVIC_DisableIRQ in EMIL's DisableIrq (#154), so an
IRQ cannot be taken after UnregisterHandler has cleared its table entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
The fault path no longer polls with a 1 ms repeating timer or calls
NVIC_DisableIRQ/NVIC_EnableIRQ directly:

- HandleFaultIrq masks the fault interrupts in PWMINTEN and reports one
  FaultEvent, so a persistent or repeating fault cannot flood the event loop.
- Stop() clears latched fault status (FLTSTAT is write-one-to-clear in latch
  mode) and re-enables the fault interrupts. Start() only sets duty cycles, so a
  control loop calling it cannot resume during a fault; the application stops,
  then starts again.
- PWMFAULT/PWMFAULTVAL now drive the outputs of fault-configured generators to
  their inactive level during a fault, instead of the fault only raising an
  interrupt.
- The destructor masks interrupts and releases the handlers before disabling
  the generators, without re-arming.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
gabrielfrasantos pushed a commit that referenced this pull request Oct 2, 2026
…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
claude added 12 commits October 2, 2026 10:25
…er pin

A dispatched GPIO interrupt scheduled one event-dispatcher action per edge, so
an edge train faster than the event loop (200 edges at 20 kHz on the bench)
filled the dispatcher queue and EMIL aborted. Each pin now counts its pending
edges atomically and schedules only when the count leaves zero; the queued
action runs the handler once per counted edge, so no edge is lost and the queue
holds at most one action per pin. DisableInterrupt drops the pending count, and
a handler removed meanwhile is not called.

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
On the EK-TM4C123GXL every asynchronous transfer timed out. With CR1.EOT set,
TXRIS never asserted at the end of transmission, so the received bytes stayed
in the FIFO. The next transfer then overran it and hit really_assert in the
SSI2 interrupt.

A batch now completes once all of its frames have been read. Besides TXIM,
the receive interrupts (RXIM for half full, RTIM for the receive time-out)
also drain the FIFO, so completion no longer depends on TXRIS. Each transfer
first drains stale receive data and clears ROR/RT. A zero-length transfer
completes immediately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
On the EK-TM4C123GXL at 80 MHz, interrupt-driven reception at 921600 Bd
overran the receive FIFO. With the trigger at 7/8 full, the interrupt had only
two characters (22 us) to start. At half full it has eight characters (87 us).
UartWithDma still sets its own trigger levels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
TM4C123 erratum WDT#03: watchdog 1 resets the device on its second time-out
even with WDTCTL.RESEN clear. On the EK-TM4C123GXL, wdt.start 1 reset=0 reset
the board one period after the first warning, while CTL read back 0x80000001.

Without reset, the interrupt handler now clears the time-out itself and reports
the early warning once until the next Refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
TM4C123 erratum ADC#14: the first two conversions after the ADC clock is
enabled may be wrong, and the workaround is a reset through SRADC. The reset
also clears the averaging, delay and comparator settings a previous user left
behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
…e FIFO

On the EK-TM4C123GXL, when the END step of a sequence was routed to a digital
comparator, its interrupt fired before the previous step's sample reached the
FIFO, so every run delivered one sample too few. All 20 digital-comparator HIL
tests failed. Moving the sampler's routing by hand showed every sample arrives
once the interrupting step goes to the FIFO. The interrupt now sits on the last
FIFO step, and END stays on the last step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
On the EK-TM4C123GXL, UartWithDma at 115200 dropped up to 7 bytes, always
starting at offset 18, and the bytes never reached the wire. How many were lost
grew with the arbitration size: single requests moved a whole arbitration unit
into a TX FIFO with fewer free entries. With USEBURST set, the channel only
transfers on the half-empty burst request, so 4 items always fit. On the bench
every size from 9600 to 921600 Bd went out complete, with no stretched stop
bits, and the 68 DMA UART tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Moving the interrupt off the comparator step (ac454b8) didn't help on the
EK-TM4C123GXL: the bench showed that the END step being routed to a digital
comparator is what drops one FIFO sample per run, wherever IE sits. Following
the workaround for TM4C123 erratum ADC#03, a free step after it now carries
END|IE into the FIFO and the driver discards its sample. With the comparator
step anywhere but the END step, every sample arrives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
The Tiva configure presets use a single-config generator without a build type,
so the build presets' configuration was ignored and firmware (CI's embedded
builds included) compiled at -O0. On the EK-TM4C123GXL that made the UART1
interrupt too slow for 921600 Bd and skewed ADC sequencer timing. Configure
with -DCMAKE_BUILD_TYPE=Debug to get an unoptimized build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
On the EK-TM4C123GXL, with 921600 Bd full duplex, UartWithDma sometimes
inserted 4-9 copies of the character received 16 earlier. A ring log showed the
uDMA writing them itself, right after the switch from the primary to the
alternate half. It served a burst request the UART had not yet withdrawn, and
read the empty FIFO. With 1-item arbitration every extra read still finds data,
because requests only come at 8 entries. 240 replay rounds and 40 test runs were
clean afterwards (4/240 and 6/40 before), with no change on the console or in
the full UART suite (160 passed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WbtsfrCF7vqzVkHsKYMoX
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants