stm32G431驱动三相无刷电机实现PI调速的完整实战指南
2026/9/12 14:23:10 网站建设 项目流程

简介:基于STM32G431的三相无刷电机驱动与PI调速项目源码包,面向嵌入式开发者和电机控制学习者,重点覆盖定时器配置、ADC采样、PWM输出以及闭环速度调节等关键环节。压缩包共1524个文件,体积43.48MB,其中C源码与头文件占比最高,另有启动汇编、链接脚本、工程配置和HAL库文件,便于直接导入工程查看外设初始化与调用关系;包内还附带ARM Cortex-M4数学库、STM32G4系列HAL驱动、编译过程文件以及HEX/AXF烧录镜像,方便核对生成流程。工程内目录划分清晰,从底层寄存器初始化到上层控制逻辑均有对应源码,PI调速部分可结合注释快速理解速度环参数作用;包内还提供完整外设库与链接配置,可帮助缩短环境搭建时间,调试记录和编译输出文件也有助于排查工程异常。目前已有2063人学习下载,被不少开发者用作无刷电机驱动项目起步模板。除可直接使用的源码外,PI调速部分还包含速度环参数整定与调试思路,结合映射文件和镜像文件,可在二次开发时减少前期排查工作量。

1. 从 stm32G431 到三相无刷电机驱动:PI调速的真正难点不在PI

驱动三相无刷电机是个“看着简单、做起来处处被卡”的活。很多人以为拿到 stm32G431,输出几路 PWM,再把速度反馈接进 PI 调速就行;实际上第一版固件往往连电机都不转,或者转起来一顿一顿、一加载就过流。拆开“基于 stm32G431 驱动三相无刷电机并实现 PI 调速”这个工程,硬件连接、六步换向、PWM 死区、转速采样、PI 调节是一条完整链路。stm32G431 是电机控制向 Cortex-M4F,HRTIM、运放、比较器都很全,但外设越专用,错法越隐蔽。下面按最小可行方案推进,从硬件接线讲到换向表、PI 闭环和验证手段,适合带霍尔 BLDC 入门,也适合后续转无感 FOC。真正难的不是 PI 参数,而是让电机可靠转起来。

2. stm32G431 驱动三相无刷电机的硬件连接与 CubeMX 配置

2.1 为什么选 stm32g431:HRTIM、OPAMP 和 COMP 才是关键

普通 MCU 驱动电机也不是不行,但 stm32g431 最大的区别在专用外设。它跑到 170MHz,带 FPU,做 PI 调速完全够用;真正提升体验的是 HRTIM,它有高分辨率 PWM、独立死区、斩波和故障保护,FOC 里想要高频高精度 PWM 时优势很大。对于本标题里的带霍尔六步换向,TIM1 高级定时器其实也够了,所以如果你手上的板子没有引出 HRTIM 引脚,也不用卡在这里,TIM1 足够跑通整个方案。

内置 OPAMP 和 COMP 通常被忽略,但对电机控制很关键。电流采样一般要放大采样电阻上毫伏级压降,G431 的 OPAMP 可以配置成可编程增益放大器,省掉外部运放;COMP 则可以直接拿母线电流和阈值比较,触发 TIM 的刹车引脚 BKIN 关断 PWM,这是秒级软件保护之外的硬件保护。我的建议是:即使 PI 调速只需要速度反馈,也把电流采样和一路过流比较留下来,否则第一次把占空比调满时,炸管概率不低。选型边界也要说清楚:只做方波调速,G431 是有余量,不是必须;但后续想上无感 FOC,G431 的 CORDIC、FMAC 和 HRTIM 能直接复用。

2.2 三相逆变桥和霍尔接口的硬件接线

电路拓扑没有新东西:六颗 NMOS 组成三个半桥,每相高边和低边都由栅极驱动芯片驱动,常见驱动是 IR2101、EG2134 或带 LDO 的 DRV8323。以低压 12V/24V 实验板为例,母线电容至少要 220uF/50V,高边自举电容选 100nF 到 1uF,死区时间和自举电容尺寸直接相关。电机三根电源线接到三个半桥中点,三根霍尔线分别接 GPIO,加 10kΩ 上拉到 3.3V。

