STM32 电机驱动代码级详细讲解 目录 共同点分析 DJI-Motor 驱动详解 Damiao-Motor 驱动详解 Step-Motor 驱动详解 VESC Motor 驱动详解 Unitree Motor 驱动详解 一、共同点分析 1.1 统一设计模式 所有驱动都遵循相同的设计模式:
初始化/反初始化模式
// 统一的API设计
uint8_t xxx_motor_init ( xxx_motor_handle_t * motor , ...);
uint8_t xxx_motor_deinit ( xxx_motor_handle_t * motor );
句柄结构体 每个驱动都有 *_handle_t 结构体,用于存储: - 电机ID - 通信接口选择(CAN1/CAN2) - 电机状态(位置、速度、扭矩、温度等) - 控制参数
1.2 通信接口分类 驱动 通信方式 特点 DJI-Motor CAN总线 标准帧ID,支持多电机广播 Damiao-Motor CAN总线 标准帧ID,三种控制模式 VESC CAN总线 扩展帧ID,指令丰富 Step-Motor GPIO+PWM 无反馈,开环控制 Unitree Motor RS485/UART 需要收发切换,CRC校验
1.3 CAN驱动统一回调机制 DJI、Damiao、VESC 都使用 can_list 统一管理CAN接收:
// 统一的回调函数原型
static void can_callback ( void * node_obj , can_rx_header_t * can_rx_header ,
uint8_t * can_msg );
// 初始化时注册回调
can_list_add_new_node ( can_select , ( void * ) motor , can_id , mask ,
CAN_ID_STD / EXT , can_callback );
优点: - 自动分发CAN消息到对应的电机句柄 - 支持ID过滤 - 统一管理多个电机
二、DJI-Motor 驱动详解 2.1 概述 支持三种电机型号: - M3508 : 19:1减速比,电流控制 - M2006 : 36:1减速比,电流控制 - GM6020 : 1:1减速比,电压/电流控制
2.2 核心数据结构 typedef struct {
// M3508/2006 专用
float real_current ; // 实际电流
int16_t given_current ; // 期望电流(仅M3508)
// GM6020 专用
int16_t torque_current ; // 转矩电流
uint8_t temperature ; // 温度
// 共用参数
uint16_t angle ; // 绝对角度(0-8192)
uint16_t last_angle ; // 上次角度
uint16_t offset_angle ; // 上电初始角度
bool got_offset ; // 是否已获取偏移
int32_t total_angle ; // 总角度(上电后为0)
int32_t round_cnt ; // 圈数计数
float rotor_degree ; // 转子角度(度)
int16_t speed_rpm ; // 速度(RPM)
uint8_t hall ; // 霍尔传感器值
dji_motor_model_t motor_model ;
dji_can_id_t motor_id ;
can_selected_t can_select ;
} dji_motor_handle_t ;
2.3 CAN回调函数详解(dji_bldc_motor.c:20-115) 关键代码分析:
static void can_callback ( void * node_obj , can_rx_header_t * can_rx_header ,
uint8_t * can_msg ) {
dji_motor_handle_t * motor_point = ( dji_motor_handle_t * ) node_obj ;
// 1. 角度解析(16位,高字节在前)
motor_point -> angle = ( uint16_t )(( can_msg [ 0 ] << 8 ) | can_msg [ 1 ]);
// 2. 首次获取角度偏移
if ( ! ( motor_point -> got_offset )) {
motor_point -> offset_angle = motor_point -> angle ;
motor_point -> last_angle = motor_point -> angle ;
motor_point -> got_offset = true ;
motor_point -> round_cnt = 0 ;
}
// 3. 根据电机型号解析不同数据
switch ( motor_point -> motor_model ) {
case DJI_M3508 :
// 速度(字节2-3)
motor_point -> speed_rpm = ( int16_t )(( can_msg [ 2 ] << 8 ) | can_msg [ 3 ]);
// 期望电流(字节4-5),负号是因为方向定义
motor_point -> given_current = ( int16_t )(( can_msg [ 4 ] << 8 ) | can_msg [ 5 ]) / -5.0f ;
break ;
case DJI_M2006 :
motor_point -> speed_rpm = ( int16_t )(( can_msg [ 2 ] << 8 ) | can_msg [ 3 ]);
// 实际电流转换:原始值 * 5.0 / 16384
motor_point -> real_current = ( float )(( can_msg [ 4 ] << 8 ) | can_msg [ 5 ]) * 5.0f / 16384.0f ;
break ;
case DJI_GM6020 :
motor_point -> speed_rpm = ( int16_t )(( can_msg [ 2 ] << 8 ) | can_msg [ 3 ]);
motor_point -> torque_current = ( int16_t )(( can_msg [ 4 ] << 8 ) | can_msg [ 5 ]);
motor_point -> temperature = can_msg [ 6 ];
break ;
}
// 4. 圈数计数(关键算法)
if ( motor_point -> angle - motor_point -> last_angle > 4096 ) {
-- ( motor_point -> round_cnt ); // 反向过零点
} else if ( motor_point -> angle - motor_point -> last_angle < -4096 ) {
++ ( motor_point -> round_cnt ); // 正向过零点
}
// 5. 计算总角度
motor_point -> total_angle = motor_point -> round_cnt * 4096 * 2 +
motor_point -> angle - motor_point -> offset_angle ;
// 6. 计算转子角度(考虑减速比)
switch ( motor_point -> motor_model ) {
case DJI_M3508 : // 19:1减速比
motor_point -> rotor_degree = ( float )( motor_point -> total_angle ) / ( 19.0f * 8192.0f ) * 360.0f ;
break ;
case DJI_M2006 : // 36:1减速比
motor_point -> rotor_degree = ( float )( motor_point -> total_angle ) / ( 36.0f * 8192.0f ) * 360.0f ;
break ;
case DJI_GM6020 : // 1:1减速比,绝对位置
motor_point -> rotor_degree = ( float )( motor_point -> angle ) / 22.75f ; // 8192/360 = 22.75
break ;
}
}
圈数计数算法详解:
角度传感器分辨率:8192(0-8192),对应360度。
当电机连续旋转时,角度会在0和8192之间跳变: - 正向旋转:8190 → 2(减少4088,但实际是正向过零) - 反向旋转:2 → 8190(增加4088,但实际是反向过零)
检测阈值:4096(8192/2)
// 示例:正向旋转
last_angle = 8190
angle = 2
angle - last_angle = 2 - 8190 = -8188 < -4096
→ round_cnt ++ // 正向增加一圈
// 示例:反向旋转
last_angle = 2
angle = 8190
angle - last_angle = 8190 - 2 = 8188 > 4096
→ round_cnt -- // 反向减少一圈
2.4 电机控制函数 M3508/2006 电流控制:
void dji_motor_set_current ( can_selected_t can_select , uint16_t can_identify ,
int16_t iq1 , int16_t iq2 , int16_t iq3 , int16_t iq4 ) {
uint8_t send_msg [ 8 ];
// 4个电机,每个2字节电流值
send_msg [ 0 ] = ( iq1 >> 8 ) & 0xFF ;
send_msg [ 1 ] = iq1 & 0xFF ;
send_msg [ 2 ] = ( iq2 >> 8 ) & 0xFF ;
send_msg [ 3 ] = iq2 & 0xFF ;
send_msg [ 4 ] = ( iq3 >> 8 ) & 0xFF ;
send_msg [ 5 ] = iq3 & 0xFF ;
send_msg [ 6 ] = ( iq4 >> 8 ) & 0xFF ;
send_msg [ 7 ] = iq4 & 0xFF ;
can_send_message ( can_select , CAN_ID_STD , can_identify , 8 , send_msg );
}
CAN ID使用: - DJI_MOTOR_GROUP1 (0x200): 控制电机1-4 - DJI_MOTOR_GROUP2 (0x1FF): 控制电机5-8
2.5 使用示例 // 定义电机句柄
dji_motor_handle_t m3508_motors [ 4 ];
// 初始化
for ( int i = 0 ; i < 4 ; i ++ ) {
dji_motor_init ( & m3508_motors [ i ], DJI_M3508 ,
CAN_Motor1_ID + i , can1_selected );
}
// 控制电流
dji_motor_set_current ( can1_selected , DJI_MOTOR_GROUP1 ,
1000 , 0 , 0 , 0 ); // 电机1设置1000mA
// 读取状态
float speed = m3508_motors [ 0 ]. speed_rpm ;
float angle = m3508_motors [ 0 ]. rotor_degree ;
2.6 难点总结 角度过零点检测 :圈数计数算法是核心 减速比处理 :不同型号需要不同的角度转换 相对位置vs绝对位置 :M3508/2006是相对位置(上电为0),GM6020是绝对位置 CAN ID管理 :标准帧ID,注意group和motor_id的关系 三、Damiao-Motor 驱动详解 3.1 概述 达妙电机驱动,支持多种型号: - J3507, J4310, J4340, J6006, J8006, J8009, J10010 - S3519, H6215, G6220
三种控制模式: 1. MIT模式 :位置+速度+扭矩+PD控制 2. 位置速度模式 :位置+速度控制 3. 速度模式 :仅速度控制
3.2 核心数据结构 typedef struct {
uint32_t master_id ; // 反馈主机ID(接收时匹配)
uint32_t device_id ; // 控制设备ID(发送时使用)
dm_model_t model ; // 电机型号
dm_mode_t mode ; // 当前模式
// 反馈数据
float position ; // 位置(rad)
float speed ; // 速度(rad/s)
float torque ; // 扭矩(N·m)
float mos_temperature ; // MOS温度(°C)
float motor_temperature ; // 电机温度(°C)
dm_error_t error ; // 错误信息
// 控制参数(需与上位机设定一致)
float pos_limit ; // 位置限制(rad)
float spd_limit ; // 速度限制(rad/s)
float torq_limit ; // 扭矩限制(N·m)
can_selected_t can_select ;
} dm_handle_t ;
3.3 错误码定义 typedef enum {
DM_OK_DISABLED = 0x00U , // 无故障,失能状态
DM_OK_ENABLED , // 无故障,使能状态
DM_ERR_OVER_VOLTAGE = 0x08U , // 过压
DM_ERR_UNDER_VOLTAGE , // 欠压
DM_ERR_OVER_CURRENT , // 过流
DM_ERR_MOS_TEMPERATURE , // MOS过温
DM_ERR_MOTOR_TEMPERATURE , // 电机过温
DM_ERR_LOST_COMMUNICATION , // 通信丢失
DM_ERR_OVER_LOAD // 过载
} dm_error_t ;
3.4 CAN回调函数详解(damiao.c:27-54) static void can_callback ( void * node_obj , can_rx_header_t * can_rx_header ,
uint8_t * can_msg ) {
dm_handle_t * motor = ( dm_handle_t * ) node_obj ;
// 检查ID匹配
if ( can_rx_header -> id != motor -> master_id ) {
return ;
}
// 解析字节0:低4位=device_id,高4位=error
motor -> device_id = can_msg [ 0 ] & 0x0F ;
motor -> error = ( can_msg [ 0 ] >> 4 ) & 0xF ;
// 解析位置(字节1-2,16位)
int temp = ( can_msg [ 1 ] << 8 ) | can_msg [ 2 ];
motor -> position = uint_to_float ( temp , - motor -> pos_limit ,
motor -> pos_limit , 16 );
// 解析速度(字节3高4位+字节4,12位)
temp = ( can_msg [ 3 ] << 4 ) | ( can_msg [ 4 ] >> 4 );
motor -> speed = uint_to_float ( temp , - motor -> spd_limit ,
motor -> spd_limit , 12 );
// 解析扭矩(字节4低4位+字节5,12位)
temp = (( can_msg [ 4 ] & 0x0F ) << 8 ) | can_msg [ 5 ];
motor -> torque = uint_to_float ( temp , - motor -> torq_limit ,
motor -> torq_limit , 12 );
// 温度(字节6-7)
motor -> mos_temperature = ( float ) can_msg [ 6 ];
motor -> motor_temperature = ( float ) can_msg [ 7 ];
}
数据编码格式:
达妙电机使用位域压缩数据,8字节传输5个浮点数:
字节0: [error(4bit)][device_id(4bit)]
字节1-2: position (16位)
字节3-4: [speed(12位)][torque(12位)]
字节6: mos_temperature (8位)
字节7: motor_temperature (8位)
3.5 编码/解码函数(使用buffer_append库) // uint转float(解码接收数据)
float uint_to_float ( int x_int , float x_min , float x_max , int bits ) {
float span = x_max - x_min ;
float offset = x_min ;
return (( float ) x_int ) * span / (( float )(( 1 << bits ) - 1 )) + offset ;
}
// float转uint(编码发送数据)
int float_to_uint ( float x , float x_min , float x_max , int bits ) {
float span = x_max - x_min ;
float offset = x_min ;
return ( int )(( x - offset ) * (( float )(( 1 << bits ) - 1 )) / span );
}
示例:位置编码
位置限制:-π ~ π
实际位置:1.57 rad (π/2)
bits: 16
编码结果:
uint = (1.57 - (-π)) * (65535 / (π - (-π)))
= 4.7124 * 65535 / 6.2832
= 49152
3.6 MIT控制模式(核心) MIT模式是达妙电机的特色,允许同时控制位置、速度和扭矩:
void dm_mit_ctrl ( dm_handle_t * motor , float position , float speed ,
float kp , float kd , float torque ) {
uint8_t send_msg [ 8 ];
uint16_t pos_tmp , spd_tmp , kp_tmp , kd_tmp , torq_tmp ;
// 编码各个参数
pos_tmp = float_to_uint ( position , - motor -> pos_limit ,
motor -> pos_limit , 16 );
spd_tmp = float_to_uint ( speed , - motor -> spd_limit ,
motor -> spd_limit , 12 );
kp_tmp = float_to_uint ( kp , DM_KP_MIN , DM_KP_MAX , 12 ); // 0-500
kd_tmp = float_to_uint ( kd , DM_KD_MIN , DM_KD_MAX , 12 ); // 0-5
torq_tmp = float_to_uint ( torque , - motor -> torq_limit ,
motor -> torq_limit , 12 );
// 位域打包(关键)
send_msg [ 0 ] = ( pos_tmp >> 8 ); // 位置高8位
send_msg [ 1 ] = pos_tmp ; // 位置低8位
send_msg [ 2 ] = ( spd_tmp >> 4 ); // 速度高8位
send_msg [ 3 ] = (( spd_tmp & 0xF ) << 4 ) | ( kp_tmp >> 8 ); // 速度低4位+KP高8位
send_msg [ 4 ] = kp_tmp ; // KP低8位
send_msg [ 5 ] = ( kd_tmp >> 4 ); // KD高8位
send_msg [ 6 ] = (( kd_tmp & 0xF ) << 4 ) | ( torq_tmp >> 8 ); // KD低4位+扭矩高8位
send_msg [ 7 ] = torq_tmp ; // 扭矩低8位
can_send_message ( motor -> can_select , CAN_ID_STD ,
motor -> device_id + MIT_MODE , 8 , send_msg );
}
MIT控制原理:
输出扭矩 = Torque_cmd + Kp × (Pos_des - Pos) + Kd × (Spd_des - Spd)
Pos_des, Spd_des, Torque_cmd: 期望值 Pos, Spd: 实际反馈值 Kp, Kd: PD控制参数 这种控制方式结合了: - 位置控制(Kp) - 速度控制(Kd) - 前馈扭矩控制
3.7 位置速度模式 void dm_pos_speed_ctrl ( dm_handle_t * motor , float position , float speed ) {
uint8_t send_msg [ 8 ];
// 直接使用IEEE754浮点格式(每个4字节)
memcpy ( & send_msg [ 0 ], & position , sizeof ( float ));
memcpy ( & send_msg [ 4 ], & speed , sizeof ( float ));
can_send_message ( motor -> can_select , CAN_ID_STD ,
motor -> device_id + POS_SPEED_MODE , 8 , send_msg );
}
3.8 速度模式 void dm_speed_ctrl ( dm_handle_t * motor , float speed ) {
uint8_t send_msg [ 4 ];
memcpy ( send_msg , & speed , sizeof ( float ));
can_send_message ( motor -> can_select , CAN_ID_STD ,
motor -> device_id + SPEED_MODE , 4 , send_msg );
}
3.9 电机使能/失能 // 使能
void dm_motor_enable ( dm_handle_t * motor ) {
uint8_t send_msg [ 8 ] = { 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFC };
uint32_t id = motor -> device_id ;
switch ( motor -> mode ) {
case DM_MODE_MIT : id += MIT_MODE ; break ;
case DM_MODE_POS_SPEED : id += POS_SPEED_MODE ; break ;
case DM_MODE_SPEED : id += SPEED_MODE ; break ;
}
can_send_message ( motor -> can_select , CAN_ID_STD , id , 8 , send_msg );
}
// 失能
void dm_motor_disable ( dm_handle_t * motor ) {
uint8_t send_msg [ 8 ] = { 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFD };
// 其余代码与enable相同
}
CAN ID格式: - MIT模式:device_id + 0x000 - 位置速度模式:device_id + 0x100 - 速度模式:device_id + 0x200
3.10 特殊功能 保存当前位置为零点:
void dm_save_zero ( dm_handle_t * motor ) {
uint8_t send_msg [ 8 ] = { 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFE };
// 发送0xFE命令
}
清除错误:
void dm_clear_error ( dm_handle_t * motor ) {
uint8_t send_msg [ 8 ] = { 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFB };
// 发送0xFB命令
}
3.11 使用示例 // 定义电机
dm_handle_t dm_motor ;
// 初始化
dm_motor_init ( & dm_motor , 0x01 , 0x01 , DM_MODE_MIT , DM_J4310 ,
3.14159f , 30.0f , 18.0f , can1_selected );
// 使能
dm_motor_enable ( & dm_motor );
// MIT控制
dm_mit_ctrl ( & dm_motor , 1.57f , 0.0f , 100.0f , 1.0f , 0.5f );
// 位置: 1.57rad (90度)
// 速度: 0.0 rad/s
// Kp: 100
// Kd: 1
// 前馈扭矩: 0.5 N·m
// 读取反馈
float pos = dm_motor . position ;
float spd = dm_motor . speed ;
float torque = dm_motor . torque ;
3.12 难点总结 位域数据打包 :MIT模式将5个参数压缩到8字节 参数限制匹配 :发送和接收的limit必须一致 三种模式切换 :不同的CAN ID偏移 MIT控制原理 :PD+前馈扭矩的组合控制 四、Step-Motor 驱动详解 4.1 概述 步进电机驱动是最简单的驱动,特点: - 开环控制 :无位置反馈 - 脉冲控制 :每个脉冲走一步 - 三线驱动 :EN(使能)、DIR(方向)、STEP(脉冲)
4.2 硬件连接 STM32 ──┬── EN ──> 步进电机驱动器
├── DIR
└── STEP (TIM PWM)
4.3 核心数据结构 typedef struct {
step_motor_state_t state ; // 状态(停止/运行)
// GPIO定义
step_motor_gpio_t en_pin ; // EN引脚
step_motor_gpio_t dir_pin ; // DIR引脚
step_motor_gpio_t step_pin ; // STEP引脚
// PWM配置
TIM_HandleTypeDef * htim ; // 定时器句柄
uint32_t channel ; // 定时器通道
uint32_t pulse_remain ; // 剩余脉冲数
step_motor_dir_t dir ; // 当前方向
} step_motor_handle_t ;
typedef struct {
GPIO_TypeDef * port ;
uint32_t pin ;
} step_motor_gpio_t ;
typedef enum {
STEP_MOTOR_STATE_RESET , // 未初始化
STEP_MOTOR_STATE_STOP , // 停止
STEP_MOTOR_STATE_RUN // 运行
} step_motor_state_t ;
typedef enum {
STEP_MOTOR_AWAY , // 远离电机方向
STEP_MOTOR_TOWARDS // 朝着电机方向
} step_motor_dir_t ;
4.4 初始化详解(step_motor.c:19-59) void step_motor_init ( step_motor_handle_t * handle ) {
GPIO_InitTypeDef gpio_init_struct = {
. Mode = GPIO_MODE_OUTPUT_PP ,
. Pull = GPIO_PULLUP ,
. Speed = GPIO_SPEED_FREQ_HIGH
};
TIM_OC_InitTypeDef tim_pwm_config = { 0 };
// 1. 配置EN引脚(推挽输出)
gpio_init_struct . Pin = handle -> en_pin . pin ;
HAL_GPIO_Init ( handle -> en_pin . port , & gpio_init_struct );
// 2. 配置DIR引脚(推挽输出)
gpio_init_struct . Pin = handle -> dir_pin . pin ;
HAL_GPIO_Init ( handle -> dir_pin . port , & gpio_init_struct );
// 3. 配置STEP引脚(复用推挽输出,连接定时器PWM)
gpio_init_struct . Mode = GPIO_MODE_AF_PP ;
gpio_init_struct . Pin = handle -> step_pin . pin ;
HAL_GPIO_Init ( handle -> step_pin . port , & gpio_init_struct );
// 4. 配置定时器基础参数
handle -> htim -> Init . Prescaler = 72 - 1 ; // 72MHz / 72 = 1MHz
handle -> htim -> Init . Period = STEP_MOTOR_INIT_PERIOD - 1 ; // 1800 - 1
handle -> htim -> Init . ClockDivision = TIM_CLOCKDIVISION_DIV1 ;
handle -> htim -> Init . CounterMode = TIM_COUNTERMODE_UP ;
HAL_TIM_PWM_Init ( handle -> htim );
// 5. 配置PWM通道
tim_pwm_config . OCMode = TIM_OCMODE_PWM1 ; // PWM模式1
tim_pwm_config . OCPolarity = TIM_OCPOLARITY_HIGH ; // 高电平有效
tim_pwm_config . Pulse = ( STEP_MOTOR_INIT_PERIOD / 2 ) - 1 ; // 50%占空比
tim_pwm_config . OCFastMode = TIM_OCFAST_DISABLE ;
tim_pwm_config . OCIdleState = TIM_OCIDLESTATE_RESET ;
HAL_TIM_PWM_ConfigChannel ( handle -> htim , & tim_pwm_config , handle -> channel );
// 6. 初始化状态
handle -> pulse_remain = 0 ;
handle -> state = STEP_MOTOR_STATE_STOP ;
step_motor_disable ( handle ); // 默认失能
}
定时器频率计算:
Prescaler = 72 - 1 = 71
Period = 1800 - 1 = 1799
定时器频率 = 72MHz / (71+1) / (1799+1) = 72MHz / 72 / 1800 = 555.5 Hz
PWM周期 = 1 / 555.5 = 1.8ms = 1800μs
4.5 使能/失能控制 void step_motor_enable ( step_motor_handle_t * handle ) {
// 低电平使能(根据驱动器规格)
HAL_GPIO_WritePin ( handle -> en_pin . port , handle -> en_pin . pin , GPIO_PIN_RESET );
}
void step_motor_disable ( step_motor_handle_t * handle ) {
// 高电平失能(释放电机,可以自由转动)
HAL_GPIO_WritePin ( handle -> en_pin . port , handle -> en_pin . pin , GPIO_PIN_SET );
}
4.6 方向控制 void step_motor_set_dir ( step_motor_handle_t * handle , step_motor_dir_t dir ) {
switch ( dir ) {
case STEP_MOTOR_TOWARDS : // 朝着电机方向
HAL_GPIO_WritePin ( handle -> dir_pin . port , handle -> dir_pin . pin ,
( GPIO_PinState ) dir ); // dir = 1
break ;
case STEP_MOTOR_AWAY : // 远离电机方向
HAL_GPIO_WritePin ( handle -> dir_pin . port , handle -> dir_pin . pin ,
( GPIO_PinState ) dir ); // dir = 0
break ;
}
handle -> dir = dir ;
}
4.7 速度控制 void step_mtoor_set_speed ( step_motor_handle_t * handle , uint16_t period ) {
if ( handle -> state == STEP_MOTOR_STATE_RUN ) {
return ; // 运行中不能修改速度
}
// 修改ARR寄存器改变PWM周期
handle -> htim -> Instance -> ARR = period - 1 ;
// 修改比较值保持50%占空比
__HAL_TIM_SET_COMPARE ( handle -> htim , handle -> channel , period / 2 );
}
速度计算:
PWM频率 = 72MHz / 72 / period = 1MHz / period (Hz)
例如:
period = 1800 → 频率 = 555.5 Hz → 速度 = 555.5 步/秒
period = 900 → 频率 = 1111 Hz → 速度 = 1111 步/秒
4.8 运动控制(step_motor.c:154-173) void step_motor_run ( step_motor_handle_t * handle , int32_t pulse_num ) {
if ( pulse_num == 0 ) {
return ; // 不需要运动
}
// 1. 使能电机
step_motor_enable ( handle );
// 2. 设置方向
if ( pulse_num > 0 ) {
step_motor_set_dir ( handle , STEP_MOTOR_TOWARDS );
} else {
step_motor_set_dir ( handle , STEP_MOTOR_AWAY );
}
// 3. 设置剩余脉冲数
handle -> pulse_remain = abs ( pulse_num );
handle -> state = STEP_MOTOR_STATE_RUN ;
// 4. 启动PWM中断
HAL_TIM_PWM_Start_IT ( handle -> htim , handle -> channel );
}
4.9 中断回调(step_motor.c:180-192) void step_motor_interrupt_callback ( step_motor_handle_t * handle ) {
// 每个PWM周期调用一次(一个脉冲)
if ( handle -> pulse_remain == 0 ) {
// 脉冲发送完毕
HAL_TIM_PWM_Stop_IT ( handle -> htim , handle -> channel );
handle -> state = STEP_MOTOR_STATE_STOP ;
step_motor_disable ( handle ); // 失能电机
return ;
}
// 减少剩余脉冲数
-- handle -> pulse_remain ;
}
中断处理流程:
PWM周期触发中断
↓
检查剩余脉冲数
↓
├─ pulse_remain = 0 → 停止PWM,失能电机
└─ pulse_remain > 0 → pulse_remain--,继续
4.10 使用示例 // 定义步进电机
step_motor_handle_t step_motor ;
// 配置GPIO
step_motor . en_pin . port = GPIOA ;
step_motor . en_pin . pin = GPIO_PIN_0 ;
step_motor . dir_pin . port = GPIOA ;
step_motor . dir_pin . pin = GPIO_PIN_1 ;
step_motor . step_pin . port = GPIOA ;
step_motor . step_pin . pin = GPIO_PIN_2 ;
// 配置定时器(TIM3 CH1)
step_motor . htim = & htim3 ;
step_motor . channel = TIM_CHANNEL_1 ;
// 初始化
step_motor_init ( & step_motor );
// 设置速度(period = 900,约1111步/秒)
step_mtoor_set_speed ( & step_motor , 900 );
// 运动400个脉冲(一圈,假设一圈400脉冲)
step_motor_run ( & step_motor , 400 );
// 运动-400个脉冲(反向一圈)
step_motor_run ( & step_motor , -400 );
4.11 宏定义 // 运动一圈需要的脉冲数(根据电机规格修改)
#define STEP_MOTOR_CIRCLE_PULSE 400
// 初始PWM周期(越大频率越低,速度越慢)
#define STEP_MOTOR_INIT_PERIOD 1800
4.12 难点总结 无反馈 :无法知道实际位置,可能丢步 定时器配置 :Prescaler、Period、Pulse的关系 PWM中断 :每个脉冲触发一次中断 方向控制 :DIR引脚电平决定方向 速度与精度的权衡 :速度过高可能导致丢步 五、VESC Motor 驱动详解 5.1 概述 VESC(Vedder Electronic Speed Controller)是开源电调,特点: - 丰富的控制指令 :占空比、电流、转速、位置等 - 扩展CAN ID :29位CAN ID - 详细的状态反馈 :5种状态数据包 - 电流限制 :支持绝对和相对电流限制
5.2 核心数据结构 typedef struct {
uint8_t vesc_id ; // 电机ID (0-255)
can_selected_t can_select ;
// 控制输入(通过指令设置)
// 状态反馈
float input_voltage ; // 输入电压 (V)
float duty ; // MOSFET占空比 (0-1)
float erpm ; // 电气转速 (ERPM)
float amp_hours ; // 电流时间 (Ah)
float amp_hours_charged ; // 充电电流时间 (Ah)
float watt_hours ; // 功率时间 (Wh)
float watt_hours_charged ; // 充电功率时间 (Wh)
float motor_current ; // 电机电流 (A)
float total_current ; // 总电流 (A)
float mosfet_temperature ; // MOS温度 (°C)
float motor_temperature ; // 电机温度 (°C)
float pid_pos ; // 转子位置 (rad)
int32_t tachometer_value ; // 转速表
vesc_fault_code_t error_code ; // 错误码
} vesc_motor_handle_t ;
5.3 CAN指令集(vesc_motor.c:27-56) typedef enum {
CAN_PACKET_SET_DUTY = 0 , // 设置占空比
CAN_PACKET_SET_CURRENT , // 设置电流
CAN_PACKET_SET_CURRENT_BRAKE , // 设置制动电流
CAN_PACKET_SET_RPM , // 设置转速
CAN_PACKET_SET_POS , // 设置位置
CAN_PACKET_FILL_RX_BUFFER , // 填充接收缓冲区
CAN_PACKET_FILL_RX_BUFFER_LONG , // 填充长接收缓冲区
CAN_PACKET_PROCESS_RX_BUFFER , // 处理接收缓冲区
CAN_PACKET_PROCESS_SHORT_BUFFER , // 处理短缓冲区
CAN_PACKET_STATUS , // 状态1
CAN_PACKET_SET_CURRENT_REL , // 设置相对电流
CAN_PACKET_SET_CURRENT_BRAKE_REL , // 设置相对制动电流
CAN_PACKET_SET_CURRENT_HANDBRAKE , // 设置手刹电流
CAN_PACKET_SET_CURRENT_HANDBRAKE_REL , // 设置相对手刹电流
CAN_PACKET_STATUS_2 , // 状态2
CAN_PACKET_STATUS_3 , // 状态3
CAN_PACKET_STATUS_4 , // 状态4
CAN_PACKET_PING , // 心跳
CAN_PACKET_PONG , // 心跳响应
CAN_PACKET_DETECT_APPLY_ALL_FOC , // FOC检测
CAN_PACKET_DETECT_APPLY_ALL_FOC_RES , // FOC检测结果
CAN_PACKET_CONF_CURRENT_LIMITS , // 配置电流限制
CAN_PACKET_CONF_STORE_CURRENT_LIMITS , // 存储电流限制
CAN_PACKET_CONF_CURRENT_LIMITS_IN , // 配置输入电流限制
CAN_PACKET_CONF_STORE_CURRENT_LIMITS_IN , // 存储输入电流限制
CAN_PACKET_CONF_FOC_ERPMS , // 配置FOC ERPM
CAN_PACKET_CONF_STORE_FOC_ERPMS , // 存储FOC ERPM
CAN_PACKET_STATUS_5 // 状态5
} can_packet_id_t ;
5.4 CAN ID格式 VESC使用29位扩展CAN ID:
位0-7: VESC设备ID (0-255)
位8-15: 指令类型(can_packet_id_t)
位16-28: 保留
示例:
设备ID = 1
指令 = CAN_PACKET_SET_DUTY (0)
CAN ID = 0x00000100
= (1 << 0) | (0 << 8)
= 0x00000100
5.5 状态数据包详解(vesc_motor.c:65-118) void vesc_can_callback ( void * can_ptr , can_rx_header_t * can_rx_header ,
uint8_t * recv_msg ) {
uint32_t can_id = can_rx_header -> id ;
vesc_motor_handle_t * vesc_motor = ( vesc_motor_handle_t * ) can_ptr ;
// 提取指令类型(位8-15)
int32_t message_status = ( can_id >> 8 ) & 0xFF ;
int32_t buffer_index = 0 ;
switch ( message_status ) {
case CAN_PACKET_STATUS : {
// 状态1:转速、电流、占空比
vesc_motor -> erpm = buffer_get_float32 ( recv_msg , 1.0f , & buffer_index );
vesc_motor -> total_current = buffer_get_float16 ( recv_msg , 10.0f , & buffer_index );
vesc_motor -> duty = buffer_get_float16 ( recv_msg , 1000.0f , & buffer_index );
} break ;
case CAN_PACKET_STATUS_2 : {
// 状态2:电流时间
vesc_motor -> amp_hours = buffer_get_float32 ( recv_msg , 10000.0f , & buffer_index );
vesc_motor -> amp_hours_charged = buffer_get_float32 ( recv_msg , 10000.0f , & buffer_index );
} break ;
case CAN_PACKET_STATUS_3 : {
// 状态3:功率时间
vesc_motor -> watt_hours = buffer_get_float32 ( recv_msg , 10000.0f , & buffer_index );
vesc_motor -> watt_hours_charged = buffer_get_float32 ( recv_msg , 10000.0f , & buffer_index );
} break ;
case CAN_PACKET_STATUS_4 : {
// 状态4:温度、电流、位置
vesc_motor -> mosfet_temperature = buffer_get_float32 ( recv_msg , 10.0f , & buffer_index );
vesc_motor -> motor_current = buffer_get_float32 ( recv_msg , 10.0f , & buffer_index );
vesc_motor -> total_current = buffer_get_float32 ( recv_msg , 10.0f , & buffer_index );
vesc_motor -> pid_pos = buffer_get_float32 ( recv_msg , 50.0f , & buffer_index );
} break ;
case CAN_PACKET_STATUS_5 : {
// 状态5:转速表、输入电压
vesc_motor -> tachometer_value = buffer_get_int32 ( recv_msg , & buffer_index );
vesc_motor -> input_voltage = buffer_get_float16 ( recv_msg , 10.0f , & buffer_index );
} break ;
}
}
数据解码函数(buffer_append库):
// 32位浮点数解码
float buffer_get_float32 ( uint8_t * buffer , float scale , int32_t * index ) {
uint32_t res = (( uint32_t ) buffer [ * index ]) |
(( uint32_t ) buffer [ * index + 1 ] << 8 ) |
(( uint32_t ) buffer [ * index + 2 ] << 16 ) |
(( uint32_t ) buffer [ * index + 3 ] << 24 );
* index += 4 ;
return ( * (( float * ) & res )) / scale ;
}
// 16位浮点数解码
float buffer_get_float16 ( uint8_t * buffer , float scale , int32_t * index ) {
uint16_t res = (( uint16_t ) buffer [ * index ]) |
(( uint16_t ) buffer [ * index + 1 ] << 8 );
* index += 2 ;
return (( float ) res ) / scale ;
}
// 32位整数解码
int32_t buffer_get_int32 ( uint8_t * buffer , int32_t * index ) {
int32_t res = (( int32_t ) buffer [ * index ]) |
(( int32_t ) buffer [ * index + 1 ] << 8 ) |
(( int32_t ) buffer [ * index + 2 ] << 16 ) |
(( int32_t ) buffer [ * index + 3 ] << 24 );
* index += 4 ;
return res ;
}
解码示例(STATUS_1):
原始数据: [0x00, 0x00, 0x0A, 0x00, 0x00, 0x64, 0x03, 0xE8]
erpm (字节0-3):
0x00000A00 = 2560
erpm = 2560 / 1.0 = 2560 ERPM
total_current (字节4-5):
0x0064 = 100
total_current = 100 / 10.0 = 10.0 A
duty (字节6-7):
0x03E8 = 1000
duty = 1000 / 1000.0 = 1.0 (100%占空比)
5.6 控制指令详解 5.6.1 设置占空比(vesc_motor.c:172-181) void vesc_motor_set_duty ( vesc_motor_handle_t * motor , float duty ) {
int32_t index = 0 ;
uint8_t buffer [ 4 ];
// 编码32位浮点数,scale=100000
buffer_append_float32 ( buffer , duty , 100000.0f , & index );
// 发送:CAN_ID = vesc_id | (CAN_PACKET_SET_DUTY << 8)
can_send_message ( motor -> can_select , CAN_ID_EXT ,
( motor -> vesc_id | ( CAN_PACKET_SET_DUTY << 8 )), 4 , buffer );
}
编码示例:
duty = 0.5 (50%占空比)
buffer_append_float32编码:
值 = 0.5 * 100000 = 50000
字节: [0x50, 0xC3, 0x00, 0x00]
5.6.2 设置电流(vesc_motor.c:189-199) void vesc_motor_set_current ( vesc_motor_handle_t * motor , float current ) {
int32_t index = 0 ;
uint8_t buffer [ 4 ];
// 编码32位浮点数,scale=1000(单位:毫安)
buffer_append_float32 ( buffer , current , 1000.0f , & index );
can_send_message ( motor -> can_select , CAN_ID_EXT ,
( motor -> vesc_id | ( CAN_PACKET_SET_CURRENT << 8 )), 4 , buffer );
}
编码示例:
current = 10.5 A
buffer_append_float32编码:
值 = 10.5 * 1000 = 10500
字节: [0x34, 0x29, 0x00, 0x00]
5.6.3 设置制动电流(vesc_motor.c:207-217) void vesc_motor_set_break_current ( vesc_motor_handle_t * motor , float current ) {
int32_t index = 0 ;
uint8_t buffer [ 4 ];
buffer_append_float32 ( buffer , current , 1000.0f , & index );
can_send_message ( motor -> can_select , CAN_ID_EXT ,
( motor -> vesc_id | ( CAN_PACKET_SET_CURRENT_BRAKE << 8 )), 4 , buffer );
}
5.6.4 设置转速(vesc_motor.c:225-234) void vesc_motor_set_erpm ( vesc_motor_handle_t * motor , float erpm ) {
int32_t index = 0 ;
uint8_t buffer [ 4 ];
// 编码32位浮点数,scale=1.0(单位:ERPM)
buffer_append_float32 ( buffer , erpm , 1.0f , & index );
can_send_message ( motor -> can_select , CAN_ID_EXT ,
( motor -> vesc_id | ( CAN_PACKET_SET_RPM << 8 )), 4 , buffer );
}
5.6.5 设置位置(vesc_motor.c:242-251) void vesc_motor_set_pos ( vesc_motor_handle_t * motor , float pos ) {
int32_t index = 0 ;
uint8_t buffer [ 4 ];
// 编码32位浮点数,scale=1.0(单位:rad)
buffer_append_float32 ( buffer , pos , 1.0f , & index );
can_send_message ( motor -> can_select , CAN_ID_EXT ,
( motor -> vesc_id | ( CAN_PACKET_SET_POS << 8 )), 4 , buffer );
}
5.6.6 设置相对电流(vesc_motor.c:259-270) void vesc_motor_set_relative_current ( vesc_motor_handle_t * motor ,
float current ) {
int32_t index = 0 ;
uint8_t buffer [ 4 ];
// 编码32位浮点数,scale=100000(范围:-1.0 ~ 1.0)
buffer_append_float32 ( buffer , current , 100000.0f , & index );
can_send_message ( motor -> can_select , CAN_ID_EXT ,
( motor -> vesc_id | ( CAN_PACKET_SET_CURRENT_REL << 8 )), 4 , buffer );
}
5.6.7 设置电流限制(vesc_motor.c:300-320) void vesc_motor_set_current_limit ( vesc_motor_handle_t * motor , float min_current ,
float max_current , bool store_to_rom ) {
int32_t index = 0 ;
uint8_t buffer [ 8 ];
// 编码最小电流(scale=1000,单位:毫安)
buffer_append_float32 ( buffer , min_current , 1000.0f , & index );
// 编码最大电流(scale=1000,单位:毫安)
buffer_append_float32 ( buffer , max_current , 1000.0f , & index );
if ( store_to_rom ) {
// 存储到ROM(永久保存)
can_send_message ( motor -> can_select , CAN_ID_EXT ,
( motor -> vesc_id | ( CAN_PACKET_CONF_STORE_CURRENT_LIMITS_IN << 8 )),
8 , buffer );
} else {
// 临时设置(掉电丢失)
can_send_message ( motor -> can_select , CAN_ID_EXT ,
( motor -> vesc_id | ( CAN_PACKET_CONF_CURRENT_LIMITS_IN << 8 )),
8 , buffer );
}
}
5.7 编码函数详解(buffer_append库) // 32位浮点数编码
void buffer_append_float32 ( uint8_t * buffer , float number , float scale , int32_t * index ) {
int32_t value = ( int32_t )( number * scale );
buffer [ * index ] = value ;
buffer [ * index + 1 ] = value >> 8 ;
buffer [ * index + 2 ] = value >> 16 ;
buffer [ * index + 3 ] = value >> 24 ;
* index += 4 ;
}
编码示例:
number = 10.5
scale = 1000.0
value = 10.5 * 1000 = 10500 = 0x00002934
字节布局:
buffer[0] = 0x34
buffer[1] = 0x29
buffer[2] = 0x00
buffer[3] = 0x00
5.8 使用示例 // 定义VESC电机
vesc_motor_handle_t vesc ;
// 初始化(设备ID=1,使用CAN1)
vesc_motor_init ( & vesc , 1 , can1_selected );
// 设置占空比50%
vesc_motor_set_duty ( & vesc , 0.5 );
// 设置电流10.5A
vesc_motor_set_current ( & vesc , 10.5 );
// 设置转速5000 ERPM
vesc_motor_set_erpm ( & vesc , 5000.0 );
// 设置位置1.57 rad (90度)
vesc_motor_set_pos ( & vesc , 1.57 );
// 设置电流限制(最小-20A,最大20A,临时)
vesc_motor_set_current_limit ( & vesc , -20.0 , 20.0 , false );
// 设置电流限制(永久保存到ROM)
vesc_motor_set_current_limit ( & vesc , -20.0 , 20.0 , true );
// 读取状态
float voltage = vesc . input_voltage ;
float current = vesc . motor_current ;
float rpm = vesc . erpm ;
float temp = vesc . mosfet_temperature ;
5.9 难点总结 扩展CAN ID :29位ID的格式和构造 状态数据包 :5种不同的状态包,包含不同信息 数据编码/解码 :scale参数的使用 绝对电流vs相对电流 :不同的单位和范围 临时vs永久存储 :电流限制的保存方式 六、Unitree Motor 驱动详解 6.1 概述 宇树GO-M8010-6关节电机驱动,特点: - RS485通信 :半双工,需要收发切换 - CRC-CCITT校验 :数据完整性校验 - 哈希表管理 :支持多电机管理 - Q格式定点数 :16位和32位定点数转换 - 角度偏移 :上电零点记录
6.2 通信协议 控制数据包格式(unitree_motor.h:68-74):
typedef struct {
uint8_t head [ 2 ]; // 包头:0xFE, 0xEE (2字节)
RIS_Mode_t mode ; // 控制模式 (1字节)
RIS_Comd_t comd ; // 控制参数 (12字节)
uint16_t CRC16 ; // CRC校验 (2字节)
} RIS_ControlData_t ; // 总共17字节
反馈数据包格式(unitree_motor.h:80-86):
typedef struct {
uint8_t head [ 2 ]; // 包头:0xFD, 0xEE (2字节)
RIS_Mode_t mode ; // 控制模式 (1字节)
RIS_Fbk_t fbk ; // 反馈数据 (11字节)
uint16_t CRC16 ; // CRC校验 (2字节)
} RIS_MotorData_t ; // 总共16字节
6.3 核心数据结构 6.3.1 控制模式结构体 typedef struct {
uint8_t id : 4 ; // 电机ID: 0-14, 15为广播
uint8_t status : 3 ; // 工作模式: 0.锁定 1.FOC闭环 2.编码器校准 3.保留
uint8_t reserve : 1 ; // 保留位
} RIS_Mode_t ; // 1字节
位域详解:
字节布局:
位0-3: id (4位, 0-15)
位4-6: status (3位, 0-7)
位7: reserve (1位)
6.3.2 控制参数结构体 typedef struct {
int16_t tor_des ; // 期望扭矩 (N·m) Q8格式
int16_t spd_des ; // 期望速度 (rad/s) Q8格式
int32_t pos_des ; // 期望位置 (rad) Q15格式
int16_t k_pos ; // 刚度系数 (-1.0~1.0) Q15格式
int16_t k_spd ; // 阻尼系数 (-1.0~1.0) Q15格式
} RIS_Comd_t ; // 12字节
6.3.3 反馈数据结构体 typedef struct {
int16_t torque ; // 实际扭矩 (N·m) Q8格式
int16_t speed ; // 实际速度 (rad/s) Q8格式
int32_t pos ; // 实际位置 (rad) Q15格式
int8_t temp ; // 温度 (°C) 整数
uint8_t MError : 3 ; // 错误标识
uint16_t force : 12 ; // 足端力传感器 (0-4095)
uint8_t none : 1 ; // 保留位
} RIS_Fbk_t ; // 11字节
6.3.4 电机句柄 typedef struct {
uint8_t motor_id ; // 电机ID
uint8_t mode ; // 0:空闲 1:FOC控制 2:电机标定
int Temp ; // 温度
int MError ; // 错误码
float T ; // 实际扭矩 (N·m)
float W ; // 实际速度 (rad/s)
float Pos ; // 实际位置 (rad)
float offset_angle ; // 上电角度偏移
int correct ; // 数据完整性标志 (0/1)
int footForce ; // 足端力传感器
bool got_offset ; // 是否已获取偏移
uint16_t calc_crc ; // 计算的CRC
uint32_t bad_msg ; // CRC错误次数
} unitree_motor_handle_t ;
6.4 RS485收发切换 // RS485收发控制(GPIO控制)
#define RS485_REDE_Pin GPIO_PIN_8
#define RS485_REDE_GPIO_Port GPIOA
// 接收模式(低电平)
#define RS485_RxMode() \
(HAL_GPIO_WritePin(RS485_REDE_GPIO_Port, RS485_REDE_Pin, GPIO_PIN_RESET))
// 发送模式(高电平)
#define RS485_TxMode() \
(HAL_GPIO_WritePin(RS485_REDE_GPIO_Port, RS485_REDE_Pin, GPIO_PIN_SET))
收发流程:
发送数据:
1. RS485_TxMode() → 切换到发送模式
2. UART发送数据
3. 发送完成中断 → RS485_RxMode()
接收数据:
1. RS485_RxMode() → 切换到接收模式
2. UART接收数据
3. 处理接收数据
6.5 CRC-CCITT校验(crc_ccitt.c) 查表法实现:
const uint16_t crc_ccitt_table [ 256 ] = {
0x0000 , 0x1189 , 0x2312 , 0x329b , 0x4624 , 0x57ad , 0x6536 , 0x74bf ,
0x8c48 , 0x9dc1 , 0xaf5a , 0xed3 , 0xca6c , 0xdbe5 , 0xe97e , 0xf8f7 ,
// ... 共256个表项
};
// 单字节CRC计算
uint16_t crc_ccitt_byte ( uint16_t crc , const uint8_t c ) {
return ( crc >> 8 ) ^ crc_ccitt_table [( crc ^ c ) & 0xff ];
}
// 数据块CRC计算
uint16_t crc_ccitt ( uint16_t crc , uint8_t const * buffer , size_t len ) {
while ( len -- )
crc = crc_ccitt_byte ( crc , * buffer ++ );
return crc ;
}
CRC使用:
// 计算控制数据包CRC
uint16_t crc = crc_ccitt ( 0 , ( uint8_t * ) & motor_send_data ,
sizeof ( RIS_ControlData_t ) - sizeof ( uint16_t ));
// 校验反馈数据包CRC
uint16_t calc_crc = crc_ccitt ( 0 , ( uint8_t * ) & motor_recv_data ,
sizeof ( RIS_MotorData_t ) - sizeof ( uint16_t ));
if ( motor_recv_data . CRC16 != calc_crc ) {
// CRC错误
}
6.6 哈希表管理多电机 // 消息结点
typedef struct rs_node {
void * rs_data ; // 电机结构体指针
uint8_t id ; // 电机ID
struct rs_node * next ; // 下一个结点(链表)
} rs_node_t ;
// 哈希表
typedef struct {
rs_node_t ** table ; // 桶数组
uint8_t len ; // 桶数量
} table_t ;
// 全局哈希表
table_t * rs_table = NULL ;
6.6.1 初始化哈希表(unitree_motor.c:169-182) uint8_t rs_list_init ( uint8_t len ) {
// 分配表结构
rs_table = ( table_t * ) calloc ( 1 , sizeof ( table_t ));
if ( rs_table == NULL ) {
return 1 ;
}
// 分配桶数组
rs_table -> table = ( rs_node_t ** ) calloc ( len , sizeof ( rs_node_t * ));
if ( rs_table -> table == NULL ) {
return 2 ;
}
rs_table -> len = len ;
return 0 ;
}
6.6.2 添加节点(unitree_motor.c:191-207) uint8_t rs_list_add_new_node ( void * node_data , uint8_t id ) {
// 分配新节点
rs_node_t * new_node = ( rs_node_t * ) malloc ( sizeof ( rs_node_t ));
if ( new_node == NULL ) {
return 2 ;
}
// 设置节点数据
new_node -> rs_data = node_data ;
new_node -> id = id ;
// 使用链地址法处理冲突
uint8_t bucket_index = id % rs_table -> len ;
new_node -> next = rs_table -> table [ bucket_index ];
rs_table -> table [ bucket_index ] = new_node ;
return 0 ;
}
哈希表结构示例:
假设 len = 4, 添加电机ID: 1, 5, 9, 2
rs_table->table[0]: NULL
rs_table->table[1]: node(id=1) -> node(id=5) -> node(id=9) -> NULL
rs_table->table[2]: node(id=2) -> NULL
rs_table->table[3]: NULL
6.7 电机初始化(unitree_motor.c:39-53) uint8_t unitree_motor_init ( unitree_motor_handle_t * motor , uint8_t motor_id ,
uint8_t mode ) {
if ( motor == NULL ) {
return 1 ;
}
motor -> motor_id = motor_id ;
motor -> mode = mode ;
motor -> got_offset = false ;
// 添加到哈希表
rs_list_add_new_node ( motor , motor_id );
// 切换到接收模式
RS485_RxMode ();
return 0 ;
}
6.8 发送数据(unitree_motor.c:61-92) void unitree_send_data ( UART_HandleTypeDef * huart , unitree_motor_handle_t * motor ,
ctrl_param_t ctrl_param ) {
// 1. 设置包头
motor_send_data . head [ 0 ] = 0xFE ;
motor_send_data . head [ 1 ] = 0xEE ;
// 2. 参数限幅
SATURATE ( ctrl_param . id , 0 , 15 );
SATURATE ( ctrl_param . mode , 0 , 7 );
SATURATE ( ctrl_param . K_P , 0.0f , 25.599f );
SATURATE ( ctrl_param . K_W , 0.0f , 25.599f );
SATURATE ( ctrl_param . T , -127.99f , 127.99f );
SATURATE ( ctrl_param . W , -804.00f , 804.00f );
SATURATE ( ctrl_param . Pos , -11.546f , 11.546f );
// 3. 编码控制参数(关键:浮点转定点数)
motor_send_data . mode . id = ctrl_param . id ;
motor_send_data . mode . status = ctrl_param . mode ;
// 扭矩:Q8格式 (×256)
motor_send_data . comd . tor_des = ctrl_param . T * 256.0f ;
// 速度:Q8格式 (×256)
motor_send_data . comd . spd_des = ctrl_param . W / 6.28318f * 256.0f ;
// 位置:Q15格式 (×32768/2π)
motor_send_data . comd . pos_des = ( ctrl_param . Pos + motor -> offset_angle )
/ 6.28318f * 32768.0f ;
// 刚度系数:Q15格式 (×32768/25.6)
motor_send_data . comd . k_pos = ctrl_param . K_P / 25.6f * 32768.0f ;
// 阻尼系数:Q15格式 (×32768/25.6)
motor_send_data . comd . k_spd = ctrl_param . K_W / 25.6f * 32768.0f ;
// 4. 计算CRC
motor_send_data . CRC16 = crc_ccitt ( 0 , ( uint8_t * ) & motor_send_data ,
sizeof ( RIS_ControlData_t ) - sizeof ( uint16_t ));
// 5. 切换到发送模式
RS485_TxMode ();
// 6. UART发送
uart_dmatx_write ( huart , & motor_send_data , sizeof ( motor_send_data ));
uart_dmatx_send ( huart );
}
控制参数编码详解:
Q格式(定点数):
Q8格式(16位有符号整数):
表示范围:-128 ~ 127
转换公式:float_value × 256 = int16_value
示例:
10.5 N·m → 10.5 × 256 = 2688
-5.2 N·m → -5.2 × 256 = -1331
Q15格式(32位有符号整数):
表示范围:-32768 ~ 32767
转换公式:float_value × 32768 = int32_value
示例:
1.57 rad → 1.57 × 32768 = 51446
位置编码特殊处理:
// 位置需要考虑偏移量和2π归一化
motor_send_data . comd . pos_des = ( ctrl_param . Pos + motor -> offset_angle ) / 6.28318f * 32768.0f ;
为什么要加offset_angle? - 上电后记录初始位置 - 所有位置都相对于初始位置计算 - 避免上电后位置突变
6.9 接收数据(unitree_motor.c:106-160) uint8_t unitree_receive_data ( UART_HandleTypeDef * huart ) {
uint8_t rx_res = 0 ;
// 1. UART接收数据
rx_res = uart_dmarx_read ( huart , ( uint8_t * ) & motor_recv_data ,
sizeof ( motor_recv_data ));
if ( rx_res == 0 ) {
return 1 ; // 接收失败
}
// 2. 检查包头
if ( motor_recv_data . head [ 0 ] != 0xFD || motor_recv_data . head [ 1 ] != 0xEE ) {
return 2 ; // 包头错误
}
// 3. 在哈希表中查找电机节点
rs_node_t * node ;
unitree_motor_handle_t * p ;
uint8_t bucket_index = motor_recv_data . mode . id % rs_table -> len ;
node = rs_table -> table [ bucket_index ];
while (( node != NULL ) && node -> id != motor_recv_data . mode . id ) {
node = node -> next ;
}
if ( node == NULL ) {
return 3 ; // 未找到电机
}
p = node -> rs_data ;
// 4. CRC校验
p -> calc_crc = crc_ccitt ( 0 , ( uint8_t * ) & motor_recv_data ,
sizeof ( RIS_MotorData_t ) - sizeof ( uint16_t ));
if ( motor_recv_data . CRC16 != p -> calc_crc ) {
memset ( & motor_recv_data , 0 , sizeof ( RIS_MotorData_t ));
p -> correct = 0 ;
p -> bad_msg ++ ; // 错误计数
return 4 ; // CRC错误
}
// 5. 解码反馈数据(定点数转浮点数)
p -> mode = motor_recv_data . mode . status ;
p -> Temp = motor_recv_data . fbk . temp ;
p -> MError = motor_recv_data . fbk . MError ;
// 速度:Q8格式 (÷256, ×2π)
p -> W = (( float ) motor_recv_data . fbk . speed / 256.0f ) * 6.28318f ;
// 扭矩:Q8格式 (÷256)
p -> T = (( float ) motor_recv_data . fbk . torque ) / 256.0f ;
// 位置:Q15格式 (÷32768, ×2π, 减去偏移)
p -> Pos = 6.28318f * (( float ) motor_recv_data . fbk . pos ) / 32768.0f - p -> offset_angle ;
// 足端力传感器
p -> footForce = motor_recv_data . fbk . force ;
p -> correct = 1 ;
// 6. 首次接收获取偏移
if ( ! ( p -> got_offset )) {
p -> offset_angle = p -> Pos ;
p -> got_offset = true ;
}
return 0 ;
}
反馈数据解码详解:
速度解码(Q8格式):
接收值:25600
解码:25600 / 256 = 100 rad/s
扭矩解码(Q8格式):
接收值:1280
解码:1280 / 256 = 5 N·m
位置解码(Q15格式):
接收值:16384
解码:16384 / 32768 × 2π = 0.5 × 6.28318 = 3.14159 rad (180°)
6.10 UART发送完成中断 void HAL_UART_TxCpltCallback ( UART_HandleTypeDef * huart ) {
if ( huart -> Instance == USART1 ) {
// 发送完成,切换到接收模式
RS485_RxMode ();
}
}
6.11 使用示例 // 初始化哈希表(桶数量=8,大于电机数量)
rs_list_init ( 8 );
// 定义电机
unitree_motor_handle_t motor1 , motor2 ;
// 初始化电机
unitree_motor_init ( & motor1 , 1 , 1 ); // ID=1, FOC控制模式
unitree_motor_init ( & motor2 , 2 , 1 ); // ID=2, FOC控制模式
// 设置控制参数
ctrl_param_t ctrl_param ;
ctrl_param . id = 1 ;
ctrl_param . mode = 1 ; // FOC控制
ctrl_param . T = 5.0f ; // 扭矩 5 N·m
ctrl_param . W = 10.0f ; // 速度 10 rad/s
ctrl_param . Pos = 1.57f ; // 位置 1.57 rad (90°)
ctrl_param . K_P = 10.0f ; // 刚度 10
ctrl_param . K_W = 1.0f ; // 阻尼 1
// 发送控制命令
unitree_send_data ( & huart1 , & motor1 , ctrl_param );
// 接收反馈数据(在中断或主循环中调用)
uint8_t ret = unitree_receive_data ( & huart1 );
if ( ret == 0 ) {
// 接收成功
float torque = motor1 . T ;
float speed = motor1 . W ;
float pos = motor1 . Pos ;
int8_t temp = motor1 . Temp ;
} else if ( ret == 4 ) {
// CRC错误
printf ( "CRC error: %lu bad messages \n " , motor1 . bad_msg );
}
6.12 难点总结 RS485半双工 :收发切换时机 CRC-CCITT校验 :数据完整性保证 Q格式定点数 :浮点与整数的转换 哈希表管理 :多电机的高效查找 角度偏移处理 :上电零点记录 参数限幅 :防止发送超出范围的值 七、总结与对比 7.1 五种驱动对比 特性 DJI-Motor Damiao-Motor Step-Motor VESC Unitree 通信方式 CAN CAN GPIO+PWM CAN RS485 反馈 有 有 无 有 有 控制方式 电流/电压 MIT/位置速度/速度 脉冲 多种 FOC 多电机管理 CAN广播 独立ID 无 独立ID 哈希表 数据校验 无 无 无 无 CRC-CCITT 复杂度 中 高 低 中 高 适用场景 RoboMaster 机器人关节 精密定位 开源电调 四足机器人
7.2 代码设计模式总结 统一设计模式: 1. 句柄结构体 :存储电机状态 2. 初始化/反初始化 :生命周期管理 3. 控制函数 :统一的API接口 4. 回调机制 :异步数据接收
关键技术点: 1. CAN通信 :标准帧vs扩展帧 2. 数据编码 :float_to_uint, uint_to_float 3. 位置处理 :相对位置、绝对位置、角度偏移 4. 多电机管理 :ID管理、哈希表 5. 数据校验 :CRC、包校验
7.3 学习建议 从简单到复杂 :Step-Motor → DJI → Damiao → VESC → Unitree 理解通信协议 :CAN、RS485、UART 掌握数据编码 :Q格式、位域、浮点编码 实践多电机管理 :ID分配、冲突处理 注意异常处理 :超时、校验失败、通信丢失 文档版本 : 1.0 编写日期 : 2025-02-23 适用平台 : STM32 HAL库