Skip to content

Motor Controller CAN Bus Protocol

This document describes the CAN bus protocol used by the S73Rx e-bike motor controller firmware. Data was gathered from a static analysis of the motor controller firmware dump version 230/310 US.

PropertyValue
Bus Speed250 kbps
Base PeripheralCAN0 (0x40006400)
Message FormatStandard 11-bit ID
Max Data Length8 bytes
NVIC Priority0x14 (20), Subpriority 4
PinFunctionMode
PA11CAN_RXInput, Pull-up
PA12CAN_TXAlternate Function, Push-Pull
ParameterValue
Prescaler12
SJW1 TQ
BS17 TQ
BS22 TQ
Total10 TQ @ 72MHz = 250kbps
ParameterValue
Filter Bank6
ModeMask
Scale32-bit
ID Filter0x0000
ID Mask0x0000 (accept all)
FIFO0

The CAN TX handler (CAN_PeriodicTX_Handler @ 0x8007AFC) runs on a 10ms tick with a counter cycling 0-59:

Counter ValueMessage(s) SentEffective Rate
Every tick0x203 (Power)10ms (100 Hz)
Even ticks (LSB=0)0x201 (Speed), 0x64A (Status)20ms (50 Hz)
70x266 (Heartbeat)600ms (~1.7 Hz)
220x202 (Temp/Params)600ms
420x202 (Temp/Params)600ms

Note: 0x222 (Motor Telemetry) is sent on-demand in response to 0x300 RX messages.


Transmit Messages (Motor Controller -> Display/BMS)

Section titled “Transmit Messages (Motor Controller -> Display/BMS)”

Echoes received throttle and cadence data back to the bus.

DLC: 8 bytes Rate: On-demand Function: CAN_SendMsg0x200 @ 0x8007B98

ByteFieldTypeDescription
0throttleuint8Echo of g_throttle_value
1mode_byteuint8Echo of g_can_403_byte
2-5cadenceuint32_leEcho of g_pedal_cadence
6-7sequenceuint16Rotating sequence from lookup table

Current speed and motor status flags.

DLC: 5 bytes Rate: 50 Hz (20ms) Function: CAN_TX_0x201_Speed @ 0x8007C04

ByteFieldTypeDescription
0-1speeduint16_leg_speed_value * 10 (0.1 unit resolution)
2-3reserveduint16Always 0
4statusuint8Status bitfield (see below)

Status Byte (byte 4) Bitfield:

BitMaskFieldDescription
00x01light_mode_6Set if g_can_rx_tail_byte == 6
10x02motor_enabledSet if g_status_flags & 0x08
20x04brake_activeSet if g_brake_active != 0
5-70xE0motor_modeg_motor_mode << 5

Motor temperature and EEPROM calibration parameters.

DLC: 8 bytes Rate: ~3.3 Hz (every 200ms at counter 22 and 42) Function: CAN_TX_0x202_TempParams @ 0x8007CA4

ByteFieldTypeDescription
0fixeduint8Always 0x22
1temperatureuint8temp_celsius + 40 (0 if temp < -40)
2-3param_d7uint16_leWheel speed factor from EEPROM
4-7param_d8uint32_leDistance factor from EEPROM

Temperature Encoding: Offset by +40 to allow negative temperatures.

  • 0x00 = -40°C or below
  • 0x28 = 0°C
  • 0x50 = 40°C
  • 0x6E = 70°C

Motor RPM and calculated power consumption.

DLC: 8 bytes Rate: 100 Hz (10ms) Function: CAN_TX_0x203_Power @ 0x8007CF0

ByteFieldTypeDescription
0-1rpmuint16_leg_motor_rpm * 40
2-5poweruint32_le`voltage *
6-7miscuint16_leData from 0x200001A2 (low nibble only for byte 7)

Scaling:

  • RPM: Divide received value by 40 to get actual RPM
  • Power: Divide received value by 40 to get milliwatts

Alternate temperature message with motor mode.

DLC: 8 bytes Rate: On-demand Function: CAN_SendMsg0x204 @ 0x8007D68

ByteFieldTypeDescription
0temperatureuint8temp_celsius + 40 (0 if temp < -40)
1reserveduint8Always 0
2motor_modeuint8Current motor operating mode
3-7reserved-All zeros

0x210/0x211/0x212 - Serial Number Response

Section titled “0x210/0x211/0x212 - Serial Number Response”

Response to serial number/device ID requests. Controller echoes the same ID.

DLC: 8 bytes (0x210, 0x211) or 4 bytes (0x212) Rate: On RX of same ID Function: CAN_MessageDispatcher @ 0x8009178

ByteFieldTypeDescription
0-5serialchar[6]Serial number characters 0-5
6-7odo_hexchar[2]Odometer 10000km digit as hex ASCII
ByteFieldTypeDescription
0-1odo_100kmchar[2]100km digit as hex ASCII
2-3odo_10mchar[2]10m digit as hex ASCII
4-7odo_combinedchar[4]Combined odometer as hex ASCII
ByteFieldTypeDescription
0device_iduint8Device identifier
1device_info_1uint8Device info byte 1
2device_info_2uint8Device info byte 2
3device_info_3uint8Device info byte 3

Variable-format telemetry message with mode selected by caller argument.

DLC: 8 bytes Rate: On-demand (triggered by 0x300 RX) Function: CAN_TX_0x222_MotorTelemetry @ 0x800795C

ByteFieldTypeDescription
0-1current_limituint16_lecurrent_limit * 8 * 0x93 / 10 / 100
2-3target_speeduint16_letarget_speed * 8 * 0x93 / 10 / 100
4-5wheel_circint16_leWheel circumference (negative if < 0)
6configuint8Configuration byte
7data_byteuint8Data byte from 0x20000000
ByteFieldTypeDescription
0-1speed_setpointuint16_leScaled speed setpoint
2-3max_speeduint16_leScaled maximum speed
4-5wheel_circint16_leWheel circumference
6configuint8Configuration byte
7data_byteuint8Data byte
ByteFieldTypeDescription
0-1data_3auint16_leData from 0x2000003A
2-3wheel_circuint16_leWheel circumference
4-5motor_outputuint16_le`
6temperatureuint8temp + 40 (0 if < -40)
7data_byteuint8Data byte
ByteFieldTypeDescription
0-7raw_datauint8[8]Direct copy from 0x20000190-0x20000197

Mode 4 - Current Limits with Battery Voltage

Section titled “Mode 4 - Current Limits with Battery Voltage”
ByteFieldTypeDescription
0-1current_limituint16_leScaled current limit
2-3target_speeduint16_leScaled target speed
4-5wheel_circint16_leWheel circumference
6battery_vuint8g_battery_voltage / 1000 (volts)
7data_byteuint8Data byte
ByteFieldTypeDescription
0-1speed_filteredint16_leFiltered speed value
2-3wheel_circuint16_leWheel circumference
4-5reserveduint16Always 0
6reserveduint8Always 0
7control_flaguint8g_control_flags & 1

Error codes and diagnostic information.

DLC: 8 bytes Rate: On-demand Function: CAN_SendMsg0x265 @ 0x8007DB4

ByteFieldTypeDescription
0reserveduint8Always 0
1-4error_datauint8[4]Data from 0x20000148-0x2000014B
5fixeduint8Always 0x13
6-7reserveduint16Always 0

Periodic heartbeat with firmware identification.

DLC: 8 bytes Rate: ~1.7 Hz (every 600ms, counter = 7) Function: CAN_TX_0x266_Init @ 0x8007DEC

