恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
C620电调双模Demo:基于STM32的CAN/PWM电机控制参考工程
首页
资讯中心
/
C620电调双模Demo:基于STM32的CAN/PWM电机控制参考工程
C620电调双模Demo:基于STM32的CAN/PWM电机控制参考工程
发布时间:2026/10/15 9:20:53
简介C620电调使用程序说明书1是一份面向STM32开发人员和机器人竞赛爱好者的技术手册以RoboMaster主控板与M3508电机为对象系统讲解C620电调Demo程序的开发环境搭建、工程框架和两种控制模式。整个资源只包含一个PDF文档压缩包大小约905KB体量虽小却覆盖了CubeMX工具安装、HAL固件库导入、Keil5工程代码分组以及FreeRTOS任务中BSP组文件的添加方法。这份说明书目前已有三千三百余人次学习浏览说明其内容对实际调试有较强的参考价值。说明书中重点阐述了PWM和CAN两种控制方式的接线差异、宏定义切换方法以及速度闭环PID的计算流程并特别强调不能同时连接CAN与PWM信号线能有效避免因接线错误导致电调失控。对于正在使用RoboMaster主控板进行M3508电机调试的开发者这份说明书可以大幅缩短环境配置时间帮助快速理解电调控制逻辑并迁移到自己的项目中。1. C620 电调双模 Demo一套能跑通 M3508 的 CAN/PWM 参考工程如果你拿到 C620 电调 M3508 电机想在 RM 主控板STM32F427上快速转起来最省事的办法不是从零翻参考手册写 CAN 滤波器而是直接找一份把两种控制模式都写好的 Demo 程序。这套 Demo 默认支持 CAN 和 PWM 双模驱动通过两个宏定义就能切换工作方式按键调速也一并封装好了。它解决的核心问题是让新手在一小时内完成 CubeMX 工程生成、固件库安装、接线和烧录直接看到电机转起来让熟手拿到一个能自定义 PID 参数和 CAN 收发格式的基座不必再折腾 HAL 库外设初始化。适合正在调机器人底盘、云台或者做课程设计的开发者前提是你已经备好一块 RM 主控板、一个 C620 电调和一个 M3508 电机。2. 环境搭建与工程生成CubeMX、JRE 和固件库的安装顺序2.1 CubeMX 下载与 JRE 前置依赖这套 Demo 的初始化代码不是手写的而是用 STM32CubeMX 生成的所以第一步是把工具链配齐。CubeMX 是 ST 官方出品的图形化配置工具它的作用是根据你在界面上勾选的引脚、外设和时钟树自动生成一套可编译的 HAL 工程。需要强调的一点是CubeMX 本身只是一个配置生成器真正的外设驱动代码存放在对应的固件库包Firmware Package里两者必须同时存在。下载 CubeMX 时要先注册 ST 账号如果你不想注册也可以去一些开源镜像站找现成的安装包。安装完成后先别急着建工程因为 CubeMX 运行在 Java 平台上缺了 JRE 会直接启动失败或者报错退出。我的做法是装完 CubeMX 之后第一时间打开命令行执行java -version确认 JRE 版本省得后面弹出莫名其妙的 Java 异常窗口。CubeMX 对 JRE 的版本要求不算苛刻常见的 Java 8 往上都能跑。装好之后建议顺手把中文学习网站收藏一下里面除了软件资源还有不少中文教程遇到英文文档读不下去的时候可以对照着看。不过我还是要提醒一句第三方站点的资源版本可能滞后优先用官方渠道下载第三方只作为备用方案。2.2 固件库安装本地 zip 导入与版本校验CubeMX 装好之后还需要安装对应芯片系列的固件库包。以这套 Demo 使用的 STM32F427 来说需要下载 STM32CubeF4 系列包。下载方式有两种一种是在 CubeMX 的固件库管理界面直接在线下载另一种是先去官网下载 zip 包再本地导入。实操中我更推荐第二种因为在线下载经常因为网络原因中断而且下载到一半失败很难排查。本地导入的步骤是打开 CubeMX进入Help - Install New Libraries - From Local...选中你下载的 zip 包确认后等待安装完成。安装成功的标志是左侧器件系列的固件库版本号前面的 checkbox 变成绿色如果还是灰色说明安装没有成功。这里有一个非常关键的注意点只装 CubeMX 不装固件库包是无法生成初始化代码的。固件库里包含了该系列所有外设的 HAL 驱动文件、启动文件和链接脚本生成工程时 CubeMX 会从这些文件里复制对应的源码到你的工程目录。如果你发现生成工程时报错“firmware package not installed”不用怀疑就是固件库没装好。这个坑我见过太多次尤其是刚从 Keil 纯寄存器开发转过来的开发者经常忽略这一步。固件库版本选择上也没有太多玄学选一个稳定的正式版本即可。F4 系列的固件库迭代比较频繁但并不是越新越好。我的习惯是选一个已经发布半年以上的版本因为太新的版本可能存在文档和代码不一致的问题。2.3 Keil5 工程生成芯片选型与 FreeRTOS 勾选固件库就绪后新建工程时选择 STM32F427 对应型号然后配置时钟树。这套 Demo 的外设主要涉及 CAN1、TIM5、按键 GPIO 和若干 LED 指示引脚不需要太复杂的时钟配置。CubeMX 的时钟树页面会自动计算合法配置你只需要把主频拉高即可这也是 CubeMX 最省心的地方。工程配置里有几个容易忽略的设置项我一般按下面的方式处理在 Middleware 中勾选 FreeRTOS因为 Demo 的控制代码跑在默认任务里在 Project Manager 中设置 IDE 为 Keil MDK-ARM版本选择 V5因为这套 Demo 用的就是 Keil5生成代码时勾选“生成独立的 .c/.h 文件”方便后续往 BSP 组里加文件。生成工程之后用 Keil5 打开就能看到 CubeMX 帮我们分好的文件结构。Hardware 层是 HAL 驱动Middleware 组件里有 FreeRTOSARM core 下面是 CMSIS。这套结构看起来好像很复杂但你真正需要动的地方只有两个一个是添加自己的 BSP 文件另一个是改 FreeRTOS 任务函数。整个工程的代码层次清晰这也是为什么我推荐直接用 CubeMX 而不是手写初始化代码——手写一套 CAN 滤波器和时钟配置的工程量远比你想象的大。组件作用需不需要改HAL 驱动层ST 官方外设库不改FreeRTOS 组件实时操作系统内核不改BSP 组用户自己的板级支持代码重点改freertos.c默认任务与控制逻辑重点改3. 硬件接线与双模选型CAN 和 PWM 的物理连接差异3.1 CAN 模式接线2-Pin 信号线与 CAN1 端口如果你打算用 CAN 控制 C620 电调接线相对简单。C620 电调上有两组信号接口一组是 2-Pin 的 CAN 信号线一组是 3-Pin 的 PWM 信号线。使用 CAN 模式时只接 CAN 信号线把 2-Pin 插到 RM 主控板的 CAN1 信号端口上PWM 信号端口保持悬空不接。另外还需要把电调的三相输出线三色粗线和 7-Pin 数据线黑色排线连接到 M3508 电机。这个连接没有方向性问题三相线对应插紧即可7-Pin 数据线注意卡扣方向插反了是插不进去的硬怼会搞坏接口。电源方面C620 电调的电源线黑色连接到 RM 主控板的 24V 电源端口。这里我要多说一句确认电源极性再上电。虽然 C620 和主控板都有反接保护设计但反接瞬间的大电流冲击仍然可能损坏电源管理芯片。我见过有人因为电源线颜色混淆导致反接直接炸了主控板上的电容这个损失就太大了。3.2 PWM 模式接线3-Pin 信号线与 TIM5 引脚如果选择 PWM 控制模式接线就换成另一组将 3-Pin 的 PWM 信号线连接到 RM 主控板的 PWM 输出端口C1、C2、C3 或 C4CAN 信号端口不接。这套 Demo 使用的是 TIM5 定时器作为 PWM 信号源引脚位置在 RM 主控板上会有红色标注你照着板子上的丝印找就行。这里有一个容易翻车的细节PWM 模式下电调内部的调速逻辑和 CAN 模式完全不同。CAN 模式下电调对电机做的是电流闭环控制你给定的是电流值而 PWM 模式下电调对电机做的是转速闭环控制你给定的是 PWM 脉宽电调自己完成转速环的计算。也就是说PWM 模式不需要你在主控代码里再写 PID只要输出一个频率和脉宽符合要求的方波信号就行脉宽大小决定电机转速。两种模式的物理接线绝不能混接。如果你同时把 CAN 信号线和 PWM 信号线都接上电调会检测到两路信号并进入异常状态控制完全失效。这不是猜测电调使用说明书里明确写了这个限制。3.3 供电选型为什么推荐动力电池而不是电源箱供电部分有一个很多人不知道的坑C620 驱动 M3508 进行急停或反转时电机会把反电动势回灌到电源线上导致主控板电源端出现瞬间的高压尖峰。如果用的是电源箱供电电源箱的稳压电路响应速度可能跟不上这种瞬态变化尖峰电压会直接作用在主控板的电源保护二极管上严重时会击穿二极管。所以电调使用说明书特别建议使用动力电池供电。电池是电压源型供电设备能够吸收一定的反向电流对反电动势的耐受能力比大多数电源箱要强。我实测过同样一组急停指令电池供电时主控板电源电压的尖峰幅度明显小于电源箱供电。如果你手头只有电源箱也务必在电源入口加一个大容量的电解电容或者 TVS 管做吸收否则迟早要烧板。CAN 模式和 PWM 模式的接线对比如下项目CAN 模式PWM 模式信号线2-Pin CAN 线接 CAN13-Pin PWM 线接 C1~C4未用接口PWM 端口不接CAN 端口不接电调控制方式电流闭环转速闭环主控是否参与转速环需要写 PID不需要烧录后操作按键调速按键调速4. 工程结构与源码走向BSP 分组、CAN 收发与 PID 计算4.1 CubeMX 生成工程的代码层次与 BSP 组打开 CubeMX 生成的 Keil5 工程在左侧项目树里能看到几个分组Hardware 层放着 HAL 驱动Middleware 组件包含 FreeRTOS 内核源码ARM core 下面是 CMSIS 启动文件。这些都不需要手动修改尽量保持原样。Demo 程序额外添加了一个名为 Bsp 的分组里面放的才是用户自己的代码文件。BSPBoard Support Package直译是板级支持包通俗理解就是“跟这块板子强相关的驱动代码”。这套 Demo 在 BSP 组里放了bsp_can.c和bsp_can.h以及pid.c和pid.h。bsp_can负责 CAN 控制器的初始化、滤波器配置、报文发送和接收中断处理pid则是标准的 PID 控制器实现包括参数初始化和计算函数。这种分层方式值得借鉴。即使你后续不用这套 Demo 做项目只把它当成代码模板把外设驱动和业务逻辑分开组织调试时会轻松很多。我自己的习惯是BSP 组里只放和硬件强相关的文件控制算法放另一个组主流程放在 freertos.c 的任务函数里。4.2 bsp_can 源码走向滤波器初始化与收发格式CAN 通信的关键在于配置好接收过滤器让主控板只接收 C620 电调反馈的报文。电调在 CAN 模式下会周期性向上位机发送转速反馈报文格式是 8 字节的标准数据帧。常见做法是初始化时配置一个接收邮箱滤波器 ID 匹配电调的反馈 ID接收到数据后触发中断回调函数。这里给一个典型的滤波器初始化示意代码void CAN_Filter_Init(void) { CAN_FilterTypeDef can_filter; // 定义滤波器结构体 // 配置滤波器为掩码模式只接收指定 ID 的报文 can_filter.FilterBank 0; // 滤波器组编号 can_filter.FilterMode CAN_FILTERMODE_IDMASK; // 掩码模式 can_filter.FilterScale CAN_FILTERSCALE_32BIT; // 32 位宽度 can_filter.FilterIdHigh 0x0000; // 标准 ID 高 16 位 can_filter.FilterIdLow 0x0201; // 匹配 0x201 反馈帧 can_filter.FilterMaskIdHigh 0x0000; // 掩码高 16 位 can_filter.FilterMaskIdLow 0x0000; // 掩码低 16 位 can_filter.FilterFIFOAssignment CAN_RX_FIFO0; // 指定 FIFO can_filter.FilterActivation ENABLE; // 使能滤波器 HAL_CAN_ConfigFilter(hcan1, can_filter); // 写入硬件 }这段代码把接收滤波器配置为只接收 CAN 标准帧 ID 为0x201的报文也就是 C620 电调的转速反馈帧。FilterMaskIdLow全部置零表示精确匹配不关心其他 ID 的报文。如果你同时挂载多个电调每个电调有不同的反馈 ID就需要增加滤波器组或者修改掩码策略。接收中断回调的典型处理逻辑是读取 8 字节数据前 4 个字节是电机转速值单位 rpm大端序后 4 个字节是其他状态信息如转矩和温度。void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rx_header; // 接收帧头 uint8_t rx_data[8]; // 接收数据缓冲区 if (hcan-Instance CAN1) // 判断是哪个 CAN { // 从 FIFO0 读取一帧数据 HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, rx_header, rx_data); // 前两个字节是高字节后两个字节是低字节大端格式 int16_t speed (rx_data[0] 8) | rx_data[1]; // 存入全局变量供控制任务使用 motor_speed_rpm speed; } }这里要注意HAL_CAN_GetRxMessage的第三个参数是接收帧头结构体指针第四个才是数据缓冲区顺序不要搞反。我当时第一次用 HAL 库的 CAN 接口时就把这两个参数搞混了编译不报错但运行后收到的全是乱码。发送端的报文格式也有讲究。C620 电调接收的电流指令是 8 字节标准帧前两个字节是电流值有符号 int16范围 -16384 到 16384对应电调最大输出电流的百分比剩余字节可以全部填零。发送时要把这个int16拆成高字节和低字节再填入发送缓冲区。void CAN_Send_Current(int16_t current) { CAN_TxHeaderTypeDef tx_header; // 发送帧头 uint8_t tx_data[8] {0}; // 发送数据缓冲区 tx_header.StdId 0x200; // 电调指令帧 ID tx_header.IDE CAN_ID_STD; // 使用标准帧 tx_header.DLC 8; // 数据长度 8 字节 tx_header.RTR CAN_RTR_DATA; // 数据帧 tx_data[0] (current 8) 0xFF; // 电流高字节 tx_data[1] current 0xFF; // 电流低字节 // 发送并等待发送完成带超时 HAL_CAN_AddTxMessage(hcan1, tx_header, tx_data, tx_mailbox); }电流值的范围映射很关键。16384对应电调最大允许电流当你用 PID 输出直接赋值给这个变量时一定记得做限幅处理。如果 PID 输出超过16384电调收到的电流指令会溢出回绕本来想全速正转结果变成反转这个 bug 极其隐蔽。4.3 PID 结构体设计与 StartDefaultTask 主循环CAN 模式下电调是电流闭环也就是说电调内部只负责让实际电流跟随你的指令值不管转速是否准确。所以主控必须在外面套一个转速环读取电调反馈的实际转速和目标转速比较经过 PID 计算得到一个电流指令再发给电调。PID 控制器的核心结构体常见做法是这样的typedef struct { float kp; // 比例系数 float ki; // 积分系数 float kd; // 微分系数 float target; // 目标值 float actual; // 实际值 float error; // 当前误差 float last_error; // 上一次误差 float integral; // 积分累加 float output; // 输出值 // 抗积分饱和参数 float integral_limit; // 积分上限 float output_limit; // 输出上限 } PID_t;很多初学 PID 的人会在结构体里忽略积分限幅和输出限幅这是不对的。C620 电调运转过程中如果电机被外力卡住转速误差会长时间存在积分项持续累加一旦外力消失积分项已经积累了相当大的值电机就会猛地冲出去非常危险。加上integral_limit之后积分项最多累加到设定上限即使堵转一段时间释放后的响应也是可控的。下面是 Demo 程序在StartDefaultTask函数中的主循环逻辑我已经把按键扫描部分省略只保留核心的周期控制void StartDefaultTask(void *argument) { // 初始化 PID 参数 PID_Init(speed_pid, 1.0f, 0.05f, 0.0f, 3000.0f, 16384.0f); int16_t target_speed_rpm 0; // 目标转速 int16_t current_output; // PID 输出电流值 while (1) { // 每 10ms 执行一次转速闭环 PID_SetTarget(speed_pid, target_speed_rpm); PID_SetActual(speed_pid, motor_speed_rpm); PID_Calculate(speed_pid); // 计算 PID 输出 // 输出限幅防止发送非法电流值 current_output (int16_t)speed_pid.output; CAN_Send_Current(current_output); // 10ms 等待周期控制 osDelay(10); } }10ms 这个时间参数是这套 Demo 默认的控制周期。如果把周期缩短到 2ms转速环会更快地响应但 PID 参数也要等比调整否则系统容易震荡。我一个做云台控制的同事把周期改成 1ms 之后电机在低速时开始轻微抖动最后把 PID 的 P 降到原来的 1/5 才稳定下来。控制周期的选择和你的机械结构、电机负载都有关系不要无脑照搬。我在实际项目中通常会先按 10ms 跑通稳定之后再逐级缩短周期测试。4.4 宏定义切换CAN_CONTROL 与 PWM_CONTROL 的零成本切换Demo 程序在freertos.c文件里定义了两个宏#define CAN_CONTROL和#define PWM_CONTROL。通过注释和取消注释其中一个宏就能切换控制模式。这段代码的巧妙之处在于把两种模式的差异全部封装在宏开关里上层任务函数不用改逻辑。当你打开 CAN 控制宏时主循环里执行的是读取电调反馈转速、做 PID 计算、发送电流指令这一套流程当你切到 PWM 控制宏时主循环里就变成了直接设置定时器比较值来改变脉宽。// 在 freertos.c 中二选一只保留一个宏 #define CAN_CONTROL // #define PWM_CONTROL void StartDefaultTask(void *argument) { #ifdef CAN_CONTROL // CAN 模式电流闭环 外环 PID PID_Init(speed_pid, 1.0f, 0.05f, 0.0f, 3000.0f, 16384.0f); while (1) { // 转速环计算发送电流指令前面已展开 osDelay(10); } #else // PWM 模式直接设置脉宽 while (1) { // 根据按键值更新 TIM 比较寄存器 __HAL_TIM_SET_COMPARE(htim5, TIM_CHANNEL_1, pwm_pulse); osDelay(10); } #endif }这里有一个容易被忽略的细节切换宏定义之后一定要重新编译整个工程而不是只下载上一次的 hex 文件。宏开关是编译期行为不重新编译的话程序执行逻辑不会有任何变化。我看到不止一个人在 Keil 里点击了 Download 按钮但忘了重新 Build结果改了半天毫无反应还以为是代码写错了。PWM 模式下的脉宽数值需要根据电调说明书换算。标准 PWM 频率为 50Hz即周期 20ms脉宽 1ms 对应电机最低转速2ms 对应最高转速。换算成定时器比较值之前先确认 TIM5 的时钟预分频配置比较值的最大范围取决于定时器计数周期。你在 CubeMX 里配置 TIM5 时理论上会自动算好这些参数但自己心里要有数后面调试波形时用得上。5. 避坑与排查C620 调试中最容易翻车的四件事5.1 同时接 CAN 和 PWM 信号线导致电调控制失效现象电机完全不受控发送转速指令没反应电调上的指示灯状态异常。原因CAN 信号线和 PWM 信号线同时连接时电调会收到两路冲突的控制信号内部逻辑判断混乱直接进入保护状态。这不是某一根线接触不良的硬件问题而是模式选择冲突。解决拆掉其中一根信号线只保留你当前准备使用的模式对应接口。如果非要快速切换模式建议在代码里改宏定义的同时物理上也把另一根线拔掉。开关电源重新上电后再测试电调会恢复正常。5.2 电源箱供电导致主控板保护二极管击穿现象电机高速运转时突然急停或反转主控板电源指示灯熄灭板子上有明显的烧灼痕迹仔细看是电源入口处的二极管损坏。原因电机急停时反电动势会向电源侧回灌能量电源箱的稳压电路反应不够快电压尖峰直接打在保护二极管上超过其反向耐压值导致击穿。解决改用动力电池供电利用电池的低内阻特性吸收反向能量。如果条件限制只能用电源箱在电源入口并联一个大电容比如 470uF/50V和一个 TVS 二极管把瞬态尖峰钳位到安全范围内。5.3 只装 CubeMX 不装固件库包生成工程时报错现象在 CubeMX 里选好芯片型号、配好时钟点击生成代码弹窗提示固件库未安装生成失败。原因CubeMX 只是一个代码生成工具外设驱动文件存放在固件库包里不安装对应的包工具无法找到 HAL 库源码来复制到工程目录。解决从Help - Install New Libraries - From Local手动导入之前下载好的 zip 包等待安装完成后左侧芯片系列版本号变绿再重新生成工程。确认在正确的位置导入而不是解压后手动覆盖文件。5.4 CAN 模式电机高速震荡声音刺耳现象电机在目标转速附近来回波动转速越高振幅越大伴随明显的啸叫声。原因这个现象的根本原因是转速环导致的振荡可能组合了两三个问题一是控制周期和 PID 参数不匹配反馈回路没有足够的稳定裕度二是电流值发送时没有限幅出现溢出回绕三是 PID 积分项无限累加导致超调。解决先把 PID 参数调保守比如kp从 0.5 开始ki从 0.01 开始。然后把电流输出强制限幅到 ±16384 以内观察反馈转速是否正常。最后在 PID 计算里加上积分限幅比如限制在 ±3000。参数调整后从低速到高速逐步测试记录每组参数下的响应不急着用一套参数跑全速域。6. 验证控制效果的三个手段从“能转”到“转得好”Demo 程序跑通说明你已经完成了“能转”这一步。但如果是做机器人底盘或云台“转得好”才是真正拉开差距的地方。这一章分享三个我常用的验证手段不需要额外硬件只靠手头的 RM 主控板和 Keil 调试器就能完成。首先是周期准确性验证。CAN 控制依赖 10ms 周期这个周期是由 FreeRTOS 的osDelay(10)提供的但任务切换、中断处理都可能让实际周期偏离设定值。我的做法是在主循环里翻转一个空闲的 GPIO用示波器测量翻转频率。没有示波器的也可以用逻辑分析仪或者把计数器值通过串口发出来在 PC 端绘制曲线。如果实测周期波动超过 1ms说明任务里塞了太多耗时操作该精简就精简。其次是反馈数据合理性验证。CAN 反馈的转速数据是 int16 类型单位是 rpmM3508 的常用转速范围是 -3000 到 3000。在调试器里给motor_speed_rpm变量加一个 Watch用手拨动电机注意断电状态观察反馈值是否跟手同步变化。如果数值跳变剧烈或者符号时正时反大概率是字节序拼接反了。我习惯在串口打印里同时输出原始 8 字节数据和解析后的转速方便对照排查。最后是阶跃响应验证。给目标转速发一个从 0 到 2000rpm 的阶跃记录实际转速的上升曲线重点观察三个指标超调量是否超过 5%、调节时间是否小于 0.5 秒、稳态误差是否在 ±50rpm 以内。超调大就减小kp调节时间长就适当增大ki如果波形尾部有小幅震荡再把kd加一点。这些调整要在前文提到的积分限幅保护下进行否则很容易出现越调越乱的情况。做完这三个验证之后电机控制的底子就算打牢了。从那以后我每次拿到新的电调工程都会强制自己先走一遍周期测量、反馈校验和阶跃响应这三个流程。效果是显着的排查问题的速度快了很多。希望这套方法论对你有用。本文还有配套的精品资源点击获取