Brushed Motor Controller (BMC)
Drives a brushed DC motor with closed-loop position or velocity control, quadrature encoder
feedback, limit switches and current sensing. Firmware lives in src/bmc.
This is the most complex board in the tree and the only one that runs a control loop. For background on the motors themselves see Brushed DC Motors.
Hardware
Section titled “Hardware”| Interface | Peripheral | Notes |
|---|---|---|
| H-bridge | htim1 PWM + direction GPIO | motor output |
| Quadrature encoder | htim4 in encoder mode | position and velocity feedback |
| Limit switches A and B | GPIO | presence and polarity are configurable |
| Current sense | ADC1 via AD8418A | published as current |
| CAN | FDCAN1 | |
| Logging | LPUART1 (DMA) |
Behavior
Section titled “Behavior”| Timer | Rate | Purpose |
|---|---|---|
htim17 | 25 Hz | control loop update |
htim6 | 10 Hz | publish BMCMotorState |
htim16 | 10 Hz | CAN watchdog |
htim2 | - | elapsed-time source for PID and encoder timing |
The board is a small state machine, selected by BMCModeCmd:
| Mode | Value | Behavior |
|---|---|---|
STOPPED | 0 | output disabled |
FAULT | 1 | latched error; output disabled until reset |
THROTTLE | 5 | open loop, target is a duty cycle |
POSITION | 6 | closed loop on encoder position |
VELOCITY | 7 | closed loop on encoder velocity |
Position and velocity modes each load their own PIDF gains from configuration when entered, so the two loops are tuned independently.
Watchdog
Section titled “Watchdog”A CAN watchdog runs at 10 Hz. If commands stop arriving the board faults with WWDG_EXPIRED and
disables the output, so a lost link or a crashed host stops the motor rather than leaving it
running at its last command.
Calibration
Section titled “Calibration”Position control requires a known reference. The board tracks an uncalibrated position until a
limit switch establishes the offset; commanding position or velocity before that faults with
UNCALIBRATED. Each limit switch can be configured as forward or backward, and can optionally
readjust the position estimate when hit.
Fault Codes
Section titled “Fault Codes”Published in BMCMotorState.fault_code:
| Code | Meaning |
|---|---|
NONE | no error |
NO_MODE | no mode selected |
INVALID_CONFIGURATION_FOR_MODE | closed-loop mode requested with no feedback configured |
INVALID_FLASH_CONFIG | configuration in flash is not usable |
WWDG_EXPIRED | CAN watchdog timed out |
UNCALIBRATED | position or velocity commanded before calibration |
CAN_ERROR_FATAL | unrecoverable CAN error |
I2C_ERROR_FATAL | unrecoverable I2C error |
SPI_ERROR_FATAL | unrecoverable SPI error |
Clear a latched fault with BMCResetCmd.
CAN Interface
Section titled “CAN Interface”Receives
Section titled “Receives”| Message | Base ID | DLC | Signals |
|---|---|---|---|
BMCModeCmd | 0x80100000 | 2 | mode (8), enable (1) |
BMCTargetCmd | 0x80110000 | 5 | target (32, signed), target_valid (1) |
BMCResetCmd | 0x80120000 | 1 | reset, clear_faults |
ESWConfigCmd | 0x80F00000 | 6 | address, value, apply |
ESWProbe | 0x80F10000 | 4 | data |
| Message | Base ID | DLC | Signals |
|---|---|---|---|
BMCMotorState | 0x80130000 | 16 | mode (8), fault_code (8), position (32, signed), velocity (32, signed), current (32, signed), limit_a (1), limit_b (1), is_stalled (1) |
ESWAck | 0x80F20000 | 4 | data |
target is interpreted according to the active mode: duty cycle in throttle, position in position
mode, velocity in velocity mode. target_valid must be set for the command to take effect.
Configuration
Section titled “Configuration”Definition: config/bmc.yaml, the largest in the tree at 84 bytes. Device values live under
rover/ra/, rover/sp/, rover/cm/ and rover/test/.
| Register | Type | Meaning |
|---|---|---|
can_id | uint8 | this board’s CAN node ID |
host_can_id | uint8 | where to address published state |
sys_cfg | uint8 | motor_en, motor_inv, quad_en, quad_phase, stall_en, lim_a_present, lim_b_present |
limit_cfg | uint8 | per switch: en, active_high, is_forward, use_readjust for A (bits 0-3) and B (bits 4-7) |
quad_cpr | float32 | encoder counts per revolution |
gear_ratio | float32 | motor to output gear ratio |
rotor_output_ratio | float32 | rotor to output ratio |
limit_a_position | float32 | position asserted when limit A is hit |
limit_b_position | float32 | position asserted when limit B is hit |
max_pwm | float32 | output ceiling |
min_pos, max_pos | float32 | software position limits |
min_vel, max_vel | float32 | software velocity limits |
pos_k_p, pos_k_i, pos_k_d, pos_k_f | float32 | position loop PIDF gains |
vel_k_p, vel_k_i, vel_k_d, vel_k_f | float32 | velocity loop PIDF gains |
stall_current | float32 | current above which is_stalled is asserted |
delta_position | float32 | position change threshold used in stall detection |
See Register Definitions and CAN Configuration Interface.
Building
Section titled “Building”./scripts/build.sh --src src/bmc --preset Debug./scripts/build.sh --src src/bmc --preset Debug --flashsrc/bmc is in ci.json, so CI builds it at both presets.
Related Scripts
Section titled “Related Scripts”Both have hardcoded CAN IDs and interfaces, so read them before running:
tools/scripts/restart_bmc.pysendsBMCResetCmdto node 49 oncan1.tools/scripts/send_can.pydrives node0x67oncan0to a fixed target at 20 Hz.