ByteFieldTypeValueDescription
0reserveduint80x00
1region_codeuint80x03 (US) / 0x04 (EU)Firmware region
2fixeduint80x0A
3reserveduint80x00
4reserveduint80x00
5fixeduint80x20
6fixeduint80x10
7fixeduint80x22

Region Codes:

  • 0x03 = US firmware (S310US)
  • 0x04 = EU firmware (S410EU)

Response to diagnostic requests (0x610/0x62A/0x702).

DLC: 8 bytes Rate: On RX of diagnostic request Function: CAN_MessageDispatcher @ 0x8009178

ByteFieldTypeDescription
0dlcuint8Always 8
1-2service_respuint16_leservice_id + 0x40 (positive response)
3addressuint8Echo of request address
4sequenceuint8Echo of request sequence number
5-8datauint8[4]Response data

Response Format by Service:

ServiceResponse Data
0x87Model number (0x136 = 310, 0x19A = 410), 0x68
0x88Odometer value (32-bit)
0x90Device ID bytes (4 bytes)
0xD6Echo of request data
0xD7EEPROM param_d7 (wheel speed factor)
0xD8EEPROM param_d8 (distance factor)

System status flags and magic identifier.

DLC: 8 bytes Rate: 50 Hz (20ms, even counter values) Function: CAN_TX_0x64A_Status @ 0x8007B68

ByteFieldTypeDescription
0-3status_flagsuint32_leg_status_flags bitmask
4-7magicuint32_leFixed value 0x002B4163

Status Flags Bitmask (g_status_flags):

BitMaskFlagDescription
30x0008MOTOR_ENABLEDMotor is enabled
130x2000WALK_MODEWalk assist mode active

Receive Messages (Display/BMS -> Motor Controller)

Section titled “Receive Messages (Display/BMS -> Motor Controller)”

0x210/0x211/0x212 - Serial Number Requests

Section titled “0x210/0x211/0x212 - Serial Number Requests”

RTR-style requests for device identification.

DLC: Variable Function: CAN_MessageDispatcher @ 0x8009178

Condition: byte[1] == 0 (DLC field check)

Controller responds with same message ID containing serial/odometer data.


Main control message from display unit.

DLC: 12 bytes Function: CAN_MessageDispatcher @ 0x8009178

ByteFieldTypeDescription
0-1--(Message ID in buffer)
2motor_modeuint80-3 = normal modes, 4+ = special modes
3enable_flagsuint8bit0 = motor_enable
4assist_leveluint80-100% power assist (capped at 100)
5speed_limituint8Speed limit value (masked 0x7F). Special: 0xA5 = set keepalive
6--Unused
7cruise_controluint80xA5 = enable, 0x5A = disable
8-10--Unused
11light_modeuint8Light mode (masked 0x0F)

Processing:

  • Resets g_state_counter to 0
  • If byte[5] == 0xA5: sets keepalive timer to 60 (0x3C)
  • If byte[3] & 0x01: sets STATUS_FLAG_ENABLED (0x0008)
  • If motor_mode < 4: sets STATUS_FLAG_WALK_MODE (0x2000)
  • If assist_level > 100: caps to 100

Manages communication timeout counter.

DLC: 12 bytes Function: CAN_MessageDispatcher @ 0x8009178

ByteFieldTypeDescription
3timeout_dir_auint8MSB (bit 7) set = increment
7timeout_dir_buint8MSB (bit 7) set = increment

Logic:

if (byte[3] & 0x80) || (byte[7] & 0x80):
g_can_timeout_counter = min(g_can_timeout_counter + 1, 20)
g_can_timeout_flag = 1
else:
if g_can_timeout_counter > 0:
g_can_timeout_counter--
if g_can_timeout_counter == 0:
g_can_timeout_flag = 0

Battery voltage and current from BMS.

DLC: 12 bytes Function: CAN_MessageDispatcher @ 0x8009178

ByteFieldTypeDescription
4-7voltageuint32_leBattery voltage (raw units)
8-11currentint32_leBattery current (signed, raw units)

Throttle input and pedal cadence data.

DLC: 12 bytes Function: CAN_MessageDispatcher @ 0x8009178

ByteFieldTypeDescription
2throttleuint8Throttle position (0-255)
8-11cadenceuint32_lePedal cadence value

Simple mode selector storage.

DLC: 12 bytes Function: CAN_MessageDispatcher @ 0x8009178

ByteFieldTypeDescription
2mode_byteuint8Stored to g_can_403_byte, echoed in 0x200 TX

Torque sensor calibration data.

DLC: 12 bytes Function: CAN_MessageDispatcher @ 0x8009178

ByteFieldTypeDescription
2-3sensoruint16_leTorque sensor raw value
4-5offsetuint16_leTorque sensor offset
6-7gainuint16_leTorque sensor gain

Sets g_can_404_received = 1 to indicate valid torque data received.


0x610/0x62A/0x702 - Diagnostic Requests (UDS-like)

Section titled “0x610/0x62A/0x702 - Diagnostic Requests (UDS-like)”

Diagnostic service requests similar to UDS protocol.

DLC: 12 bytes Function: CAN_MessageDispatcher @ 0x8009178

ByteFieldTypeDescription
2sub_functionuint80x2B = write, 0x40 = read
3addressuint8Sub-address (0x1F or 0x69)
4--Unused
5service_iduint8Service type (see below)
6--Unused
7sequenceuint8Request sequence number
8-11datauint32_leWrite data (for write requests)

Service IDs:

ServiceAddressSub-FunctionDescription
0x870x1F0x40 (read)Read model number (returns 0x136/0x19A, 0x68)
0x880x1F0x40 (read)Read odometer
0x880x1F0x2B (write)Write odometer (seq=1), saves to Flash
0x900x1F0x40 (read)Read device ID (4 bytes)
0x900x1F0x2B (write)Write device ID, saves to I2C EEPROM @ 0xFC
0xD60x690x2B (write)Parameter echo
0xD70x690x40 (read)Read wheel speed factor
0xD70x690x2B (write)Write wheel speed factor (EEPROM: 0x7F, 0xA7, 0xCF)
0xD80x690x40 (read)Read distance factor
0xD80x690x2B (write)Write distance factor (EEPROM: 0x78, 0xA0, 0xC8)

Response: Sent on ID 0x5AA with service_id + 0x40 for positive response.

EEPROM Triple Redundancy: Write operations store data at 3 different EEPROM addresses for redundancy.


Magic sequence to trigger system reset/bootloader entry.

DLC: 12 bytes Function: CAN_MessageDispatcher @ 0x8009178

ByteFieldTypeRequired Value
2-3magic_worduint16_le0x55AA
4magic_byte_1uint80x2A
5magic_byte_2uint80x2A

Safety Check: Only executes if:

  • g_speed_value < 5 AND
  • g_motor_rpm < 5

If conditions met, calls System_Reset() which does not return.


Wheel circumference and configuration byte.

DLC: 12 bytes Function: CAN_MessageDispatcher @ 0x8009178

ByteFieldTypeDescription
2-3circumferenceuint16_leWheel circumference in mm
4config_byteuint8Configuration flags

Updates:

  • g_wheel_circumference (16-bit)
  • g_wheel_circumference_32 (32-bit)
  • g_config_byte

Base address: 0x40006400 (CAN0)

OffsetRegisterDescription
+0x000CAN_CTLControl register
+0x004CAN_STATStatus register
+0x008CAN_TSTATTransmit status
+0x00CCAN_RFIFO0RX FIFO 0 status
+0x010CAN_RFIFO1RX FIFO 1 status
+0x180CAN_TMI0TX Mailbox 0 Identifier
+0x184CAN_TMP0TX Mailbox 0 Properties
+0x188CAN_TMDATA0_0TX Mailbox 0 Data Low
+0x18CCAN_TMDATA0_1TX Mailbox 0 Data High
+0x1B0CAN_RFIFOMI0RX FIFO 0 Identifier
+0x1B4CAN_RFIFOMP0RX FIFO 0 Properties
+0x1B8CAN_RFIFOMDATA0_0RX FIFO 0 Data Low
+0x1BCCAN_RFIFOMDATA0_1RX FIFO 0 Data High

