简介:面向STM32F1嵌入式开发者的FOC电机控制解决方案,聚焦永磁同步电机(PMSM)应用,整合中文用户手册与可移植固件库,帮助开发者从原理到代码完整落地。压缩包共241个文件、约2.39MB,以C源码和头文件为主,辅以ICF链接配置、EWP/EWD工程文件、LD脚本、LIB静态库及汇编文件等,可在IAR或Keil等常见IDE中直接编译调试。已有1114人学习下载,适合正在调试电机驱动、希望快速上手FOC的软硬件工程师。手册系统讲解FOC理论基础、库整体结构、参数配置、中断处理及错误管理,并给出初始化示例;随附的v2.0固件库包含经测试的PMSM FOC示例工程,涉及电流环控制、SVPWM生成、单电阻采样、转子位置估算等关键模块,开发者可基于示例代码快速移植,再结合具体电机调整观测器与PID参数,从而缩短开发周期并提升系统稳定性。
1. stm32f1 跑 FOC:不是能不能,是换一套控制思路
很多嵌入式开发者对 FOC 的第一反应是“F1 太弱跑不了”。这个印象来自 Cortex-M3 没有 FPU,但真实瓶颈从来不在乘法器上,而在外设协同。一套靠谱的 stm32f1 FOC 库,内含的操作手册和示例源码,会告诉你怎样用 TIM1 的三相互补 PWM、ADC 注入组和 Park 变换,在 72MHz 下把电流环跑进几十微秒。FOC 不是数学竞赛,它是一笔资源账:你会算账,F1 就是高性价比方案;不会算账,换 F4 也一样翻车。这篇笔记适合做过 F1 项目、想上 FOC 却还没跑通闭环的嵌入式开发工程师,不谈抽象理论,只讲从拆库到调参的落地路径。
2. 拆开这份 FOC 库:从坐标变换到 F1 的算力账
FOC 的控制对象不是“三相电压”,而是“旋转的磁场”。要让定子磁场始终和转子磁极保持一个合适的角度,就得把三相定子电流从静止坐标变换到一个跟随转子旋转的坐标里。这个过程拆成两步:Clarke 变换把 Ia/Ib/Ic 变成静止 α-β 轴上的分量,Park 变换再把 α-β 变成旋转 d-q 轴上的直流分量。到了 d-q 轴,你就能像控制直流电机一样,分别控制“励磁电流 Id”和“转矩电流 Iq”,输出 Vd/Vq 后再逆变换回三相,用 SVPWM 生成占空比。这套思想放在任何芯片上都一样,F1 的区别只是怎么把每一步算得快一点。
2.1 先算一笔账:72MHz 跑矢量控制到底紧不紧
F1 的 Cortex-M3 没有硬件浮点单元,所有 float 运算都由软件库模拟,单次乘法加法的开销比 M4 大不少。但 FOC 电流环的计算量其实没有很多人想的那么夸张:一次完整的“采样→Clarke→Park→两个 PI→逆 Park→SVPWM 查表”,在 72MHz 主频下用软浮点,典型开销大约 3000 到 5000 个周期,也就是 40 到 70 微秒。如果你把 PWM 频率设在 15kHz,中断周期是 66.7 微秒,刚好卡在临界线上;把优化等级打开、三角函数用查表,压到 40 微秒以内是常态。所以这不是“能不能跑”的问题,而是“怎么把每一拍的时间分配好”的问题。
下面这张表是按 15kHz 电流环中断做的典型算力预算,不同库实现会有差异,但量级可以参考:
| 计算段 | 典型开销(72MHz 软浮点) | 说明 |
|---|---|---|
| 读取 ADC 与零漂修正 | 4~8μs | 含两路电流换算,与采样电阻标定相关 |
| Clarke + Park 变换 | 6~10μs | 用 sin/cos 查表比调用 libm 快 5 倍以上 |
| 电流环两个 PI | 8~12μs | d 轴和 q 轴各一个,含积分限幅 |
| 逆 Park + SVPWM | 8~14μs | 含扇区判断与三相占空比更新 |
| 其他开销 | 4~8μs | 变量搬运、保护逻辑、中断进出 |
合计 30~52μs,留出 20% 余量给速度环或通信任务。如果你把 PWM 频率提高到 25kHz 以上,再叠加所有中断都开最高优先级,F1 就开始吃力了。我一般建议 F1 上的 FOC 电流环不要超过 20kHz,PWM 频率选 15kHz 是最平衡的起点:听不到啸叫,算力余量也够。
2.2 库的文件层:操作手册和示例源码往往各管一摊,先找三个文件
这套库的目录通常分成四块:doc/放操作手册,描述接线、引脚映射和参数表;src/放电机控制算法本体,包括 PI、Clarke/Park、SVPWM;app/放用户层逻辑,比如速度设定、启停状态机;project/是 Keil 或 IAR 工程文件。拿到库先别急着编译,而是按“操作手册 → 引脚表 → 算法文件”的顺序去读。操作手册里最值钱的就是那几张表格,不是原理章节。
翻开代码目录后,我建议你优先找三个文件:一个是 PWM 定时器初始化文件,里面定义了 TIM1 的三相互补输出;一个是电流采样文件,里面定义了 ADC 的触发源和转换结果读取;一个是 FOC 算法主文件,里面会有foc_run()之类的中断主函数。如果这套库里这三个文件命名和位置都很清晰,移植起来会省很多事;如果所有代码都堆在一个 main.c 里,先把里面的TIM1_UP_IRQHandler函数圈出来,那是整套系统的主动脉。
2.3 一个最小可读的 FOC 中断骨架:每一拍在算什么
为了让你对“电流环中断里到底要干什么”有个具体印象,我把典型 FOC 库的中断主流程简化成下面这段。不同库命名有差异,但顺序和依赖关系完全一致:
void TIM1_UP_IRQHandler(void) { // 1. 读取 ADC 转换结果,至少两路相电流 i_a = (adc_buf[0] - offset_a) * current_scale; i_b = (adc_buf[1] - offset_b) * current_scale; // 2. Clarke 变换: i_a/i_b -> i_alpha/i_beta i_alpha = i_a; i_beta = (i_a + 2.0f * i_b) * 0.57735f; // 1/sqrt(3) // 3. 读取电角度 theta,做 Park 变换得到 i_d / i_q theta = encoder_get_elec_angle(); // 0 ~ 2PI i_d = i_alpha * sin_cos(theta).cos + i_beta * sin_cos(theta).sin; i_q = -i_alpha * sin_cos(theta).sin + i_beta * sin_cos(theta).cos; // 4. d/q 轴电流环 PI,输出 Vd_ref / Vq_ref v_d_ref = pi_run(&pi_d, id_ref, i_d_filt); v_q_ref = pi_run(&pi_q, iq_ref, i_q_filt); // 5. 逆 Park 变换得到 V_alpha / V_beta v_alpha = v_d_ref * sin_cos(theta).cos - v_q_ref * sin_cos(theta).sin; v_beta = v_d_ref * sin_cos(theta).sin + v_q_ref * sin_cos(theta).cos; // 6. SVPWM 计算三相占空比并写入定时器比较寄存器 svpwm_update(v_alpha, v_beta, vbus); }逻辑说明:第 1 步把 ADC 原始值换算成安培,offset_a/offset_b是零漂校准值,没有它你会看到“电机没转但电流很大”的假象。第 2 步的 Clarke 变换只用了两相电流,第三相由基尔霍夫定律算出,这是三电阻采样法的常规操作。第 3 步的sin_cos(theta)应当走查表,不要在中断里调用cos()/sin()库函数——那会让单次中断时间翻倍还不止。第 4 步的电流环 PI 是核心,两个 PI 的输出限幅必须单独设置,后面参数章节细说。
参数说明:current_scale与采样电阻阻值、运放增益和 ADC 基准电压有关,必须按键位实测标定,不能只看手册默认值。vbus是母线电压,SVPWM 输出占空比会依据它做归一化;如果这个参数填错,表现为“低速正常、高速转不动”。整体来看,这段代码在任何 Cortex-M3 上都能跑,只要你把中断优先级设置得比主循环高,同时保证不会再被其他中断随意打断。
3. 把示例源码在 Keil 里跑通:引脚核对、时钟配置和启动顺序
很多人在这一步就卡住了——烧录进去,电机不转,蜂鸣器乱叫,然后开始怀疑库有问题。实际八成是板子和示例源码的引脚不一致。F1 的引脚功能是固定的,TIM1 的互补输出通道焊死在特定引脚上,这既是限制也是好事:只要核对一次,后面都是确定的。
3.1 第一件事:把手册里的硬件映射表变成你自己的板子引脚表
操作手册通常会给一张硬件映射表,格式类似“TIM1_CH1→PA8、TIM1_CH1N→PB13、TIM1_CH2→PA9、TIM1_CH2N→PB14、TIM1_CH3→PA10、TIM1_CH3N→PB15、ADC1_IN10→PC0、ADC1_IN11→PC1、霍尔 A→PB4…”。这套映射基于 F1 的 TIM1 高级定时器,芯片引脚已经固化,你只能顺着用,不能像其他单片机那样自由映射。所以第一步就是打开原理图,对照这张表确认你的板子是不是真的把这些引脚接到了栅极驱动或传感器上。
最常见的翻车点有两个。第一个是三相桥臂的下管 PWM 接到了普通 GPIO 而不是互补通道,导致上下桥臂不同步;第二个是霍尔/编码器接口用了另一组定时器,中断和 TIM1 的更新事件抢优先级。前者会让电机“吧嗒吧嗒”响但不转,后者会让电角度读取偶尔跳变,电流环跟着抖动。如果你用的是自制板,建议核对完引脚后,在gpio_init()里用代码把每一路 GPIO 的复用功能打印出来,而不是只靠眼睛看原理图。
3.2 时钟树:F1 的 72MHz 怎么给电机控制留“余粮”
FOC 对时钟的要求核心是“确定性”:TIM1 的时钟、ADC 的时钟要和 PWM 周期严格同步,任何异步都会造成采样点漂移。F1 的典型配置是外部 8MHz 晶振,经 PLL 倍频到 72MHz,APB1 36MHz,APB2 72MHz。TIM1 挂在 APB2 上,要注意一个细节:当 APB2 预分频不为 1 时,TIM1 的时钟是 PCLK2 的两倍,所以 PCLK2=72MHz 时 TIM1 实际也是 72MHz。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_ADC1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); /* 速度环定时器 */ RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_9); /* 8MHz * 9 = 72MHz */ RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); RCC_HCLKConfig(RCC_SYSCLK_Div1); RCC_PCLK1Config(RCC_HCLK_Div2); /* APB1 = 36MHz */ RCC_PCLK2Config(RCC_HCLK_Div1); /* APB2 = 72MHz */逻辑说明:先把系统时钟锁定在 72MHz,再单独打开 TIM1 和 ADC1 的 APB2 时钟。第 4 行的RCC_PLLMul_9是 8MHz 晶振的倍频系数;如果你的板子上是 12MHz 晶振,这个值必须改成RCC_PLLMul_6,否则系统时钟会跑到 108MHz。F1 最高只能承受 72MHz,超频后电机转得飞快但 ADC 转换值不稳定,看起来像 FOC 算法有问题,实际是芯片过热或时序异常。遇到任何“说不清道不明”的异常,第一反应永远是检查时钟配置。
参数说明:RCC_PCLK1Config(RCC_HCLK_Div2)里的 Div2 很关键。APB1 上的定时器 TIM2/3/4 在 APB1 预分频不为 1 时同样会翻倍,所以 TIM3 实际也是 72MHz。如果你在速度环里用了 TIM3,要按 72MHz 算周期;如果按 36MHz 算,速度环的采样频率就会实际翻倍,导致 PI 参数扭出虚高值。
3.3 上电时序:母线电压、传感器对齐、使能 PWM
示例源码里main()的启动顺序有讲究,不是“初始化→while”两段式就完事。一个相对可靠的顺序是:系统时钟初始化→GPIO/定时器/ADC 初始化→编码器/霍尔接口初始化→读取当前电角度存入 FOC 结构体→等待母线电压稳定→校准 ADC 零漂→设置电流环目标值为 0→使能 TIM1 互补 PWM 输出→进入主循环处理速度指令和状态机。
这里面最容易犯的错是:还没等母线电容充满就使能 PWM。母线电压偏低时,FOC 算出来的占空比对应电压会失真,电机可能猛冲一下。解决方法是加一个母线电压检测,使用 ADC 另一通道采样母线电压,低于安全阈值时禁止 PWM 输出。很多库会在操作手册里写“上电后 100ms 内不要使能 PWM”,就是给电容充电留时间。
另外,有传感器的情况下,使能 PWM 之前必须知道转子位置。如果你在位置未知时直接给 Iq,电磁力方向和转子磁极可能相反,电机会被反向猛拉一下,轻则抖一下,重则过流。无感模式下,库通常会先用“预定位”给 d 轴一个固定电流,把转子吸到零位,再开环强拖一段,最后切入闭环。新手第一次跑无感时如果发现电机一启动就抽搐,多半是预定位时间太短或电流太小,转子没来得及对齐就被拖走了。
3.4 Keil 工程里三个容易被忽略的开关
FOC 库对编译环境比较敏感。我拿到示例工程后会第一时间检查三个设置:优化等级、MicroLib、C99 支持。
FOC 中断里的浮点运算如果没有开优化,Keil 默认的 O0 会让单次中断时长飙到 100 微秒以上,直接超过 15kHz 的周期。工程选项中C/C++ → Optimization建议设成-O2或-O3,但-O3有时会改变某些变量的读取时序,如果你发现开 O3 后电流波形反而不如 O2 稳定,退回 O2 即可。
第二个是 MicroLib。如果工程用printf输出调试信息到串口,MicroLib 会让 printf 的体积小很多,但它对浮点格式化的实现比标准库弱,打印速度和精度都有差异。嵌入式开发里这不是大问题,但不要因为它导致串口输出延迟来干扰 FOC 主循环。
第三个是 C99。FOC 库里大量使用“在 for 循环内声明变量”的写法,Keil 默认 C90 模式下会报一堆编译错误。在 Keil 的C/C++页面勾选C99 Mode,或者用--c99,能省掉你改几十处声明的功夫。这三项全部设置好之后,再点击编译,才算真正进入调试阶段。
4. 操作手册里最值钱的三张表:电流环、速度环、PWM 与采样参数
操作手册正文通常不跟你讲控制理论,但它一定会给参数表。这些表就是这套库的“调试地图”。很多使用者跳过了这些表,一上来就用默认值,然后因为电机转不动去改 SVPWM——这是最典型的徒劳。下面按我的调试顺序讲四块参数,前三块对应手册里最常见的三张表。
4.1 电流环 PI:Kp、Ki、积分限幅和“为什么不用 D”
电流环是 FOC 的内环,它决定了力矩响应的快慢和稳定性。常见库用 PI 而不是 PID,原因很简单:电流采样噪声大,微分项会把噪声放大,导致 PWM 占空比在每一拍里剧烈跳动;而比例项本身已经能提供足够的动态响应。所以你别一上来就想着加 D,先把 P 和 I 调明白。
一个典型的电流环初始化大概长这样:
pi_init(&pi_d, 0.35f, 0.015f, -1.2f, 1.2f); /* Kp, Ki, 输出下限, 输出上限 */ pi_init(&pi_q, 0.35f, 0.015f, -1.2f, 1.2f);逻辑说明:Kp 决定响应速度,Ki 消除稳态误差,输出限幅的单位是电压伏特(或归一化后的 0~1 值)。这里的输出不是 PWM 占空比,而是 d/q 轴电压指令,后面还要经过逆 Park 和 SVPWM,所以限幅值必须和母线电压匹配。比如母线 24V,SVPWM 最大可输出电压约 0.577 倍母线电压(约 13.8V),但你通常还要留 10% 余量,限幅设在 12V 左右比较安全。
参数说明:Ki 的积分项一定要限幅,否则就是“积分饱和”。表现是:电机堵转时 Iq 误差持续累计,输出顶到上限,恢复转动后电机猛冲一下。应对办法是“积分分离”:误差大时停止积分或反向削减积分值。很多库会内置这个处理,如果没有,你可以在pi_run()里手动加一句“当输出达到限幅时,把积分项往回调一点”,这一行代码能少烧很多板子。
4.2 PWM 频率与死区时间:静音、效率、稳定性的三角平衡
F1 的高级定时器 TIM1 可以输出三对互补 PWM,并带硬件死区。PWM 频率的选择直接影响三个东西:噪声、开关损耗和电流采样窗口。15kHz 是一个在工程上非常常见的起点——高于人耳最敏感区间,不算太吵,开关损耗也还在小功率 MOSFET 的承受范围内。如果你用 20kHz,算力余量变少,但噪声会更小;如果你驱动的电机电感特别小,20kHz 反而有助于降低电流纹波。
死区时间的设置则是安全性的关键。死区太小,上下桥臂直通,烧 MOSFET;死区太大,电流波形畸变,低速扭矩脉动大。一个合理的起点是 1μs。如果你的功率管是 MOSFET 且驱动电压足够,0.5μs 也可以;如果是 IGBT 或驱动芯片本身有延迟,建议 1.5μs 起步。
怎么判断死区够不够?上电不使能 PWM,测母线待机电流;然后使能 PWM 但不给电流指令,再测母线电流。如果待机电流从几毫安涨到几十毫安,说明有直通或换流重叠,死区应该加大。注意这不一定是死区本身不够,也可能是 SVPWM 在占空比极限时没有正确处理。所以死区参数和 PWM 频率要一起看,不要单独调。
4.3 ADC 采样:采样点、转换时长、电流重构与零点校准
ADC 采样质量是整个 FOC 的地基。三电阻采样法要在 PWM 中心点附近、下桥臂都打开时采样,这样相电流才会通过下桥臂的采样电阻。为了做到这一点,ADC 必须由 TIM1 触发而不是软件随意触发。常见配置如下:
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC4; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_SampleTime = ADC_SampleTime_239Cycles5;逻辑说明:ADC_ExternalTrigConv_T1_CC4表示由 TIM1 的第 4 通道事件触发 ADC 采样。TIM1 的 CH1/CH2/CH3 用来发 PWM,CH4 不输出,只作为触发源,在 PWM 周期的指定时刻产生 CC4 事件。ContinuousConvMode = DISABLE必须显式关闭,否则 ADC 会按照自己的节奏连续转换,完全失去与 PWM 的同步。
参数说明:采样时间ADC_SampleTime_239Cycles5在 F1 的 12MHz ADC 时钟下约为 20μs。如果换成ADC_SampleTime_13Cycles5,采样时间缩短到约 1.1μs,转换速度更快,但抗噪声能力下降。大功率板子采样电阻上的电流信号更“脏”,采样时间宜长不宜短;小电感低压板才适合短采样时间。
另一个关键参数是“最小采样窗口”。当某相占空比接近 0% 或 100% 时,对应桥臂的导通时间太短,电流重构会失效。常见库的处理是“移相”或“注入零矢量”,让三路 PWM 中心稍微错开,人为拉长采样窗口。如果你的库没有做这个处理,低速大电流时会发现电流反馈突然跳变,那就是重构盲区。这时候别怀疑电流环参数,先把 PWM 最大调制比限制在 0.9 左右。
最后是零点校准。上电后让电机静止,不需要任何 PWM,连续采 100 次 ADC 取平均,这个平均值就是零点偏置。很多库会在初始化时自动做一次校准,但如果你换了板子、运放增益变了,零点偏置也会变。经验是:每换一次硬件,手动重新校准一次;不要信任固件里写死的偏移值。
4.4 速度环和启动斜坡:给电流环一个“不惊吓”的参考值
速度环是外环,它的输出作为 Iq 的参考值。速度环的带宽一般只有电流环的 1/10 到 1/20。电流环 15kHz 时,速度环采样频率 1kHz 是一个稳妥的选择。速度环 PI 的 Kp 同样不能过猛——如果速度环一次更新就给出一个大台阶的 Iq 目标值,电流环会跟着剧烈波动,听起来就像电机在“咳嗽”。
启动斜坡也很重要。典型做法是速度参考值不是直接跳变到目标转速,而是一个斜坡发生器:speed_ref += ramp_rate * dt,限制转速每秒最大变化量。比如设定“每秒转速变化 10% 额定转速”,启动电流就会平缓很多。这样不仅能保护功率管,还能避免低速时因为采样窗口问题导致的重构失败。如果你发现电机一启动就过流报警,先检查是不是速度参考值斜坡太快。
下面这张表是我在 F1 上调 FOC 时常用的初始值,供你对照手册默认值参考:
| 参数 | F1 常见起点 | 调整方向 |
|---|---|---|
| PWM 频率 | 15kHz | 噪声大/电感小可调 20kHz,算力不够则降 10kHz |
| 死区时间 | 1.0μs | 待机电流大则加大,波形畸变则减小 |
| 电流环 Kp | 0.3~0.6 | 响应慢则增大,啸叫或抖动则减小 |
| 电流环 Ki | 0.01~0.02 | 稳态误差明显则增大,超调大则减小 |
| 速度环频率 | 1kHz | 跟随慢可提高到 2kHz,但注意中断对齐 |
| 速度环 Kp | 0.05~0.15 | 先从小往大加,观察是否抖动 |
这个表不是铁律,不同电机参数差异很大,但它能让你少走很多弯路。如果你照着这个范围调还是抖,问题多半不在 PID,而在采样时序和电角度对齐上。
5. FOC 调试避坑:转不起来、抖动、发烫的排查顺序
调 FOC 是最考验耐心的事,表面上一样的现象,底层原因可能完全不同。这里整理五条我用血泪经验换来的排查顺序,每一条都按“现象→原因→解决”来写。
5.1 现象:上电使能后电机“嗡嗡”响但不转
原因:电角度和实际转子位置不匹配。这是新手最常见的坑,占比至少一半以上。具体表现是 PWM 输出了,SVPWM 也在算,但合成磁场的角度和转子磁极的角度差得远,电机只会原地振动,发不出扭矩。常见根因包括:编码器 A/B 相接反、编码器零位与电机电气零位没对齐、电角度符号取反。
解决:先手动缓慢转动电机轴,通过串口打印电角度,确认它在 0~2π 之间连续且单调变化。如果角度乱跳,交换编码器 A/B 相。然后做一个“电角度偏移扫描”:在程序中加一个可调参数angle_offset,每次给它增加 60 度,短暂使能 PWM 并观察电机是否突然顺滑转动。找到让电机“一使能就转”的偏移值,写入配置文件。这个方法不需要示波器,只要串口和几分钟时间,比瞎猜快得多。
5.2 现象:能转但低速抖动、高速吱吱响
原因:低速抖动优先怀疑速度环参考值更新和电流环采样不同步。很多库的速度环是在另一个定时器中断里跑的,如果这个中断和 TIM1 更新中断没有对齐,速度环每次更新都会在电流环里注入一个“台阶”,低速时表现为周期性的抖动。高速吱吱响则多半是 SVPWM 在占空比接近极限时,采样窗口变得过窄,电流重构畸变。
解决:把速度环频率设为电流环的整数分之一,比如电流环 15kHz、速度环 1kHz,并在代码里让速度环中断从 TIM1 的更新事件触发,而不是用独立定时器随意中断。对于高速吱吱响,限制 PWM 最大调制比到 0.9 以下,并检查有没有移相/注入零矢量的处理逻辑。两者都不是 PID 能兜住的,先修正架构再谈调参。
5.3 现象:电机发热明显,但电流并不大
原因:电流不大却发热,说明有功功率没有变成转动机械能,而是变成了热。最常见的情况是换相时序错误——SVPWM 输出矢量与转子位置差了一个固定角度,虽然闭环没崩,但每一拍都在做“半对半错”的换相。这时候电流表读数可能只有一两安,但某一相开关管或电机绕组温度飙升。
解决:用示波器看三相电压波形。正常 FOC 运转时,三路 PWM 的包络应该是平滑的正弦波,各路之间相差 120 度。如果看到某一相在某个 60 度扇区内明显不对称,那就是 SVPWM 扇区判断或查表有问题。另一个可能性是电流反馈饱和:采样电阻上的电压被运放放大后超过 ADC 量程,导致实际电流被“截顶”。检查方向是current_scale是否按最大电流设计了余量,以及 ADC 采样时间是否足够长,避免信号还没稳定就转换。
5.4 现象:电流环 Kp 一加大就啸叫,怎么调都不稳
原因:最可能的不是 “Kp 太大”,而是“采样的电流太脏”。如果 ADC 没有过采样或软件滤波,反馈给 PI 的电流里叠加了开关噪声和采样毛刺,Kp 稍微一加大,PI 就在追噪声,输出占空比乱跳,听上去就是啸叫。这是 F1 平台特别常见的现象,因为它的 ADC 缺少硬件过采样。
解决:在电流反馈进入 PI 之前加一阶滤波,而不是在 PI 输出之后加滤波。
i_d_filt = i_d_filt * alpha + i_d * (1.0f - alpha); i_q_filt = i_q_filt * alpha + i_q * (1.0f - alpha);逻辑说明:alpha 取 0.8 左右,滤波带宽约降到采样频率的 1/5 以下,能有效滤掉 15kHz 开关噪声的残余。但 alpha 别设到 0.98 以上,否则电流环带宽太低,高速时力矩跟不上。用了滤波后,啸叫通常会明显减弱,这时再重新从较低的 Kp 往上加。还有一个细节:有些库的 PI 内部参数单位是“归一化电压”,有些是“实际电压”,你按经验填的 0.6 可能实际相当于 6.0,调参前先确认库的 PI 结构是位置式还是增量式。
5.5 现象:一使能 PWM 母线电流飙到保护值
原因:这是最危险的坑,十有八九是硬件层面的死区短路或相序接反,而不是软件 PID 问题。常见有两种情况:一是死区时间设得太短,上下桥臂功率管直通;二是三相输出接线顺序错误,比如把 A 相接到了 B 相的驱动口,导致 SVPWM 的换相逻辑和实际绕组方向不符。
解决:先把死区时间拉到 1.5μs 以上,PWM 频率降到 10kHz,单独给每路 PWM 一个固定占空比(开环),用电流探头看母线电流,而不是全景式地使能整个 FOC。逐相通电测试还能顺便验证“哪一路 PWM 控制的是哪个绕组”。如果每一相通电电流都正常,再合起来跑 FOC,问题多半就定位在相序或齐次化配置上。这个排查过程不需要看算法代码,优先怀疑硬件。
6. 进阶:无感模式的三段式启动,以及把 FOC 接到调试上位机
如果你没有编码器也没有霍尔,想在 F1 上做无感 FOC,先接受一个事实:零速带载启动是无感最难的区间,F1 这种算力做滑模观测器只能算“勉强够用”。我在类似平台上跑过比较可靠的无感方案是“三段式启动”:第一步转子预定位,给 d 轴一个恒定电流,把转子强制吸到已知零位;第二步开环强拖,按斜坡频率施加一个旋转磁场,让转子跟着走;第三步等转速高于某阈值后切入闭环观测器,用反电动势估算转子位置和转速。这套状态机写成代码并不难,难的是切换点切得平顺——切换太早,观测器角度没收敛,电机会抖一下;切换太晚,开环强拖的时间和电流都很大,电机发热。我的经验是在转速达到额定 5% 以上再切换,同时把切换时的 Iq 参考值做成连续过渡,不要跳变。
调好第一版有感 FOC 后,强烈建议你花半天时间搭一个调试上位机。最省钱的方式是:F1 通过串口以 500Hz~1kHz 的节奏发送speed_ref, speed_fb, iq_ref, iq_fb, angle这五个量,PC 端用 Python 的 matplotlib 或者现成串口绘图工具画出曲线。看到速度和电流的波形,你才能准确判断是哪个环在抖,而不是靠耳朵听。注意不要在 FOC 中断里直接调阻塞式串口发送函数,应该先把数据写进环形缓冲区,由主循环或 DMA 发送;否则串口波特率一旦和中断时序冲突,电机就会给你表演“瞬断”。
我个人还有个板子级验证习惯:新板子先不上电机,用手动模式控制三相桥臂给一个固定电压矢量,也就是每个比较寄存器写死一个值,观察三路 PWM 波形和母线电流是否正常;再上电机,先开环给电角度,再闭环;确认电流环稳了,最后才接速度环和启动斜坡。这个顺序让我少烧了三块驱动板,也少了很多“波形看起来对但电机就是不转”的玄学时间。FOC 库把电机转起来只是第一步,让它带载、发力、抗冲击才算真正落地。希望这篇笔记能帮你在 stm32f1 上跑通自己的第一套 FOC 闭环。
本文还有配套的精品资源,点击获取