霍尔传感器BLDC六步换相指南:真值表、STM32代码与调试技巧
2026/9/21 1:22:29 网站建设 项目流程

做电机控制的这些年,我见过太多人第一次调BLDC驱动板时,看着霍尔波形都正常,电机却只嗡嗡叫不转圈。这个问题十有八九出在霍尔真值表和换相顺序上。这篇我把六步换相这套东西完整拆一遍:霍尔真值表怎么来的,STM32上的代码怎么落,PWM调速怎么做,以及实际调试中那些让你抓狂的坑。

不管你是做散热风扇、水泵、电动工具还是机器人关节模组,只要用的是带霍尔传感器的无刷直流电机,这套控制逻辑基本通用。本文默认你懂点C语言、用过STM32的GPIO和定时器,并不需要多深的电机理论。

1. 先搞清楚:BLDC为什么非要“电子换相”

1.1 有刷和无刷的本质区别

有刷直流电机能转,靠的是换向器和碳刷:转子线圈转到某个位置,换向器自动把电流方向切一下,磁场极性随之翻转,转子就持续转下去。听起来很聪明,但碳刷是机械接触,时间长了会磨损,转速高了会有火花,噪声也大。

无刷电机把线圈挪到了定子上,转子直接用永磁体。好处是没有碳刷损耗和换向火花,坏处是电流方向没人帮忙切了,你得用功率开关管按顺序“人工换向”。这就是所谓“电子换相”。你问BLDC和PMSM有什么差别?从电机本体看两者结构几乎一样,差异主要在于设计时是往梯形波反电动势还是正弦波反电动势去优化。反电动势是梯形波,适合用方波电流驱动,这就是BLDC;反电动势是正弦波,适合用SVPWM做正弦驱动,那是PMSM。六步换相就是最常见的一种方波驱动方式。

1.2 三相全桥与六步换相的工作逻辑

BLDC定子通常是三相绕组,A、B、C三相星形连接。驱动电路是一个三相全桥,上桥臂三个管分别是AH、BH、CH,下桥臂三个管是AL、BL、CL,总共6个MOS管。

六步换相的基本规则是:任意时刻只导通一个上管和一个下管,形成一条从电源正极经过两相绕组再到地的电流回路。另外一相悬空不导通。每60°电角度换一次相,一个电周期正好走6个状态,所以叫“六步换相”。

拿第一步举例:AH和BL导通,电流从AH进A相绕组,再从B相绕组经BL流回地,此时C相悬空。定子电流方向A到B,产生一个固定方向的磁场,拉着转子转。转子转60°后,霍尔信号发生变化,控制器再切到下一步,比如AH和CL导通,电流变成A进C出,定子磁场方向跟着跳60°,继续拉转子。

这就像六组灯轮流亮,但每个灯的亮起时刻必须严格跟着转子位置走,不能乱来。现在你就明白了,换相的关键就两个问题:第一,怎么知道转子转到哪了;第二,每个位置应该让哪两个管子导通。

1.3 霍尔传感器:让控制器看见转子位置

有霍尔BLDC在定子上装了三颗霍尔开关芯片,用来感知转子永磁体的磁场方向。转子转一圈,三颗霍尔输出三路方波信号,彼此相位差120°电角度。三路信号组合起来,一个电周期内会产生6种有效状态,正好对应6个60°的转子位置扇区。

霍尔是数字信号,不需要ADC采样,直接用MCU的GPIO或者外部中断读取就行。这一点比无感方案简单太多。无感方案没有霍尔,只能通过检测悬空相的反电动势过零点来推算位置,然后再延时30°电角度换相,低速时反电动势太小很难检测。所以新手入门,强烈建议先从有霍尔BLDC做起,把换相逻辑吃透,再回头看无感方案就轻松了。

2. 霍尔真值表:六步换相的地图

2.1 三路霍尔状态如何编码转子位置

我用A、B、C代表三路霍尔输出,约定读到高电平为1,低电平为0。三路信号组合有2的3次方共8种状态,其中只有6种会实际出现,剩下000和111是无效状态,要么是没安装好,要么是霍尔坏了。

假设某颗电机,正转时霍尔状态按以下顺序循环:001 → 101 → 100 → 110 → 010 → 011 → 001。注意,不同电机霍尔安装位置和绕组方向不同,顺序可能不一样,但一定是6个状态循环,这个规律不会变。

三路霍尔输出其实可以简单理解成转子磁场方向的“三比特编码器”,每个状态代表转子的N极落在某个60°扇区内。控制器拿到这个状态后,就知道该怎么给三相绕组通电了。

2.2 从霍尔状态到MOS管开关组合

我直接给一张标准正转真值表。桥臂命名AH/BH/CH为上管,AL/BL/CL为下管,霍尔状态排列按我刚才说的顺序整理,这样更容易看出规律。

霍尔状态导通相上管下管电流路径
001A+ B-AHBL电源→AH→A相→B相→BL→地
101A+ C-AHCL电源→AH→A相→C相→CL→地
100B+ C-BHCL电源→BH→B相→C相→CL→地
110B+ A-BHAL电源→BH→B相→A相→AL→地
010C+ A-CHAL电源→CH→C相→A相→AL→地
011C+ B-CHBL电源→CH→C相→B相→BL→地

每一行代表一个60°电角度的驱动状态。你会发现规律很明显:上管在A、B、C三相之间循环切换,下管则是在另外两相里切换。这就是“一上一下、两相导通、一相悬空”的完整表达。

需要注意的是,这张表不是万能的。你换一颗电机、换一种霍尔安装角度、甚至把霍尔线插错顺序,真值表可能整个对不上。所以更稳妥的做法是下面的“实验法建表”。

2.3 反转方向与实验法建表技巧

如果要反转,最省事的办法有两个:交换任意两相电机线,或者把换相顺序反过来。交换电机线是硬件层面的操作,立竿见影;软件层面就是准备一张反转表。拿上面的正转表举例子,把霍尔状态按反向顺序填一遍,每行对应的导通组合也会变:

霍尔状态导通相上管下管
001C+ B-CHBL
101A+ B-AHBL
100A+ C-AHCL
110B+ C-BHCL
010B+ A-BHAL
011C+ A-CHAL

如果你手里的电机资料不全,或者霍尔线序已经被前任折腾乱了,别硬猜。用实验法建表,很土但非常有效:

  1. 给驱动板通电,占空比调到很小,比如5%,换相表先置空。
  2. 手动缓慢转动电机,用串口打印三路霍尔状态,确认6个状态都能出现,没有缺相。
  3. 在程序里写一个测试模式,每次只输出一组导通组合,比如只让AH和BL导通,用手转动电机,感受转子的“吸力”。
  4. 记录下每个霍尔状态对应哪个导通组合能让转子向目标方向转,把这些映射倒进真值表。

这套方法我实际用过很多次,尤其在客户寄来的杂牌电机上,比翻说明书快得多。

3. 代码实现:让电机转起来

3.1 外设准备:PWM、GPIO和霍尔中断

我以STM32F103为例,但它适用于所有带高级定时器的MCU。三路上管AH/BH/CH接到TIM1的CH1/CH2/CH3,三路下管AL/BL/CL用普通GPIO输出。霍尔A/B/C分别接到三个具有外部中断功能的GPIO上,边沿触发。

TIM1配置为PWM模式,频率建议设到16kHz以上,避免听到刺耳的啸叫。我常用24kHz,占空比精度0.1%,人耳基本无感。PWM周期寄存器ARR决定占空比分辨率,比如主频72MHz时,PSC设为2,ARR设为999,PWM频率就是72MHz除以3再除以1000,等于24kHz。

初始化时,三个PWM通道的CCR都写0,也就是占空比0%,电机不会转。等你把换相表准备好、霍尔中断打开、duty设成一个安全值,电机才会动。

3.2 换相表与换相函数的代码实现

下面这段代码是六步换相的核心。我用一个字节的低6位表示6个MOS管的状态,bit0是AH,bit1是AL,bit2是BH,bit3是BL,bit4是CH,bit5是CL。

