Skip to content

GrigorG1/mspm0-hil-validation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

MSPM0 Hardware-in-the-Loop (HIL) Validation Framework

Overview

This project implements a lightweight HIL testing framework for the TI MSPM0G3507 LaunchPad. It demonstrates automated hardware validation by pairing a firmware test agent (running on the MCU) with a Python-based host controller.

As of v1.1, the firmware adds a Sensor Monitor mode that samples an external potentiometer, drives a 3-LED status indicator (breadboarded with current-limiting resistors), and streams telemetry over the same UART β€” a small demo of closed-loop sensor processing on top of the same framework.

20260130_004017222_iOS

Photo above shows the v1.0 HIL loopback setup (PB2 ↔ PB3 jumper only). Updated photos covering the v1.1 Sensor Monitor breadboard (pot + 3 LEDs) are pending β€” tracked in Future Enhancements.

Key Features:

  • Bare-Metal Firmware: Custom UART command parser written in C, direct TI DriverLib (no SysConfig).
  • Automated Testing: Python pytest suite using pyserial to drive inputs and verify outputs.
  • Hardware Loopback: Physically verifies GPIO logic levels using a closed-loop wiring setup.
  • Sensor Monitor Extension (v1.1): ADC-driven threshold detection with LED indicators and UART telemetry; HIL commands remain available while in Sensor Monitor mode.
  • Test Artifacts: JUnit XML reports and timestamped logs for CI/CD integration.

Hardware Setup

Requirements

HIL loopback (v1.0):

  • Board: TI LP-MSPM0G3507
  • USB Cable: Micro-USB (included with LaunchPad)
  • Jumper wire: 1Γ— female-to-female, for the PB2 ↔ PB3 loopback

