mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2813 字
8 分钟
M3508无刷电机驱动和闭环控制
2026-05-01

M3508无刷电机驱动#

RoboMaster M3508 直流无刷电机(以下简称为“电机”)专为高性能机器人设计,搭配RoboMaster C620 无刷电机调速器(以下简称为“电调”,电调需另外购买)可以实现正弦驱动,相比传统方波驱动具有更高的效率、机动性和稳定性。

一、开环驱动M3508无刷电机#

1.在CubeMX初始化CAN通讯#

设置CAN通讯,将CAN的波特率设置为1Mbps,并把发送模式设为普通模式 HAL库初始化CAN的主要设置 在NVIC中开启CAN接收中断 HAL库初始化CAN的接收中断

2.在代码中初始化CAN通讯#

开启CAN

//开启CAN
HAL_CAN_Start(hcan);

代码详情:CAN相关代码,相关解释:CAN的原理与配置方法

3.发送CAN的数据#

(1)阅读M3508无刷电机手册#

由于M3508需要搭配C620电调,所以我们需要阅读M3508电机和C620电调的手册 M3508电机手册;C620电调手册

C620电调图片 根据手册上的相关内容,我们可以知道M3508电机采用CAN通讯发送电调ID为14的数据时应将CAN的帧标识符(CAN_ID)设置为0x200,类型为标准数据帧,字节长度DLC为8字节的帧头文件。 而输入范围为-1638416384,所以存储发送的数据应采用int16存储(215=327682^{15}=32768),采用2个uint8来组成16位发送数据。 可以由数据手册看到,CAN_ID的显示为绿灯每搁1秒闪烁n次为电调ID,更改ID可以参考手册自行查看。 CAN_ID手册查看方式

(2)编写通过CAN发送目标电流给M3508#

由M3508无刷电机和C620电调的手册可知,假设C620电调的ID为1,通过CAN1通道发送数据时,应把标识符0x200,DLC=8,在发送数据的第1、2字节中装入数据,并将其他初始化为0。 因此代码应为:

static CAN_TxHeaderTypeDef can1_tx_message;
static uint8_t can1_send_data[8];
uint8_t Can1_send_data(uint16_t ID,int16_t Torque1, int16_t Torque2, int16_t Torque3, int16_t Torque4)
{
//防止出现空ID和发送数据繁忙
if(ID == NULL)
{return HAL_ERROR;}
if (HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0U)
{return HAL_BUSY;}
//拼凑需要发送的数据
can1_send_data[0] = (uint8_t)(Torque1 >> 8 & 0xFF);
can1_send_data[1] = (uint8_t)(Torque1 & 0xFF);
can1_send_data[2] = (uint8_t)(Torque2 >> 8 & 0xFF);
can1_send_data[3] = (uint8_t)(Torque2 & 0xFF);
can1_send_data[4] = (uint8_t)(Torque3 >> 8 & 0xFF);
can1_send_data[5] = (uint8_t)(Torque3 & 0xFF);
can1_send_data[6] = (uint8_t)(Torque4 >> 8 & 0xFF);
can1_send_data[7] = (uint8_t)(Torque4 & 0xFF);
//编写发送头文件
CAN_TxHeaderTypeDef can1_tx_message = {0};
can1_tx_message.StdId = ID;
can1_tx_message.ExtId = 0;
can1_tx_message.IDE = CAN_ID_STD;
can1_tx_message.RTR = CAN_RTR_DATA;
can1_tx_message.DLC = len;
can1_tx_message.TransmitGlobalTime = DISABLE;
//建立邮箱、发送数据并返回
uint32_t used_mailbox;
return (uint8_t)HAL_CAN_AddTxMessage(&hcan1, &can1_tx_message, can1_send_data, used_mailbox);
}

其中,头上两个用来存储发送的标志位等帧头和发送的数据

static CAN_TxHeaderTypeDef can1_tx_message;
static uint8_t can1_send_data[8];

HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)函数则是可以返回对应CAN的剩余空余邮箱数,用来判断是否还有空的邮箱来发送数据。 通过对发送的数据移位,从而使得原本的int16数据变成2个uint8类型的数据。而与0xFF互余可以防止数据类型不对的问题,避免出现发送数据不正确的问题。

can1_send_data[0] = (uint8_t)(Torque1 >> 8 & 0xFF);
can1_send_data[1] = (uint8_t)(Torque1 & 0xFF);

(3)电机未转动可能的问题#

a.时钟树不对#

CubeMX中的芯片所接收的外部晶振信号是一个区间范围内的信号,CubeMX所默认的晶振主频可能并不是实际的主频,所以需要在配置时钟树时检查核心板的晶振主频。

由于时钟树不对,所以CAN发送数据的实际波特率并非1Mbps,使得M3508并不能接收到由核心板发送的数据。 检查和解决方法:通过检查使用的核心板的原理图,查看对应连接的晶振的主频是多少,是否与CubeMX中配置的相同。若不同,则需要修改外部晶振的主频并重新更改对应时钟数的配置。

b.无空的发报邮箱#

通过Keil Debug可以通过打断电来检查是否进入到HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0U中的判断中,如果出现了这种情况,说明以下几种情况:

  • 时钟树不对,参考上方
  • 连接的CAN线方法不对,

将CAN线的接口并排朝向一致,应为黑红红黑或红黑黑红的对称排布,并且可通过打万用表的欧姆蜂鸣档来排查是否由接触不良或断线的现象存在。

  • 对应M3508的电源线未接上或损坏

检查M3508所连接的C620电调指示灯,若出现非显示ID的灯的提示,或并未亮灯,则M3508或C620损坏,需检查

二、接收M3508数据#

1.CubeMX配置#

在NVIC中开启CAN接收中断 HAL库初始化CAN的接收中断

2.开启CAN接收回调函数#

配置CAN接收回调中断函数,设置CAN的滤波器相关设置和回调函数(方便多设备时辨别CAN总线是哪一个)

//开启CAN中断(此处开启FIFO0的中断回调函数)
__HAL_CAN_ENABLE_IT(hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
//设置滤波器参数(详见CAN配置文章)
void CAN_Feilter_Mask_Config(CAN_HandleTypeDef *hcan, uint32_t Filter_ID, uint32_t Filter_Mask, uint32_t Filter_FIFO)
//设置回调函数
CAN1_Manage_Object.Callback_Function = Callback_Function;

3.重定义接收中断回调函数#

由于void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)在CubeMX所生成的代码为弱定义,因此可以对其进行重定义从而更加简洁直观的处理中断和其中接收到的数据。

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
static struct Struct_CAN_Rx_Buffer CAN_Rx_Buffer;
if(hcan->Instance == CAN1)
{
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &CAN_Rx_Buffer.Header, CAN_Rx_Buffer.Data);
if(CAN1_Manage_Object.Callback_Function != NULL)
{
CAN1_Manage_Object.Callback_Function(&CAN_Rx_Buffer);
}
}
if(hcan->Instance == CAN2)
{
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &CAN_Rx_Buffer.Header, CAN_Rx_Buffer.Data);
if(CAN2_Manage_Object.Callback_Function != NULL)
{
CAN2_Manage_Object.Callback_Function(&CAN_Rx_Buffer);
}
}
}

static struct Struct_CAN_Rx_Buffer CAN_Rx_Buffer;处先声明两个静态变量,这可以节省内存,保证数据的安全性。 而HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &CAN_Rx_Buffer.Header, CAN_Rx_Buffer.Data);则可以获得到此次CAN所接收到的数据,hcan代表其的哪一个CAN总线,CAN_RX_FIFO0则代表是哪一个接收邮箱,&CAN_Rx_Buffer.Header, CAN_Rx_Buffer.Data这两个变量则是数据的存储地址,方便临时的存储数据 之后数据会由CAN1_Manage_Object.Callback_Function(&CAN_Rx_Buffer);到达在初始化时所设置的函数回调指针。 在此处,会进入到不同的分支中,用于不同类型的电机/超级电容和其他通讯的数据处理。

void Dispose_CAN_Call_Back(struct Struct_CAN_Rx_Buffer *CAN_Rx_Buffer)
{
if(CAN_Rx_Buffer->Header.StdId == 0x201)
{
Dispose_CAN_Call_Back_Data(CAN_Rx_Buffer, &M3508_1_Data);
}
if(CAN_Rx_Buffer->Header.StdId == 0x205)
{
Dispose_CAN_Call_Back_Data(CAN_Rx_Buffer, &GM6020_1_Data);
}
}

