STM32F103下推式磁悬浮实现:原理、调参与避坑指南
简介这是一份基于STM32F103的下推式磁悬浮工程资料包面向嵌入式学习者、电子设计爱好者以及参与智能硬件设计的学生。压缩包内包含一套完整且清晰的电路原理图工程文件、物料清单、C语言源码及大量编译中间文件共计239个文件压缩后大小约4MB。其中原理图与物料清单用于电路搭建和元件核对C源码及相关文件则覆盖初始化流程、传感器数据读取、PID控制算法和驱动信号生成等关键内容能够帮助读者理解磁悬浮系统的稳定控制原理。目前该资源已有2950人次学习下载。通过研读这份资料可以掌握下推式磁悬浮的设计思路包括电磁场检测、功率驱动、反馈控制与整体调试方法同时也可以作为基于STM32F103的嵌入式系统开发参考具有较高的工程参考价值与实用性。1. 下推式磁悬浮的stm32f103实现这份资料到底能做什么我调试下推式磁悬浮的时候最怕的就是浮子刚上电就飞出去。你盯着霍尔传感器数据数值是对的PID参数也按教程设了可它就是挂不住。后来才发现是线圈极性反了PWM输出刚好越推越远。这就是 stm32f103 磁悬浮这类资源的核心价值不能只看概念得有一份能直接烧录、能复现的完整图纸和代码。磁悬浮stm32.rar就是干这个的。这份资源解决了从原理图到软件落空的中间地带——它不教你电磁学而是给你一条可以直接走通的路径霍尔传感器读浮子位置stm32f103 做 PIDPWM 驱动线圈最后让一个永磁体稳定悬停。适合手里有基础硬件、想快速跑通闭环的人也适合准备电子设计作品、需要在一个周末内看到稳定悬浮效果的人。读完之后你会发现难点不在 PID 公式而在传感器方向、PWM 频率、限幅和积分饱和这些细节。下面我会按原理、源码、硬件、调参和踩坑的顺序拆开讲。2. 下推式磁悬浮怎么工作从排斥力到PID闭环动手前先搞懂这条链路2.1 为什么叫下推式和上拉式磁悬浮的本质区别磁悬浮小项目里最常见的是两类上拉式也叫吸浮式电磁铁在悬浮体上方悬浮体是铁磁性材料通电后产生吸引力把物体吸住并悬在半空下推式则刚好反过来电磁铁线圈固定在底座上悬浮体是一块永磁体线圈通电后产生和永磁体底部相同的磁极以排斥力把浮子“推”起来。很多人第一次听到“下推式”会想既然是排斥那线圈和悬浮体不是应该保持距离吗事实上如果没有主动控制两者会迅速错开。排斥力会把悬浮体推向一边永磁体还会因为磁极相对而翻转。这就是所谓的不稳定平衡虽然有向上的推力但任何横向扰动都会被放大所以下推式磁悬浮一定得是一个负反馈闭环系统。我之前测试时把线圈直接通电后放手永磁体会先被弹起来然后翻个面吸在线圈上。原因是纯排斥力没有阻尼位置稍微一动力就往错误方向变化。所以控制系统的任务很明确实时检测悬浮体与线圈的距离如果它偏高就减小电流偏低就增大电流让作用点始终压在设定高度上。从力学链条看整套系统可以用一个二阶模型近似线圈电流产生磁感应强度磁感应强度和永磁体相互作用产生力力改变悬浮体位移位移又被霍尔传感器感知。把这条链路上每个环节的延迟控制住是它悬浮得稳不稳的关键。2.2 stm32f103在这套系统里能做什么选 stm32f103 不是因为性能有多高而是因为这项目的接口要求它刚刚好一个 12 位 ADC 用来采样霍尔电压一个高级定时器输出 PWM外加一个定时器触发控制周期中断。72MHz 主频在 1kHz 的控制周期下能留出大量余量给滤波和通信实测一条控制路径从采样到更新 PWM 寄存器大约只需要几十微秒。在下推式磁悬浮中stm32f103 的角色是典型 MCU 控制器它本身不产生力只负责做决策。决策周期一般取 0.5ms 到 2ms对应 500Hz 到 2kHz 的闭环带宽。太慢会让系统出现明显滞后浮子会像喝醉一样晃太快又会放大传感器噪声PWM 占空比也会抖得很厉害。这也是这类资源里最常见的隐含设定控制频率和 PWM 频率是两回事。常见做法是在一个 1kHz 的定时中断里完成一轮“采集-计算-输出”。ADC 读取霍尔电压通过查表或线性映射换算成悬浮高度PID 模块根据高度误差输出一个控制量这个控制量再被映射成 PWM 占空比更新到定时器的比较寄存器。所有步骤必须在中断周期内完成否则下一条采样来了数据还没算完连续性就断了。2.3 一个控制周期的完整时序从中断到PWM更新我一般会把控制循环写成下面这种结构看起来像状态机实际上就是固定顺序的任务// 1kHz定时器中断每个周期执行一次完整闭环 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) 0) return; TIM_ClearITPendingBit(TIM2, TIM_IT_Update); uint16_t hall get_hall_level(); // 第1步读取霍尔滑动滤波结果 float pos hall_to_pos(hall); // 第2步把ADC值映射成悬浮高度(mm) float ctl pid_calc(pid, target, pos); // 第3步PID计算出控制量 uint16_t duty ctl_to_duty(ctl); // 第4步控制量映射成占空比(20%-95%) set_pwm_duty(TIM3_PWM_CH1, duty); // 第5步更新占空比寄存器 }这段代码里get_hall_level 不是直接读 ADC 原始值而是一个经过 10 次滑动的均值这是为了抑制线圈开关带来的尖峰噪声。hall_to_pos 负责把霍尔电压换算成距离通常是一段标定好的线性关系例如把 0.5V 到 2.5V 映射成 5mm 到 15mm。pid_calc 拿到设定高度和当前高度的误差后输出一个带方向的控制量正值表示需要增大排斥力负值表示减小。ctl_to_duty 再把这个控制量映射到 PWM 占空比范围同时做饱和钳位。这个顺序每一步的数据都作用在下一次采样之前所以如果中间某一步耗时太长最好把控制频率调低一点不要强行压到 2kHz。我自己的经验是1kHz 下整个中断体耗时约 20-30us足够稳定运行。注意 PWM 频率我通常设在 20kHz 到 25kHz目的是避开人耳听阈线圈不会发出刺耳啸叫。2.4 为什么不直接用模拟模块而要坚持MCU闭环市面上有一些模拟磁悬浮模块不需要 MCU用比较器和线性功放就能浮起来。但模拟方案有个明显短板参数不可调传感器温漂和人手扰动只能被动忍受。用 stm32f103 做数字闭环最大的好处是把整个控制律搬进了代码里你可以随时改 PID、改悬浮高度、加滤波、加前馈而不需要改硬件。另一个好处是可视化。同样的板子模拟方案一旦不稳定你只能拿示波器慢慢戳数字方案可以用串口把数据拉到上位机直接看误差曲线和输出曲线哪里出了问题一眼就能定位。这个优势在后面的调参环节会体现得非常明显。3. 拆开磁悬浮stm32.rar核心源码与关键参数怎么改拿到这份资源后建议不要直接打开工程就点编译。先花十分钟搞清楚文件布局能省掉后面很多麻烦。这一章我会按照我从里面挑出的核心文件讲一遍控制逻辑部分给出直接能用的代码片段。3.1 文件结构先分清哪些是必须看的哪些是库文件一套典型的 stm32 工程解压后会分成这样几块Core 文件夹是系统启动文件和内核相关文件Libraries 里是外设库文件这些是编译支撑平时不用打开。需要关心的主要是 User 目录里的主程序Hardware 目录里的传感器、驱动模块以及 Doc 目录里的原理图、BOM 清单和调试说明。这份 rar 里真正值钱的部分我列在下面文件/模块作用需要改吗main.c初始化外设启动定时器中断根据引脚改pid.c位置式PID计算与限幅经常改参数hall.c霍尔ADC采样与滤波一般不用改pwm.cPWM初始化和占空比设置根据定时器改原理图/PCB线圈驱动、传感器电路抄板时参考有的初学者喜欢把每个文件都打开看一遍结果被库函数绕晕。我的建议是先从 pid.c 和 hall.c 看起因为这两个文件决定了控制系统的主干。系统跑起来之后再看 main.c 里初始化了什么理解接线关系。这样读代码的路径最短。3.2 霍尔传感器读数滤波滑动平均比中值滤波更适合这个场景霍尔传感器输出的模拟电压代表浮子到线圈的距离但这个电压会在 PWM 开关瞬间被耦合出毛刺尤其是电流变化率大的时候。不能直接把这个值丢给 PID否则浮子会抖。下面这段代码是资源基础上习惯性改用的滑动平均#define HALL_FILTER_N 10 static uint16_t hall_buf[HALL_FILTER_N]; static uint8_t fifo_idx; static uint32_t hall_sum; uint16_t get_hall_level(void) { uint16_t raw adc_read(ADC_CH_HALL); // 读取原始ADC hall_sum - hall_buf[fifo_idx]; hall_buf[fifo_idx] raw; hall_sum hall_buf[fifo_idx]; fifo_idx (fifo_idx 1) % HALL_FILTER_N; return (uint16_t)(hall_sum / HALL_FILTER_N); }逻辑上很简单维护一个长度为 10 的环形缓冲每次采样把新值放进去同时丢到最旧的那个值所有缓冲值的和除以 10 就是当前输出。为什么要用环形缓冲而不是每次重新累加因为重新累加在中断里会引入额外计算量虽然不过几十个周期但在 1kHz 下不值得浪费。这里有个取舍窗口长度 N 太小滤波作用有限N 太大数据滞后增加。10 次滑动平均在 1kHz 采样下对应 10ms 等效延迟对 1kHz 的控制周期来说已经接近一个半周期再往上就得小心稳定性。如果板上数字电源噪声特别大我一般会先提高 PWM 频率而不是无脑增大 N。3.3 PID计算和限幅位置式PID的两个关键钳位控制代码里最容易被改坏的就是 PID。资源里给出的通常也是位置式 PID形式为输出等于比例项加积分项加微分项。看完整代码typedef struct { float kp; float ki; float kd; float integral; float last_error; float out_max; float integral_max; } Pid_t; float pid_calc(Pid_t *pid, float setpoint, float pos) { float error setpoint - pos; pid-integral error; if (pid-integral pid-integral_max) pid-integral pid-integral_max; if (pid-integral -pid-integral_max) pid-integral -pid-integral_max; float derivative error - pid-last_error; pid-last_error error; float out pid-kp * error pid-ki * pid-integral pid-kd * derivative; if (out pid-out_max) out pid-out_max; if (out -pid-out_max) out -pid-out_max; return out; }这里加了两个限幅积分限幅和输出限幅。积分限幅极其重要如果没有它浮子在启动瞬间被压到最低点误差长期存在积分项会不断累积成一大坨饱和值等外力消失后输出仍然维持在最满浮子会一下子蹿出去这就是积分饱和。输出限幅的数值要跟后面的 PWM 映射对齐。比如 out_max 设为 100ctl_to_duty 把 100 映射成 95% 占空比-100 映射成 20%这意味着 PID 的输出不是直接写进寄存器而是经过换算。注意换算后的占空比下限不要设到 0因为下推式线圈需要一个基础电流来建立悬浮平衡点从 0 开始会有几十毫秒都吸不住浮子。3.4 PWM驱动与死区为什么要留出最小占空比PWM 模块一般使用定时器的某个通道输出极性、频率、占空比都靠寄存器配置。磁悬浮的负载是电感线圈不需要正反向换向所以一个普通 PWM 加一个 MOSFET 驱动就能工作。死区功能是 H 桥控制电机时才需要这里保持关闭即可。单片机的 PWM 分辨率也影响稳定度。stm32f103 的定时器是 16 位如果 PWM 频率设在 20kHz那么有效分辨率约为 72MHz 除以 20kHz约 3600 阶足够平滑。如果为了追求高分辨率把 PWM 降到 8kHz则会引入可闻噪声。我在资源基础上测试时8kHz 下线圈噪声很恼人23kHz 是很好的折中。4. 硬件搭建与调参顺序浮子稳定悬浮不是靠运气软件逻辑搞清楚后照着一份可靠的 BOM 表把硬件焊出来是很快的。但原理图上的元件参数和实际布局对最后效果影响很大。这一章我把搭建和调试分成三步每步都有可以直接参考的做法。4.1 关键硬件怎么选线圈、霍尔传感器、MOS管驱动下推式磁悬浮的线圈一般自己绕制在资源里通常能找到推荐的线径和匝数。常见参数是 0.35mm 铜线绕 200 匝左右电感在几毫亨到十几毫亨之间。这个范围不需要太精确因为闭环控制会主动调节电流。但线圈电阻太大时同样的 PWM 占空比下电流不够浮子会被“压死”。霍尔传感器建议用线性霍尔比如集成线性霍尔 IC输出模拟电压。开关型霍尔不能用它只能给出有/无信号无法区分远近。供电最好单独用 LDO不要和 MOSFET 驱动共用电源否则电流变化会通过地线窜扰进传感器的参考电压。MOSFET 驱动方面用逻辑电平 N-MOSFET 是常见选择栅极直接由 stm32f103 的 PWM 脚驱动。不过要注意MCU 的 GPIO 驱动能力有限如果 MOSFET 栅极电容较大建议加一个 10 欧栅极电阻避免开关振铃。线圈两端还需要一个续流二极管否则断开瞬间的感应电压可能击穿引脚。元件建议范围备注线圈线径0.3mm-0.5mm匝数多则电感大线圈匝数150-300匝看铁芯/骨架霍尔传感器线性霍尔模拟输出勿用开关型PWM频率20kHz-25kHz避免啸叫控制周期0.5ms-2ms对应500Hz-2kHz4.2 从源码到烧录一条最稳妥的落地路径拿到 rar 里的工程后第一步不是改代码而是确认引脚定义和你的原理图一致。晶振频率也要检查默认一般是 8MHz 外部晶振通过倍频到 72MHz。如果板子上实际焊接的是 25MHz 晶振那么代码里的一切定时都会偏调试时看到的现象会比预期慢或快很多。先在电脑上用串口 ISP 或者调试器烧录一个现成的 bin 文件确认板子能跑。很多资料会同时给出 hex 和完整源码hex 的意义就在于让你先验证硬件。如果 hex 烧进去浮子仍然不动问题大概率在硬件如果 hex 能悬浮而源码编译出来不行再回头看源码里的参数配置差异。# 以串口ISP为例接线正确后将BOOT0拉高复位后执行 stm32flash -b 115200 -w build/maglev.bin -v /dev/ttyUSB0命令里的 -b 指定波特率-w 写文件-v 表示写完校验。烧完后把 BOOT0 拉回低电平并复位程序就开始运行。如果烧录失败先检查串口是否被占用再确认 BOOT0 电平确实到了 3.3V而不是悬空。如果源码是自己用 Keil 编译的务必确认优化等级。有些默认优化等级会导致变量在中断和主循环之间共享时出现意外行为比如标志位被优化掉。我一般把编译器优化设到 -O0 或者在关键变量上加 volatile。4.3 PID调参顺序先P后D再I每步都要观察波形这一步是大多数人翻车的地方。我强烈建议先用串口把实时高度数据发出来看着波形调参数而不是盲扭。第一次通电后先不要期望浮子悬起来重点是看传感器响应。先把 PID 参数清零只留 P。P 从 0.5 开始每次增加 0.5。你会看到浮子开始有反应表现为向下压它时它能弹回来一点。继续加大 P直到出现周期性的上下抖动这表示 P 已经踩到低阻尼阈值记下当前值把它减去一半作为工作 P。然后加 DD 从 0.5 开始逐步加大抖动的幅度会明显减小。最后加 II 只要很小比如 0.02 到 0.05它只用来消除长时间的高度偏离。调参过程里有一个容易忽略的细节目标悬浮高度必须设在霍尔曲线中心附近的线性区间如果设得太靠边缘霍尔电压变化斜率变小灵敏度下降再多 P 也拉不稳。我一般先用手推浮子到一个位置读一下霍尔电压再选中间值作为 target。调参步骤参数预期现象下一步1P0.5开始浮子开始有回弹继续加P2P逐级加出现周期抖动记录临界P回退一半3D从0.5加抖动幅度变小加D直到临界阻尼4I0.02起静差逐渐消除保持最小有效I调参的时候一定记得把能稳定悬浮的原始参数先拍照或者注释保存。这行代码很多人不重视等到把 P 加到浮子开始乱飞再想恢复原状态就要重新摸索。我会在 pid 结构体定义旁边加一行注释写上“稳定参数: kp2.0 kd0.8 ki0.02高度12mm”调乱后直接改回来。4.4 没有示波器时怎么判断浮子状态如果没有示波器可以写一个简单的状态指示在 PID 输出超过一定范围时点亮一个 LED或者通过串口定时打印高度。用耳朵也能辅助判断——震荡声音频率高通常是 D 不够低频喘息声多半是 P 太小或 I 过大。把 LED 亮灭和声音配合起来一样能完成调参只是速度慢一些。如果手头连串口都没接也可以在 PID 误差绝对值超过一定值时翻转一个 GPIO 口用万用表量那个端口的占空比间接判断控制量。总之调试的核心是找到一种可观测的信号而不是凭感觉。5. 避坑与常见问题排查浮子飞走、震荡、漂移逐个拆给你看这部分内容完全从实际调试中整理每一条都是我在复现下推式磁悬浮时遇到的问题。现象对应的原因可能有多种但按照顺序排查大多数能在十分钟内定位。5.1 现象上电瞬间浮子直接被弹飞或者反向被吸过去原因控制方向接反。下推式系统里浮子距离线圈越近霍尔电压变化方向与电流增大方向完全相反。如果代码里的误差定义是 target - pos但霍尔接线却让距离增大时电压也增大那么 PID 输出就会在浮子过高时继续增加电流形成正反馈。解决先逐句检查 hall_to_pos 映射。调试时让浮子靠近线圈观察串口打印的 pos 数值是变大还是变小。正确的行为是浮子靠近线圈时pos 应变小这样在 pid_calc 里 target - pos 为正值PWM 会增加排斥力。如果反了把 hall_to_pos 里的比例系数 k 取负或者交换霍尔传感器的信号线。5.2 现象浮子能够被吸起来一点但就是达不到悬浮高度一直躺在下面原因电流推不动。最常见原因是线圈供电电压不足。下推式磁悬浮不是用小功率线圈就能浮起来的需要线圈在占空比最大时产生的力大于浮子重力再加上扰动裕量。测量线圈两端电压如果峰值远低于设计值说明 MOSFET 没有完全导通或电压被电源压降吃掉。解决用示波器测 MOSFET 漏极波形看 PWM 高电平时是否达到电源电压。如果波形边缘很缓检查栅极驱动电阻是否太大或者 GPIO 推挽模式没有开启。另一个因素是占空比下限如果基础占空比在 20%而浮子需要 25% 才能起浮你会在设定点附近看到出力不足此时把 ctl_to_duty 的下限降到 15% 再试。5.3 现象悬浮稳定后耳朵能听见明显的吱吱声或尖锐啸叫原因PWM 频率落到了可听范围。很多默认工程 PWM 频率是 16kHz 到 20kHz但人的听阈上限可以到 20kHz而且线圈骨架在高次谐波一起振动时会发出刺耳声音。另一个来源是电源线上的纹波让线圈发出啸叫这个通常不是 MCU 的问题。解决把 PWM 频率改到 25kHz 以上。修改时注意分频系数和重装载值要一起算否则频率改了但分辨率下降很多。同时在驱动电源和地之间加一个 100uF 电解电容和 0.1uF 陶瓷电容吸收高频开关纹波。改完频率后需要重新验证控制周期因为 PWM 频率和定时器中断是两个独立定时器不会互相影响。5.4 现象悬浮高度随开机时间慢慢变化早上调的参数下午就偏了原因热漂移。线圈通电后会发热铜线电阻随温度升高而增大同样的 PWM 占空比下电流减小导致同样的控制量实际产生的力变小。霍尔传感器本身也有温度系数它的参考电压如果由 MCU 的 3.3V 供电而 3.3V 来自开关电源温度变后电压变化也会让读数偏移。解决在每次上电后做一次零点校准。具体做法是让浮子放在参考支撑架的最低点读取一个基准值然后计算目标高度时使用相对差值而不是绝对电压。这样即使传感器漂移只要差分关系不变控制量还会稳定。更彻底的办法是在线圈供电回路加一个电流采样在 PID 里做前馈补偿这个会在下一章展开。5.5 现象烧录后串口有数据但PWM就是没有输出浮子没有任何动作原因PWM 通道引脚没有禁用 JTAG 或者引脚复用没配置好。stm32f103 的某些引脚默认是 JTAG 端口例如 PB3、PB4、PA15如果 PWM 用在这些脚上不把 JTAG 功能关闭的话输出会被调试逻辑占用。另外定时器的 PWM 模式配置里经常漏掉“自动重装载预装载”这一项导致更新比较寄存器后没有按预期生效。解决在初始化里优先调用 jtag_deinit() 关闭 JTAG 功能然后再配置 GPIO 为复用推挽。确认 TIM_Cmd 是 enable 的并且在修改比较寄存器前关闭 MOE 主输出使能如果需要。调试时用逻辑分析仪直接量引脚如果始终是低电平逐步回退到裸灯测试比如把该引脚配成普通 GPIO 输出翻转排除硬件问题。6. 更稳的两个技巧串口实时标定和电流前馈6.1 用串口把PID中间量拉出来标定调 PID 最怕盲目。我会在控制中断里增加一个计数器每 100 个控制周期向串口发送一次pos,target,ctl,duty四个值形成一条完整的控制记录。这样能同时观察反馈、设定值和控制量快速判断问题是传感器方向、PID 稳定性还是 PWM 限幅。发送的代码可以放在主循环里用一个标志位触发if (send_flag) { send_flag 0; printf(%u,%u,%d,%u\r\n, hall, target, ctl, duty); }通过串口波形能看到如果 pos 和目标值之间总有固定偏差说明积分太小如果 pos 在目标值两侧快速交换说明微分不足或传感器噪声太大如果 ctl 已经顶到上限但 pos 还在偏差侧说明输出限幅太小或电流不够。这一步能省下大量时间。6.2 给电流叠加一个前馈抵抗线圈发热PID 是典型的负反馈反馈的缺点是等到误差产生了才动作。线圈发热后同样占空比下电流下降于是产生了静差。要解决这个问题可以在 PID 输出基础上叠加一个静态工作点补偿项也就是根据目标悬浮高度直接给一个恒定的基础 PWMPID 只负责叠加修正量。我一般这样算基础占空比等于悬浮空载时维持目标高度所需的近似占空比然后 PID 输出按比例调整。这个基础项不需要很准因为作用是给系统一个合适的工作点剩下的偏差由 PID 微调。实际测试中加上这个基础项后热漂移高度从每小时 0.5mm 降到 0.1mm 以内。从那以后我每次调磁悬浮都会先写一小段脚本把霍尔曲线拉出来确认传感器线性段再上电不然都是在瞎调。希望帮到你。本文还有配套的精品资源点击获取