STM32移植DengFOC:无刷电机闭环速度控制实现
简介这是一份面向STM32开发者的DengFOC闭环速度控制移植工程基于STM32F4系列与HAL库实现适合正在学习无刷电机FOC控制、希望直接在Keil环境中运行验证的入门与进阶用户。压缩包共194个文件容量约1.42MB主要包含HAL库底层驱动如tim、i2c、uart、dma等、应用层C代码、头文件、Keil工程配置文件uvprojx、uvoptx以及hex/axf固件便于程序烧录与对照验证。资源目前已有523人学习下载。通过该工程可快速掌握DengFOC在STM32平台上的移植流程、速度闭环的代码结构以及HAL库外设初始化与中断处理逻辑工程内保留的map、dep等辅助文件也有助于排查链接错误和依赖关系适合在调试电机控制时作为参考模板。若与DengFOC主控板配合使用还能直接对比移植前后的控制效果是理解FOC开源方案落地的好样本。 DengFOC 这个项目玩无刷电机控制的同学应该都听过。它最大的特点就是“简单粗暴”——用极简的代码把云台无刷电机的 FOC 控制跑起来全部代码加起来就几百行从零开始跟着捋也不会绕晕。我这次做的事情就是把这套原本跑在 Arduino 上的代码完整迁移到 STM32 HAL 库环境下并且把控制目标从“转个角度”升级成“让电机稳定维持某个速度”也就是闭环速度控制。先说结论这套方案移植完成之后速度闭环在 1000rpm 以下可以做到 ±10rpm 左右的稳定度对于云台电机这种低惯量电机来说已经能覆盖大多数视觉云台、机械臂关节和桌面小机器人的需求。整个过程踩了不少坑尤其是相序校准和 PID 参数整定这两块下面我会把从环境配置到最终调试的完整过程都梳理一遍希望能给你省点时间。1. 项目概述与核心设计思路1.1 DengFOC 是什么它解决了什么问题DengFOC 是开源社区里一个很有名的无刷电机 FOC 控制项目作者把整套控制逻辑在 Arduino 平台上做成了一个轻量级库用户只需要调用几个简单函数就能让云台电机按指定角度转动或者按给定电压旋转不需要自己推导坐标变换和 SVPWM 这些繁琐的数学流程。我最初接触这个项目时的最大感受是它把 FOC 的复杂度充分封装起来了。传统的 FOC 控制链路里Clarke 变换、Park 变换、SVPWM、电流环、速度环、位置环听上去一个都不能少。而 DengFOC 走的是“简配”路线——在低速力矩控制场景下省掉电流闭环用电压直接驱动编码器只读角度不做复杂估算一样能获得平滑的旋转效果。这个设计思路对初学者特别友好但对想深入研究或者做产品的人来说也就意味着需要二次开发。闭环速度控制就是最典型的需求之一——原始库里面更多是提供“设置电压”和“读取角度/速度”这类原语真正要稳定维持某个速度还得靠自己在外面套一个速度环。1.2 为什么选择 STM32 HAL 而不是原版 ArduinoArduino 版本跑起来确实方便但短板也很明显。普通的 Arduino 开发板主频只有 16MHz 或者 48MHzFOC 主循环跑到 1kHz 就差不多了而高级一点的云台电机控制需要 8kHz 甚至 20kHz 的 PWM 频率再加上电流采样、编码器读取都要占用时间留给算法计算的余量非常紧张。STM32 的优势在于主频更高、定时器外设更丰富比如 F103 是 72MHzF4 系列能到 168MHz。更重要的是 HAL 库提供了统一的驱动接口之后从 F103 换到 F4、G4只需要改少量配置代码绝大多数底层逻辑不用重写。对于后续要做产品化或者功能扩展的同学这个可迁移性非常关键。我选择 STM32F103C8T6 作为验证平台虽然它只是入门级芯片但用来跑双环控制绰绰有余。如果后续要把电流闭环也加上F4/G4 系列会是更合适的升级路线。提示如果项目对实时性要求比较高建议选带硬件 FPU 的芯片比如 F4 系列。FOC 计算里涉及不少三角函数硬件 FPU 能给 PID 和坐标变换明显提速F103 上用软件算三角函数会吃掉不少 CPU 时间。1.3 闭环速度控制的核心思路闭环速度控制本质上是一个典型的负反馈系统编码器测量实际转速 → 计算与目标速度的误差 → PID 调节器输出控制量 → 驱动电机。在 FOC 场景下最终的控制量不是直接给 PWM 占空比而是对应到电机的电压矢量dq 坐标系下的 Uq。DengFOC 原始库里其实没有内置完整的闭环速度控制所以我移植过程中最重要的补充就是自己实现了速度 PID 环并把 PID 输出接到原有的电压控制接口上。速度环的采样频率直接影响控制效果。我使用的是 1kHz 的速度环频率——编码器每 1ms 更新一次实际速度PID 计算一次输出。在低速50rpm 以下和高速600rpm 以上工况下这个频率都够用没必要盲目调高。速度环频率太高反而容易引入编码器量化噪声让电机出现高频抖动。2. 硬件平台搭建与 CubeMX 工程配置2.1 电机、编码器与驱动板选型一套完整的最小系统包括三部分电机、编码器、驱动板。电机的选择上我用的是常见的 2204 云台无刷电机。这种电机定子槽数多、齿槽转矩小低速力矩平稳非常适合 FOC 控制。供电电压是 12V和 DengFOC 官方推荐的参数一致。如果你手头只有 2208 或者 2804 这类电机也可以跑只是 PID 参数和电流上限要重新调。编码器选择 AS5600 磁编码器I2C 接口12bit 分辨率能读到 0.0879° 的角度精度对于速度控制完全够用。需要注意 AS5600 必须安装在电机转子轴的正后方且磁铁和芯片之间的距离要均匀否则读取的角度会有周期性误差导致速度控制不稳定。驱动板我使用了一体化的 FOC 驱动小板板载三相全桥和电流采样电阻。这类板子内部集成了栅极驱动电路STM32 只需要输出三路 PWM或者六路互补 PWM就能驱动电机。具体型号上我没有特别纠结只要能支持 12V/2A 左右持续电流的基本都可以用。我自己用的是带 8301 驱动芯片的小板和 DengFOC 推荐的方案一致驱动能力足够。2.2 STM32CubeMX 中定时器与 PWM 配置在 CubeMX 里新建工程芯片选择 STM32F103C8T6需要依次配置以下关键外设TIM1配置为 PWM Generation CH1、CH2、CH3用于三相输出。I2C1连接 AS5600 编码器。USART1串口输出调试信息。系统时钟使用外部晶振 HSE 8MHz通过 PLL 倍频到 72MHz。PWM 频率我设置为 20kHz 中心对齐模式计算方式很简单f_pwm 72MHz / (PSC1) / (ARR1)我实际用的配置是 PSC 1ARR 179972M / 2 / 1800 20kHz。中心对齐模式下一个 PWM 周期对应两次计数溢出所以实际 FOC 更新频率也是 20kHz。速度环则通过另一个定时器中断跑 1kHz两者互不干扰。注意如果使用三电阻采样方案ADC 采样点和 PWM 边沿要严格对齐不然采到的不是母线电流而是续流反弹的杂波。这也是很多人移植之后电流波形乱成一团的根本原因。2.3 I2CAS5600与串口调试配置AS5600 的 I2C 地址默认是 0x36CubeMX 里把 I2C1 配置为标准模式 100kHz 即可。我要强调一点读取角度时速度快不快不是关键稳定性最重要。AS5600 的输出是 12 位角度数据需要连续读两个字节再拼接中间不能被打断。串口配置很简单115200-8-N-1主要用来输出目标速度、实际速度、PID 输出这些调试数据。这部分千万别省在调 PID 的时候能帮你节省大量时间。3. 核心代码的移植与实现3.1 源码结构与关键接口改写DengFOC 的 Arduino 版源码组织得很简洁核心文件包括DengFOC.h/DengFOC.cppFOC 控制主逻辑包括初始化、设置电压、设置角度、读取角度/速度。DengFOC_Current.h/DengFOC_Current.cpp电流闭环相关。DengFOC_Utils.cpp工具函数集合。移植到 HAL 时我保留了核心数学结构把底层硬件依赖的接口单独抽出来重写。替换原则如下Arduino 原始接口STM32 HAL 替代说明analogWrite(pin, val)__HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_x, val)PWM 占空比写入Wire.beginTransmission()HAL_I2C_Mem_Read()I2C 读取编码器寄存器micros()HAL_GetTick() SysTick 扩展毫秒级时间戳delay()HAL_Delay()延时Serial.print()HAL_UART_Transmit()串口输出所有硬件相关部分集中到一个bsp_motor.c文件里算法部分保持独立这样后续换芯片只需要改底层接口算法逻辑一行都不用动。这个分层的思路在工程上是非常重要的——我见过很多移植项目把硬件调用和算法耦合在一起最后换一个平台就要重写一大半非常痛苦。3.2 AS5600 编码器读取的 HAL 化原始 Arduino 代码读取 AS5600 时调Wire库我改成 HAL 后典型代码如下uint16_t AS5600_ReadAngle(void) { uint8_t buf[2]; HAL_I2C_Mem_Read(hi2c1, 0x36 1, 0x0E, I2C_MEMADD_SIZE_8BIT, buf, 2, 100); return (buf[0] 8) | buf[1]; }注意0x36 1这个细节——HAL 的 I2C 地址需要 8bit 格式而 Arduino Wire 库直接传 7bit 地址这是第一次移植时最容易踩的坑。如果这里忘了移位I2C 通信会完全不通排查半天也找不到原因。实际使用中我发现直接读取的原始角度包含安装偏移不能直接当作电角度用。需要先做零点校准具体做法在第四章详述。3.3 SVPWM 与正弦波驱动的 HAL 实现DengFOC 的电压控制部分核心是根据转子电角度把期望电压矢量分解到 dq 轴再通过逆 Park 变换得到 alpha-beta 坐标最后用 SVPWM或者简化正弦波调制输出到三相。在 STM32 上实现时需要注意定时器是中心对齐模式通道比较值对应的是占空比。我写的核心换算逻辑是// 电角度 angle_el 范围 0~2PI // Uq: q轴电压分量, Ud: d轴电压分量通常为0 float ua Uq * cosf(angle_el) Ud * sinf(angle_el); float ub Uq * cosf(angle_el - 2.094f) Ud * sinf(angle_el - 2.094f); float uc Uq * cosf(angle_el 2.094f) Ud * sinf(angle_el 2.094f);这里的 2.094 是 120° 的弧度值。三相电压算出来后还要做中点偏置处理让三路 PWM 的占空比始终在合理范围。实际实现时我把角度和电压范围都做了标准化角度用 0~2π电压用 -1~1 表示满量程百分比这样上层代码逻辑清晰也方便调试。这里有一个很实用的经验先把开环正弦波跑顺了再谈闭环。我第一次移植时直接上了速度闭环结果电机嗡嗡响就是不转后来退回开环先确认三相 PWM 和电角度计算正确才把问题定位到相序上。3.4 速度闭环 PID 的实现速度 PID 我直接自己写的没用第三方库核心代码如下typedef struct { float kp; float ki; float kd; float integral; float prev_error; float out_limit; float integral_limit; } pid_controller_t; float PID_Update(pid_controller_t *pid, float target, float current) { float err target - current; pid-integral err; // 积分限幅防止积分饱和 pid-integral fmaxf(fminf(pid-integral, pid-integral_limit), -pid-integral_limit); float derivative err - pid-prev_error; pid-prev_error err; float out pid-kp * err pid-ki * pid-integral pid-kd * derivative; // 输出限幅 out fmaxf(fminf(out, pid-out_limit), -pid-out_limit); return out; }积分限幅非常重要这个我在调试里吃过亏。没限幅的积分在电机堵转时会持续累积PID 输出直接飙到满电压等堵转解除电机会猛冲出去非常危险。输出限幅则是为了保护驱动板和电机不合理的输出值在进入 SVPWM 之前就应该被拦下来。速度环的调用位置在 1kHz 定时器中断里流程是读角度 → 计算速度 → 更新 PID → 调用 DengFOC 的电压控制接口。整个循环的执行时间在 F103 上大约是 30~50µs只占 1ms 中断周期的 5%留有很大余量即使后面再加电流环也完全跑得动。4. 调试过程与常见问题排查4.1 零点校准与电角度偏移这是整个移植过程最容易踩的坑也是必然要踩的坑。FOC 算法的坐标变换必须知道电机转子的电角度而 AS5600 直接读出来的是机械角度两者之间的差值就是电角度偏置 offset。校准方法很直接给电机三相中的一相通直流电让转子强制转到零位然后读编码器角度这个值就是电角度偏置。具体操作时我先给 A 相和 B 相同时通电相当于在 A-B 之间加一个直流电压矢量转子会快速锁到某个稳定位置此时读 AS5600 的原始值记录下来。需要注意的是这个校准必须在确定相序正确之后再做。如果相序反了转子不会锁到预期位置而是往反方向偏这时候校准出来的 offset 是错的闭环一转就跑飞。4.2 电机相序与正反转问题相序问题是 FOC 移植中的经典问题。DengFOC 代码默认驱动板 U、V、W 三相对应电机 A、B、C 三相。如果你在接线时把任意两相对调了整个坐标变换的旋转方向就反了电机要么嗡嗡响不转要么反转后飞车。我的排查方法是先用开环电压模式让电机缓慢转动。如果代码里给定一个正电压矢量电机却反转或者卡死就把三相中的任意两相对调再试。多数情况下两三次交换就能找对。实际操作中我习惯把相序做成PWM_U、PWM_V、PWM_W三个宏定义调试时只改宏定义比反复焊接和插拔线快得多。这也是一种工程思维——所有可能频繁调整的东西都集中抽象到一处改起来风险最小。4.3 PID 参数整定流程速度环 PID 参数整定我总结了一套适合自己的步骤这套方法不依赖什么高深理论纯靠观察速度响应曲线就能完成。Kp 从小往大加先设置 Ki0、Kd0目标速度设为 300rpm。Kp 从 0.01 开始加观察速度响应。如果转速一直振荡说明 Kp 偏大如果响应很慢说明 Kp 偏小。我最终停在 0.12 左右。加 Ki 消除稳态误差Kp 定下来后加 Ki 从 0.001 开始每次按比例往上试。当 Ki 太大时会看到速度在目标值附近低频振荡。我最后用的是 0.05。Kd 尽量不加速度环由角度微分而来本身对噪声相当敏感Kd 加多了很容易把编码器量化噪声放大成高频抖动。我最后的 Kd 是 0完全不用微分项。提示不同电机的参数差别很大我给的数值只适用于 2204 云台电机 AS5600 12V 供电。换电机后一定要重新整定不要指望一组 PID 通吃所有电机。4.4 低速抖动和高速失步的排查调试中最大的两个现象低速抖动、高速失步。低速抖动的主要原因是编码器分辨率不够。AS5600 虽然有 12 位分辨率但在低速时每个控制周期内角度变化很小速度计算角度差分的量化噪声非常大导致 PID 输入噪声剧烈电机表现为“咔咔”抖动。解决思路有两个一是提高速度环采样周期用多个周期内的角度增量来算平均速度二是做一阶低通滤波。我速度环里加了一个 10Hz 的低通滤波效果立竿见影。具体滤波系数可以根据实际噪声水平调整太大会让速度响应变慢太小则滤波效果不明显。高速失步的常见原因是母线电压不够。FOC 高速模式下反向电动势会逐渐接近母线电压逆变器“没电压可压”最终失控。解决方案是提高母线电压比如 12V 改 24V或者限制速度上限。如果硬件上没法改就只能让速度环的输出饱和限制在合理范围。还有一个容易被忽略的点中断嵌套。调试中我发现如果把串口打印放在速度环中断里会阻塞中断执行导致 FOC 控制频率忽高忽低速度自然稳不住。后来我把调试输出改到主循环循环或者用 DMA 方式发送就不再影响控制周期了。5. 实操过程中的几点补充整个项目从开始到跑通我大概花了两天时间其中一半时间都花在相序校准和 PID 整定上。经历过之后我对 DengFOC 那句“简配算法、专注可靠”的理解更深了一层——FOC 的数学并不复杂真正复杂的往往是你对一个控制系统的理解和耐心。移植完成后这套框架让我后面再验证不同的电机和负载时非常顺手。把 DengFOC 的核心算法抠出来底层用 HAL 重新包一层后续换到 STM32G4 上也基本只是改改配置文件算法完全不用动。最后再分享一个小技巧把 AS5600 的角度数据直接通过串口打印出来用电脑上的波形工具实时画图。你可以直接看到角度从 0 到 2π 的锯齿波变化以及速度曲线的毛刺分布。调 PID 的时候眼睛盯着波形变化比看一串串的数字要直观得多。这套调试流我从 FOC 移植一直用到现在屡试不爽。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →