Skip to content

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.

InterfacePeripheralNotes
H-bridgehtim1 PWM + direction GPIOmotor output
Quadrature encoderhtim4 in encoder modeposition and velocity feedback
Limit switches A and BGPIOpresence and polarity are configurable
Current senseADC1 via AD8418Apublished as current
CANFDCAN1
LoggingLPUART1 (DMA)
TimerRatePurpose
htim1725 Hzcontrol loop update
htim610 Hzpublish BMCMotorState
htim1610 HzCAN watchdog
htim2-elapsed-time source for PID and encoder timing

The board is a small state machine, selected by BMCModeCmd:

ModeValueBehavior
STOPPED0output disabled
FAULT1latched error; output disabled until reset
THROTTLE5open loop, target is a duty cycle
POSITION6closed loop on encoder position
VELOCITY7closed 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.

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.

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.

Published in BMCMotorState.fault_code:

CodeMeaning
NONEno error
NO_MODEno mode selected
INVALID_CONFIGURATION_FOR_MODEclosed-loop mode requested with no feedback configured
INVALID_FLASH_CONFIGconfiguration in flash is not usable
WWDG_EXPIREDCAN watchdog timed out
UNCALIBRATEDposition or velocity commanded before calibration
CAN_ERROR_FATALunrecoverable CAN error
I2C_ERROR_FATALunrecoverable I2C error
SPI_ERROR_FATALunrecoverable SPI error

Clear a latched fault with BMCResetCmd.

MessageBase IDDLCSignals
BMCModeCmd0x801000002mode (8), enable (1)
BMCTargetCmd0x801100005target (32, signed), target_valid (1)
BMCResetCmd0x801200001reset, clear_faults
ESWConfigCmd0x80F000006address, value, apply
ESWProbe0x80F100004data
MessageBase IDDLCSignals
BMCMotorState0x8013000016mode (8), fault_code (8), position (32, signed), velocity (32, signed), current (32, signed), limit_a (1), limit_b (1), is_stalled (1)
ESWAck0x80F200004data

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.

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/.

RegisterTypeMeaning
can_iduint8this board’s CAN node ID
host_can_iduint8where to address published state
sys_cfguint8motor_en, motor_inv, quad_en, quad_phase, stall_en, lim_a_present, lim_b_present
limit_cfguint8per switch: en, active_high, is_forward, use_readjust for A (bits 0-3) and B (bits 4-7)
quad_cprfloat32encoder counts per revolution
gear_ratiofloat32motor to output gear ratio
rotor_output_ratiofloat32rotor to output ratio
limit_a_positionfloat32position asserted when limit A is hit
limit_b_positionfloat32position asserted when limit B is hit
max_pwmfloat32output ceiling
min_pos, max_posfloat32software position limits
min_vel, max_velfloat32software velocity limits
pos_k_p, pos_k_i, pos_k_d, pos_k_ffloat32position loop PIDF gains
vel_k_p, vel_k_i, vel_k_d, vel_k_ffloat32velocity loop PIDF gains
stall_currentfloat32current above which is_stalled is asserted
delta_positionfloat32position change threshold used in stall detection

See Register Definitions and CAN Configuration Interface.

Terminal window
./scripts/build.sh --src src/bmc --preset Debug
./scripts/build.sh --src src/bmc --preset Debug --flash

src/bmc is in ci.json, so CI builds it at both presets.

Both have hardcoded CAN IDs and interfaces, so read them before running:

  • tools/scripts/restart_bmc.py sends BMCResetCmd to node 49 on can1.
  • tools/scripts/send_can.py drives node 0x67 on can0 to a fixed target at 20 Hz.