// 桥臂位定义 // bit0: AH, bit1: AL, bit2: BH, bit3: BL, bit4: CH, bit5: CL // 正转换相表 const uint8_t comm_table_fwd[8] = { 0x00, // 000 无效 0x09, // 001: A+ B- = AH + BL 0x12, // 010: C+ A- = CH + AL 0x18, // 011: C+ B- = CH + BL 0x24, // 100: B+ C- = BH + CL 0x21, // 101: A+ C- = AH + CL 0x06, // 110: B+ A- = BH + AL 0x00 // 111 无效 }; // 反转换相表 const uint8_t comm_table_rev[8] = { 0x00, // 000 无效 0x18, // 001: C+ B- = CH + BL 0x06, // 010: B+ A- = BH + AL 0x12, // 011: C+ A- = CH + AL 0x21, // 100: A+ C- = AH + CL 0x09, // 101: A+ B- = AH + BL 0x24, // 110: B+ C- = BH + CL 0x00 // 111 无效 }; volatile uint16_t g_duty = 300; // 当前占空比,0~1000 volatile uint8_t g_direction = 1; // 1正转,0反转

换相函数做的事情很简单:根据霍尔状态查表,关掉不该导通的管子,打开该导通的管子。三个上管始终由PWM输出,只是CCR被设为0的通道等效于关断;下管直接用GPIO控制。