下面是一份 LQFP48 封装的常用引脚映射,注意不同封装和驱动板丝印可能不同,核对数据手册后再接线:

stm32g431 引脚CubeMX 配置外接信号说明
PA8TIM1_CH1AHA 相上桥 PWM
PA7TIM1_CH1NALA 相下桥互补 PWM
PA9TIM1_CH2BHB 相上桥
PB0TIM1_CH2NBLB 相下桥
PA10TIM1_CH3CHC 相上桥
PB1TIM1_CH3NCLC 相下桥
PA3GPIO_EXTI3Hall_A霍尔 A 相,带上拉
PA4GPIO_EXTI4Hall_B霍尔 B 相
PA5GPIO_EXTI5Hall_C霍尔 C 相

电流采样一般把 0.01Ω 采样电阻放在三相全桥的低边母线处,差分信号经过 G431 内置 OPAMP 放大后进 ADC。PCB 布线时,驱动芯片的功率地和 MCU 信号地要单点连接,霍尔线和 PWM 线不要平行走太长,否则换向信号会被斩波噪声干扰。

2.3 CubeMX 生成工程时最容易忽略的三个配置

CubeMX 里先从时钟开始,用外部或内部 PLL 配到 170MHz。接着配置 TIM1,这里有三个位置特别容易错。第一是时基:如果 PWM 频率想要 20kHz,时钟 170MHz、Prescaler 设 0 时,ARR 写成 8499,而不是 8500;ARR 写错一计数,频率会漂到 19.988kHz,短时间内看不出来,但后续 PI 输出和占空比换算不够整齐。第二是死区:CubeMX 里 Dead time 的单位是计数,170MHz 下每个周期约 5.88ns,500ns 死区就填 85。填太小,高低边会重叠导通;填太大,重载时相电流畸变明显。第三是霍尔引脚的中断触发,三个霍尔信号要分别使能 EXTI3/EXTI4/EXTI5 的上升和下降沿,否则霍尔状态一变,状态机无法及时换向。

死区配置代码在 CubeMX 自动生成的函数里长这样,手动调整时不要漏掉 Break 部分:

/* TIM1 死区和刹车配置,170MHz 时钟下的参考值 */ TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0}; sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE; sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_ENABLE; sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; sBreakDeadTimeConfig.DeadTime = 85; /* 约 500ns 死区 */ sBreakDeadTimeConfig.BreakState = TIM_BREAK_ENABLE; sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.BreakFilter = 0; HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);

这里BreakState开启后,一旦 BKIN 引脚出现有效电平,TIM1 所有输出会立刻进入复位状态,硬件关断 PWM。这个引脚不能悬空,不使用时把 BreakPolarity 设成 HIGH,再通过外部 10kΩ 电阻把 BKIN 拉低。死区时间不是越小越好,要大于栅极驱动和 MOS 管关断延迟之和,通常 200ns 到 2us 之间。

提示:PB1 同时可能是 TIM1_CH3N 的复用脚,配置 PWM 和刹车引脚时注意别冲突,过流保护引脚尽量选择 TIM1_BKIN 复用,并加一挡 BreakFilter 滤波,防止上电瞬间毛刺误关断。

3. 三相无刷电机六步换向与互补 PWM 输出

3.1 六步换向表的建立:霍尔和导通相位的对齐

带霍尔的 BLDC 用六步换向是最容易上手的驱动方式。每个电周期分 6 步,每步导通三相中的两相:一个高边、一个低边,第三相悬空;电流从高边相流入绕组,从低边相流出,转子每走过 60 度电角度换一次相。所谓对齐,就是让霍尔信号的 6 种组合和这 6 个导通状态一一对应。

网上能搜到很多现成换向表,比如霍尔 001、101、100、110、010、011 的顺序,但直接抄会翻车,因为霍尔安装角度、三相输出线顺序和驱动板半桥标号都可能不同。常见做法是先用低压限流来判断:把母线电压限制到比额定值低很多,比如 24V 电机用 5V、限流 0.1A,依次强制导通 AB、AC、BC、BA、CA、CB 六种状态,观察转子朝哪个方向走,同时记下每个状态对应的霍尔编码。这样得到的表再按驱动方向取顺序。我一般会让电机先空载转起来,再统一核对方向。

