MustafaMulla29/rp2040-bldc-motor-controller-new

73×72 mm 4-layer USB‑C PD (5/9/15/20 V) RP2040 controller PCB with protected power switching, MCT8329A sensorless BLDC three-phase MOSFET bridge, USB data/programming, onboard controls, and three motor outputs feeding a 2212 motor through rigid bullet adapters.

Version
0.3.100
License
unset
Stars
0

docs/knob-firmware.md

# Knob and ARM firmware — 19 October 2026

`firmware/knob_app.py` implements the RV1/SW3 interface on the existing board.
It reads ADC29 and generates a 20 kHz PWM **command** on GPIO2 for U7's SPEED
input. GPIO3 controls the existing Q5/DRVOFF enable path. It does not drive the
six MOSFET gates itself. The command carrier is independent of U7's configured
motor-phase PWM frequency. No PCB changes are required.

**The default remains preview-only.** `motor_setup.PROFILE = None` because no
commissioned MCT8329A motor image or measured ADC calibration is available.
The application reports knob movement but keeps both outputs low. Software
tests do not establish a usable motor configuration or a physical speed rating.

## Implemented behavior

- Clockwise knob movement raises the normalized speed request; the first 3%
  of travel is a zero deadband. An exponential filter reduces ADC jitter.
- The command increases at no more than 32768/65535 full-scale units per second,
  up to the commissioned command ceiling. This is a command ramp, not an RPM
  acceleration guarantee. U7's tuning determines the physical response.
- Arming requires a healthy supply and zero knob for 250 ms, followed by a
  debounced release/press of ARM. A held button at boot cannot arm.
- Another ARM press or USB `stop` disarms. Zero knob clears PWM and disables
  the gates on the next sample; the motor can coast. Logical ARM remains set at
  zero so the knob can request movement again until explicitly disarmed.
- Detected PD loss, bad voltage, overcurrent, nFAULT, profile mismatch, ADC/I2C
  error or a late sample disables drive. Recovery requires returning to zero
  and a fresh button press. Driver faults are never automatically cleared.
- PD voltage commands disarm first and retain the existing SW4-OFF / VMOTOR
  below 1 V checks. They do not write NVM. A new voltage outside the motor's
  qualified list cannot arm. Cold power-up still uses the provisioned 9 V.
- The loop nominally sleeps 20 ms between samples. An interval above 100 ms
  is rejected; a 1-second watchdog covers a blocked interpreter. These are
  software supervision mechanisms, not fast short-circuit protection.

The PD-only `power_manager.py` and `bringup.py` remain separate diagnostics that
always keep drive disabled. Run only one application at a time.

## Preview the knob

Copy the firmware Python modules (excluding `test_*.py`) to the RP2040's
MicroPython filesystem. Keep `motor_setup.PROFILE` as `None`. Connect J1 DATA;
motor power is not needed for ADC preview. From the MicroPython console:

```python
import knob_app
knob_app.run()
```

Turn RV1 and enter `status`. The displayed knob percentage changes while
`command=0%` and `armed=False` remain. `PD-request` is a requested setting, not
a voltage measurement. `stop` disables drive; Ctrl-C also cleans up PWM and
holds ARM low. The watchdog remains enabled and can subsequently reset the
MCU. This application is not automatically installed as `main.py`.

## Commissioning before motor operation

1. Provision/verify the STUSB4500 as described in `connectors-and-firmware.md`.
2. Develop and validate the motor's MCT8329A EEPROM configuration on hardware:
   gate drive, phase-current sensing/limits, startup, stopping, speed response
   and bus-energy handling. Keep the rotor unloaded/guarded during initial
   work and follow `bringup-acceptance.md`. This application does not program
   EEPROM or supply guessed motor settings.
3. Record all 24 loaded configuration words, addresses 0x80 to 0xAE inclusive
   in steps of two, including the reserved/parity bits as read. Configure PWM
   command input, direct reference mapping, standby rather than sleep, the
   325 Hz–95 kHz input range, internal clock, and I2C address 0x60.
