简介:针对意法半导体STM32F10X系列微控制器的直流无刷电机无霍尔控制例程,面向嵌入式电机控制开发者和学习BLDC驱动原理的入门读者。无传感器控制中反电动势是判断转子位置的关键,该例程围绕反电动势检测实现电机定位与速度控制,涵盖三段式启动、上电直接启动等逻辑,并完整给出三相六步换向与PWM调速的工程实现。压缩包共342个文件,约9.96MB,以C源码、H头文件、工程配置文件、HEX/AXF可执行文件和TXT说明为主,便于直接阅读、编译和烧录调试;MAP、LST、CRF等编译过程文件供对照分析。目前已有3652人学习/下载,适合作为入门与进阶的电机控制参考资料。通过学习该例程,可以掌握无感BLDC控制中的反电动势采样、换相时序处理、以及用示波器观察驱动波形验证输出正确性的调试方法,对理解微控制器定时器、ADC与驱动电路的协同工作有直接帮助。 这几周一直在调一套基于反电动势检测的无感BLDC驱动方案,从最开始上电就抖、转不起来,到后来逐步稳定跑起来,中间踩了不少坑。国内社区里关于STM32做无感BLDC的例程和资料其实不少,但大多停留在"给你一份源码"的层面,真正把反电动势检测的原理、硬件配合、换相逻辑讲透的文章不多。这篇想把从0到1实现反电动势检测BLDC驱动STM32例程的完整思路整理出来,重点是那些示波器上看得到、代码里需要细抠的细节,给正在做无感无刷驱动或准备从有感方案转无感的朋友一个参考。
反电动势检测是BLDC无感驱动的核心,它的意义在于去掉霍尔传感器,让控制器通过电机三相绕组自身的电压信号来感知转子位置。下面我会从原理、硬件设计、定时器资源、启动流程和调试经验几个层面展开,基本按照一套可复用的工程实现路径来组织。
1. BLDC为什么要用反电动势,六步换相的基本盘
BLDC电机和PMSM最大的区别在于控制方式,BLDC是梯形波反电动势,用方波电流驱动,所以不需要像FOC那样做SVPWM和Clark/Park变换。六步换相的意思是,逆变器每分钟电角度60度切换一次开关组合,每个周期完成6次换相,每次换相时有三相中两相导通、一相悬空。霍尔传感器的作用就是告诉控制器"现在转子在哪个60度扇区",从而决定下一次开通哪两个管子。
去掉霍尔之后,控制器失去位置反馈,靠什么推断转子位置?就是反电动势。转子上的永磁体转动时,会在定子三相绕组中感应出电压,这个电压的幅值和转子的电角速度成正比。当某相的反电动势过零时,意味着转子正好处在该相绕组的中性位置,再过30度电角度,就是这相的换相点。所以无感BLDC的关键任务就变成两件事:一是准确检测三相反电动势的过零点,二是根据过零点推算换相时刻。
一个容易混淆的地方是,很多人以为反电动势检测必须在电机转动起来之后才有效。确实,静止状态下反电动势为零,所以无感方案必须有额外的启动策略,这个后面专门讲。但反电动势检测本身也不是只能靠硬件比较器来实现,用STM32的ADC轮流采样三相端电压、在软件里找过零也是常见做法。两种方案各有优势:比较器电路简单、延迟小,适合高转速;ADC方案省掉了外围比较器,但需要注意同步采样时刻,避免PWM开关噪声干扰。
从六步换相的逻辑看,无感驱动和多霍尔驱动的区别,本质上只是把"读取霍尔电平"换成了"检测反电动势过零事件",换相表本身、PWM占空比控制、加减速策略这些基本一致。把这一点想通,理解无感方案的代码会快很多。
2. 反电动势过零检测的数学模型与两种检测窗口
要检测过零,得先明白反电动势信号长什么样。假设电机星形接法,母线电压为Vdc,某一相桥臂的下管导通时该相输出电压为0,上管导通时为Vdc。当某相悬空时,它的端电压主要由反电动势和中性点电压构成。忽略电感和续流二极管的管压降,悬浮相的端电压表达式近似为:
V_phase = V_neutral + EMF_phase
其中V_neutral是星形中性点电压,理想情况下处于Vdc/2附近,EMF_phase是该相反电动势。因此,悬浮相端电压的反电动势过零,发生在V_phase = V_neutral的时刻。工程上更简单的检测方式是:把悬浮相电压与母线电压的一半(Vdc/2)做比较,比较器翻转时刻就是反电动势过零点。
这就解释了为什么几乎所有无感BLDC的硬件方案里,都会看到两个关键元素:一个母线分压网络形成Vdc/2参考电压,三个比较器分别接三相的悬浮相电压。STM32外部比较器输出的边沿信号接到定时器的捕获通道,微控制器就能通过输入捕获中断记录过零时刻。
检测窗口需要区分PWM_ON和PWM_OFF两种状态。在PWM_ON阶段,母线电压通过上管加到绕组上,悬浮相端电压包含一整个PWM周期的斩波波形,此时反电动势信号被叠加在高压PWM噪声上,直接采样误差很大,所以比较器中通常要加低通滤波器。在PWM_OFF阶段,也就是下管续流期间,悬浮相端电压相对纯净,更适合检测。因此STM32例程里通常会在中心对齐PWM的下管导通中点附近打开比较器窗口,或者用ADC触发采样,专门抓这个时间段的数据。
换相点不是过零点本身,而是过零点之后延迟30度电角度。如果电机当前电角速度为ω_e,那么延迟时间为τ = (π / 6) / ω_e。在代码里,这个延迟可以用一个硬件定时器实现:每次捕获到过零边沿后,启动定时器计时到τ,计时溢出触发换相中断。也有做法是把过零信号直接接PWM定时器的刹车输入,用换相表直接改变PWM输出组合,能省掉一个定时器,但灵活性差一些,调试初期不推荐。
3. 硬件电路设计细节:分压电阻、比较器与滤波电容的选型陷阱
反电动势检测电路的核心结构不复杂,但参数选不对,检测出来的过零点就是错的。最常见的是分压电阻和滤波电容的组合问题。我第一版原理图用的是100k/100k分压,后级加了10nF电容滤高频,结果波形上看到的过零点延迟非常严重,因为10nF电容和100k电阻构成的低通滤波器时间常数到了1ms级别,转快了根本不能用。
分压电阻的选择需要考虑两点:输入阻抗和分压比。STM32的比较器输入漏电流很小,但100k级电阻会引入热噪声,且与滤波电容形成RC延迟;10k/10k是工程上比较稳妥的折中,功耗略大但对信号影响小。分压比方面,由于比较器参考电压是Vdc/2,分压网络把端电压按比例降到比较器输入范围以内即可,同时注意分压后信号的共模范围不要超出STM32的IO承受能力,最好用双比较器方案做差动检测,不过对5V以内的低压BLDC驱动的确没必要。
滤波电容是反电动势检测的一个关键矛盾。电容小了,滤不掉PWM斩波噪声,过零会抖动;电容大了,信号被相移,过零点不准。比较合理的做法是:先确认PWM频率,然后设定RC截止频率为PWM频率的1/10左右,再根据实际波形微调。比如PWM为20kHz时,RC截止频率约2kHz,若选10k电阻,电容约8nF,取标准值10nF后实测过零延迟大约几十微秒,在10000rpm以上的高速场合就需要做软件补偿。还有一个办法是利用定时器在PWM OFF中点附近采样,滤波电容可以取得更小,减少相位延迟。
比较器选型上,尽量选推挽输出的高速比较器,像LMV331这一类响应时间在几百纳秒级别的型号就够了。注意输出端要加上拉电阻到单片机IO电压,这会影响捕获边沿的陡峭度,上拉电阻太小会加大功耗,太大会让上升沿变缓。我用的是一颗1k上拉,配合STM32的输入捕获,边沿检测很干净,几乎没有误触发。
硬件布局上有个容易被忽视的点:三相端电压检测走线一定要短,尽量靠近MOS管驱动输出端取信号,不要从电机线末端采样,否则线缆的寄生电感和噪声会叠加到信号上。另外,比较器电源要单独退耦,避免MOS管开关瞬间的地弹干扰比较器翻转。
4. STM32资源分配与核心代码实现:定时器、输入捕获与换相状态机
STM32的例程,资源规划比代码本身更重要。一套典型的无感BLDC方案需要的资源包括:一个高级定时器输出六路PWM(比如TIM1或TIM8),一个通用定时器做换相延时和速度计算,至少三个捕获通道接收比较器过零信号,再加上若干GPIO控制换相组合。
PWM输出要用中心对齐模式,也就是计数寄存器向上计数到ARR之后再向下计数回零。中心对齐的好处是PWM ON在中间,PWM OFF在两个边缘,采样窗口放在下管导通的中点处可以得到对称且稳定的信号。六路PWM对应三组互补输出,如果驱动板用的独立MOS驱动芯片,不一定需要互补带死区模式,但如果用的半桥驱动芯片,那TIM1的互补输出和死区生成功能就非常有用了。
核心代码部分,先初始化PWM定时器和捕获定时器。PWM初始化这里不再赘述,重点说说换相状态机。状态机可以简单用一个换相索引表示当前扇区,每个扇区对应一组开关组合:
// 六步换相表:每行对应一个扇区的上管/下管开关组合 // 通道为0表示关闭,1表示PWM调制 const uint8_t commutation_table[6][6] = { {1,0,0, 0,0,1}, // 扇区0: A上管PWM, B悬空, C下管导通 {1,0,0, 0,1,0}, // 扇区1: A上管PWM, B下管导通, C悬空 {0,1,0, 0,1,0}, // 扇区2: A悬空, B上管PWM, C下管导通 {0,1,0, 1,0,0}, // 扇区3: A下管导通, B上管PWM, C悬空 {0,0,1, 1,0,0}, // 扇区4: A下管导通, B悬空, C上管PWM {0,0,1, 0,1,0}, // 扇区5: A悬空, B下管导通, C上管PWM };这里表格中的每一项,前三个是上管A、B、C,后三个是下管A、B、C。实际代码里一般直接操作定时器的比较寄存器,把对应通道的CCR设为0(关断)或ARR值(全开)或PWM占空比对应的值。
输入捕获中断函数里要做的,是把当前捕获的边沿时刻记录下来,同时判断比较器翻转对应哪一相,然后启动换相延时定时器。延时时间由目标电角速度和当前测量的周期决定:
void EXTI_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0p00(0)) { // 捕获到过零事件 zero_cross_event.current_time = timer_get_counter(); float electrical_speed = calculate_elec_speed(); uint32_t delay_ticks = (TIMER_CLOCK_HZ / (6.0f * electrical_speed)); timer_set_compare(delay_ticks); // 启动延时定时器 LL_EXTI_ClearFlag_0p00(); } }延时定时器溢出后进入换相中断,此时把commutation_index加1并取模6,读取换相表刷新PWM输出,同时更新这次换相的实际时间戳,用来计算下一拍转速。这套流程本身不复杂,真正的调试点在于:过零事件是否稳定出现、延时时间是否准确、换相瞬间有没有电流尖峰。建议把换相时刻、过零时刻通过串口打印出来,配合示波器看波形,能快速定位问题。
5. 启动过程的三大阶段:定位、强拖与闭环切换的切换逻辑
无感BLDC的启动是一个独立于稳态运行的复杂逻辑。电机静止时反电动势为零,所以上来就得用别的方式建立位置参考。启动过程分三段:预定位、开环强拖、闭环切入。
预定位和合理解锁。我的例程里是给一固定扇区的两个管子导通几百毫秒,让转子转到和该磁通方向一致的位置。这个阶段不需要编码器,也不需要反电动势检测,纯粹靠定子磁场把转子拉过去。定位时间不能太短,否则转子在负载较重时会抖动不到位;也不能太长,否则会发热,我一般取200ms左右。
接下来是开环强拖。从定位扇区开始,按照设定的加速度逐步提高换相频率,让转子跟着定子旋转磁场转起来。强拖阶段的换相是开环的,定时器按照计算好的时间间隔刷新换相表。这里有一组很重要的参数:启动电压和启动加速度。电压太低,力矩不够,转子跟不上;电压太高,电流过大甚至失步。比较好的做法是PWM占空比从较低值开始,配合一个递增的斜坡函数,转速每上升一点,占空比跟上来一点。
闭环切入是整段代码里风险最高的环节。判断能否切入闭环的依据是:强拖阶段是否连续检测到N次有效的反电动势过零事件。我一般设至少连续5次以上,检测频率连续保持稳定后再切入。切换瞬间,因为换相时序可能和实际转子位置存在偏差,电流会有一定冲击,所以在切换前的最后一个强拖周期和新闭环的第一个周期之间,最好保持同一个换相扇区不跳变,也就是相位接续,避免突然跳扇区。切换后还要做一次闭环转速与目标转速的PI调节,直到稳定。
启动失败的头号原因就是误判过零。强拖时PWM占空比过高,反电动势被淹没在斩波噪声里,或者检测窗口打开时机不对,导致捕获到的过零不是真实的过零点。因此启动阶段很多人会故意降低PWM占空比,并且只在下管导通阶段打开比较器,宁可牺牲一些力矩,也要保证第一个过零信号是真实的。
6. 实测中发现的问题与参数整定经验
最后聊几个调试中的实际问题,这些都是只看源码学不到的东西。
第一个是过零信号抖动。我最初用比较器输出直接触发输入捕获,结果每分钟转速波动很大。排查后发现问题出在滤波电容上,10nF电容滤掉的不仅是噪声,还把过零边沿整得又慢又软,触发点每次都差几个微秒,导致换相时刻漂移。后来换成2.2nF,并在比较器上加了正反馈做迟滞,抖动明显改善,转速稳定性跟着提升。
第二个是高速时滤波相位延迟导致的换相滞后。转速跑到12000rpm以上时,电角度快速拉高,反电动势频率随之升高,RC滤波器带来的相位延迟不再是可忽略的小量。经验公式是补偿角度 ≈ arctan(2π * f_emf * RC),可软件做角度提前。实测在15000rpm下,给30度补偿后电流波形趋于正弦,转矩脉动明显变小。
第三个是低速大负载时的检测失效。低转速反电动势幅值小,比较器的参考电压Vdc/2必须非常精确,否则过零电压偏移导致检测失败。有一个技巧是分压电阻网络用0.1%精度的电阻,并且把参考电压做成可以由PWM控制的虚拟中性点——部分高级例程用PWM的通道输出一个50%占空比的方波,经过RC滤波得到一个半母线电压的参考,精度可以做得比固定电阻分压更高。
第四个是关于观察波形的方法。调试反电动势时,示波器探头不要直接接电机三相,而是接比较器输入分压之后的信号,否则母线高压可能损坏探头。更推荐的是把比较器的输出引脚引一个测试点出来捉翻转边沿,这样可以直接看到过零检测的真实时间。跟霍尔信号(如果手头有有感电机可以临时做对照)同时抓波形,能很直观地看到过零点和霍尔边沿之间是否严格相隔30度电角度。
整个方案跑通后,再把CMake工程、启动参数表、不同极对数适配等等整理成可复用的例程,后续换电机、换板子时只需要改几个宏定义就能重新调起来。这也是我建议所有做无感BLDC的朋友应该花时间做的一步:把硬件相关的参数和算法核心逻辑解耦,下次调试会快很多。
本文还有配套的精品资源,点击获取