AddressFunctionDescription
0x080043C4CAN_InitInitialize CAN peripheral with filters
0x08004544CAN_ResetClockReset CAN peripheral clock
0x08004570CAN_FilterInitConfigure CAN acceptance filters
0x0800463CCAN_SetInterruptBitsEnable CAN interrupts
0x08004734CAN_ReceiveRead message from RX FIFO
0x080047C4CAN_InitStructInitialize TX message struct
0x080047E4CAN_TransmitSend message to TX mailbox
0x0800795CCAN_TX_0x222_MotorTelemetrySend motor telemetry
0x08007AFCCAN_PeriodicTX_HandlerPeriodic TX scheduler (10ms)
0x08007B68CAN_TX_0x64A_StatusSend status message
0x08007B98CAN_SendMsg0x200Send throttle echo
0x08007C04CAN_TX_0x201_SpeedSend speed telemetry
0x08007CA4CAN_TX_0x202_TempParamsSend temp/params
0x08007CF0CAN_TX_0x203_PowerSend power telemetry
0x08007D68CAN_SendMsg0x204Send temperature/mode
0x08007DB4CAN_SendMsg0x265Send error data
0x08007DECCAN_TX_0x266_InitSend heartbeat
0x08009088CAN0_RX0_IRQHandlerCAN receive interrupt
0x08009110CAN_SendMessageHigh-level send wrapper
0x08009168CAN_ProcessRxMessageCheck and dispatch RX message
0x08009178CAN_MessageDispatcherMain message handler (switch)

IDNameRateDLCDescription
0x200Throttle EchoOn-demand8Echo throttle/cadence data
0x201Speed50 Hz5Speed and status
0x202Temp/Params~3 Hz8Temperature and EEPROM params
0x203Power100 Hz8RPM and power consumption
0x204Temp/ModeOn-demand8Temperature and motor mode
0x210Serial RespOn-request8Serial number chars 0-5
0x211Odo RespOn-request8Odometer digits
0x212Device IDOn-request4Device ID bytes
0x222Motor TelemOn-demand8Multi-mode telemetry
0x265Error DataOn-demand8Error/diagnostic info
0x266Heartbeat~1.7 Hz8Init/heartbeat message
0x5AADiag RespOn-request8UDS-like response
0x64AStatus50 Hz8Status flags
IDNameDescription
0x210Serial ReqRequest serial number
0x211Odo ReqRequest odometer
0x212Device ReqRequest device ID
0x300Motor CmdMotor control command
0x400TimeoutTimeout counter control
0x401BatteryBattery voltage/current
0x402ThrottleThrottle and cadence
0x403ModeMode byte storage
0x404TorqueTorque sensor config
0x610Diag ReqDiagnostic request
0x62ADiag ReqDiagnostic request (alt)
0x67FResetSystem reset trigger
0x702Diag ReqDiagnostic request (alt)
0x777Wheel ConfigWheel circumference

The CAN RX buffer (g_can_rx_buffer @ 0x200004A8) stores received messages:

OffsetFieldSizeDescription
+0x00msg_id2Message ID
+0x02byte[2]1Data byte 2
+0x03byte[3]1Data byte 3
+0x04byte[4]2Data bytes 4-5
+0x06byte[6]1Data byte 6
+0x07byte[7]1Data byte 7
+0x08byte[8]4Data bytes 8-11
+0x0Bbyte[11]1Data byte 11 (tail)

Note: Buffer layout matches the dispatcher’s access patterns.