ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32F103无刷电机BLDC控制:有感无感六步换相与PID闭环实战

STM32F103无刷电机BLDC控制:有感无感六步换相与PID闭环实战 简介一份基于STM32F103的无刷直流电机控制源码工程同时覆盖无感反电动势检测与有感霍尔传感器两种模式并采用方波PID闭环控制实现速度与位置调节。工程代码呈模块化设计包含电机控制、PID算法、串口通信和OLED显示等独立模块启动过程经专门优化电机可从静止平稳加速到设定速度运行中速度波动小、响应快。压缩包内含202个文件以C源码和头文件为主另有编译生成的调试配置文件、目标文件等整体大小约5.69MB目录结构清晰便于查阅与二次开发。目前已有3723人学习适合嵌入式初学者及电机控制开发人员用于研究无感/有感BLDC方案、PID整定以及STM32工程搭建是一份具有较强实用性和参考价值的完整源码资料。 做这个工程的时候我其实带着一个很朴素的想法把无刷电机的“有感”和“无感”放在同一套源码里跑通方波六步换相再用PID把速度闭环稳住。整份工程基于STM32F103写成硬件上只需要一块最小系统板、一个三相驱动桥和电机本体就行。它解决的核心痛点很直接——很多人一开始接触BLDC要么被FOC的数学劝退要么在网上扒到的代码只支持有感、不支持无感换了电机就傻眼。这篇就把我从零开始搭这个工程时的方案选型、换相逻辑、PID落地和调试心得完整捋一遍尤其适合手里有电机、想快速跑起来又想把原理吃透的嵌入式开发者。1. 项目整体架构与方案选型1.1 为什么还用STM32F103先说选型。STM32F103放在今天看确实不算“新”72MHz主频、128KB Flash跟动不动几百兆的国产M4比不了。但做电机控制它有一个被很多人低估的优势外设刚好够用而且文档生态极其成熟。TIM1高级定时器自带互补PWM输出和硬件死区插入ADC可以定时器触发采样再加上足够快的GPIO翻转速度方波BLDC这套玩法全部满足。另一个现实因素是我手头这类板子多几块钱一片坏了不心疼。如果你做产品原型用F103验证完算法逻辑后面换G431或者FOC专用的芯片代码框架也能平移过去。很多人纠结要不要直接上FOC。我的看法是方波控制永远是第一课。六步换相的逻辑、反电动势过零检测、PID闭环这些是理解旋转磁场和电机模型的底层能力。直接跳去FOC遇到奇怪噪声或者相电流波形不对你根本不知道是硬件问题、采样问题还是变换矩阵算错。先把方波跑透再上FOC踩坑的深度完全不同。1.2 方波控制的核心六步换相有刷电机靠机械换向无刷电机靠电子换相。方波控制也叫六步换相法本质是让三相逆变桥按固定顺序输出6个电压矢量每个矢量持续60度电角度一个电周期刚好走完一圈。每个时刻只有两相导通第三相悬空所以电流路径是固定的从某一相流入从另一相流出。为什么叫“方波”因为每一相绕组的电压波形是典型的方波——导通时是母线电压或者PWM斩波后的有效值关断时为零。跟FOC的正弦波电流相比方波的转矩脉动理论上更大但胜在实现简单、对MCU算力要求低。在电动工具、风机、水泵这些对噪音不太敏感的场景里方波控制依然是性价比之王。换相顺序不是拍脑袋定的它依赖于电机转子的当前位置。有感模式用霍尔传感器直接读位置无感模式则通过反电动势过零检测推算位置。同一个工程里我把这两套逻辑各自封装成独立的模块只向上层暴露“当前位置”和“换相动作”两个接口这样上层PID完全不用关心底层是霍尔还是反电动势。1.3 统一抽象让有感/无感共用一套PID工程里我做了一个关键的架构决定把“转子位置感知方式”和“电机控制算法”彻底解耦。在上层看来只要有一个函数返回当前电角度或者霍尔状态另一个函数执行换相动作就行。至于这个状态是从霍尔GPIO读到的、还是从反电动势比较器拿到的上层不关心。这样做的直接好处是调试有感模式时可以用Hall状态验证六步换相表和PWM时序对不对调通之后切换到无感模式只需要关心无感启动和过零检测的鲁棒性速度环和PID参数直接复用。实测下来这套抽象让整个工程的可维护性大大提升——改动无感算法的时候完全不需要担心破坏已经有感的稳定性。2. 硬件平台与底层外设设计2.1 最小系统与驱动桥要点先说硬件。我的测试平台用了常规的STM32F103C8T6最小系统板电机驱动部分用了一颗三相半桥驱动芯片类似IR2101或EG2134这种自举驱动配合6个N-MOS管搭成三相全桥。电源端用了一个12V的开关电源给电机供电MCU这边单独用3.3V稳压。这个架构里最需要注意的有两点。一是驱动的自举电容和MOS栅极电阻要选对否则上管开不起来或者开关振铃大二是逻辑地与功率地要单点连接采样电阻的地线要单独走线回电源负极避免大电流在PCB地线上产生压差干扰控制信号。我之前在洞洞板上飞线搭过一次结果每次换相都会导致MCU复位查了两天才发现是地线干扰换成分开布线后彻底解决。霍尔传感器方面如果用的是5V供电的霍尔需要注意STM32F103的GPIO是否容忍5V。F103的数据手册里标明大部分引脚是FT5V容忍的但个别ADC引脚不是建议接到普通GPIO、开漏输入然后用10k电阻上拉到3.3V。这样信号幅值安全也不影响霍尔信号跳变沿的检测。2.2 PWM互补输出、死区与DMA方波控制中PWM生成有几种策略。最省事的方式是换相周期内上管给固定占空比的PWM下管常通。这样可以实现斩波调压不需要同时调制上下管。我用的是TIM1的CH1、CH2、CH3三路输出分别控制A、B、C三相的上管下管用普通GPIO控制。每次换相就是一组GPIO电平和PWM通道开关的组合变化。这里需要特别重视的是死区时间。上下管如果同时导通就是直通短路轻则炸MOS重则烧驱动。TIM1的互补输出可以直接插入硬件死区但如果像我这样上下管分开控制就必须在换相状态切换时用软件保证“先关旧相、延时、再开新相”。我的代码里定义了一个SWITCH_DELAY宏实测在MOS管Qg比较大时需要2~3微秒的延时小管子1微秒足够。PWM频率我选的是16kHz。这个值兼顾了两点高于人耳听觉上限电机运行时没有明显的啸叫声频率不算太高MOS管的开关损耗可控。F103的定时器主频72MHz16kHz对应的ARR值是4499分辨率足够调出1%以内的占空比精度。// 以TIM1为例频率16kHzARR4499CH1-CH3为PWM输出 void pwm_timer_init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_OCInitTypeDef TIM_OCInitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_TimeBaseInitStruct.TIM_Prescaler 0; TIM_TimeBaseInitStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInitStruct.TIM_Period 4499; // 72MHz / (44991) ≈ 16kHz TIM_TimeBaseInitStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM1, TIM_TimeBaseInitStruct); TIM_OCInitStruct.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStruct.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStruct.TIM_Pulse 0; TIM_OCInitStruct.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM1, TIM_OCInitStruct); TIM_OC2Init(TIM1, TIM_OCInitStruct); TIM_OC3Init(TIM1, TIM_OCInitStruct); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }代码里值得多提一句的是TIM_CtrlPWMOutputs(TIM1, ENABLE);。STM32高级定时器的PWM主输出默认是关闭的如果忘了这个函数PWM波形死活出不来这是新手最容易踩的坑。2.3 反电动势采样电路无感模式要检测反电动势过零点。我用的方案是通过三个电阻把电机三相端电压分压到ADC可采样的范围同时用虚拟中点做比较参考。简单说就是MCU的ADC定时采集悬空相的端电压软件里和虚拟中点电压做差判断过零时刻。硬件上比较简单的做法是用三个等值电阻接成星形中点接到ADC然后将三相端电压也分别引到ADC。这样每个PWM周期里采样两次一次在两相导通期间一次在换相前的悬空期。需要注意分压电阻的阻值让端电压折算后不超过3.3V。母线12V时用10k和3.3k分压比例大约1/4最大约3V处于安全范围。3. 有感模式霍尔状态机与速度闭环3.1 霍尔换相表是“测”出来的网上很多代码直接给出换相表抄过来却发现电机反转、抖动、电流巨大——这是最常见的问题。霍尔换相表本质上跟你的电机相序、霍尔安装位置强相关必须实测。我的做法是先写一个简单的开环测试程序固定输出6个电压矢量给三个半桥手动或用一个低压电源把转子拨到某个位置读一下当时的霍尔电平。几个位置测下来就能建立“霍尔组合→转子区域→应该导通的相”的映射。把这个映射整理成一个长度为7的查表数组索引0不用霍尔值1~6有效工程里直接查表换相。我实测下来自己测的换相表和网上流传的通用表经常会有两个状态的差异这就是为什么照抄会出问题。3.2 状态机换相实现有感模式的换相逻辑是一个简单的状态机。TIM1的更新中断里做PID计算和速度累加换相则放在霍尔GPIO的外部中断里每次任一霍尔信号跳变读取三个霍尔引脚的电平查表得到新状态立即更新PWM通道和GPIO输出。速度采样用的是M法也就是在固定时间窗口内统计换相次数。BLDC一个电周期换相6次所以换相频率乘以一个系数再除以极对数就是每秒的机械转数。这个速度值作为PID反馈每1ms计算一次查表得到占空比写入TIM1比较寄存器实现闭环调速。3.3 速度环PID的软件落地速度环我做了增量式PID。增量式的好处是不需要对误差做累积输出就是占空比的增量天然自带了一定的抗积分饱和能力。PID参数从纯P开始往上加响应慢就加P有静差加I震荡就加D或者降低P。这个落地过程放到第5部分详细讲。4. 无感模式三段式启动与反电动势闭环4.1 无感启动为什么难无感模式最大的挑战在启动阶段。电机静止时反电动势为零根本没有位置信息你无法从悬空相电压判断转子在哪。这时候直接按某个换相顺序给电很可能会因为初始角度不对而抖动、反转甚至根本转不起来。我的方案是业界常用的三段式启动预定位、强制换相加速、切入反电动势闭环。4.2 预定位、强拖加速与闭环切换第一步预定位任选一个电压矢量给电一段时间大约200~500ms让转子被拉到一个已知位置。这个电压不用太大能让电机转子对准就行太大会把电机顶得过冲反而影响下一步。第二步强制换相从预定位的矢量开始按固定的时间间隔依次切换6个矢量形成一个旋转磁场拖着转子跑。这个强制换相频率需要从较低值起步然后按一定加速度提升相当于开环梯形加速。我实测下来如果加速斜率太大电机会失步表现为“咔咔”响但不转斜率太小启动又太慢。参数上一般从10Hz左右的电频率起步每50ms增加一点直到达到150Hz左右这时候转子已经有可观的反电动势了。第三步切入闭环在强拖过程中持续检测悬空相的反电动势过零信号。当过零检测连续成功N次后将控制权从“定时强制换相”切换到“过零检测触发换相”。这里要注意切换时机切换早了闭环失败切换晚了可能过零检测信号被强制换相的干扰淹没要么失败卡死、要么剧烈抖动。我最后采用的策略是连续成功4次过零检测才允许切换同时切换后的第一个换相时刻仍然沿用强拖的定时频率之后才完全交给过零检测。4.3 过零检测的软件处理细节过零检测的实现每个PWM周期当上管PWM导通且采样点稳定后启动ADC采样悬空相电压。数字滤波上用“连续三次过零才认”的方式防止单个毛刺误判。检测到过零后标准做法是再延迟30度电角度才换相。因为反电动势过零点落在悬空相电压的中点而过零点到下一次换相点之间正好隔30度。延迟时间根据当前电周期时长计算如果最近一次完整换相周期时间是T那30度对应的延迟就是T/12。每换相一次都更新这个延迟值保证转速变化时依然准确。5. PID调参与上位机联调经验5.1 增量式PID的实现速度环的PID我用的是增量式。和位置式相比它输出的是占空比的增量不会让积分项一直累积造成饱和代码上也好写。typedef struct { float kp; float ki; float kd; float target; float actual; float err[3]; float output; } IncrementalPID; void inc_pid_calc(IncrementalPID *pid) { float delta; pid-err[2] pid-err[1]; pid-err[1] pid-err[0]; pid-err[0] pid-target - pid-actual; delta pid-kp * (pid-err[0] - pid-err[1]) pid-ki * pid-err[0] pid-kd * (pid-err[0] - 2.0f * pid-err[1] pid-err[2]); pid-output delta; // 输出限幅 if (pid-output 90.0f) pid-output 90.0f; if (pid-output 0.0f) pid-output 0.0f; }限幅非常重要。占空比最大给到90%而没用100%是给电流上升留出余量防止在重载时直接把母线拉垮。其实这个限幅值应该根据你的电源能力来定电流大的场景甚至可以压到80%。5.2 VOFA上位机调参流程调参没有上位机可视化就是瞎子摸象。我用VOFA这个免费上位机用JustFloat协议把速度目标值、实际速度、PID输出三个量通过串口发到电脑上实时绘制波形。它在GitHub上的源码可以直接编译进工程只需要实现一个简单的字节拼包函数非常方便。调参步骤我按顺序这样走只给P参数从很小的值开始比如P0.1观察实际速度是不是稳定、有没有震荡加大P直到速度开始出现持续的等幅振荡然后退回振荡前的值大约取振荡值的50%~60%作为P加I参数消除稳态误差。观察加速到目标速度后是否“掉速”慢慢增加I直到稳态误差变小注意I太大会超调如果启动瞬间有尖峰和震荡加一点D通常0.1左右就够调大反而会把噪声放大。5.3 我的调参心得先机械后电气实际调参时还遇到过一个坑无感模式下如果PID给电太快强制换相阶段还没走完PID就会因为反馈速度太低而疯狂积分导致占空比瞬间打满启动失败。解决方法是无感启动阶段强制使用开环占空比等切入闭环后再切回PID输出。这个切换逻辑很关键不然你调好的PID参数在低速启动时会完全失效。另外我强烈建议用两个观测目标来确认参数是否合适一是在空载下进行阶跃速度响应测试观察超调量二是在带负载力下加速观察是否有掉速和振荡。这两个场景比单纯看空载波形更能暴露问题。6. 常见问题排查与避坑实录6.1 启动失败、电机咔咔响不转这是无感模式最典型的问题。大概率是强制换相加速度太大或者预定位时间太短。先检查启动时的换相频率是不是过高可以降到5Hz起步慢慢加上去。同时用示波器或者上位机看启动瞬间的母线电流如果电流瞬间飙升到额定值以上说明转子没有跟上定子磁场失步了。这时候不要急着调PID先降低强拖频率和加速度让转子能稳定跟上再谈闭环。另外一个隐蔽原因是过零检测的采样点没有避开PWM开关噪声。PWM开通瞬间由于寄生电容和走线电感的存在端电压会有一个明显的振铃尖峰如果在这个时间点采样测到的反电动势就是假的。解决方式是把ADC触发点挪到PWM周期中间避开开关沿。6.2 PWM输出方波带尖端毛刺用示波器看电机相线或者MOS管漏极波形会发现方波的上升沿和下降沿有尖刺和振铃。这主要来自两个原因一是MOS管的寄生电容和走线电感构成的LC回路在开关瞬间产生高频振荡二是栅极电阻太小MOS管开关太快di/dt过大。处理办法排序先加大栅极电阻从10Ω试到33Ω让开关边沿稍微变缓然后在MOS管的D-S之间加一个RC吸收电路阻容值取100Ω和1nF左右吸收振铃最后是PCB布线上尽量缩短功率走线的环路面积。如果是洞洞板搭的电路建议先加栅极电阻和RC吸收效果立竿见影。这里要注意的是加栅极电阻会导致开关损耗增加发热变多需要在毛刺和温升之间取平衡。6.3 PID震荡与电流尖峰速度闭环下电机来回抖动、声音发闷、电流波形杂乱基本是PID参数问题。最常见的是P太大其次是I太大。如果已经陷入“震荡→调P→更震荡”的循环我建议把参数全部清零从P0.1开始重新走一遍调参流程不要想着微调经常是推倒重来更快。还有一个容易被忽略的是采样频率和PID频率的匹配。如果速度采样太慢比如10ms才计算一次PID而电机响应时间只有几ms那PID看到的“速度”已经滞后了再怎么调都不稳。我的做法是速度采样和PID计算都放在1ms定时中断里保证采样和输出频率的一致。6.4 排除干扰的技巧实际调试中最容易影响判断的其实是示波器探头和接线。电机是大电流设备探头地线夹如果太长会像一个天线把噪声耦合进测量回路。我习惯用接地弹簧代替长地线夹波形马上干净不少。另外调试串口的波特率尽量用115200以上降低传输延迟对波形观察的影响。写在最后这套源码工程前前后后我调了两周多最大的体会是BLDC控制的难点从来不在代码本身而在你什么时候、用什么方式感知到转子的真实状态。霍尔换相表要实测、无感启动要反复试加速曲线、PID参数要在负载下验证每一环都在考验你对电机模型的直觉。整个工程最终跑通的那一刻看着反电动势波形和换相脉冲完美对齐电流波形平滑稳定那种感觉是单纯看资料完全体会不到的。如果你正在搭自己的BLDC工程我建议先从有感模式起步把换相表和PWM时序调顺再切换到无感模式逐个攻克启动和过零检测最后回头加上PID闭环。这个顺序能帮你把变量控制在最小范围出问题时定位也更准。后面如果有精力可以在方形控制的基础上加入电流采样做双闭环甚至基于这套架构平滑迁移到FOC一条路走下去收获会非常大。本文还有配套的精品资源点击获取
返回列表