Sensor Monitor (v1.1), additionally:

  • Potentiometer: 1Γ— 10 kΞ© linear
  • LEDs: 1Γ— green, 1Γ— yellow, 1Γ— red (standard 3 mm or 5 mm indicator LEDs, ~2.0–2.2 V Vf)
  • Resistors: 3Γ— 270 Ξ© (current-limiting; one per LED)
  • Breadboard: half-size (~400 tie points, β‰₯30 columns) or larger
  • Jumper wires: ~10 female-to-female (one female end plugs directly onto the LaunchPad's male header pins; the other female end is converted to a pin for the breadboard via a short male-male pin-header adapter) and ~13 male-to-male (breadboard-to-breadboard hops)

Wiring Diagrams

HIL loopback (v1.0):

    LP-MSPM0G3507 (Header J1)
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚                         β”‚
    β”‚   Pin 9  (PB2) ────┐    β”‚
    β”‚                    β”‚    β”‚   <- Jumper Wire
    β”‚   Pin 10 (PB3) <β”€β”€β”€β”˜    β”‚
    β”‚                         β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

    PB2 = Stimulus Output (firmware drives HIGH/LOW)
    PB3 = Measurement Input (firmware reads state)

Sensor Monitor (v1.1):

Voltage divider β€” potentiometer to ADC:

    J1.1  (3V3) ───┐
                   β”‚
                 [ 10 kΞ© pot ]── wiper ──▢ J1.2  (PA25, ADC0.2)
                   β”‚
    J3.22 (GND) β”€β”€β”€β”˜

LED chains β€” one per color, cathodes tied to the breadboard GND rail
(which is strapped to J3.22):

    J1.4 (PA8)  ── 270 Ξ© ──▢|── GND      Green  ("NORMAL")
    J1.6 (PB24) ── 270 Ξ© ──▢|── GND      Yellow ("WARNING")
    J1.7 (PB9)  ── 270 Ξ© ──▢|── GND      Red    ("ALERT")

Legend:  β–Ά|  = LED (anode left, cathode right)
         Active-high: GPIO HIGH β†’ LED on.

Pin Configuration

HIL loopback (v1.0):

Pin Function Direction Config
PB2 (J1.9) Stimulus Output Push-pull
PB3 (J1.10) Measurement Input Pull-down* enabled

*If the loopback wire is disconnected, the input reads a deterministic LOW (0) instead of floating garbage. This enables fault detection.

Sensor Monitor extension (v1.1):

Pin Header Function Direction Config
PA25 J1.2 Potentiometer wiper Analog input ADC0 channel 2, VDDA reference
PA8 J1.4 Green LED ("NORMAL") Output Push-pull, active high (270 Ξ© β†’ GND)
PB24 J1.6 Yellow LED ("WARNING") Output Push-pull, active high (270 Ξ© β†’ GND)
PB9 J1.7 Red LED ("ALERT") Output Push-pull, active high (270 Ξ© β†’ GND)
3V3 J1.1 Pot supply β€” Powers the voltage divider
GND J3.22 Common ground β€” LED cathode returns + pot low end

The potentiometer forms a voltage divider between the 3.3 V rail and GND; its wiper returns the analog sense voltage to PA25. Each LED is driven directly by its GPIO through a 270 Ξ© current-limiting resistor to the common GND rail (active-high: GPIO HIGH turns the LED on).


Serial Protocol (v1.1)

UART Settings: 115200 baud, 8 data bits, no parity, 1 stop bit (8N1)

Command Format

  • Input: Single character (newline optional; \r and \n are ignored)
  • Output: OK <payload>\n on success, E <code>\n on failure

Commands

Cmd Description Success Response
? Get firmware identity OK MSPM0_HIL_v1.1
H Set PB2 HIGH (3.3V) OK
L Set PB2 LOW (0V) OK
R Read PB3 state OK 0 or OK 1
S Get status OK <uptime_ms> <cmd_count>
M Toggle Sensor Monitor mode OK SENSOR or OK HIL

Unknown characters produce E BAD_CMD\n. HIL commands work in both HIL and Sensor Monitor modes.


Sensor Monitor Mode (v1.1)

Sensor Monitor turns the LaunchPad into a simple closed-loop sensor demo. Enter by sending M; send M again to return to HIL mode.

What it does

  1. Samples the external potentiometer (PA25, ADC0 ch2) every 200 ms.
  2. Maps the 12-bit reading into one of three bands and drives the matching LED:
Band Raw range LED Status label
Normal 0 – 1364 Green (PA8) NORMAL
Warning 1365 – 2729 Yellow (PB24) WARNING
Alert 2730 – 4095 Red (PB9) ALERT
  1. Emits one telemetry line per sample over UART:
[<uptime_ms>ms] ADC: <raw_0_4095> | Status: NORMAL|WARNING|ALERT

Example transcript:

OK SENSOR
[12345ms] ADC: 812 | Status: NORMAL
[12545ms] ADC: 2011 | Status: WARNING
[12745ms] ADC: 3550 | Status: ALERT

HIL commands (H/L/R/S/?) still work while Sensor Monitor is active β€” they interleave with telemetry lines because the main loop polls UART non-blockingly between samples.

Architecture notes

  • Super-loop + two ISRs. The main loop runs two cooperative "tasks": a non-blocking UART poll and a periodic ADC/LED/telemetry step. TIMG0 provides a 1 ms tick (g_uptime_ms) for scheduling; the UART RX ISR latches incoming bytes for the main loop to consume.
  • Why the firmware bypasses the TI Drivers UART. The SDK's UART_readTimeout(..., 0) did not appear to return immediately on MSPM0 in this configuration β€” it looked like it was pending on a WAIT_FOREVER semaphore inside UART_readBufferedMode, which breaks the super-loop pattern. Callback mode would likely fix that but requires DMA, which is disabled in ti_drivers_config.c. uart_io.c therefore talks to the UART directly via DriverLib (DL_UART_*) with its own interrupt handler and a single-byte RX slot β€” tiny, deterministic, non-blocking.

    Caveat: This diagnosis is based on reading the SDK source and symptom matching, not a deep instrumented trace, and the workaround was chosen under time pressure for the demo. Before building more on top of it, the root cause should be re-verified (e.g., step through UART_readBufferedMode with the debugger to confirm the WAIT_FOREVER path is actually entered) and the alternatives (enable DMA + callback mode, tweak the driver config, or open a TI E2E thread) re-evaluated.


Getting Started

1. Flash the Firmware

Prerequisites: (other versions may be suitable as well, however these are the only tested versions)

Steps:

  1. Open CCS and import the project from firmware/
  2. Connect the LaunchPad via USB
  3. Build the project
  4. Flash
  5. Open a serial terminal to verify: you should see MSPM0_HIL_v1.1: Ready (H/L/R/S/?, M=mode toggle)

Build/flash without CCS (optional):

  • This repo does not include a standalone makefile; the supported build flow is CCS/Theia import.

2. Run the Tests

Prerequisites:

  • Python 3.10+
  • Virtual environment (recommended)

Steps (in PowerShell 7.0+):

cd tests
python -m venv venv

venv\Scripts\Activate.ps1

pip install -r requirements.txt

If PowerShell blocks Activate.ps1 with an execution-policy error, run it once in your shell: Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned.

Run all tests:

New-Item -ItemType Directory -Force -Path results | Out-Null
pytest test_hil_loopback.py --port COM7 --junitxml=results/test_results.xml

(Replace COM7 with your actual serial port)

Find your port:

  • Windows: Device Manager β†’ Ports β†’ "XDS110 Class Application/User UART"

Test Cases

Test Description Pass Criteria
test_identity_returns_correct_version Verify firmware responds Response = MSPM0_HIL_v1.1
test_set_high_read_high Loopback HIGH H β†’ R returns 1
test_set_low_read_low Loopback LOW L β†’ R returns 0
test_toggle_sequence Rapid toggle H-L-H-L sequence reads 1-0-1-0
test_status_returns_uptime_and_count Status valid uptime > 0, count β‰₯ 1
test_uptime_increases Timer running uptime increases over 500ms
test_invalid_command_returns_error Error handling X returns E BAD_CMD
test_device_responds_after_error Recovery Valid command works after error

Troubleshooting

Common Failures

Symptom Likely Cause Fix
TimeoutError: No response Wrong COM port Check Device Manager, update --port
TimeoutError: No response CCS terminal still open Close CCS serial console
test_set_high_read_high fails with OK 0 Loopback wire disconnected Connect PB2 β†’ PB3
test_set_low_read_low fails with OK 1 Wrong pins wired Verify J1.9 β†’ J1.10
All tests fail Firmware not flashed Rebuild and flash firmware
E BAD_CMD on valid command Baud rate mismatch Ensure 115200 baud
pytest shows UNEXPECTED: [<ms>] ADC: ... lines Firmware is still in Sensor Monitor mode from a prior interactive session Press the LaunchPad reset button before running pytest (tracked as a known issue in Future Enhancements)
No LEDs light in Sensor Monitor mode LED polarity reversed or 270 Ξ© resistor missing / miswired Verify: GPIO pin β†’ 270 Ξ© β†’ LED anode β†’ LED cathode β†’ GND rail
ADC stuck at 0 or 4095 regardless of pot position Pot 3V3 / GND ends swapped, or wiper not connected to PA25 Verify J1.1 (3V3) on one outer terminal, J3.22 (GND) on the other, wiper β†’ J1.2

Manual Verification

If tests fail, verify hardware manually:

  1. Open a serial terminal (PuTTY, CCS Terminal, etc.)
  2. Connect at 115200 baud
  3. Type ? β†’ should see OK MSPM0_HIL_v1.1
  4. Type H β†’ should see OK
  5. Type R β†’ should see OK 1 (with wire connected)
  6. Type M β†’ should see OK SENSOR, followed by a telemetry line every 200 ms (see Sensor Monitor Mode). Type M again to return to HIL mode.

Sample Output

Pytest Console

$ pytest test_hil_loopback.py --port COM7
========================= test session starts =========================
collected 9 items / 1 deselected / 8 selected

test_hil_loopback.py::TestIdentity::test_identity_returns_correct_version PASSED
test_hil_loopback.py::TestGPIOLoopback::test_set_high_read_high PASSED
test_hil_loopback.py::TestGPIOLoopback::test_set_low_read_low PASSED
test_hil_loopback.py::TestGPIOLoopback::test_toggle_sequence PASSED
test_hil_loopback.py::TestStatus::test_status_returns_uptime_and_count PASSED
test_hil_loopback.py::TestStatus::test_uptime_increases PASSED
test_hil_loopback.py::TestErrorHandling::test_invalid_command_returns_error PASSED
test_hil_loopback.py::TestErrorHandling::test_device_responds_after_error PASSED

========================= 8 passed, 1 deselected in 1.44s =========================

Log File (excerpt)

2026-01-27 14:41:54,264 | INFO     | Logging to: logs\hil_test_20260127_144154.log
2026-01-27 14:41:54,264 | INFO     | Connecting to COM7...
2026-01-27 14:41:54,365 | INFO     | Connected successfully
2026-01-27 14:41:54,366 | INFO     | TEST: Identity check
2026-01-27 14:41:54,368 | INFO     |   Response: MSPM0_HIL_v1.1
2026-01-27 14:41:54,370 | INFO     | Disconnecting...
2026-01-27 14:41:54,371 | INFO     | Connecting to COM7...
2026-01-27 14:41:54,474 | INFO     | Connected successfully
2026-01-27 14:41:54,474 | INFO     | TEST: Set HIGH, read HIGH
2026-01-27 14:41:54,484 | INFO     |   H command: OK
2026-01-27 14:41:54,485 | INFO     |   R command: 1
2026-01-27 14:41:54,486 | INFO     | Disconnecting...

See tests/sample_output/ for complete example files.


Project Structure

mspm0_hil_validation/
β”œβ”€β”€ firmware/                          # CCS Theia project (bare-metal, no SysConfig)
β”‚   β”œβ”€β”€ hil_firmware.c                 # Main: command parser, mode toggle, super-loop
β”‚   β”œβ”€β”€ uart_io.c / uart_io.h          # Non-blocking UART I/O (direct DriverLib; bypasses TI Drivers UART)
β”‚   β”œβ”€β”€ adc.c / adc.h                  # ADC driver (potentiometer on PA25, ADC0 ch2)
β”‚   β”œβ”€β”€ led.c / led.h                  # 3-LED indicator driver (green/yellow/red)
β”‚   β”œβ”€β”€ ti_drivers_config.c / .h       # TI Drivers config (v1.0 scaffolding; UART handler intentionally dropped β€” see note in .c)
β”‚   β”œβ”€β”€ mspm0g3507.cmd                 # Linker command file
β”‚   β”œβ”€β”€ startup_mspm0g350x_ticlang.c   # Cortex-M reset vector + startup
β”‚   └── targetConfigs/                 # CCS debugger target configuration
β”œβ”€β”€ tests/                             # Python test suite
β”‚   β”œβ”€β”€ conftest.py                    # Pytest fixtures (serial port, logging)
β”‚   β”œβ”€β”€ hil_client.py                  # Serial client wrapping the command protocol
β”‚   β”œβ”€β”€ test_hil_loopback.py           # Automated HIL loopback tests
β”‚   β”œβ”€β”€ test_client_manual.py          # Manual/exploratory client for interactive smoke-testing
β”‚   β”œβ”€β”€ pytest.ini                     # Pytest configuration
β”‚   β”œβ”€β”€ requirements.txt               # Python dependencies
β”‚   └── sample_output/                 # Example JUnit XML + log from a real run
β”œβ”€β”€ LICENSE
└── README.md

Future Enhancements

  • S response should include the current mode (HIL or SENSOR)
  • Add photos of the v1.1 Sensor Monitor breadboard (pot voltage divider + 3 LED chains) alongside the existing v1.0 loopback photo
  • Add test cases for the 1.1 changes
  • Soak testing (12-hour continuous run)
  • Fault injection (firmware-simulated delays/drops)
  • Power cycle testing (USB relay integration)
  • IΒ²C sensor validation
  • Logic analyzer trace capture
  • Auto-return to HIL mode on pytest connect. g_mode persists across serial disconnects, so if the firmware is left in Sensor Monitor mode from a prior interactive session, the 200 ms telemetry lines can race ahead of OK/E responses in the serial buffer and cause intermittent UNEXPECTED: parse failures. Proposed fix in tests/hil_client.py: on connect(), query mode (e.g., add a dedicated query command, or inspect the response to a probe M and toggle back if needed) so the test session always starts in a known HIL state. Until then, press the board's reset button before running pytest if you've been using M interactively.

License

MIT License - See LICENSE file for details.


About

Automated Hardware-in-the-Loop (HIL) testing framework for TI MSPM0 microcontrollers using Python and Pytest.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors