STM32有感BLDC驱动:六步换相与换相表实战解析
2026/9/16 14:04:39 网站建设 项目流程

简介:基于STM32的有感直流无刷电机驱动控制程序,采用C语言编写,面向自动化、电子信息等专业的学生及嵌入式开发工程师。资源适用于毕业设计、课程设计及工程入门实践,代码结构清晰,注释完整,可直接编译运行,也可在现有基础上修改扩展,实现不同控制逻辑,满足学习与科研的双重需求。压缩包共733个文件,以C源码和头文件为主,包含433个c文件、226个h文件,另含42个汇编文件、链接脚本与ARM数学库,以及STM32F3系列HAL驱动代码,包体约24.11MB,目录组织规范,便于按模块检索学习。已有680人学习下载。内容涵盖电机驱动核心控制模块、外设初始化配置、底层驱动接口及配套数学库,可加速控制算法开发与验证,适合需要深入理解直流无刷电机控制原理、撰写课程报告或进行项目二次开发的读者参考借鉴。

1. 从一份 zip C 源码说起:有感 BLDC 驱动程序里最有价值的为什么是换相表

"基于STM32有感直流无刷电机驱动控制程序C源码.zip"看起来只是一份普通工程压缩包,但对做电机控制的嵌入式工程师来说,这个标题已经划出了三重边界:主控是 STM32,电机是有感直流无刷电机(BLDC),交付物是 C 源码。比起无感方案里复杂的反电动势检测启动,霍尔传感器让转子位置在零速下就可读,六步换相因此变成一张查表;换相表、PWM 互补输出和闭环参数,才是这个源码真正的核心。这篇按换相逻辑、驱动实现、闭环调参、移植排错四个层次,把一个可复现的 STM32 有感 BLDC 驱动控制程序讲透,适合正在做风机、水泵、机器人底盘或毕业设计的开发者。

2. 有感 BLDC 六步换相:霍尔真值表与换相表到底怎么查

2.1 三个霍尔信号为何能确定六个换相状态

有感 BLDC 的转子是永磁体,定子三相绕组按星形或三角形连接。沿定子圆周相隔 120° 放置的三个开关型霍尔传感器,只能输出 0 或 1,因此每转过 60° 电角度,霍尔信号组合跳变一次,一圈下来正好得到 6 个互不相同的状态。控制器拿到这 6 个状态后,就可以确定当前转子磁极位置,并据此决定给哪两相绕组通电。

注意这里的“120° 布局”指的是三个霍尔传感器在空间上相隔 120° 电角度,实际电机还有 60° 布局的产品。两种布局在霍尔真值表上表现为高低电平互补,换相顺序也会整体平移。所以拿到任何一份 C 源码,第一步不是看 PID,而是确认头文件里的换相表跟电机手册里的霍尔电气角定义一致。

2.2 霍尔真值表与六步导通顺序

以常见的 120° 霍尔布局为例,三个霍尔信号 H3、H2、H1 的组合与六步导通顺序如下:

状态序号H3H2H1应导通相(A+ 表示电流从 A 相流入)
1101A+ B-
2100A+ C-
3110B+ C-
4010B+ A-
5011C+ A-
6001C+ B-

这张表看起来只是六个组合,但它是整个驱动控制程序的“坐标系”。如果霍尔信号编码顺序和表对不上,轻则电机抖动,重则启动瞬间电流直逼限流值。实际电机驱动中,同一相的上桥和下桥不能同时导通,所以每个状态必须恰好有一相上桥导通、一相下桥导通,另一相悬空。

2.3 在 C 源码中把换相表做成数组

把霍尔状态编码成 3 bit 数值后,六步换相就可以退化成一次查表。我在自己的工程里常用这样的结构:

typedef struct { uint8_t high_side; /* 上桥恒通的相: 0=A, 1=B, 2=C */ uint8_t low_side; /* 下桥 PWM 斩波的相: 0=A, 1=B, 2=C */ } commutation_step_t; /* 按 2.2 的真值表顺序排列,使用前先用 get_commutation_index() 映射 */ const commutation_step_t bldc_table[6] = { {0, 1}, /* 101: A 上桥通,B 下桥斩波 */ {0, 2}, /* 100: A 上桥通,C 下桥斩波 */ {1, 2}, /* 110: B 上桥通,C 下桥斩波 */ {1, 0}, /* 010: B 上桥通,A 下桥斩波 */ {2, 0}, /* 011: C 上桥通,A 下桥斩波 */ {2, 1}, /* 001: C 上桥通,B 下桥斩波 */ }; /* 霍尔状态 0b101=0x5, 0b100=0x4, 0b110=0x6, * 0b010=0x2, 0b011=0x3, 0b001=0x1 * 000 与 111 是非法状态,返回 0xFF */ uint8_t get_commutation_index(uint8_t hall_code) { switch (hall_code) { case 0x5: return 0; case 0x4: return 1; case 0x6: return 2; case 0x2: return 3; case 0x3: return 4; case 0x1: return 5; default: return 0xFF; } }