void set_low_side(uint8_t phase) { HAL_GPIO_WritePin(AL_GPIO_Port, AL_Pin, (phase & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(BL_GPIO_Port, BL_Pin, (phase & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(CL_GPIO_Port, CL_Pin, (phase & 0x20) ? GPIO_PIN_SET : GPIO_PIN_RESET); } void set_pwm_channel(uint8_t ch) { // 先把三个通道CCR全部清零 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 0); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, 0); if (ch == 1) __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, g_duty); else if (ch == 2) __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, g_duty); else if (ch == 3) __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, g_duty); } void bldc_commutate(uint8_t hall_state) { const uint8_t *table = g_direction ? comm_table_fwd : comm_table_rev; uint8_t ph = table[hall_state & 0x07]; // 先切下管 set_low_side(ph); // 再切上管PWM通道 if (ph & 0x01) set_pwm_channel(1); // AH else if (ph & 0x04) set_pwm_channel(2); // BH else if (ph & 0x10) set_pwm_channel(3); // CH else set_pwm_channel(0); // 无效状态,全部关断 }

这个写法有两点值得解释。第一,为什么先切下管再切上管?因为同一时刻只有一个下管导通,一个上管有PWM,不存在同一桥臂上下管同时导通的直通风险,所以顺序不敏感。但如果你的电路用了带死区要求的互补PWM,那就不能这么写了,后面章节会说。第二,为什么不清除PWM输出使能位,而是把CCR清零?因为CCR清零后PWM输出始终为低电平,等效关断,同时避免反复开关通道造成输出毛刺。

3.3 霍尔中断:每个跳变都是一次换相指令

霍尔信号每变化一次,就代表转子转过了60°电角度,控制器必须立刻响应。所以换相函数应该放在霍尔引脚的外部中断回调里,而不是主循环轮询。主循环轮询的问题在于,转速高了以后霍尔变化可能非常快,主循环一身任务根本忙不过来。

uint8_t read_hall(void) { uint8_t hall = 0; if (HAL_GPIO_ReadPin(HALL_A_GPIO_Port, HALL_A_Pin)) hall |= 0x04; if (HAL_GPIO_ReadPin(HALL_B_GPIO_Port, HALL_B_Pin)) hall |= 0x02; if (HAL_GPIO_ReadPin(HALL_C_GPIO_Port, HALL_C_Pin)) hall |= 0x01; return hall; } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == HALL_A_Pin || GPIO_Pin == HALL_B_Pin || GPIO_Pin == HALL_C_Pin) { bldc_commutate(read_hall()); } }

霍尔中断函数要尽量轻量。查表、切换GPIO、写CCR,加起来也就几条指令,非常适合在中断里做。不要在中断里做浮点计算、打印日志、延时,这些都会拖慢响应,高速时容易失步。

3.4 转速计算:霍尔跳变频率怎么换算成RPM

既然每次霍尔跳变等于60°电角度,那么只要测量相邻两次跳变的时间间隔,就能得到当前转速。如果电机极对数是P,那么机械上转一圈,电角度要转P圈,也就是霍尔跳变次数为6乘以P。

volatile uint32_t hall_last_tick = 0; volatile uint32_t hall_period_us = 0; volatile uint16_t speed_rpm = 0; #define POLE_PAIRS 4 // 在霍尔中断里加入计时 uint32_t now = TIM2->CNT; // TIM2配置为1MHz向上计数 hall_period_us = now - hall_last_tick; hall_last_tick = now; if (hall_period_us > 0) { speed_rpm = 60000000UL / (6UL * POLE_PAIRS * hall_period_us); }

举个例子,极对数4的电机,如果测量到两次霍尔跳变间隔是250微秒,那转速就是60秒除以6乘以4再除以0.00025秒,等于10000转每分钟。这个测速值可以用来做闭环,也可以串口打印出来看当前转速。

4. PWM调速与启动方式

4.1 占空比如何控制转速

六步换相解决的是“哪一相通电”的问题,PWM调速解决的是“通电多久”的问题。两者独立,互不干扰:每次换相决定把PWM输出切到哪个通道,而占空比决定这个通道在一个PWM周期里高电平的时间占比。

电机绕组是感性负载,平均电压约等于占空比乘以母线电压。占空比越大,绕组两端平均电压越高,电流越大,转矩越大,转速自然越高。所以调速就是直接修改g_duty这个全局变量。

void bldc_set_duty(uint16_t duty) { if (duty > 1000) duty = 1000; g_duty = duty; }

注意,占空比不能一下从0跳到1000,尤其带负载时,瞬间大电流可能把MOS管或电源干趴。所以调速要“缓”,后面启动策略里会说。

4.2 单边调制和双边调制怎么选

六步换相里PWM可以只作用在上管,下管恒通,这叫单边调制。优点是下管没有开关损耗,控制逻辑简单,我前面的代码就是单边调制。缺点是绕组电流纹波较大,声音略微发闷,而且下管一直导通可能导致自举电容没有机会刷新充电。

如果上下管都用PWM互补输出,叫双边调制。高频开关时通过死区时间防止上下管直通,电流纹波更小、电机运行更平顺,但下管也有开关损耗,驱动信号要更小心。许多集成驱动芯片内部就用了这种互补机制。

我个人的建议是:先把单边调制跑通,确认换相表没问题,再根据噪声和发热情况决定要不要上双边。新手阶段,少一个出错点比少一点噪声重要。

4.3 软启动与限流策略

有霍尔的电机,转子静止时也能通过霍尔信号直接读出位置,所以理论上可以直接按当前霍尔状态上电驱动,不需要无感方案那种“预定位”。但直接给定高占空比仍然会冲击电流。正确做法是软启动。

我常用的启动策略是:占空比从3%到5%开始,每隔10毫秒左右加一点点,直到达到目标转速。这样启动电流小,对机械冲击也小。在代码里就是主循环按节奏调用bldc_set_duty,慢慢抬高g_duty。

如果负载很重,启动时可能会因为转矩不足卡住。这时候微调启动占空比起点,或尝试换相表里加一点提前量。实在不行就缩短软启动时间,但要注意电流上限,必要时用采样电阻做过流保护。

4.4 从开环调速到闭环调速

用前面算出来的速度speed_rpm,就可以做闭环了。最简单的做法是增量式PID:每个控制周期,根据目标转速和实际转速的误差,调整g_duty。

int16_t pid_output = 0; int16_t last_error = 0; void speed_pid(int16_t target_rpm, int16_t actual_rpm) { int16_t error = target_rpm - actual_rpm; pid_output += kp * (error - last_error) + ki * error; last_error = error; if (pid_output < 0) pid_output = 0; if (pid_output > 1000) pid_output = 1000; bldc_set_duty((uint16_t)pid_output); }

这个代码只是个雏形,真正的PID还要限幅、抗积分饱和。但至少你知道了:六步换相只是底层执行机构,上层可以叠加任意调速策略。

5. 调试实录:那些最常见的坑和排查方法

5.1 电机嗡嗡叫但就是不转

这是最常见的现象,很多人一上来就怀疑MOS管坏了,其实60%以上的原因是换相表不对或者占空比太低。先看霍尔信号:用手慢慢转动电机,串口打印霍尔状态,确认6个状态都在,并且顺序是连续的。如果霍尔状态忽跳忽停,先查接线。

然后确认占空比。有些电源带载能力弱,启动瞬间电压跌落,电机根本带不动,把软启动占空比调高一点试试。还有一类情况是高端MOS驱动没电,用了自举电路但PWM占空比太小,自举电容充不上电,高端管子根本没完全导通,此时量一下高端栅极波形就会看到幅度不够。

5.2 转是能转,但方向反了或者一顿一顿

方向反了最省事,交换电机任意两相线就能解决。如果你坚持软件改方向,就把g_direction翻一下,换成反转表。但注意,半路切方向要先把转速降到很低,否则大电流加反电动势很容易烧管。

一顿一顿、每个位置都像被刹车又放开的,多半是换相表里有几项错了,导致霍尔状态和导通组合不匹配。别整体反转,直接对照前面那张正转表逐项检查。搞不清楚哪一行错,就用实验法重新建表,别硬猜。

5.3 高速时失步或者霍尔信号乱跳

转速高了以后,霍尔信号边沿变陡,PWM开关噪声容易耦合进去,导致霍尔信号出现毛刺,换相时机不对,电机瞬间“咔”一声就失步了。硬件上给霍尔信号加RC低通滤波,或者用施密特触发器整形,软件上在检测到霍尔边沿后加一个极短的消抖窗口,比如50到100微秒,屏蔽掉尖峰。

但消抖时间不能太长,否则换相响应跟不上。一个经验值:消抖时间不要超过最高换相周期的十分之一。比如最高换相频率是8kHz,周期125微秒,消抖10微秒左右就差不多了。

5.4 换相瞬间炸管:多半不是换相表的问题

炸管,就是MOS管被击穿,十有八九是同一桥臂上下管直通。六步换相理论上同一桥臂不会同时导通,但实际工程里到处都是意外。比如切换瞬间一老一新两个信号同时有效,驱动芯片逻辑没理清,或者下管关断慢、上管开通快,就会瞬间把母线短路。

对策分软件和硬件。软件上,换相顺序写成“先全关,再开新相”,保证切换过程有一个极短全关状态,哪怕几十纳秒都行。硬件上,使用带死区控制的驱动芯片,比如同桥臂上下管用互补PWM加上死区时间。另外,母线电容要足够,否则关断瞬间电感续流产生的高压尖峰也会把管子击穿。

注意:调试MOS管驱动电路最好串联一个限流电阻或者使用直流稳压电源的限流功能,电流设小一点。真炸管的时候,限流能救回不少价值不菲的功率管。

5.5 传感器线序不匹配时,怎么快速定位问题

如果电机就是转得不对,手头又没有厂家资料,可以按这个顺序排查:先打印霍尔状态,确认6个状态循环;然后手动转电机,确认状态变化顺序是否连续;再用一个小程序,每次固定导通A+B-、A+C-、B+C-等六个组合,用手感受哪个位置有吸力,把霍尔状态和导通组合对应起来。这个过程可能有点费手,但比你对着电路图猜线序靠谱得多。

换相表的坑基本都是接线造成的。我的习惯是画板时把霍尔信号和MOS管信号都引出测试点,调试时直接量电压波形,一眼就能看出哪一相没跟上。

6. 最后聊几句个人体会

六步换相这个东西,代码本身不到一百行,难的是把电机、驱动、控制三条线上的知识串起来。我到现在调新的电机驱动板,第一步永远是先看霍尔波形,第二步查真值表,第三步才敢把占空比调大。这套顺序帮我在现场解决过无数次“看起来正常但就是不动”的问题。

如果你把这篇里的换相表、中断代码和软启动逻辑都跑通了,你已经掌握了无刷电机控制最核心的一块。之后再去看SVPWM、无感观测器、FOC这些高级话题,你会发现六步换相里学到的“转子位置到开关组合的映射”思路,依然贯穿始终。祝你的电机早日欢快地转起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询