下面是示意换向表,霍尔码以 PA3-PA5 读出的二进制值为准,实际项目以测出来的为准:

霍尔码导通上管导通下管悬浮相
0001ABC
1101ACB
2100BCA
3110BAC
4010CAB
5011CBA

表里霍尔码的位序要和代码里的读取顺序一致,如果把 PA3 和 PA5 读反,电机转不到完整一圈。反方向旋转只要把表从 0 到 5 映射成 5、4、3、2、1、0 执行。

3.2 用 TIM1 产生互补 PWM:为什么换相不能直接 Stop/Start

TIM1 配置成 PWM1 模式后,CHx/CHxN互补输出自带死区,这是六步换向的硬件基础。但要注意,六步换向并不是六对引脚同时输出。以第一步 AB 导通为例,实际应该是 A 高边以 20kHz PWM 斩波,B 低边常通,其他引脚全部关断。如果每次换相都用 HAL 的HAL_TIM_PWM_Start/Stop去启停,定时器重新初始化,输出会先掉到无效电平再重建,虽然只有几微秒,但对电机端电流冲击和 EMI 都不好。

我一般不用 Stop/Start,而是直接改写CCER寄存器,只切换输出使能位。PWM 时基一直跑,换向只是改变哪几路输出被连接到引脚上:

/* 以 2-2 导通为例,换相时只改 CCER,不停定时器 */ void bldc_set_step(uint8_t step) { TIM_TypeDef *tim = TIM1; uint32_t ccer = 0; switch (step) { case 0: /* A+ B- */ ccer = TIM_CCER_CC1E; /* A 高边 PWM 输出 */ ccer |= TIM_CCER_CC2NE; /* B 低边常通 */ break; case 1: /* A+ C- */ ccer = TIM_CCER_CC1E; ccer |= TIM_CCER_CC3NE; break; case 2: /* B+ C- */ ccer = TIM_CCER_CC2E; ccer |= TIM_CCER_CC3NE; break; case 3: /* B+ A- */ ccer = TIM_CCER_CC2E; ccer |= TIM_CCER_CC1NE; break; case 4: /* C+ A- */ ccer = TIM_CCER_CC3E; ccer |= TIM_CCER_CC1NE; break; case 5: /* C+ B- */ ccer = TIM_CCER_CC3E; ccer |= TIM_CCER_CC2NE; break; default: ccer = 0; break; } __HAL_TIM_DISABLE_IT(&htim1, TIM_IT_UPDATE); tim->CCER = ccer; __HAL_TIM_ENABLE_IT(&htim1, TIM_IT_UPDATE); }

这里CC1E使能通道 1 主输出,即 A 相高边;CC2NE使能通道 2 互补输出,即 B 相低边。互补输出仍保留死区,所以即使同一个半桥的 CHx 和 CHxN 都被使能,也不会同时导通超过死区时间。CC1NE是 A 相低边,CC2NE是 B 相低边,CC3NE是 C 相低边,别把CC2ECC1NE搞混。用中断保护CCER写入,防止换向中断和霍尔中断同时发生时出现中间状态。

3.3 霍尔采集与换向时机:在中断里只做一件事

霍尔信号变化时,必须尽快更新换向状态。把 PA3、PA4、PA5 配成 EXTI 上升和下降沿触发,在回调里读取电平、查表并执行bldc_set_step

volatile uint32_t hall_edge_count; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { uint8_t hall; uint8_t step; if (GPIO_Pin == GPIO_PIN_3 || GPIO_Pin == GPIO_PIN_4 || GPIO_Pin == GPIO_PIN_5) { hall = (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) << 2) | (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) << 1) | HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5); step = hall_to_step[hall]; if (step < 6) { bldc_set_step(step); hall_edge_count++; } } }

这段代码的要点是中断里不做 PI 计算、不打印日志,只读三个 GPIO 并查表。霍尔信号受 PWM 斩波影响会出现抖动脉冲时,硬件上加 RC 滤波。hall_edge_count是只增不减的计数器,供后面的转速测量使用。如果某次读到的霍尔码不在表里,保持当前输出不动作,不要关闭所有 PWM,否则电机在某个角度会突然失去力矩。