4. Measure the ADC reference, both voltage-divider corrections, zero-current
   offset and current-sense gain. Do not mark nominal values as measured.
5. Populate `motor_setup.PROFILE` with the data below and the commissioning
   record. Full readback is checked before arming after startup/power loss;
   one configuration word is rechecked each operating loop.

| PROFILE field | Required data |
| --- | --- |
| `motor` | Exact motor identification |
| `commissioning_record` | Reference to recorded tuning/calibration/bench results |
| `allowed_voltages_mv` | Qualified selections from 5000, 9000, 15000, 20000 |
| `max_command_u16` | Validated command ceiling, 1–65535 |
| `phase_current_limit_ma` | Documented physical phase-current limit |
| `registers` | Dictionary of all 24 integer addresses and their uint32 values |
| `calibration.reference_mv` | Measured 3.3 V ADC reference in millivolts |
| `calibration.vbus_scale_ppm` | Measured correction to the nominal VBUS divider; 1000000 means unity |
| `calibration.vmotor_scale_ppm` | Measured correction to the nominal VMOTOR divider |
| `calibration.current_zero_u16` | ADC28 reading at zero motor-feed current |
| `calibration.current_uv_per_amp` | Measured current-sense sensitivity; nominally 400000 µV/A |

The profile schema and readback comparison confirm compatibility/identity;
they do not judge whether the motor tuning is electrically appropriate. The
phase-current metadata is a record, not an independent phase-current limiter.
Runtime current supervision uses R13/INA180 and the existing provisional
0.8 A (5 V) / 0.9 A (9/15/20 V) **motor-feed** limits. Phase current is different.
The driver must have suitable independent current protection configured.

With a commissioned profile, start at zero, turn POWER on, wait for the supply
checks, release/press ARM, then increase the knob. A second ARM press disarms.
`voltage 5`, `voltage 9`, `voltage 15`, and `voltage 20` use the same guarded
selection as the PD diagnostic console; POWER must be OFF during the change.
No USB command enables the motor or overrides the knob in this application.

## Status and physical limits

Blue means waiting or preview-only, green means the motor-supply/profile checks
have passed, cyan means an active nonzero speed command, and red means a latched
software fault. Cyan does not prove rotation; FG/RPM monitoring is not implemented.

The existing SW4 circuit removes power and clamps ARM in hardware. **There is
no dedicated SW4 position feedback GPIO.** Firmware detects its effect through
VMOTOR/nFAULT; it cannot guarantee latching a very short OFF/ON toggle if the
motor capacitor holds voltage and no fault is sampled. Test this explicitly;
a guaranteed fresh-ARM requirement for every switch toggle would require a
reliable switch-state feedback/latch design. No PCB alteration is made here.

5 V load margin, motor speed/current/thermal limits, stopping energy, sensor
faults and I2C/ADC timing must be verified on the actual prototype. A knob wiper
shorted high while already armed cannot be distinguished from an intentional
high-speed request using this single ADC. This is prototype control firmware,
not a certified emergency-stop or functional-safety system.

## Verification and references

Run `python3 -B -m unittest discover -s firmware -p 'test_*.py' -v`.
Tests cover mapping/filtering, debounce, startup/re-arm interlocks, slew/zero,
power/current faults, profile readback, byte-level I2C framing and timing,
entrypoint exceptions, USB stop, and the unchanged PD-only lockout.
All device tests use simulated hardware; this is not an RP2040 bench result.

- [TI MCT8329A datasheet](https://www.ti.com/lit/ds/symlink/mct8329a.pdf),
  sections 7.3.7.2, 7.5.1, 7.6.2 and tables 7-35–7-37.
- [MicroPython SoftI2C primitives](https://docs.micropython.org/en/latest/library/machine.I2C.html#primitive-i2c-operations).
- [MicroPython PWM](https://docs.micropython.org/en/latest/library/machine.PWM.html).