这段时间在AT32F403上专门做电机控制,恰好拿到一份某品牌MD500E变频器的完整控制代码,文件包写得非常直白:AT32F403版本、完全版、有感无感全包含、数码管显示已屏蔽。懂行的人看到这几个关键词就知道这东西的价值——工业变频器级别的FOC代码,电流环、速度环、SVPWM、完整保护逻辑一样不少。我把这套代码在自己的验证板子上完整跑通了,有感模式和无感模式都实际转起来并带载测过,顺手把这段时间的移植过程、参数整定技巧和踩坑记录整理出来。如果你正在找一份可参考的工业级电机控制框架,或者准备在AT32F403上做无感FOC,这篇能帮你省下大量摸索时间。
1. 拿到一份完整的MD500E代码,先搞清里面到底是什么
1.1 工业变频器代码和教学Demo的差距在哪
市面上能买到的电机控制例程大多停留在“让电机转起来”的层面:开环六步换向、V/F曲线、或者一个裸的FOC小程序,转起来就完事。真正到了产品级变频器这个量级,代码的复杂度完全不在一个维度。MD500E这套代码给我的第一感觉是“每个环节都在做防御”,这不是贬义,工业设备最怕的是把管子烧了、把人伤了,所以代码里到处是限幅、超时判断、状态机保护。
完整跑通之后,我整理了这套代码中最核心的几块,你在任何教学资料里都不一定能看到这么完整的组合:
- 完整的FOC算法链路:三相电流采样、Clark变换、Park变换、电流PI调节、逆Park、SVPWM输出,每个环节都做了限幅和溢出保护。
- 有感模式:支持ABZ增量编码器反馈,带零位偏移校准,掉电后参数可以保存,重新上电不需要每次找零。
- 无感模式:基于反电动势的观测器估算转子位置和速度,带静止状态预定位、开环强拉启动、自动切入闭环的完整状态机。
- 保护逻辑:过流、过压、欠压、过温、堵转、编码器断线检测,每一项都不是简单比较,而是带滤波和延迟确认的。
- 控制调度:高频电流环、中频速度环、慢速状态机和通讯任务,各跑各的优先级,不互相堵塞。
这套东西如果自己从零开始写,没有两三个月的调试验证很难达到同等稳定性。拿到现成框架之后再改造成自己的产品,路径会短很多。
1.2 为什么MD500E这种变频器级别的代码会选择AT32F403来完成
先说结论:AT32F403这颗料做电机控制确实很合适,而且国产芯片在这个价位带做到了几乎完美替代上一代主流电机控制MCU的性能。
从电机控制需要的外设来看,AT32F403几乎是“定制级”配置:
- Cortex-M4内核带单精度FPU,FOC算法里的浮点运算、三角函数、PI调节全都能直接计算,不需要查表或定点模拟,精度高、调试方便。
- 主频最高240MHz,目前我用的是200MHz配置,电流环16kHz跑下来CPU占用率还有大量富余,将来要加通讯或算法扩展都有空间。
- 高级定时器TMR1带6路互补PWM输出和刹车输入,死区时间可配置,做三相全桥驱动是天生的匹配。
- ADC速度快,支持注入组同步采样,可以用PWM计数器比较事件直接触发电流采样,省掉一个中断同步的麻烦。
- 内置DAC,这是调试电机控制的“神外设”,可以把电流反馈、电角度、速度指令直接输出成模拟波形,用示波器看内部变量。
有一个细节很关键:这套代码的架构和之前ST平台的经典FOC框架高度接近。AT32F403的外设命名、固件库风格、寄存器布局都与经典M3/M4平台非常相似,所以把原本基于ST标准库写的电机代码移植到AT32上,工作量主要集中在时钟树和库函数名上,算法部分可以原样保留。这也是你拿到“AT32F403版本”这一版能直接跑起来的原因。
1.3 有感无感双模式在一个框架里怎么共存
这是这套代码架构上最值得学习的一点。很多实现要么只做有感、要么只做无感,而MD500E这种产品级代码把两种模式放在同一套FOC内核上,通过反馈源切换来兼容。核心思路是:
- FOC的内核只接收“转子角度 + 速度反馈”这两个量,至于角度和速度是从编码器算出来的,还是从观测器估算出来的,FOC内核根本不关心。
- 有感模式下,位置反馈来自编码器的正交解码计数,速度由单位时间内脉冲增量换算,低速时精度很高。
- 无感模式下,位置和速度来自滑模观测器,通过αβ轴反电动势估算转子位置,再经锁相环提取速度。
- 两种模式的切换在软件里就是一个状态机,根据当前使能标志选择对应的角度和速度来源,然后正常进入Park变换和PI调节。
这个“反馈源解耦”的设计思路,在没有传感器的情况下用观测器替代编码器,在有传感器时则使用更高精度的反馈,是整套代码的灵魂。后面我在实际调试中深有体会,只要角度反馈正确,电流环和速度环的行为几乎不区分有感无感。
2. 拆解核心代码:FOC主中断、观测器和编码器处理的细节
2.1 电流环中断的调度逻辑:为什么系统不卡
先看这套代码的中断分配,理解了调度方式,后面看代码才有条理。主要分三个频率层级:
- 高频层:电流环中断,由PWM上溢或比较匹配事件触发,频率就是载波频率,常见的是8k到16k,这套代码默认16k。中断里只做最紧急的事:采样电流读取、坐标变换、电流PI、SVPWM计算、更新比较寄存器。
- 中频层:速度环和观测器更新,频率1k到4k。速度环输出作为电流环的给定,所以电流环必须比速度环快,这套代码把速度环放在主循环里由定时器节拍驱动。
- 低频层:状态机、保护逻辑、启停逻辑、参数刷新,几十毫秒到几百毫秒处理一次都没问题。显示任务理论上也在这个层级,原代码把它屏蔽了,后面细说。
这个分层最关键的准则是:高频中断里绝对不能做耗时操作,比如串口打印、EEPROM写入、复杂浮点三角函数。我拿到代码后第一反应就是检查电流环中断里有没有“隐藏”的慢操作,确认没有之后才敢挂负载跑。
代码里电流环的中断触发方式我也提一下,用的TMR1和ADC的联动:
tmr_period_complementary_output_config(TMR1, TMR_TRUE, TMR_FALSE); adc_injected_sequence_config(ADC1, 2, ADC_CHANNEL_1 | ADC_CHANNEL_2); adc_injected_channel_config(ADC1, 0, ADC_CHANNEL_1, ADC_INJ_SAMPLING_TIME_239_5); adc_injected_channel_config(ADC1, 1, ADC_CHANNEL_2, ADC_INJ_SAMPLING_TIME_239_5); trigger_config(ADC1, ADC_TRIGGER_INJ_TMR1TRGOUT);这个配置的意思是由TMR1的TRGO信号触发ADC注入组采样,采样完成之后进入ADC中断,然后直接在ADC中断里执行整个电流环。这种“硬件定时 + 硬件触发采样 + 中断执行控制”的组合是工业变频器最常见、最可靠的做法,比软件定时器中断里读ADC要稳得多,采样点始终落在PWM周期的固定位置。
2.2 无感观测器:滑模估算的核心逻辑
这套代码里无感模式用的是滑模观测器加锁相环提取速度的方案,在电机中高速段表现很不错,低速段则靠开环强拉弥补。滑模观测器的本质很简单:利用电机反电动势中包含转子位置信息,通过构造一个与真实电机同步的观测模型,让模型电流与实际电流的误差趋近于零,此时模型里的估算反电动势方向就指向转子磁极位置。
代码核心逻辑简化如下:
// 在αβ坐标系下做滑模观测 alpha_err = alpha_current - alpha_est_current; beta_err = beta_current - beta_est_current; alpha_emf = sign(alpha_err) * SMO_GAIN; beta_emf = sign(beta_err) * SMO_GAIN; alpha_speed = alpha_est_current_derivative + alpha_emf; beta_speed = beta_est_current_derivative + beta_emf; // 通过锁相环提取角度和速度 pll_error = -alpha_emf * cos(observer_angle) - beta_emf * sin(observer_angle); observer_speed = pll_pi_update(pll_error); observer_angle += observer_speed * dt;滑模增益SMO_GAIN的选取很讲究。太小的话,观测器无法克服模型误差,会对反电动势估计失真;太大会导致反电动势波形出现明显抖振,表现在电机上就是电流噪声大、啸叫严重。实际调试时我是从理论值开始,然后以电流波形噪声为评价指标,逐步微调,直到估算角度与实际角度在台架对比下基本重合。
这套代码中的无感启动策略也写得比较完整,状态机大概是:
- 静止预定位:给一个固定的电流矢量,把转子拉到已知位置。
- 开环强拉:频率从0慢慢升高,电流闭环但在角度上用斜坡给定的开环角度。
- 软切换:当观测器估算速度达到设定阈值并且角度误差收敛之后,慢慢把角度反馈从开环角度切换到观测器角度。
- 闭环运行:完全切到观测器反馈,进入正常无感闭环。
这里有一个调机时的核心经验:切换前一定要给观测器足够时间“看到”真实反电动势,判断依据是切换前观测器估算速度和开环给定速度的偏差,一般偏差持续稳定在很小范围内再切换,成功率会高很多。很多无感方案切闭环失败炸掉,就是切换点找得太早。
2.3 有感模式:编码器反馈和零位校准
有感部分用的ABZ增量编码器。代码里有专门的编码器接口模块,主要做三件事:读取计数脉冲、判断方向、换算成角度和速度。
正交解码一般用TMR的编码器模式。AT32F403有专门的编码器接口模式,对应代码大致是这样:
tmr_encoder_mode_config(TMR3, ...); tmr_prescaler_value_config(TMR3, 0); tmr_counter_value_config(TMR3, 0); tmr_period_value_config(TMR3, ENCODER_PPR * 4 - 1);用4倍频计数,分辨率和稳定性都更好。角度换算代码只要注意方向极性:编码器方向接反,表现就是启动就过流保护,因为电流矢量完全反了。
有感调试里最容易出问题的是零位偏移。电机在机械安装时,编码器的Z脉冲不一定刚好对应电机的A相反电动势峰值位置。所以代码里有一个“编码器零位校准”功能:上电时先给一个固定电压矢量,让转子转到已知电角度,然后记录当前编码器计数,存储为偏移量。之后所有实际角度都由“编码器计数减去偏移量”得到。
这个校准逻辑必须走通,而且要注意掉电保存。MD500E这套代码用EEPROM或者片上Flash扇区保存校准值,重新上电直接读取。调试时千万别跳过校准直接跑,否则低速满载时的电流会非常大,轻则报警重则烧驱动。
2.4 数码管显示被屏蔽后,调试怎么补位
这是标题里特别提到的一点。原代码把数码管显示模块整体屏蔽了,我拿到之后先确认了屏蔽范围,发现它保留了参数存储、按键扫描、运行状态机,只是没有把数值传到显示驱动上去。这种处理对移植者来说其实是好事,不用去适配原有数码管的段码和位选脚,省了不少接线。
但这也带来一个实际痛点:变频器运行时看不到当前频率、电流、母线电压这些关键量,出了问题没法第一时间判断。解决办法我列一下,都是我实测过有效的方案:
- 串口Modbus通讯:MD500E本来就支持标准的Modbus RTU,屏蔽的是数码管,不是通讯。接一个USB转485,用调试助手读写寄存器,频率、电流、故障码全部能拿到。
- DAC模拟输出:如果芯片有DAC,可以把速度反馈、电流反馈直接变成0-3.3V的模拟量,用示波器看波形。这套代码我实测把电角度接到DAC上,能看到非常干净的角度锯齿波。
- 外接一块LCD或者数码管模块:代码里显示层和硬件层本来就是分开的,自己提一个显示回调函数,接上你要用的屏就能恢复显示。
- 串口打印:最简单粗暴,在慢速任务里定期打印状态,但注意别放在高频中断里。
我个人的处理是把DAC和串口打印都用上了:DAC看内部波形,串口看状态和故障码。这样调试效率不比原装数码管差,甚至信息量更大。
3. 从拿到代码到电机转起来,我走的完整流程
3.1 先用表格理清资源和IO,别急着编译
我拿到代码到真正编译通过用了差不多一个晚上,主要时间花在核对MCU资源映射。强烈建议先画一张功能和外设的对应清单,对照原代码和你的板子原理图,确认哪些要改、哪些能复用。我当时整理的表格大致如下:
| 功能模块 | AT32F403外设 | 引脚/通道 | 说明 |
|---|---|---|---|
| 三相PWM输出 | TMR1 | CH1/CH2/CH3互补输出 | 驱动桥上三管和下三管 |
| 故障刹车输入 | TMR1 BKIN | 指定GPIO | 硬件快速关断PWM |
| 相电流采样 | ADC1注入组 | IN1/IN2 | 两相采样,第三相计算 |
| 母线电压采样 | ADC1规则组 | IN3 | 过压欠压保护 |
| 编码器接口 | TMR3正交解码 | CH1/CH2 | 有感模式反馈 |
| 串口通讯 | USART1 | TX/RX | Modbus RTU调试 |
| 数码管显示 | 原GPIO | 被屏蔽 | 暂时不接 |
| DAC调试输出 | DAC1 | PA4 | 内部波形观察 |
这一步做完之后,代码里大部分宏定义和引脚配置就一目了然了。移植时最怕的就是一上来就编译,报错几百个,根本分不清是语法问题还是硬件配置问题。
3.2 时钟和外设初始化:AT32移植最容易栽跟头的地方
AT32F403的库函数和ST经典库很接近,但内核时钟配置这一块必须重写。原因很简单:ST和AT32的时钟树不完全相同,外设时钟使能位的位置也有差异。我当时先写了一个简单的GPIO翻转程序验证时钟和启动文件正常,再往下移植电机代码,这是一个很节省时间的习惯。
下面是一个可以复用的AT32F403初始化片段:
crm_clocks_freq_type clocks; crm_clock_source_enable(CRM_CLK_SOURCE_HEXT, TRUE); crm_pll_config(CRM_PLL_SOURCE_HEXT, CRM_PLL_MULT_20, CRM_PLL_OUTPUT_RANGE_GT72MHZ); crm_clock_freq_update(&clocks); crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_TMR1_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_ADC1_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_DAC_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_USART1_PERIPH_CLOCK, TRUE);注意这里的PLL配置参数和ST标准库完全不同,AT32的内核频率、AHB分频、APB分频的寄存器位定义需要查官方头文件确认。如果这部分配错,表现非常隐蔽:代码能跑但PWM频率不准,电流环采样周期完全错乱,电机转起来要么啸叫要么直接过流。
PWM和死区配置也在这里面:
tmr_base_init(TMR1, 1999, 59); // 160MHz/(2000*60)=1.33kHz示例,实际按载波频率调整 tmr_clock_div_set(TMR1, TMR_CLOCK_DIV_1); tmr_dead_time_config(TMR1, 150); // 死区时间设置,单位取决于时钟 tmr_compare_value_config(TMR1, TMR_SELECT_CHANNEL_1, 500); tmr_compare_value_config(TMR1, TMR_SELECT_CHANNEL_2, 500); tmr_compare_value_config(TMR1, TMR_SELECT_CHANNEL_3, 500);载波频率和死区时间是两个必须结合你的IGBT型号和驱动电路去确认的参数。IGBT的开关时间、驱动芯片的传播延迟都会影响死区设置,犯懒直接套默认值容易炸管子。我用的是1.5微秒死区,配上常用的IGBT模块,实测稳定。
3.3 有感无感的调试顺序:从开环到闭环一步步来
这也是我最想分享的经验之一。很多朋友拿到代码就着急直接切闭环,结果电机不转就怀疑代码是坏的。我实测下来,正确的调试顺序应该是:
第一步,先跑V/F开环。不解码编码器、不使能观测器,只给SVPWM一个固定的电压矢量,让电机空转。这一步验证的是PWM波形、死区、ABC三相顺序是否正确。如果三相接反了,在开环模式下会转得异常,检测方法比较直观。
第二步,验证电流采样。开环运行中用示波器看ADC采到的相电流波形,确认波形是完好的正弦包络,幅值合理。采样波形毛刺大,优先检查采样点是不是在PWM中心点,以及ADC触发方式是否与PWM同步。
第三步,无感低速度试运行。开环强拉启动,让速度跑到比如5Hz,此时观测器已经有了比较清晰的反馈,观察DAC输出的估算角度是否与开环角度同步。同步之后再做切换。
第四步,有感模式调试。挂上编码器,先做零位校准,然后从低速闭环开始跑速度环。
这套流程走下来,每一步的失败都能快速定位到具体环节,不会出现“所有东西都同时坏了”的绝望局面。
3.4 关键PI参数和整定参考
调PI是电机控制里绕不开的环节。这套代码的PI参数全部是浮点结构体,修改起来很方便。我提供一组可以用的出发点,具体数值要结合电机参数调整:
| 控制环 | 比例系数Kp | 积分系数Ki | 积分限幅 | 输出限幅 |
|---|---|---|---|---|
| 电流环d轴 | 0.18 | 0.02 | 0.8 | 0.9 |
| 电流环q轴 | 0.18 | 0.02 | 0.8 | 0.9 |
| 速度环 | 0.02 | 0.005 | 1.0 | 3.0 |
电流环的Kp理论上与电机电感有关:电机相电感越大,需要越大的Kp才能快速响应。但Kp过大容易导致电流振荡,表现是电流波形上叠加高频毛刺,电机啸叫明显。Ki太小会让稳态误差偏大,低速时可能看到电流缓慢爬坡追不上给定。
速度环整定时要特别小心,因为有编码器时速度反馈很干净,可以把Kp调得偏高;无感模式下速度反馈本身带有滞后,Kp太高会让系统振荡甚至反转。我调试时的办法是,无感模式先按有感参数的一半起步,然后逐步增加观察。
4. 常遇到的问题和排查技巧,都是现场踩过的坑
4.1 启动瞬间过流保护,先查这几样
这是所有变频器调试中最常见、也最容易让人慌的问题。过流保护不仅包括IGBT过流硬件保护,还有软件通过电流采样判断的过流。遇到启动就报过流,我的排查顺序是:
先看驱动波形。用示波器测上下管驱动信号,确认死区是否存在、互补是否正确。死区为零或太短,开机瞬间上下管直通,电流直接飙升,这种情况过流保护动作反而是它在正常工作。
再看ABC三相到电机的接线顺序和编码器方向。有感模式下编码器方向反了,闭环一启动,电流矢量方向和实际磁动势方向完全相反,控制器只会拼命增大电流去追目标,瞬间过流。
最后看电流采样换算。检查采样电阻的放大的倍数和ADC满量程标定值是否一致,采样值偏大会导致软件层面的“虚假过流”,电机没坏但报故障。
4.2 无感低速抖动严重,问题多半在启动参数
无感低速抖动是这种方案的通病,但抖动到什么程度可以接受,什么程度说明参数不对,需要自己把握。我遇到最多的原因是切换点过早,观测器还没收敛就切了。表现是电机在开环切换到闭环的一瞬间速度突然下降、电流冲击、甚至反转。
解决办法是先减小启动开环电流会有效果。很多代码默认开环强拉电流给得过大,切换点的角度误差反而大。我实测把开环电流从额定电流的150%降到100%附近,切换时角度误差明显减小。
还有一个容易忽略的点:观测器滤波参数的截止频率。速度越高,反电动势越大,滤波截止频率可以相应提高;低速时截止频率太高会把真实位置信号滤掉,导致角度波动。这个参数在调试板上一台一根的参数,不要指望默认值通吃所有电机。
4.3 AT32F403与原本ST平台代码的差异,这几个坑比较典型
我顺手整理了和AT32相关的几个移植坑,如果你也是从ST平台代码迁移过来,会很有帮助:
- 库函数命名不同。AT32的固件库是CRM、TMR、ADC这套前缀,不是RCC、TIM、ADC那个老体系。直接搜索替换容易漏,建议先编一次,根据报错逐个改。
- 时钟配置位置不同。ST代码里面有专门的SystemInit,AT32也要做,但PLL系数、分频因子的计算方式有差异。建议用官方给的示例时钟树逐一核对。
- ADC校准寄存器不同。AT32的ADC校准位和状态位与ST不完全一样,如果漏了校准步骤,采样值会有几毫伏到几十毫伏的偏移,电流环里表现出来是固定的零偏。
- 中断优先级分组方式。AT32和ST的NVIC优先级分组命名有差异,配置不正确会导致电流环中断被其他慢速中断打断,轻则电流波形变差,重则运行时间漂移。
4.4 常见问题速查表
| 现象 | 可能原因 | 快速处理 |
|---|---|---|
| 上电就过流 | 死区时间太小或三相输出短路 | 检查PWM死区配置和驱动板硬件 |
| 启动瞬间报过流 | 编码器方向反了或零位不准 | 先开环确认方向,再做零位校准 |
| 无感模式低速抖动 | 切换点太早、开环电流太大 | 降低强拉电流,延后切换频率 |
| 电流波形毛刺 | 采样点不在PWM中心、采样时间太短 | 调整ADC触发点和采样保持时间 |
| PWM输出有偶然跳变 | 中断优先级配置不当 | 把电流环中断优先级提到最高 |
| 代码编译报一堆错 | AT32库和ST库命名差异 | 先编译再逐个改外设函数名 |
| 电机啸叫明显 | 电流环PI过强或载波频率低 | 降低Kp,检查载波频率与电机匹配 |
| 母线电压显示偏差大 | 采样分压电阻标定不准 | 用万用表实测校准系数 |
4.5 一个不算问题但很实用的经验:用DAC搭一个虚拟示波器
最后分享一个我个人用下来效率提升很明显的技巧。AT32F403内置DAC,把内部调试变量通过DAC输出,用示波器看波形。我实际操作中会把电角度、q轴电流、母线电压这几个变量选择性地映射到DAC通道上,代码只需要在一个固定频率的中断里更新DAC输出:
void debug_dac_output(float value) { uint16_t dac_data = (uint16_t)((value + 3.3f) * 4095.0f / 6.6f); if(dac_data > 4095) dac_data = 4095; if(dac_data < 0) dac_data = 0; dac_data_channel1_set(DAC_ALIGN_12B_R, dac_data); }这个办法比看电流表、听声音、摸温度都直观得多。我之前排查无感切换问题时,就是同时观察开环给定角度和观测器估算角度两条DAC曲线,找它们相交的时刻,一眼就看出切换点选得不对,省的反复试错。
最后再补几句实在的
这套MD500E代码拿到手之后,对我来说最大的收获不是“有代码可以用”,而是终于理解了一台工业变频器的软件框架该长什么样:高频中断做控制、慢速任务做管理、每个保护都带防抖确认、每个限幅都有理有据。数码管显示被屏蔽也不算坏事,恰恰逼着我用DAC和串口建立了一套更高效的调试手段。
如果你也打算在AT32F403上做电机控制,建议先别急着改算法,先把原代码的工程结构吃透,把每个模块的职责边界画出来,然后再按自己的需求改参数和外设映射。只要框架理解了,后面换电机、换驱动板、加功能都是水到渠成的事。这套代码里还有很多值得细读的细节,比如死区补偿、母线电压动态前馈、弱磁控制预留逻辑,我后续继续跑深了再单独写文章展开。