ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

电赛磁悬浮实战:STM32 HAL库与PID整定从零到稳定

电赛磁悬浮实战:STM32 HAL库与PID整定从零到稳定 1. 电赛选题背后的取舍为什么磁悬浮是控制类赛题的“硬骨头”电赛控制类题目里磁悬浮装置几乎是每年都会被反复拿出来练手的一个经典项目。它看起来简单——一个线圈、一个霍尔传感器、一颗铁球通电让球浮起来就完事了。但真正上手做过的人都知道这东西从焊完板子到球能稳稳悬停中间隔着的坑比想象中多得多。我前后做过三版磁悬浮第一版用STM32F103C8T6最小系统板加HAL库调了整整四天才让球勉强浮起来而且抖得像筛糠第二版换了驱动方案和传感器布局稳定时间缩短到半天第三版帮学弟做的时候从裸板到稳定悬浮只用了三个小时。这篇文章就把这三版积累下来的经验完整拆开从器件选型、HAL库配置、PID整定到实际调试中那些文档里不会写的细节全部讲清楚。先说为什么选STM32F103C8T6这颗芯片。电赛现场时间紧、任务重选型的第一原则不是性能最强而是“你最熟、资料最多、出问题最容易查”。F103C8T6是ARM Cortex-M3内核72MHz主频64KB Flash20KB SRAM对于磁悬浮这种需要一路ADC采样、一路PWM输出、一个PID运算循环的任务来说资源绰绰有余。更重要的是它的生态——HAL库把底层寄存器操作封装得很完整Keil MDK下的调试工具链成熟DAPLink下载器便宜且稳定网上能找到的参考代码和原理图铺天盖地。国产替代型号虽然引脚兼容但在电赛这种不容出错的场景下我个人的建议还是用原厂或者经过大量验证的批次避免因为芯片本身的一致性差异引入额外变量。磁悬浮的核心控制逻辑其实不复杂霍尔传感器实时检测铁球位置STM32通过ADC读取这个位置信号和设定的目标位置做差得到误差误差经过PID运算输出控制量再通过PWM调节线圈电流产生对应的电磁力把球拉到目标位置。整个系统是一个典型的闭环负反馈控制。但“不复杂”和“能做出来”之间差的是对每一个环节的精确把控。线圈的电磁力与电流的关系是非线性的霍尔传感器的输出有噪声PID参数稍微激进一点系统就振荡稍微保守一点球就吸上去或者掉下来。这些细节才是决定成败的关键。这篇文章适合正在准备电赛控制类题目的同学也适合想通过一个完整项目来学习STM32 HAL库开发和PID控制的嵌入式初学者。我会从硬件选型和电路搭建讲起然后深入HAL库的配置细节接着是PID算法的实现和整定方法最后重点讲调试过程中遇到的典型问题和解决思路。每一部分都会给出具体的参数、代码和操作步骤尽量做到看完就能复现。2. 硬件选型与电路搭建那些原理图上不会标注的细节2.1 线圈驱动方案的选择与对比磁悬浮的线圈驱动是整个系统的执行机构它的响应速度和线性度直接决定了控制效果。我试过三种驱动方案这里把各自的优缺点和适用场景列出来。驱动方案核心器件优点缺点适用场景三极管开关驱动S8050/MOS管电路简单、成本低线性度差、发热严重仅适合粗略演示专用驱动芯片L298N/L293D集成度高、带保护压降大、响应慢对精度要求不高的场合MOS管续流二极管IRF540N/SS34响应快、效率高需要自己搭电路电赛推荐方案我最终选用的是IRF540N N沟道MOS管配合SS34肖特基续流二极管。IRF540N的导通电阻只有44毫欧最大电流33A对于磁悬浮线圈通常几百毫安到一两安的电流来说余量充足。续流二极管的作用是在MOS管关断瞬间为线圈电流提供泄放回路防止反向电动势击穿MOS管。这个二极管不能省我第一版就是因为省了它烧了两颗MOS管才反应过来。PWM频率的选择也有讲究。频率太低线圈电流纹波大球会跟着抖动频率太高MOS管的开关损耗增加而且HAL库的PWM分辨率会下降。经过实测20kHz到30kHz是比较合适的区间。我最终定在25kHz此时STM32F103的72MHz主频下PWM分辨率为72M/25k2880级足够精细。在CubeMX里配置TIM1的CH1为PWM Generation模式Prescaler设为0Counter Period设为2879这样得到的PWM频率就是72MHz/(01)/(28791)25kHz。2.2 霍尔传感器的选型与线性化处理位置检测是磁悬浮的反馈环节传感器的精度和线性度直接影响控制效果。常用的方案有线性霍尔传感器如SS49E、A1321和红外对管。红外对管受环境光影响大而且铁球表面反光特性不一致容易引入干扰所以我推荐用线性霍尔传感器。SS49E的输出电压与磁场强度呈近似线性关系但实际使用中有两个问题需要注意。第一它的输出范围大约在0.5V到4.5V之间而STM32的ADC参考电压是3.3V所以需要做分压或者选择输出范围更合适的型号。我用的A1321输出范围是0.5V到4.5V同样需要分压。第二霍尔传感器的输出存在零点漂移和温漂需要在软件中做校准。我的做法是在系统上电时先做一次零点校准让线圈不通电铁球放在远离传感器的位置读取此时ADC的值作为“无球参考值”然后把铁球放在目标悬浮位置读取ADC值作为“目标参考值”。这两个值在后续的PID运算中作为基准。实际运行时ADC读取的实时值减去无球参考值再除以目标参考值与无球参考值的差就得到了归一化的位置误差。这样处理之后不同传感器之间的个体差异和温漂影响都被大幅削弱。分压电路的设计也要注意。A1321输出最大4.5V要降到3.3V以下分压比至少是4.5/3.31.36。我用了10k和20k的电阻分压分压比是20/(1020)0.6674.5V分压后是3.0V留了0.3V的余量。分压电阻的精度建议用1%的金属膜电阻普通碳膜电阻的温漂会影响长期稳定性。2.3 电源方案与退耦电容的布置磁悬浮系统的电源噪声是导致球抖动的一个重要原因。线圈在PWM驱动下会产生大量的高频噪声这些噪声会通过电源线传导到STM32和霍尔传感器导致ADC采样值跳动。我的解决方案是三级退耦在线圈驱动电路的电源输入端并联一个470uF的电解电容和一个0.1uF的陶瓷电容在STM32的电源引脚附近放置0.1uF和10uF的退耦电容在霍尔传感器的电源引脚附近放置0.1uF的陶瓷电容。电源本身建议用线性电源或者质量好的开关电源。我试过用便宜的开关电源纹波达到200mV球抖得根本停不下来。换成线性电源后纹波降到20mV以下效果立竿见影。如果手头只有开关电源可以在电源输出端加一个LC滤波电路电感用100uH电容用1000uF能把纹波压到可接受的范围。3. HAL库配置与工程搭建CubeMX里那些容易忽略的选项3.1 时钟树配置与ADC采样时间的计算用CubeMX配置STM32F103C8T6的时钟树时外部晶振选8MHzPLL倍频到72MHz。ADC的时钟不能超过14MHz所以ADC预分频器要选6分频得到12MHz的ADC时钟。这个细节很多人会忽略如果ADC时钟超频采样值会不稳定表现为球的位置反馈信号跳动。ADC的采样时间设置也很关键。STM32F103的ADC采样时间可选1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5个ADC时钟周期。采样时间越长采样值越稳定但转换速度越慢。磁悬浮的控制周期通常在1ms到5ms之间ADC转换时间远小于这个周期所以可以选较长的采样时间来提高稳定性。我选的是71.5周期加上12.5周期的转换时间总转换时间约84个ADC时钟周期在12MHz下约7us完全满足要求。ADC的参考电压选择也要注意。STM32F103C8T6的VREF引脚默认接VDDA也就是3.3V。如果电源纹波较大VREF也会跟着波动导致ADC读数不准。可以在VREF引脚附近加一个0.1uF的陶瓷电容和一个10uF的钽电容来滤波。另外ADC的输入阻抗和采样时间要匹配霍尔传感器经过分压后的输出阻抗大约是6.7k10k和20k并联在71.5周期的采样时间下采样电容能够充分充电不会引入额外的误差。3.2 PWM输出的配置与死区时间TIM1的CH1配置为PWM Generation模式Mode选PWM Mode 1Pulse初始值设为0。这里有一个容易踩的坑如果Pulse设为0而GPIO又配置成了复用推挽输出上电瞬间MOS管可能会误导通。我的做法是在初始化完成后先调用HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1)然后把Pulse设为0再使能MOS管的驱动电路。或者在硬件上用一个下拉电阻把MOS管的栅极拉到地确保上电时MOS管是关断的。死区时间在单管驱动中不需要设置但如果用半桥或者全桥驱动死区时间就很重要了。磁悬浮通常用单管驱动加续流二极管所以不需要死区。但如果你的线圈电流较大用半桥驱动可以更好地控制电流方向这时候死区时间要设置在1us左右防止上下管直通。PWM的初始频率和占空比要根据线圈的参数来定。线圈的电感量通常在几毫亨到几十毫亨之间电阻在几欧到几十欧之间。在25kHz的PWM频率下线圈的阻抗主要是感抗电流纹波较小。占空比的初始值设为0让球在重力作用下落在底部然后PID运算逐渐增加占空比把球拉起来。3.3 中断优先级与控制周期的确定磁悬浮的控制周期决定了系统的响应速度。周期太长球的位置变化来不及响应会振荡周期太短PID运算和ADC采样占用过多CPU时间而且噪声的影响会被放大。经过实测1ms到2ms的控制周期比较合适。我最终用的是1ms用TIM2的更新中断来触发。中断优先级的配置要注意ADC采样中断和PWM更新中断的优先级要合理设置。如果ADC采样在中断中进行ADC中断的优先级要高于定时器中断确保采样值是最新的。如果ADC用DMA传输DMA的优先级也要设置好。我的做法是用TIM2的更新中断触发ADC采样ADC转换完成后触发DMA把数据搬到内存然后在TIM2中断中读取DMA搬运后的数据做PID运算。这样ADC采样和PID运算在同一个中断中完成时序确定不会出现采样值滞后的情况。HAL库的中断处理函数中要尽量精简代码避免浮点运算和耗时操作。PID运算中的浮点乘法在72MHz的M3内核上没有硬件FPU一次浮点乘法大约需要几十个时钟周期1ms的控制周期下完全来得及但如果中断中还有其他耗时操作就可能超时。我的做法是把PID运算放在中断中但把一些非实时的操作比如OLED显示更新、串口打印放在主循环中通过标志位来同步。4. PID算法的实现与整定从抖动到稳定的完整过程4.1 位置式PID与增量式PID的选择依据PID算法有两种常见实现位置式PID和增量式PID。位置式PID的输出直接对应控制量的绝对值增量式PID的输出是控制量的增量。对于磁悬浮来说我推荐用位置式PID原因是位置式PID的输出直接对应PWM占空比物理意义明确整定参数时更容易理解每个参数的作用。位置式PID的离散化公式是u(k) Kp * e(k) Ki * Σe(i) Kd * [e(k) - e(k-1)]其中e(k)是当前误差Σe(i)是历史误差的累加e(k)-e(k-1)是误差的变化率。在代码实现中积分项需要做限幅防止积分饱和。积分饱和是磁悬浮调试中最常见的问题之一当球被吸到线圈上时误差持续为负积分项不断累加导致输出饱和在最大值当球掉下来时积分项需要很长时间才能降下来系统响应变得迟钝。我的做法是把积分项限幅在输出范围的±30%以内同时当输出饱和时停止积分累加。增量式PID的公式是Δu(k) Kp * [e(k) - e(k-1)] Ki * e(k) Kd * [e(k) - 2e(k-1) e(k-2)]增量式PID不需要累加历史误差积分饱和的风险较小但输出的物理意义不如位置式直观。对于初学者来说位置式PID更容易理解和调试。我第一版用的就是位置式PID虽然遇到了积分饱和的问题但通过限幅解决了。4.2 参数整定的实操步骤与经验值PID参数整定是磁悬浮调试中最耗时的环节。我总结了一套“三步法”在实际操作中比较有效。第一步先整定Kp。把Ki和Kd设为0从小到大增加Kp直到球开始出现等幅振荡。记录此时的Kp值称为临界增益Ku。然后根据Ziegler-Nichols经验公式Kp取0.6KuKi取2Kp/TuKd取Kp*Tu/8其中Tu是振荡周期。但Z-N公式给出的参数通常比较激进实际使用时我会把Kp再降低20%到30%让系统有足够的稳定裕度。第二步加入Ki消除稳态误差。Ki的作用是消除稳态误差但过大的Ki会导致系统振荡。我的经验是Ki取Kp的1/100到1/50之间。比如Kp50Ki取0.5到1.0。加入Ki后观察球是否能稳定在目标位置。如果球在目标位置附近缓慢漂移说明Ki太小如果球出现低频振荡说明Ki太大。第三步加入Kd抑制超调。Kd的作用是预测误差的变化趋势提前给出反向控制量抑制超调。Kd太大会放大噪声导致球高频抖动Kd太小则超调明显。我的经验是Kd取Kp的1/10到1/5之间。比如Kp50Kd取5到10。加入Kd后球的响应会变得更“干脆”超调明显减小。我最终使用的参数是Kp45Ki0.8Kd6控制周期1ms。这个参数下球从底部被拉到目标位置大约需要0.5秒超调量约10%稳定后位置波动在±0.5mm以内。当然不同的线圈、传感器和机械结构对应的参数会有所不同这套参数只能作为起点具体还需要根据实际系统微调。4.3 积分限幅与微分滤波的代码实现积分限幅和微分滤波是保证PID稳定运行的两个关键细节。积分限幅的代码实现如下typedef struct { float Kp; float Ki; float Kd; float integral; float prev_error; float integral_max; float output_max; float output_min; } PID_TypeDef; float PID_Compute(PID_TypeDef *pid, float error) { float output; float derivative; // 积分项累加 pid-integral error; // 积分限幅 if (pid-integral pid-integral_max) { pid-integral pid-integral_max; } else if (pid-integral -pid-integral_max) { pid-integral -pid-integral_max; } // 微分项计算 derivative error - pid-prev_error; // PID输出 output pid-Kp * error pid-Ki * pid-integral pid-Kd * derivative; // 输出限幅 if (output pid-output_max) { output pid-output_max; } else if (output pid-output_min) { output pid-output_min; } pid-prev_error error; return output; }微分滤波是为了抑制噪声对微分项的影响。霍尔传感器的输出噪声经过微分后会被放大导致球高频抖动。我的做法是对微分项做一阶低通滤波// 微分项低通滤波 float derivative_raw error - pid-prev_error; pid-derivative_filtered pid-alpha * derivative_raw (1 - pid-alpha) * pid-derivative_filtered;其中alpha是滤波系数取0.1到0.3之间。alpha越小滤波效果越强但微分项的响应也越慢。我最终取的是0.2在抑制噪声和保持响应速度之间取得了平衡。5. 调试过程中遇到的典型问题与排查思路5.1 球吸上去不下来的原因分析球被吸到线圈上不下来是磁悬浮调试中最常见的问题。原因通常有三个PID输出饱和、积分饱和、或者机械结构问题。PID输出饱和是指PID计算出的输出值超过了PWM的最大占空比导致线圈一直以最大电流工作。这种情况下球会被吸到线圈上。排查方法是观察PID的输出值如果它一直卡在最大值说明Kp或者Ki太大需要减小。我的做法是在代码中加一个调试变量通过串口把PID的输出值打印出来实时观察。积分饱和是指积分项累加到很大值即使误差已经反向积分项仍然很大导致输出无法及时反向。排查方法是观察积分项的值如果它一直卡在限幅值上说明积分限幅设置得太宽松或者Ki太大。我的做法是把积分限幅收紧到输出范围的±20%同时当输出饱和时停止积分累加。机械结构问题包括线圈和铁球的距离太近、铁球太重、线圈匝数太少等。如果线圈在最大电流下产生的电磁力仍然大于铁球的重力球就会被吸上去。解决方法是增加线圈和铁球的距离或者换用更轻的铁球或者增加线圈匝数。我第一版用的铁球是10mm直径的钢球重约4克线圈是500匝在1A电流下产生的电磁力刚好能平衡重力。后来换成8mm的钢球重约2克控制起来容易很多。5.2 球持续抖动的噪声来源与抑制球持续抖动是另一个常见问题。抖动的原因可能是传感器噪声、电源噪声、PID参数不当、或者机械共振。传感器噪声是最常见的原因。霍尔传感器的输出噪声经过ADC采样后进入PID运算导致输出抖动。排查方法是用示波器观察霍尔传感器的输出波形如果看到明显的高频噪声说明需要加滤波。我的做法是在霍尔传感器的输出端加一个RC低通滤波电路截止频率设在1kHz左右同时在软件中对ADC采样值做滑动平均滤波。电源噪声是第二常见的原因。线圈的PWM驱动会在电源线上产生高频噪声传导到STM32和霍尔传感器。排查方法是用示波器观察电源线上的纹波如果纹波超过50mV说明需要加强退耦。我的做法是在线圈驱动电路的电源输入端并联470uF电解电容和0.1uF陶瓷电容在STM32和霍尔传感器的电源引脚附近也加退耦电容。PID参数不当也会导致抖动。Kp太大或者Kd太大都会导致高频抖动。排查方法是先把Kd设为0观察抖动是否消失。如果消失说明是Kd太大如果仍然抖动说明是Kp太大或者噪声问题。机械共振是指线圈和铁球之间的机械结构在某些频率下产生共振导致球抖动。排查方法是改变控制周期如果抖动频率跟着控制周期变化说明是控制系统的问题如果抖动频率不变说明是机械共振。解决方法是增加机械结构的阻尼或者改变线圈的安装方式。5.3 不同铁球重量下的参数适配铁球的重量直接影响PID参数的选择。较重的铁球需要更大的Kp和Ki来提供足够的电磁力但同时也需要更大的Kd来抑制超调。较轻的铁球则相反Kp和Ki可以小一些Kd也可以小一些。我做过一个对比实验用4克和2克两种铁球分别整定PID参数。4克铁球的参数是Kp60Ki1.2Kd82克铁球的参数是Kp40Ki0.6Kd5。可以看出铁球越重Kp和Ki越大Kd也越大。这是因为较重的铁球惯性大需要更大的控制量来驱动同时也需要更大的阻尼来抑制超调。在实际操作中如果更换了铁球建议重新整定PID参数不要直接套用之前的参数。整定的方法还是“三步法”先整定Kp再加入Ki最后加入Kd。每次只调整一个参数观察系统的响应逐步逼近最优值。6. 从能浮起来到浮得稳进阶优化与经验总结6.1 串级PID的引入时机与效果单级PID在大多数情况下已经能让球稳定悬浮但如果对稳定性要求更高或者系统存在较大的非线性可以考虑引入串级PID。串级PID是在位置环外面再套一个电流环或者速度环内环的响应速度比外环快能够更快地抑制扰动。对于磁悬浮来说电流环的作用是让线圈电流快速跟踪位置环的输出。线圈的电流响应速度受电感的影响如果电感较大电流的响应会滞后导致位置环的控制效果变差。加入电流环后位置环的输出作为电流环的给定电流环通过调节PWM占空比让线圈电流快速跟踪给定值从而改善位置环的控制效果。电流环的实现需要在电路中增加一个电流采样电阻用ADC读取采样电阻上的电压得到线圈电流。电流环的PID参数整定方法和位置环类似但控制周期要更短通常在100us到500us之间。我试过加入电流环位置环的Kp可以从45提高到80系统的响应速度明显加快抗扰动能力也更强。但电流环的调试比位置环更复杂需要额外的硬件电路和ADC通道对于电赛来说如果时间紧张单级PID已经足够。6.2 基于HAL库的调试技巧与工具链HAL库的调试工具链中我最常用的是串口打印和Keil的Debug模式。串口打印可以把PID的中间变量误差、积分项、微分项、输出值实时发送到电脑用串口示波器软件如SerialPlot绘制曲线直观地观察系统的响应。Keil的Debug模式可以设置断点、查看变量、单步执行适合排查逻辑错误。Keil的Debug模式下有一个很实用的功能逻辑分析仪Logic Analyzer。它可以把变量的值以波形的方式显示出来不需要额外的硬件。我经常用逻辑分析仪观察PID的输出值和ADC采样值判断系统的响应是否符合预期。使用方法是进入Debug模式后打开Logic Analyzer窗口添加要观察的变量设置好采样频率然后运行程序。还有一个技巧是用DAPLink的虚拟串口功能。DAPLink下载器除了下载程序外还可以通过虚拟串口和STM32通信省去了额外的USB转串口模块。在CubeMX中配置USART1的引脚为PA9和PA10然后在电脑上安装DAPLink的虚拟串口驱动就可以用串口助手和STM32通信了。6.3 电赛现场的时间分配与应急预案电赛现场的时间通常很紧张四天三夜要做完硬件、软件、调试和报告。我的时间分配建议是第一天完成硬件搭建和CubeMX工程配置第二天完成PID算法和基本调试第三天进行参数整定和优化第四天写报告和准备答辩。应急预案方面最重要的是准备备用方案。如果磁悬浮调试不顺利要有一个能演示的保底方案。我的做法是在调试初期就把球能浮起来作为第一个里程碑哪怕浮得不稳只要能浮起来就有保底分数。然后再逐步优化稳定性。如果时间实在不够可以把PID参数调得保守一些让球浮得稳但响应慢总比球一直抖或者吸上去要好。另外代码的版本管理也很重要。每调好一个版本就把代码备份一份标注好参数和现象。这样如果后续调乱了可以快速回退到之前的版本。我通常用文件夹加日期的方式管理版本比如“v1_球能浮起来_20240501”、“v2_球稳定_20240502”简单有效。6.4 从磁悬浮项目中学到的通用控制思路磁悬浮项目虽然小但涉及的控制思路是通用的。闭环控制的核心是“检测-比较-调整”三个环节任何一个环节出问题系统都无法稳定。检测环节要保证传感器的精度和抗干扰能力比较环节要保证误差计算的准确性调整环节要保证执行机构的响应速度和线性度。PID参数整定的“三步法”也是通用的先整定比例项让系统有基本的响应能力再加入积分项消除稳态误差最后加入微分项抑制超调。这个顺序不能乱因为每个参数的作用不同同时调整多个参数会让人无法判断哪个参数在起作用。最后调试过程中最重要的心态是“耐心”。磁悬浮的调试不是一蹴而就的需要反复观察、分析、调整。我第一版调了四天第二版调了半天第三版调了三个小时不是因为第三版的技术有多先进而是因为前两版积累的经验让我知道问题出在哪里知道下一步该调什么。这种“知道下一步该做什么”的能力才是电赛中最宝贵的收获。
返回列表