shibosoftwaredev/f1c100s-linux-nema17-stepper-controller
A four-layer NEMA 17 motor-controller PCB combining an Allwinner F1C100S Linux computer, USB‑PD/buck power system, W5500 Ethernet, STM32 real-time motion control, DRV8825 stepper driver, protection/temperature/current telemetry, USB interfaces, and local LED/buzzer diagnostics.
- Version
- 0.4.56
- License
- unset
- Stars
- 0
firmware/PROTOCOL.md
# Linux-to-motion-MCU protocol
UART0 runs at 115200 baud, 8 data bits, no parity and one stop bit. Multi-byte
values are little-endian.
Each frame is:
| Bytes | Field |
|---:|---|
| 2 | sync: `A5 5A` |
| 1 | version: `01` |
| 1 | sequence number |
| 1 | command |
| 2 | payload length |
| N | payload |
| 2 | CRC-16/CCITT-FALSE over version through payload |
Responses repeat the request sequence and set bit 7 of the command. Their first
payload byte is status: 0 success, 1 bad command, 2 bad argument, 3 not armed,
4 busy, 5 fault and 6 over-temperature.
| Command | Payload | Meaning |
|---:|---|---|
| `01` | none | heartbeat |
| `02` | `u8 armed` | arm/disarm; arming is rejected with any fault |
| `10` | `i32 microsteps, u32 rate` | one active relative move, rate 1–20,000/s |
| `11` | none | controlled stop |
| `12` | none | immediate disable |
| `20` | none | status request |
| `21` | none | signed coil-current telemetry; does not arm, move or refresh heartbeat |
| `22` | `u16 nominal_peak_mA` | request 0–396 mA; accepted only while disarmed; does not arm or refresh heartbeat |
| `23` | none | read current-limit readiness, DAC readback and nominal setting; no heartbeat refresh |
The MCU defaults to disabled, enables its independent watchdog, requires a
valid heartbeat at least every 100 ms, and disable the bridge after 250 ms
without a valid frame. CRC, framing, UART overrun, DRV8825 nFAULT, TMP102 alert
and over-temperature errors must never leave the bridge enabled.
The Linux utility and `stm32/` implementation use this framing. The MCU firmware
compiles and its portable state-machine tests pass, but it is experimental until
reviewed and tested on first articles. An unprogrammed STM32 cannot move a motor.
See `stm32/README.md` for build, factory option-byte and programming requirements.
Payload length is limited to 32 bytes. Identical sequence/command/payload retries
repeat the cached reply without executing a move twice. A malformed frame latches
a protocol fault. Disarm with healthy inputs to clear faults, send a heartbeat,
then arm. A move during the first three ticks after arming returns busy.
A status response has this 19-byte payload (including status byte):
| Offset | Type | Field |
|---:|---|---|
| 0 | u8 | command status |
| 1 | u8 | armed |
| 2 | u8 | latched fault mask |
| 3 | u32 | remaining microsteps |
| 7 | u32 | current rate, microsteps/s |
| 11 | u32 | measured raw PD VBUS, mV |
| 15 | i32 | temperature, milli-degrees Celsius |
Fault bits: 0 power, 1 driver, 2 over-temperature, 3 sensor, 4 protocol, 5 heartbeat,
6 current-limit DAC I/O/readback. All pre-existing response layouts are unchanged.
The existing `20` response is unchanged. Command `21` returns an 18-byte payload:
| Offset | Type | Field |
|---:|---|---|
| 0 | u8 | command status |
| 1 | u8 | flags: bit 0 A valid, bit 1 B valid, bit 2 A clipped, bit 3 B clipped |
| 2 | i32 | phase A filtered current, mA |
| 6 | i32 | phase B filtered current, mA |
| 10 | u16 | raw A ADC count |
| 12 | u16 | raw B ADC count |
| 14 | u16 | measured VDDA, mV |
| 16 | u16 | sample age, ms (saturates at 65535) |
Sampling is best-effort foreground at nominal 1 kHz, interruptible by the
unchanged high-priority STEP timer. Each analog channel has a 1k/100nF low-pass
filter (about 1.59 kHz); values are signed, filtered instantaneous telemetry,
not true-RMS readings or a replacement for the driver's current regulator.
Readings older than 20 ms, failed conversions and invalid VDDA clear validity.
Rail-adjacent readings are flagged clipped and must not be used as measurements.
Repeated `21` requests take a fresh snapshot rather than a cached age flag.
Positive A/B means current from AOUT1/BOUT1 towards J_MOTOR pin 4/pin 1.
Nominal conversion is `(Vout - VDDA/2) / (10 * 0.300 ohm)`; resistor tolerance,
ADC errors, temperature and switching behavior require bench calibration.
`python3 firmware/motor-control.py --current` reads both channels without
sending a motion, arm or disable command. Hardware without the inline sensing
parts cannot use this firmware's current readings; retain matched revisions.
The USB-PD voltage remains a power-off hardware selection (`R_PD_12V`).
Winding current is now a software setpoint through the MCP4725 at I2C address
0x60. TMP102 remains at 0x48. `R_ISET_210` MUST remain DNP for the nominal mA
conversion; fitting that legacy option lowers the hardware ceiling and invalidates
the reported nominal setting. The 9.1k/2k divider and regulation shunts are unchanged.
The DAC uses the same regulated 3.3 V source immediately upstream of the fitted
zero-ohm R_V3V3_MOTION star link. VDDA compensation assumes that link is fitted;
rail drop, DAC/driver error and resistor tolerances still require calibration.
The nominal range is not an upgrade to drive 1.5 A NEMA17 motors.
Command `22` queues an asynchronous setting; an ACK does NOT mean it is ready.
Arming is rejected until the DAC write and register readback succeed and the
reference has settled for at least 3 ms. A zero-current setting cannot arm the
bridge. Current changes while armed (even at standstill) return busy, avoiding
unannounced holding-torque changes and preserving the step generator.
Command `23` returns 10 bytes including command status:
| Offset | Type | Field |
|---:|---|---|
| 0 | u8 | status |
| 1 | u8 | bit 0 ready, bit 1 DAC I/O healthy |
| 2 | u16 | requested nominal peak mA |
| 4 | u16 | verified DAC code (0xffff before successful initialization) |
| 6 | u16 | nominal applied mA, including VDDA compensation/code saturation |
| 8 | u16 | VDDA mV used to calculate the code |
The default requested limit remains 396 mA for the existing motor. The bridge
is hardware-pulled asleep during CPU reset. EEPROM contents are NOT trusted:
firmware first writes and reads back zero, then applies the requested setting.
Only volatile fast writes are used (never EEPROM writes). DAC readback is checked
every 20 ms; NACK, wrong register code, busy or power-down state latch a fault and
disable the driver. Fault recovery requires a healthy DAC and the existing explicit
disarm/clear sequence. I2C transactions run in foreground, not with IRQs masked;
the STEP timer, its priority and the independent watchdog are unchanged.
These are nominal full-scale chopping limits, not measured winding current.
Actual current depends on DAC/driver/shunt errors; DRV8825 accuracy degrades below
1 V VREF, and first-article calibration/torque/noise tests remain required. At a
low VDDA the requested maximum may saturate, so read `nominal applied mA` too.
Examples (matching new PCB and firmware required):
```
python3 firmware/motor-control.py --set-current-ma 200 # no motion/arming
python3 firmware/motor-control.py --limit # read only
python3 firmware/motor-control.py --current-ma 300 --steps 3200 --hz 200
```