从零上手:STM32 CAN总线驱动大疆3508电机实战解析
1. 硬件准备与连接指南第一次接触STM32和大疆3508电机的时候我完全被那一堆线缆搞懵了。后来才发现硬件连接其实就几个关键点需要注意。先说下必备的器材清单STM32F4系列开发板我用的是F407ZGT6大疆3508电机带配套电调CAN总线收发器模块常用TJA105012V电源给电机供电USB转串口模块调试用最关键的接线部分CAN总线需要接四根线。开发板的CAN_TX接收发器的TXDCAN_RX接收发器的RXD然后收发器的CANH和CANL分别接电调的CAN_H和CAN_L。这里有个坑我踩过——千万别把CANH和CANL接反否则通信完全没反应。电源部分要特别注意电机供电和开发板供电要共地不然后续通信会有干扰。电机端的接口比较简单电调上标得很清楚电源接12V输入三根粗线接电机三相细线的杜邦头接PWM信号不过我们用CAN控制就不需要接PWM了。建议先用万用表确认下所有连接点是否导通特别是自己焊的转接板虚焊会导致各种灵异问题。2. CAN总线初始化详解刚开始用HAL库配置CAN时我被那一堆参数搞得头晕。后来拆解后发现主要就分三大块配置2.1 基本参数设置hcan1.Instance CAN1; hcan1.Init.Prescaler 6; // 这个决定波特率 hcan1.Init.Mode CAN_MODE_NORMAL; hcan1.Init.SJW CAN_SJW_1TQ; hcan1.Init.BS1 CAN_BS1_5TQ; hcan1.Init.BS2 CAN_BS2_3TQ;波特率计算有个实用公式波特率APB1时钟/(Prescaler*(BS1BS21))。比如APB1是42MHz想要1Mbps波特率Prescaler就设642/(6*(531))1M。实测发现3508电机对波特率不敏感500K-1M都能正常工作。2.2 GPIO配置在HAL_CAN_MspInit里要设置正确的复用功能。以STM32F4为例GPIO_InitStruct.Pin GPIO_PIN_8|GPIO_PIN_9; // 注意查看芯片手册 GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate GPIO_AF9_CAN1; // 这个最关键不同型号STM32的复用功能编号可能不同我就曾经因为没查手册用AF8配置了半天没反应。建议直接翻参考手册的Alternate function mapping章节。2.3 中断配置接收中断必不可少HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 5, 0); HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);有个细节要注意新版HAL库默认不开启接收中断需要在初始化后手动调用HAL_CAN_ActivateNotification(hcan1, CAN_IT_RX_FIFO0_MSG_PENDING)。3. 滤波器配置实战滤波器是CAN最让人困惑的部分其实把它理解成邮件分拣规则就简单了。大疆电机用的标准帧ID是0x200-0x203我们这样配置CAN_FilterTypeDef filter; filter.FilterBank 0; // 滤波器组编号 filter.FilterMode CAN_FILTERMODE_IDMASK; filter.FilterScale CAN_FILTERSCALE_32BIT; filter.FilterIdHigh 0x0000; filter.FilterIdLow 0x0000; filter.FilterMaskIdHigh 0x0000; // 全0表示接收所有 filter.FilterMaskIdLow 0x0000; filter.FilterFIFOAssignment CAN_FILTER_FIFO0; filter.FilterActivation ENABLE; HAL_CAN_ConfigFilter(hcan1, filter);如果想只接收特定ID比如0x201就把FilterIdHigh设为0x2015标准帧ID要左移5位FilterMaskIdHigh设为0x1FFFFFFF。调试阶段建议先全接收用下面代码打印所有收到的IDvoid HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef header; uint8_t data[8]; HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, header, data); printf(ID:0x%X DLC:%d Data:, header.StdId, header.DLC); for(int i0; iheader.DLC; i) printf(%02X ,data[i]); printf(\n); }4. 电机控制协议解析大疆电机的CAN协议其实很简洁主要就两种帧4.1 控制帧发送void DJI_Motor_Control(CAN_HandleTypeDef* hcan, uint8_t id, int16_t current) { uint8_t data[8] {0}; data[0] current 8; data[1] current 0xFF; CAN_TxHeaderTypeDef header; header.StdId 0x200 id; // 0x201对应电机1 header.IDE CAN_ID_STD; header.RTR CAN_RTR_DATA; header.DLC 8; uint32_t mailbox; HAL_CAN_AddTxMessage(hcan, header, data, mailbox); }电流值范围是-16384~16384对应-20A~20A。实际测试发现3508电机在3000左右就能明显转动。4.2 反馈帧接收typedef struct { int16_t angle; // 0-8191 int16_t rpm; int16_t current; // 实际电流 uint8_t temperature; // 温度 } Motor_Feedback; void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef header; uint8_t data[8]; HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, header, data); if(header.StdId 0x201 header.StdId 0x204) { Motor_Feedback fb; fb.angle (data[0]8) | data[1]; fb.rpm (data[2]8) | data[3]; fb.current (data[4]8) | data[5]; fb.temperature data[6]; // 处理反馈数据... } }角度值是0-8191循环的需要自己处理溢出。我通常用这个函数计算连续角度int32_t calc_total_angle(uint16_t current, uint16_t last) { static int32_t total 0; int16_t delta current - last; if(delta 4096) delta - 8192; else if(delta -4096) delta 8192; total delta; return total; }5. PID控制实战技巧给3508调PID时建议先调速度环再调位置环。分享我的参数整定过程5.1 速度环PIDpid_t speed_pid; PID_Init(speed_pid, POSITION_PID, 10000, 5000, 5, 0.1, 0);调试步骤先设Ki0,Kd0逐渐增大Kp直到电机开始振动取振动时Kp值的50%作为初始值增加Ki消除静差但别超过Kp/10最后加Kd抑制超调5.2 位置环PID位置环需要处理角度回环问题float target_angle 180.0; // 目标180度 float current_angle (motor_fb.angle / 8191.0) * 360.0; // 处理角度差 float error target_angle - current_angle; if(error 180) error - 360; else if(error -180) error 360; float output PID_Calc(position_pid, current_angle, target_angle);5.3 抗积分饱和实际项目中一定要加积分限幅void PID_Init(pid_t* pid, uint32_t mode, float max_out, float i_limit, float kp, float ki, float kd) { pid-IntegralLimit i_limit; // 比如设为max_out的2倍 //...其他初始化 } float PID_Calc(pid_t* pid, float get, float set) { //...计算过程 if(pid-iout pid-IntegralLimit) pid-iout pid-IntegralLimit; else if(pid-iout -pid-IntegralLimit) pid-iout -pid-IntegralLimit; }6. 常见问题排查调试CAN总线时我遇到过这些典型问题收不到数据先用示波器看CANH-CANL是否有差分信号。没有的话检查终端电阻120Ω是否接对波特率设置是否一致收发器供电是否正常数据错乱尝试降低波特率检查电源地线是否共地。我曾因为开发板用USB供电、电机用电池供电两地没连接导致通信异常。电机不响应确认控制帧ID是否正确0x200电机编号检查电流值是否够大至少1000以上用CAN分析仪抓包看是否发出数据HAL库版本问题新旧版本API变化很大建议统一用CubeMX生成代码。遇到奇怪的错误时可以对比stm32f4xx_hal_can.c文件版本。有个实用的调试技巧在循环里定时发送一帧数据用逻辑分析仪抓取波形确认时序和内容是否符合预期。我通常会用下面这种测试代码uint32_t last_send 0; while(1) { if(HAL_GetTick() - last_send 100) { DJI_Motor_Control(hcan1, 1, 2000); // 电机12000电流值 last_send HAL_GetTick(); } //...其他处理 }