代码里把霍尔信号拼接成hall_code的动作一般在捕获中断里完成,拼接顺序必须是(H3 << 2) | (H2 << 1) | H1,否则查表结果会乱。bldc_table里的high_sidelow_side并不是最终的 GPIO 电平,而是给驱动层用的开关索引;上位程序拿到这两个值后,再去操作定时器的输出比较通道。

这个查表设计的优点是换相时间固定,不会因为 switch 分支过多产生纳秒级抖动。缺点是要保证bldc_table的顺序和get_commutation_index()一一对应,工程里最常出现的 bug 也是这两处被分别维护后对不上。建议把两个数组放在同一个.c文件中,并加上static const限定。

2.4 换相顺序错误时会看到什么

换相表装反不是完全不能转,下面的现象可以帮你定位:

  • 电机像卡住一样来回抖动,声音发闷:多半是霍尔编码或换相顺序不对,此时电流非常大,要立即断电;
  • 能转但电流明显偏大,空载运行几十秒电机烫手:换相角偏前或偏后,常见于 60° 霍尔布局用了 120° 真值表;
  • 猛推一下才能启动,松开又停:可能是其中一个霍尔信号接反,或者非法状态处理为直接关断导致换相缺相。

提示:初次上电验证换相表时,母线电压不要超过 24V,并且串联一个 60W 白炽灯做限流。灯泡亮说明电流大,换相顺序很可能反了。

3. 基于 STM32 的驱动控制程序:PWM 互补输出与霍尔捕获

3.1 用定时器输出 6 路互补 PWM 与死区

STM32 驱动三相桥最常见的是用高级定时器 TIM1/TIM8 输出三相互补 PWM。CH1/CH2/CH3 输出上桥,CH1N/CH2N/CH3N 输出下桥,互补通道之间由硬件自动插入死区。以 STM32 标准外设库为例,核心配置如下:

TIM_TimeBaseInitTypeDef tim_base; TIM_OCInitTypeDef tim_oc; TIM_BDTRInitTypeDef bdtr; tim_base.TIM_Prescaler = 71; /* 72MHz / 72 = 1MHz */ tim_base.TIM_Period = 999; /* PWM 频率 = 1MHz / 1000 = 1kHz,调试用 */ TIM_TimeBaseInit(TIM1, &tim_base); tim_oc.TIM_OCMode = TIM_OCMode_PWM1; tim_oc.TIM_Pulse = 500; /* 初始占空比 50% */ tim_oc.TIM_OutputState = TIM_OutputState_Enable; tim_oc.TIM_OutputNState = TIM_OutputNState_Enable; tim_oc.TIM_OCPolarity = TIM_OCPolarity_High; tim_oc.TIM_OCNPolarity = TIM_OCNPolarity_High; tim_oc.TIM_OCIdleState = TIM_OCIdleState_Reset; tim_oc.TIM_OCNIdleState = TIM_OCNIdleState_Reset; TIM_OC1Init(TIM1, &tim_oc); /* CH2、CH3 的初始化完全一样,只是通道号不同 */ bdtr.TIM_DeadTime = 0x50; /* 死区约 1us,由 BDTR 寄存器配置 */ TIM_BDTRConfig(TIM1, &bdtr); TIM_CtrlPWMOutputs(TIM1, ENABLE); /* 高级定时器主输出使能 */

这段代码里有三个参数最值得注意:预分频、周期、死区。预分频和周期共同决定 PWM 频率,1kHz 只是验证用的低频率,真正跑电机一般取 15kHz 到 30kHz,需要把TIM_Period改到 50 以内并按实际主频重算。TIM_DeadTime的单位由芯片的 BDTR 寄存器位定义决定,标准库传入的数是定时器时钟 tick 数,具体微秒值必须查参考手册。

TIM_CtrlPWMOutputs是高级定时器特有的主输出使能,忘记调用会导致端口量不到任何 PWM 波形,这是 STM32 电机驱动源码里最常见的“无声失败”。

注意:STM32 的 IO 驱动能力不足以直接驱动功率 MOS,必须经过栅极驱动芯片。选型时要看驱动芯片的传播延时和上下桥死区时间的匹配,一般控制板取 0.5~2us 死区。

3.2 霍尔信号接入与转速计算

霍尔信号三个引脚建议接在同一个定时器的输入捕获通道上,常见做法是用 TIM2 的 CH1/CH2/CH3 做输入捕获,并且上升沿和下降沿都捕获。相邻两次边沿之间的时间就是一次换相周期。

void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) { static uint32_t last = 0; uint32_t now = TIM_GetCapture1(TIM2); uint32_t diff = now - last; /* uint32 回绕特性,天然支持溢出 */ last = now; if (diff > 0) { /* 转速(转/分) = 10,000,000 / (极对数 * diff_us) * 每个机械周期有 6 个极对数 * 换相沿 */ speed_rpm = 10000000UL / (POLE_PAIRS * diff); } TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); } }

计算转速时要注意三点:diff是微秒还是计数器 tick,取决于定时器时钟频率;相邻换相沿是 60° 电角度,所以机械周期里有6 * POLE_PAIRS个换相沿;now - last用无符号减法可以自动处理计数器回绕,不用手动判断谁大谁小。低速时diff很大,转速分辨率会变差,一般只在 200RPM 以上使用这个公式,更低转速用 M/T 法测量更准。

3.3 换相状态机:在中断里查表还是主循环查表

换相的核心动作是“读霍尔状态 -> 查表 -> 改 CCR”。这个动作不能放在主循环里一直执行,因为主循环会被各种耗时任务打断。常见做法是把查表放在霍尔捕获中断里,但只更新开关状态,另一路速度环中断根据计算结果修改TIM_Pulse

void Hall_Task_From_ISR(void) { uint8_t hall_code = get_hall_code(); uint8_t idx = get_commutation_index(hall_code); if (idx == 0xFF) { return; /* 非法状态,保持当前输出 */ } bldc_table_apply(idx); /* 内部修改定时器 CCR 与通道开关 */ }

bldc_table_apply()要做的事不是直接操作 GPIO,而是通过定时器的输出比较寄存器,把对应的通道设为常开、常关或 PWM 斩波。工程里如果换相后电流尖峰大,可以在查表后加一个固定换相延时,这个延时补偿电机反电动势相位,通常 1us 到 20us,只能在示波器上看相电流波形后微调。

3.4 标准外设库还是 HAL 库

老一点的有感 BLDC 源码多基于标准外设库,原因是电机控制需要精确控制寄存器时序,标准库代码短、执行路径明确、中断延迟可控。HAL 库在定时器和 DMA 配置上封装更完整,适合工程规模大、需要快速移植的场景。两种库都可以跑,关键是换相中断和 PWM 更新不能走 HAL 的阻塞式延时。如果你拿到的是标准库工程,强行把驱动文件塞进 HAL 工程会有一堆结构体重名,最稳妥的是保留原驱动层,只换启动和时钟配置。

4. 有感 BLDC 速度闭环:电流采样、PI 参数与 C 源码限幅

4.1 为什么需要速度闭环而不是固定占空比

固定占空比驱动 BLDC 时,负载一变,转速就往下掉;母线电压波动也会直接反映到转速上。所以在风机、水泵这类需要稳速的场景里,C 源码必然有一个速度环。常见结构是速度外环、电流内环:速度环输出电流给定,电流环输出 PWM 占空比。对大多数开发板和模组方案,电流环不是必须的,但至少要在速度环里加输出限幅,否则堵转时占空比会冲到 100%。

4.2 电流采样与 PWM 同步

电流采样最常见的做法是在下桥臂串联采样电阻,流过电流时在电阻上产生压降,经运放放大后送 STM32 的 ADC。由于 PWM 周期内电流是断续的,采样点必须放在下桥导通的中点,这时电流最稳定。

/* 在 PWM 周期中点触发 ADC 采样,避免开关噪声 */ ADC_ExternalTrigConvCmd(ADC1, ENABLE); ADC_ExternalTrigConvConfig(ADC1, ADC_ExternalTrigConv_T8_TRGO, ENABLE);

这里的 TRGO 来自定时器的事件触发,具体选择哪一路触发源由定时器的主从模式决定。如果不用硬件触发,也可以让定时器更新中断里延时几十微秒再读 ADC,但那样会占用中断时间,载波频率高时容易丢中断。采样电阻的压降一般在 0.1V 到 0.5V,STM32 ADC 满量程是 3.3V,运放增益至少要放 6 到 30 倍,具体按母线最大电流计算。

4.3 PI 参数表与整定顺序

有感 BLDC 的常规调参顺序是先电流环后速度环,因为电流环带宽高,参数错误容易看出振荡;速度环的采样周期远大于电流环,不会互相干扰。下面是适合大多数 24V 到 48V 驱动板的初始范围:

环路Kp 初始范围Ki 初始范围采样周期调节现象
电流环0.3 ~ 1.50.01 ~ 0.0550us ~ 100us占空比抖动,母线电流噪声大
速度环0.5 ~ 2.50.05 ~ 0.51ms ~ 5ms转速超调、起动回退

Kp 过大会让控制量随噪声剧烈变化,表现为电机啸叫和电流毛刺;Ki 过大会在转速给定变化时产生明显超调。整定手法是先把 Ki 置 0,只加 Kp,观察稳态转速波动;波动小于 5% 后,再逐步加 Ki,直到满载时稳态误差落在目标转速的 1% 以内。整个过程中速度环输出必须限幅在电流环允许的范围内,否则堵转瞬间积分无限增长,恢复时会有一次猛烈冲击。

4.4 C 源码中 PI 控制器的典型实现

小型 BLDC 控制程序里不需要完整 PID,速度环用 PI 足够,微分项会把霍尔测速的高频噪声放大,反而更难调。下面是一段可以直接抄进工程的结构:

typedef struct { float kp; float ki; float integral_max; /* 积分限幅 */ float limit; /* 输出限幅 */ float integral; float out; } pi_ctrl_t; float pi_update(pi_ctrl_t *h, float err, float Ts) { h->integral += h->ki * err * Ts; if (h->integral > h->integral_max) h->integral = h->integral_max; if (h->integral < -h->integral_max) h->integral = -h->integral_max; h->out = h->kp * err + h->integral; if (h->out > h->limit) h->out = h->limit; if (h->out < -h->limit) h->out = -h->limit; return h->out; }

注意Ts不能写死,必须把实际速度环中断周期传进去,否则换主频或者改定时器分频后所有参数都要重调。integral_max常见取值是limit的一半,给比例项留出调节余量。在纯粹速度闭环程序里,这个函数的返回值就是 PWM 占空比;在带电流环的程序里,返回值作为电流环给定,是不是直接赋值要看驱动层怎么分层。

4.5 启动阶段与开环预定位

有感 BLDC 虽然零速下能读到霍尔位置,但静止时转子正好停在换相边界或非法状态附近的情况不少见。直接进入闭环可能因为换相表索引跳变导致启动反向或堵转。常见做法是先开环预定位,再切闭环:

void startup_routine(void) { for (uint32_t duty = 600; duty <= 3500; duty += 50) { set_open_loop_duty(duty); /* 按固定换相序续流 */ delay_ms(2); if (hall_change_count >= 24) { /* 检测到足够换相沿,认为转起来了 */ break; } } reset_pi_integral(); }

开环阶段换相序固定,不管霍尔信号是什么,按同一方向给三相通电;hall_change_count在霍尔捕获中断里自增,累计 24 次说明转子已经转过至少两圈电角度。切入闭环前务必将 PI 积分清零,否则开环期间累积的误差会让转速冲高。

5. 把 C 源码移植到自己的 STM32 工程:换相序验证与调试技巧

5.1 从压缩包到可编译工程的三件事

先把驱动源文件放进要使能的模块组里,再处理芯片型号和时钟树。Keil5 工程如果报错找不到芯片,需要安装对应系列的器件包;标准外设库工程还要确认启动文件是startup_stm32f10x_hd.s还是cl.s,Flash 容量不同会直接影响中断向量布局。最后对着原理图把霍尔引脚、PWM 通道号、ADC 通道号全部核对一遍,避免库里的宏定义与接线不一致。

5.2 用低速盘转子法验证换相表

在不通母线或者限流情况下,给电机某一相通一个很小的直流电流,让转子转到固定位置;然后手动把转子转过一个完整电周期,同时用调试器读霍尔状态和换相表输出。每一步霍尔跳变,都应看到驱动状态按预想顺序推进一次。如果某一跳变没有触发更新,多半是输入捕获通道配置错误;如果触发了但输出相号顺序不对,就是换相表本身问题。

5.3 用逻辑分析仪抓霍尔与 PWM 的对应关系

把三个霍尔信号和某一路 PWM 同时接到逻辑分析仪上,采样率设到 50MHz 以上。正常波形中,霍尔跳变沿应稍早于 PWM 斩波开关动作。如果 PWM 先动、霍尔后到,说明中断响应慢了或换相延时设成了负值;如果 PWM 输出有毛刺,检查死区时间和互补极性,高速下毛刺最容易出现在上下桥切换瞬间。

5.4 调试中最高频的三个报错

程序能编译但下载时报No STM32 target found,多半是 SWD 接线、目标板供电或复位电路问题,先检查 boot0 是不是拉到了高电平,再用 ST-Link Utility 读芯片 ID。电机只抖不转,优先查换相表而不是 PID,用前面盘转子的方法十分钟就能确认。上电瞬间烧下桥 MOS,常见原因是死区过小或 PWM 输出使能先于栅极驱动供电,要保证驱动芯片电压稳定后再开TIM_CtrlPWMOutputs

正式带载前建议做一次锁定转子测试,母线电压降到 12V 以下,让程序缓慢增加占空比并记录母线电流。这个测试能同时暴露换相表、死区、电流采样极性和 PI 限幅四类问题,比直接带扇叶调参安全得多。

本文还有配套的精品资源,点击获取

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

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

立即咨询