4. PI 调速闭环:从转速测量到 PI 参数整定

4.1 用霍尔边沿计数计算三相无刷电机转速

PI 调速的前提是有可靠速度反馈。带霍尔电机最简单的是 M 法测速:统计固定时间窗口内的霍尔边沿数,再换算成 RPM。一个电周期产生 6 个霍尔状态,也就是 6 个边沿;极对数为 p 时,转子每转一圈产生 p 个电周期,所以每转边沿数为 6 乘以 p。

速度计算放在 5ms 定时器中断里:

#define POLE_PAIRS 4 /* 8 极电机,极对数就是 4 */ #define CTRL_PERIOD_MS 5 volatile uint16_t hall_edge_count; float speed_rpm; void speed_tick_handler(void) { static uint16_t last_count = 0; uint16_t diff = (uint16_t)(hall_edge_count - last_count); last_count = hall_edge_count; /* diff 是 5ms 内霍尔边沿数,*1000/5 得到每秒边沿频率 */ speed_rpm = (float)diff * 1000.0f / CTRL_PERIOD_MS; speed_rpm = speed_rpm / (6.0f * POLE_PAIRS); speed_rpm = speed_rpm * 60.0f; }

diff除以 5ms 再乘以 1000 得到每秒边沿数;除以6*POLE_PAIRS得到每秒转数;乘以 60 得到 RPM。极对数不要填错,常见 8 极电机极对数是 4。M 法在低速时性能差,比如 300RPM、极对数 4 时,5ms 窗口平均只有 0.6 个边沿,速度读数跳变很大。改进办法是拉长窗口到 10ms 或 20ms,或者改用 T 法测量相邻霍尔边沿时间。

4.2 PI 调速代码实现:积分限幅比 P 参数更重要

速度反馈有了以后,PI 输出用占空比百分比表示。位置式 PI 的好处是积分项直观、限幅容易做:

typedef struct { float kp; float ki; float error_integral; float output_limit; float sample_time; } SpeedPi; float pi_calc(SpeedPi *pi, float target_rpm, float current_rpm) { float error = target_rpm - current_rpm; float p_out = pi->kp * error; pi->error_integral += error * pi->sample_time; if (pi->error_integral > pi->output_limit) pi->error_integral = pi->output_limit; if (pi->error_integral < -pi->output_limit) pi->error_integral = -pi->output_limit; float i_out = pi->ki * pi->error_integral; float output = p_out + i_out; if (output > pi->output_limit) output = pi->output_limit; if (output < 0.0f) output = 0.0f; return output; }

这个实现把积分器储能和最终输出都限制在output_limit,输出最小值限制为 0,防止负占空比。output_limit建议设 95% 而不是 100%,因为互补 PWM 需要时间给高边自举电容充电,输出 100% 占空比时高端 MOS 一直导通,自举电容反而充不进去。调用时sample_time取 0.005f,对应 5ms 控制周期。

占空比更新也要在定时器中断里做,计算完duty = pi_calc(...)后,再写对应高边通道的 CCR:

#define ARR_VALUE 8499 /* 与 TIM1 ARR 一致 */ void bldc_set_duty(float duty_percent) { uint32_t ccr = (uint32_t)(ARR_VALUE * duty_percent / 100.0f); /* 当前高边在 A、B、C 中的哪一相,就写哪个 CCR */ if (current_step == 0 || current_step == 1) { TIM1->CCR1 = ccr; } else if (current_step == 2 || current_step == 3) { TIM1->CCR2 = ccr; } else { TIM1->CCR3 = ccr; } }

current_step是第 3 章里bldc_set_step记录下来的当前步。写 CCR 时只更新导通相上管的占空比,下管是常通状态,不需要 CCR 值。

4.3 PI 参数怎么定:从临界比例度到一组可用初值