4.编写处理M3508数据的函数#

由于当处于RTOS系统时,中断函数的时间会被严格限制,因此此处的处理数据应以将数据分类为实现目的,而非常规的数据挪移、拼接和乘除

在接收时,应传入参数CAN_Rx_Buffer和Motor_Data,从而可以处理数据

struct Motor_Receive_Data
{
int16_t angle;
int16_t speed;
int16_t current;
uint8_t temperature;
uint8_t Error;
};
struct Motor_Receive_Data M3508_1_Data = {0};
void Dispose_CAN_Call_Back_Data(struct Struct_CAN_Rx_Buffer *CAN_Rx_Buffer, struct Motor_Receive_Data *Motor_Data)
{
Motor_Data->angle = (CAN_Rx_Buffer->Data[0] << 8) | CAN_Rx_Buffer->Data[1];
Motor_Data->speed = (CAN_Rx_Buffer->Data[2] << 8) | CAN_Rx_Buffer->Data[3];
Motor_Data->current = (CAN_Rx_Buffer->Data[4] << 8) | CAN_Rx_Buffer->Data[5];
Motor_Data->temperature = CAN_Rx_Buffer->Data[6];
Motor_Data->Error = CAN_Rx_Buffer->Data[7];
}

其中(CAN_Rx_Buffer->Data[i] << 8) | CAN_Rx_Buffer->Data[i+1]可以通过移位拼接出对应的数据(角度、速度和电流值),而温度值和Error是单独的两个8字节长度的数值,不需要移位,

三、通过PID算法闭环控制M3508无刷电机#

1.编写PID控制#

PID通过速度环控制电机的速度,其中可以将kp,ki,kd和iout变为结构体使函数更加清晰明了。 此处采用的是位置式Pid算法,通过作差得出对应的值,而采用静态变量来存储对应的值,可以改用结构体传参的方法来优化代码的可读性。

int16_t PID_Speed(int16_t target_speed, int16_t current_speed, float Kp, float Ki, float Kd)
{
static int16_t previous_error = 0;
static int16_t integral = 0;
int16_t error = target_speed - current_speed;
integral += error;
int16_t derivative = error - previous_error;
int16_t output = (int16_t)(Kp * error + Ki * integral + Kd * derivative);
previous_error = error;
if(output > 10000)
{output = 10000;}
if(output < -10000)
{output = -10000;}
return output;
}

在此处,对输出的限幅为10000,但其实2000左右足以做到比较广范围的PID在3508空载时。

2.将对应数据同样传输给VOFA#

Vofa+ 是一个可扩展,高颜值的串口调试软件,除了基本的串口调试的功能,还支持TCP/IP通信、数据可视化、数据分析等高级功能,可以大大方便我们日常的开发调试和提高效率。

对于电机的数据,我们需要使用VOFA这类的串口绘图软件将串口发送的数据转化为图片,从而可以更加直观的调试参数。以下为大致例子

void UART_Transmit_Motor_Data(需传入参数)
{
sprintf(UART_Tx_Buffer,"channels: (%d%f)\n",传入参数);
HAL_UART_Transmit_DMA(&huart6, (uint8_t *)UART_Tx_Buffer, strlen(UART_Tx_Buffer));
}

此处为FireWater模式,可以自行修改为其他模式的代码。

3.编写实际代码#

通过整合上面的代码,来做出最后的代码

//传输电机数据给VOFA通过UART
UART_Transmit_Motor_Data(需要传入给VOFA+的参数);
//计算PID数据并发送
M3508_Speed_Transmit1.speed1 = PID_Speed(M3508_Target_Speed1.speed1, M3508_1_Data.speed, kp, ki, kd);
Can1_send_data(0x200, M3508_Speed_Transmit1.speed1, 0, 0, 0);
//循环延迟
HAL_Delay(1);

4.调参#

根据先P再D最后I的方法进行调参,先通过大P可以使其转动,然后再I抑制低频大幅震荡,最后添加I来消除静差。

总结#

一、开环驱动 M3508(发送目标电流)#

1.CubeMX 初始化 CAN#

  • 配置 CAN 波特率为 1 Mbps,发送模式设为普通模式
  • NVIC 中开启 CAN 接收中断(用于后续接收反馈)。

2.代码中初始化 CAN#

  • 启动 CAN 外设:
    HAL_CAN_Start(hcan);
  • 发送前可检查邮箱是否有空余,避免一直忙导致发送失败。

3.发送 CAN 控制数据(给 C620 电调)#

  • M3508 需要搭配 C620 电调,控制本质是向电调发送“目标电流/力矩”指令。
  • 当控制 ID=1~4 的电调时:
    • 帧标识符(StdId / CAN_ID)通常用 0x200
    • 标准帧数据帧DLC=8
    • 每个电机占 2 字节(int16_t),共 4 路:Torque1~Torque4
  • 典型做法:将 int16_t 拆成两个 uint8_t 发送(高字节在前)。

4.电机不转的常见原因#

  • 时钟树配置不匹配:实际 CAN 波特率不对,导致电调收不到数据。
  • 无空闲发送邮箱:可能由波特率/连线/硬件供电问题引起。
  • CAN 线序或接触问题:检查线序对称、连通性。
  • 电机/电调供电异常或损坏:观察 C620 指示灯状态确认。

二、接收 M3508 回传数据(电机反馈)#

1.CubeMX 配置#

  • 在 NVIC 中开启 CAN 接收中断(如 FIFO0 中断)。

2.开启接收中断与滤波器#

  • 使能 FIFO0 接收中断(示例):
    __HAL_CAN_ENABLE_IT(hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
  • 配置 CAN 滤波器(Mask/ID 等),并设置回调函数指针,便于区分 CAN1/CAN2 或不同设备。

3.重定义 HAL 接收回调函数(弱定义可重写)#

  • 重写 HAL_CAN_RxFifo0MsgPendingCallback()
    • 在回调中调用 HAL_CAN_GetRxMessage() 取出报文
    • 按 CAN1/CAN2 分发到各自管理对象的回调函数
  • 使用 static 缓冲区可减少栈开销、保证临时数据安全。

4.解析 M3508 回传数据格式#

  • 常用反馈结构包含:

    • angle(角度)
    • speed(速度)
    • current(电流)
    • temperature(温度)
    • Error(错误码)
  • 解析方式:两字节拼接(高字节左移 8 位再或低字节):

    angle = (data[0] << 8) | data[1];
    speed = (data[2] << 8) | data[3];
    current = (data[4] << 8) | data[5];
  • RTOS 场景注意:中断里尽量少做复杂处理,避免大量数据搬运/拼接/尤其是乘除等耗时操作;更推荐“分类 + 快速存储”,细处理放到任务里。


三、PID 闭环控制 M3508(速度环)#

1.PID 控制器(速度环)#

  • 采用位置式 PID(误差、积分、微分)输出目标电流/力矩。
  • 输出需要限幅(示例限制到 ±10000;实际空载可能 2000 左右就够用)。
  • 代码中用静态变量保存积分与上次误差,后续可用结构体封装提高可读性与可复用性。

2.配合 VOFA+ 做可视化调参#

  • 将目标值、反馈值、PID 输出等通过 UART 发送到 VOFA+ 绘图观察趋势。
  • 可用 sprintf 组织字符串,配合 HAL_UART_Transmit_DMA() 提升效率。

3.闭环主流程(整合)#

  • 每周期典型流程:
    1. 采集电机反馈(speed 等)
    2. 计算 PID 输出
    3. 通过 CAN 发送到 0x200(对应电调 1~4 的电流指令槽位)
    4. 延时循环(如 1ms)

4.调参顺序建议#

  • 常用经验:先 P → 再 D → 最后 I
    • P:先让系统“能跟上/能转起来”
    • I:消除稳态误差(静差),但过大易引入低频震荡/积分饱和
    • D:抑制震荡、改善动态
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

M3508无刷电机驱动和闭环控制
https://github.com/zhenzhen0915
作者
zhenzhen0915
发布于
2026-05-01
许可协议
CC BY 4.0

部分信息可能已经过时

目录