不要凭空猜 PI 参数。先把ki设为 0,只留kp,从 0.05 开始,电机空载给一个中速目标,比如 1000RPM,看转速曲线。逐渐增大kp直到转速开始持续等幅振荡,记录此时的临界增益 Ku 和振荡周期 Tu,再用 Ziegler-Nichols 方法整定:kp=0.45*Kuki=0.54*Ku/Tu。这个方法在 BLDC 速度环上很好用,因为速度环通常没有纯滞后,稳定裕度要求也不高。

如果不想做振荡试验,我一般给 24V 小功率无刷电机一个相对保守的起点:

控制周期kpkioutput_limit适用情况
5ms0.150.2095%低速启动平稳,无过冲
5ms0.400.6095%中高速响应快,噪声偏大
10ms0.100.1095%大惯量负载,先保证稳定

按表里第一行启动后,实际转速一直差几十转,就保持kp不变,把ki每次加 0.05;转速过冲超过 20%,优先降ki。一个常见问题是积分饱和:电机堵转时 PI 输出冲到最大值,恢复后积分项攒得太高,转速会过冲很久。上面代码里积分限幅和输出限幅共用output_limit,如果把它设成 100%,饱和现象会明显加重。堵转保护最好单独做:连续 200ms 转速低于 50RPM 且占空比大于 80%,直接停 PWM。

4.4 速度反馈低通滤波和低速精度

PI 参数调不出来时,反馈信号往往已经坏了。M 法测速在低速窗口内只有几个霍尔边沿,转速值像台阶一样跳变,PI 会跟着抖动。加一阶低通滤波是最便宜的办法:

float speed_filtered; #define ALPHA 0.30f speed_filtered = ALPHA * speed_rpm + (1.0f - ALPHA) * speed_filtered;

这段滤波放在 5ms 控制周期开头,算出speed_filtered后再去参与 PI。ALPHA越大响应越快,但滤波效果差;0.3 对小功率无刷电机是折中。低速时建议把控制周期从 5ms 拉到 10ms,让每个周期至少有两次霍尔边沿参与测速,反馈就不至于频繁跳变到 0。滤波会给速度环增加相位滞后,ALPHA低于 0.2 时,PI 的ki要相应调小,否则闭环容易低频振荡。

5. 验证 stm32G431 三相驱动与 PI 调速的几个诊断技巧

5.1 用手转电机诊断换向表和三路霍尔

上电之前,先用手慢慢转动电机轴,把霍尔状态打印到串口。正常的霍尔组合会按格雷码顺序循环,所以日志中看到的先是 1、5、4、6、2、3 这样的序列,说明霍尔接线和读数顺序没问题。接着强制给六个换向状态通电,用限流很低的低压电源,转子应该每次稳定停在同一个位置附近;有跳步说明换向表与霍尔码对不上。这个阶段不要开 PI 闭环,直接给固定 20% 以下占空比即可。

5.2 用逻辑分析仪确认 PWM 与死区

六步换向下,重点看三对互补输出:PA8/PA7PA9/PB0PA10/PB1。用逻辑分析仪同时测一个半桥的上下管和一路霍尔信号,一帧至少抓 2ms,能看到上管 PWM 与下管 PWM 在任何时刻都不重合,并且从高到低切换时有一段低电平间隔,就是死区。死区时间应约等于 CubeMX 里DeadTime=85换算出的 500ns。如果看到上下管同时为高,说明CCER输出使能位或互补极性配错了,立即断电调极性,不能指望软件保护。

5.3 串口打印速度曲线排除 PI 振荡

调 PI 时,把 target、raw speed、filtered speed、duty 四个变量通过串口打印,格式用逗号分隔,每 10ms 一行。电机恒速后,转速曲线在半分钟内持续在一个窄带内波动是正常的;如果出现 1 到 2Hz 甚至更慢的大幅振荡,先检查是不是 M 法测速在低速跳变太大,其次才是 PI 参数。有一个实用技巧是把ki临时设成 0,转速若有稳态误差,用手加减负载看反馈曲线;若此时出现等幅振荡,问题不在积分,而在换向表和电流过流保护。把采样电阻上的电流波形叠加到速度曲线上看,换向表错一位时电流波形会出现一个持续几毫秒的缺口,这比调 PI 参数之前更值得修。

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

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

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

立即咨询