简介:基于STC8H1K28单片机的三相无刷电机驱动源码包,是面向嵌入式开发者和电机控制入门者的完整实战工程。它同时提供“无感”和“带霍尔传感器”两种驱动方式,均含电位器调速功能,可帮助理解无刷电机不同控制策略的差异;STC8H1K28增强型8051内核,自带ADC、PWM等资源,适合此类电机控制应用。包内共18个文件,以C源文件、头文件、编译产物hex固件以及电路原理图PDF为主体,辅以Keil工程文件、工程备份、编译日志等,整体压缩包仅158KB,结构清晰便于查阅。目前已有1927人浏览学习;通过阅读源码,可以系统学习STC8H1K28的寄存器配置、霍尔信号捕捉、PWM波形生成、六步/十二步换相逻辑以及故障保护等核心模块,hex固件适合直接烧录验证,PDF电路图可辅助对照硬件设计,是理论与实践结合的优质参考资料。
1. 三相无刷电机驱动源码包:STC8H1K28 能干的活比你想的多
解压开这个 rar,里面是一套 20220512 版的三相无刷电机驱动源码,主控用 STC8H1K28。很多人一听 51 就摇头,觉得 BLDC 怎么也得 STM32,实际上 STC8H1K28 是 1T 8051,主频跑到 24MHz 左右时,高级 PWM、ADC、比较器、定时器刚好像拼图一样凑齐了一套方波控制的硬件底子。这份资源帮你省掉的是从零啃电机控制算法那段路:六步换相时序、反电动势过零检测、三段式起动、串口调参,代码直接在工程里改参数就能转起来。适合正在做风机、水泵、电动工具,或者拿它交课程设计的人;也适合手里有一块基于 STC8H1K28 的驱动板、想先跑通逻辑再改硬件的人。
2. 板子设计与电压采样:STC8H1K28 的外设分配和三相全桥
拿到底层源码之前,先把芯片外设和硬件对一遍。新手最容易犯的错是软件写完了才发现引脚冲突,或者发现电压采样网络把 ADC 口打坏。
2.1 外设分配:高级 PWM、ADC 和比较器各管一段
STC8H1K28 的 8051 内核只是便宜大碗的开胃菜,真正的硬菜是 PWMA 高级 PWM 定时器。它有六路输出,支持互补输出和硬件死区插入,三相全桥六路驱动信号就是从这里出来的。很多人以为 51 要软件模拟 PWM,实际上 STC8H 系列的 PWM 外设已经做到和 STM32 的 TIM1 一个用法:一个 ARR 定周期,三个 CCR 定占空比,BKR 寄存器管死区,换相时更新 CCR 就行。
ADC 这边要承担两个任务:一个是母线电压采样,用来做过压欠压保护和母线中点参考;另一个是悬空相反电动势采样,这是无感方波控制的核心。比较器也可选,有的方案不用 ADC 采反电动势,而是用比较器把悬空相端电压和虚拟中点直接比,输出跳变沿进外部中断,省掉 ADC 中断里的软件判断。两种方式的取舍后面避坑章会细说。
定时器和串口属于后勤角色。一个定时器负责换相间隔计时,也就是给 30° 电角度延时做“秒表”;另一个定时器一般用来做 ADC 的 PWM 中点触发,让采样点避开开关噪声。串口则承担调速指令下发和状态监控,调试的时候把换相状态机编号打出来,比看电流波形直观得多。
下面给一张外设功能分配表,具体通道以工程里 config.h 的引脚定义为准,不同批次板子可能换引脚:
| 外设资源 | 分配功能 | 说明 |
|---|---|---|
| PWMA 六路输出 | 三相全桥驱动 | 互补输出带死区,换相时改 CCR |
| ADC(2~3 通道) | 母线电压、两相端电压 | PWM 中点触发采样,避开开关噪声 |
| 比较器 | 反电动势过零(可选) | 跳变沿进中断,替代 ADC 软件判断 |
| 定时器 | 换相计时 | 实现 30° 电角度延时 |
| UART | 调速与状态输出 | 115200bps,调试期主力 |
| 外部中断 | 霍尔信号/过零信号 | 有感方案接霍尔,无感方案接比较器 |
这里插一句选型层面的判断:用 STC8H1K28 而不是 STM32,核心原因是成本和上手速度。整个工程代码量不大,寄存器直写的方式反而让 BLDC 的控制流程更容易理解,出问题时你能逐行看明白它在做什么。如果用 STM32 的标准库,光初始化代码就能铺满好几屏,对只做电机控制的场景来说有点杀鸡用牛刀。
2.2 电压采样网络:分压比、滤波电阻和那个容易烧 ADC 的坑
基于 STC8H1K28 的板子设计里,电压网络是最容易画错的部分,也是最容易冒烟的部分。三相全桥本身不复杂:六个 NMOS 组成三个半桥,栅极驱动芯片负责电平转换和自举充电。常见做法是用 EG2131 这类三路半桥驱动,一片直接带三个半桥,自举电容选 0.22μF 左右,PWM 频率在 16kHz 以上时工作稳定。控制电源用 5V 给 STC8H1K28 供电,电机母线 12V 或 24V,两套电源共地,采样网络全部从电机地参考。
先算母线电压分压。STC8H1K28 的 ADC 参考电压默认等于 Vcc,如果 Vcc 是 5V,那么送入 ADC 的电压必须压在 0~5V 以内。12V 系统用 110kΩ 和 10kΩ 分压,分压比 1/12,12V 母线在 ADC 口是 1.0V,留了 4 倍以上余量。注意,电机是感性负载,母线电压会被反电动势抬升,尤其是刹车或者急停瞬间,母线电压可能冲到 1.5 倍标称值,分压比必须按最高母线电压留余量,而不是按标称电压算。
反电动势采样更讲究。以无感方波控制为例,每一拍里有一相悬空,悬空相的端电压等于 0.5 倍母线电压附近叠加反电动势,过零时穿越母线中点。所以采样网络的核心是把这个叠加了直流的反电动势压到 ADC 量程内。算法上直接把这相端电压分压后送 ADC,软件里用母线电压的一半做比较基准。RC 滤波电阻电容要一起算:滤波时间常数不能超过换相周期的 5%,否则过零信号延迟折算成电角度后,高速时可能滞后十几度,电机温度会明显上升。我一般先取 1kΩ 和 47nF,时间常数约 47μs,适合低速小电机;转速过万后要换成 100Ω 级别。
这里有一个一定要在画板前就想清楚的坑:悬空相端电压在 PWM 关断期间会跌到接近 0V 或负压,在 PWM 导通期间抬到接近母线电压,如果不做分压直接进 ADC 口,轻则读数飘,重则把引脚打坏。正确做法是高压侧电阻取值大(100k 级别),并在 ADC 引脚对地并一个 5V 稳压管或钳位二极管,成本几毛钱,能救命。
电源部分还有一个小细节:STC8H1K28 的 ADC 参考电压就是 Vcc,所以 Vcc 的纹波会直接耦合进采样结果。控制电路的 LDO 输出建议加 10μF + 100nF 滤波,PWM 大电流走线绕开 ADC 采样引脚所在区域。板子上的采样地要单点连接电机地,不要让采样回流路径和功率回路共享一段铜皮。
注意:分压电阻选 1% 精度。母线中点计算依赖分压比一致性,几个电阻的精度差太多,软件里做电压换算会出现几伏的偏差,过零判断也会跟着飘。
3. 六步换相与反电动势过零:源码里的状态机在做什么
顺着代码往下走之前,先把包的目录结构讲清楚。我拆这类 STC 无刷工程有个习惯,先找 config.h 或者 main.c 的开头,把引脚定义和外设使能先读一遍,再谈算法。这样不会出现“代码里写的引脚和你的板子对不上”的尴尬。你拿到的 20220512 版包文件命名可能有差异,但核心模块通常是这样组织的:
BLDC_STC8H1K28/ ├── Source/ │ ├── main.c # 系统初始化,主循环状态机 │ ├── pwm.c/pwm.h # PWMA 初始化、占空比更新、死区设置 │ ├── adc.c/adc.h # 母线电压/反电动势采样与滤波 │ ├── bldc.c/bldc.h # 换相表、过零判断、起动流程 │ └── uart.c/uart.h # 串口调参和状态输出 ├── Board/ │ └── config.h # 引脚分配、系统频率、算法参数集中地 └── Doc/ └── 接线说明.txt对照目录来看,这份资源的算法主体在 bldc.c,参数集中在 config.h。改参数优先动 config.h,不要到处翻代码找魔法数字。如果打开包发现文件名不一样,按函数识别:带 comm/step 字样的函数就是换相逻辑,带 zero_cross 字样的就是过零检测,带 start 字样的就是起动流程。
3.1 模块职责:改参数前先知道自己动的是谁
模块职责用一张表收拢一下,后面讲代码会反复引用:
| 模块/文件 | 关键函数 | 职责 |
|---|---|---|
| config.h | 宏定义 | PWM 频率、死区、采样参数、起动参数 |
| pwm.c | PWMA_Init / PWMA_Set_Duty | 周期与占空比更新 |
| adc.c | ADC_Read / ADC_ISR | 采样与滤波、过零判定 |
| bldc.c | Comm_Step / Zero_Check / Start_Machine | 换相状态机、过零检测、三段式起动 |
| uart.c | UART_Send / UART_Process | 调速指令解析和状态输出 |
从这张表能看出来,BLDC 控制的核心代码量其实很小,真正费时间的是参数匹配。你拿到源码后第一件事不是看代码,而是把 config.h 里每个宏和上面对应起来,搞清楚哪些是频率项、哪些是时序项、哪些是保护阈值。改参数时每次只动一个宏、跑一次测试,不要一次改五个参数然后说“怎么又不行了”。
3.2 换相表:六步换相的本质是一张三位数组
三相无刷电机的方波驱动,听起来玄学,落到代码里就是一个 6×3 的二维数组和一个定时器中断。换相表如下:
#define PH_LOW 0 // 下管导通,该相拉低 #define PH_PWM 1 // 上管 PWM 调制,下管截止 #define PH_HIZ 2 // 上下管全关断,进入高阻状态 // 换相顺序:AB -> AC -> BC -> BA -> CA -> CB(电角度每 60° 换一步) // 三列依次对应 A 相、B 相、C 相 const unsigned char comm_step[6][3] = { {PH_PWM, PH_LOW, PH_HIZ}, // 第 0 步:A 上管 PWM,B 下管导通,C 悬空 {PH_PWM, PH_HIZ, PH_LOW}, // 第 1 步:A 上管 PWM,C 下管导通,B 悬空 {PH_HIZ, PH_PWM, PH_LOW}, // 第 2 步:B 上管 PWM,C 下管导通,A 悬空 {PH_LOW, PH_PWM, PH_HIZ}, // 第 3 步:B 上管 PWM,A 下管导通,C 悬空 {PH_LOW, PH_HIZ, PH_PWM}, // 第 4 步:C 上管 PWM,A 下管导通,B 悬空 {PH_HIZ, PH_LOW, PH_PWM}, // 第 5 步:C 上管 PWM,B 下管导通,A 悬空 };逻辑说明:每一行代表当前 60° 电角度区间里三相桥臂的状态,其中一相是上管 PWM、一相是下管导通、一相完全悬空。电流从 PWM 相流入电机绕组,从导通相流出,悬空相不参与换流,专门用来观察反电动势。换相就是每隔一定时间把状态索引加一,到 6 归零,如此循环。这里 PH_PWM 只调上管,下管恒截止,这种“上管斩波、下管换相”的模式是方波驱动最常见的组合,比上下管同时斩波少一半开关损耗。
参数说明:这个表的顺序决定了电机转向。如果上电后电机反转,不用改任何计算逻辑,把数组元素按相反方向重排,或者直接交换电机任意两根相线,二选一即可。改数组比改电机线方便,但要注意如果加了霍尔传感器,霍尔信号和换相表之间的映射也要一起调。
3.3 反电动势过零:在 PWM 中点采样悬空相
六步换相不是闷头按固定时间切,那样带负载就会失步。闭环的关键是知道转子当前在哪个位置,无感方案就靠悬空相的反电动势过零来推断。反电动势过零的含义是:悬空相的端电压从母线中点的一侧穿越到另一侧,这一时刻转子的磁极正好经过该相绕组的中性位置。
常规实现是每次换相后启动 ADC,在 PWM 载波中点触发一次采样,把悬空相端电压读回来,和母线电压的一半做比较。
// 这是从 adc.c 里抽出来的过零判断骨架,具体寄存器按头文件为准 void ADC_ISR(void) { unsigned int adc_val = ADC_RES; // 当前悬空相端电压采样值 unsigned int v_mid = bus_voltage >> 1; // 母线中点,由母线电压采样换算 if (zero_cross_enable) { // 检测到从小于中点变到大于中点,说明反电动势过零 if (last_val <= v_mid && adc_val > v_mid) { zero_cross_flag = 1; // 记录当前时间,准备启动 1/2 换相周期延时 comm_timer_restart(); } // 反向穿越也要记,因为过零方向决定位置区间 if (last_val >= v_mid && adc_val < v_mid) { zero_cross_flag = 1; comm_timer_restart(); } last_val = adc_val; } }逻辑说明:判断过零只需看穿越方向,不需要知道反电动势的绝对值。这大大降低了对 ADC 精度的要求,10 位 ADC 完全够用。为了避免 PWM 开关毛刺触发误判,每次采样之间通常加一个软件滤波窗口:连续两次采样都越过中点才确认过零,代价是相位滞后一点点,由提前角补偿找回来。
参数说明:bus_voltage 是上次采样得到的母线电压,右移一位就是中点。如果你的板子上三相端电压都用固定比例分压,那这里的比较基准也要用同一个分压系数换算,不能直接拿母线 ADC 原始值的一半去比。很多新手第一次调就在这里翻车,现象是低速转得好好的,速度一上来就乱套。
提示:ADC 采样时刻建议放在 PWM 载波中点,也就是计数到 ARR/2 的位置。此时斩波管刚好处于导通中点,开关噪声最小,采样值最接近真实端电压。
4. 参数配置实战:PWM 频率、死区时间和换相提前角
这份源码能跑,但跑得好不好全看参数。上一章理解了状态机,这一章把所有该改的地方改对。
4.1 PWM 频率与死区:先让桥臂活着,再谈性能
PWM 频率的选择逻辑很直接:低于 15kHz 会听到明显的啸叫,高于 25kHz 开关损耗明显上升,MOS 管温度不好压。所以我一般把方波驱动的 PWM 频率定在 16kHz~20kHz。20220512 版源码里默认值就是这一类范围,具体在 config.h 的 PWM_FREQ 宏里改。
STC8H1K28 的 PWMA 频率由预分频和 ARR 共同决定,计算公式是 PWM 频率 = 系统时钟 / (PSC + 1) / (ARR + 1)。系统时钟 24MHz、PSC=0、ARR=1499 时,PWM 频率刚好 16kHz:
void PWMA_Init(unsigned int pwm_freq_hz) { unsigned long arr; // 先关输出,防止初始化过程中桥臂乱动 PWMA_BKR = 0x00; // 预分频写 0,系统时钟 24MHz 直通 PWMA_PSCRH = 0x00; PWMA_PSCRL = 0x00; // ARR = Fsys / Fpwm - 1,这里按 24MHz 计算 arr = 24000000UL / pwm_freq_hz - 1UL; PWMA_ARRH = (unsigned char)(arr >> 8); PWMA_ARRL = (unsigned char)(arr & 0xFF); // 使能互补输出,插入硬件死区 PWMA_BKR = 0x80; // MOE 主输出使能 PWMA_BKR |= 0x0F; // DTG 低 4 位=15,死区约 0.625us@24MHz }逻辑说明:PSC 和 ARR 决定频率,BKR 同时管着主输出使能和死区,这是 STC8H1K28 上最容易漏配的地方。死区时间公式是 DTG / Fsys,24MHz 下 DTG=15 约为 0.625μs,覆盖常见小功率 MOS 的开关延时富富有余。换相过程中上下桥切换,如果没有这段死区,两个 MOS 会有一瞬间同时导通,母线直接短路,冒烟往往就是这里。
参数说明:如果用的是大电流 MOS 或者栅极驱动芯片本身带死区,可以把 DTG 调小甚至设成 0,以厂家器件手册为准。千万不要在代码里用软件延时模拟死区,中断一来时序全乱,硬件死区才是可靠做法。
调试顺序建议:先不接电机,示波器测一个半桥的上下管栅极波形,确认没有同时为高,再接电机。这一步花两分钟,能省掉后面排查冒烟事故的一整天。
4.2 换相提前角与三段式起动:参数怎么填
换相不是过零之后立刻动作,而是要再等 30° 电角度,让反电动势和相电流保持正确的相位关系。实现上通常是一个定时器:过零中断到达时,把定时器装载成“半个换相周期”,定时到就走换相。
半个换相周期怎么估?用最近两次换相的时间间隔除以 2。转速在变化,所以这个值要每个换相周期更新一次。源码里常见的写法是:
// 每次换相时记录时间戳,计算当前 60° 间隔 void Comm_Step(void) { unsigned int now_us = timer_get_us(); unsigned int t60_us = now_us - last_comm_us; // 过零到换相的理论延时是 30° 电角度,即 t60_us / 2 half_comm_delay_us = t60_us >> 1; // 在这里叠加提前角补偿,抵消滤波延时 // half_comm_delay_us -= advance_comp_us; last_comm_us = now_us; comm_index = (comm_index + 1) % 6; apply_comm_step(comm_index); // 把半周期装载进定时器,用于过零后的换相延时 timer_load(half_comm_delay_us); }逻辑说明:t60_us 是从上一次换相到本次换相的真实间隔,它反映的是当前电角速度。过零发生的时刻理论上处在两次换相的中间点,所以再过半个间隔就该换相。这个算法不用查表也不用算转速,纯靠时间戳递推,代码量很小,适合 51 这种资源紧张的单片机。
参数说明:advance_comp_us 是提前角补偿,用来抵消反电动势滤波和软件滤波引入的相位滞后。补偿量不是定死的,高速时要大、低速时要小,简单做法是按当前 t60_us 反比估算,或者用一张转速-补偿小表。注意,补偿过头会导致换相超前,电流畸变更严重,补偿值要从小到大慢慢加。
起动这部分是最玄学的,因为电机参数未知,全靠三段式硬顶。第一段预定位:随便挑一相通电几百毫秒,把转子吸到已知位置。第二段强制换相:按固定的换相时间递减序列切换,占空比从小往上加,让转子跟着磁场转起来。第三段切换闭环:当连续几次过零检测都稳定时,断开强制换相定时器,交给过零中断接管。
调试时优先动这三个量:
| 参数 | 位置 | 典型起点 | 调整方向 |
|---|---|---|---|
| 预定位时间 | config.h | 200~500ms | 定位抖动就加长 |
| 起动占空比 | config.h | 10%~20% | 转不起来就加大 |
| 递减斜率 | config.h | 换相周期每步 -20% | 失步就把斜率调缓 |
我一般从 10%~15% 占空比起步,换相周期从 20ms 开始按 15% 递减,等转子响应跟上后再逐步收紧。小风扇和大水泵的负载惯量差很远,同一套参数几乎不可能通吃,这也是为什么源码里要留那么多可调宏。
5. 常见问题与避坑:四台板子换来的血泪经验
这一章不写原理,只写我实测中踩过的坑。每一条都按现象、原因、解决的顺序讲,照着排查比重新查一遍数据手册快得多。
5.1 MOS 管上电就烫:死区配置被后续初始化覆盖
现象:代码烧进去,一接电机母线电流就往上飙,MOS 管几秒钟烫得不能摸,严重一点的直接炸管。
原因:八成是死区没生效。STC8H1K28 的 PWMA_BKR 寄存器既要控制死区也要控制主输出使能。很多人先配置了 BKR,后面初始化 CCR 或者更新占空比时,复用同一个寄存器把死区位冲掉了。还有一版是用了库函数,库函数内部的初始化顺序和手写寄存器不一致,死区位被第二次写 0。
解决:先用示波器测任意一个半桥的上下管栅极波形,看有没有同时为高的尖峰;然后把 BKR 的赋值放到所有 PWM 配置的最后一行,强制再写一次死区位。我现在的习惯是在 main 函数里加一行注释“死区最后写,不要乱动顺序”。运行正常后,上下管驱动波形之间能看到一个明显的凹陷区,这就是死区。
5.2 高速突然停转:RC 滤波把过零点拖没了
现象:低速起动正常,转速拉到七八千转以后电机突然一顿,然后停转,重新起动又能转,但高速还是掉。
原因:反电动势采样网络的 RC 滤波时间常数太大。低速时电角度变化慢,滤波相位滞后可以忽略;转速翻倍后,同样的滤波时间换算成电角度滞后也翻倍,过零判断晚了十几度,换相跟着滞后,相电流和反电动势相位对不上,转矩骤降,电机就停了。
解决:一是把滤波电阻从 1kΩ 降到 100Ω 级别,代价是开关噪声变大,需要软件滤波窗口兜底;二是在换相定时器里加提前角补偿。补偿角度公式很简单:滞后角度 = 转速(rpm) × 极对数 × 6 × 滤波时间常数(s)。比如 10000rpm、4 极对、47μs 滤波,滞后约 11.3°,这已经不能忽略了。把补偿角度换算成延时,从半周期里扣掉即可。
5.3 起动来回抖:强制换相序列太激进
现象:三相无刷电机上电后嗡嗡响,转子来回摆动但转不起来,电流表读数忽大忽小。
原因:三段式起动的第二段,换相周期递减太快。磁场的旋转速度是换相频率决定的,转子是机械惯性体,跟不上磁场就失步,表现为抖动。另外起动占空比太小,电流不够,转矩不足,也会在原地踏步。
解决:把换相周期递减斜率调缓,比如从 20ms 开始,每一步只减 1~2ms,而不是对半砍。同时把起动占空比从 10% 提到 20%~30%,让电流先把转子“拖”起来再谈效率。调完之后电流表应该是平稳地逐步增大,而不是来回跳。
5.4 ADC 口被打坏:悬空相端电压超了 Vref
现象:用 ADC 采集反电动势时,单片机某个引脚附近有糊味,ADC 读数一直是满量程,电机转起来噪声特别大。
原因:悬空相端电压是 PWM 斩波波形,峰值能到母线电压附近,分压电阻选取得太激进,导致 ADC 引脚电压超过 Vref。PWM 关断瞬间的负压尖峰还会把引脚拉到地以下,造成闩锁效应,长期累积就可能打坏引脚。
解决:分压比按最大母线电压留 1.5 倍以上余量,并在 ADC 引脚对地并一个稳压管钳位。5V 系统用 5V1 稳压管,3.3V 系统用 3V3。钳位管的结电容会影响采样网络,选结电容小的型号,或者把钳位管挂在分压电阻之后、RC 滤波电阻之前。这个顺序别搞反,否则滤波白做。
6. 进阶验证:示波器测换相点,顺滑起动调法
东西到这个阶段已经能转,但转得好不好不能靠耳朵,要靠示波器。最有效的验证方法是双通道同时测两相端电压对地,观察波形是不是标准的梯形波:导通相是 PWM 调制的平顶,悬空相是过零穿越的斜坡。把通道 A 接 A 相端电压,通道 B 接 B 相端电压,用换相信号或者母线中点做触发,能看到每隔 60° 电角度有一次状态切换,悬空相的波形穿越中点那条线,交叉点就是过零点。
起动质量看串口更直接。源码里 UART 默认 115200bps,起动过程会打印当前状态机的索引和计数。观察起动时状态切换是否均匀。如果某个状态停留时间远超其他状态,说明该相的过零没测到,多半是采样网络或分压不对;如果切换快慢不均匀,说明换相周期和转子实际转速没对上,把起动占空比和递减斜率往回调。
调顺滑起动的顺序我固定是三步:先加起动占空比到能转,再把递减斜率调缓到不失步,最后加提前角补偿到高速不发热。顺序不能反,因为占空比是基础,斜率是动态过程,补偿是锦上添花。整套调试下来,最花时间的往往不是代码本身,而是参数和负载匹配的过程。
从那以后我每次接手这类 STC 无刷工程,都强制自己先做三件事:关死区跑空载、量母线分压、串口看状态机。这三件事做完,九成的坑都已经排掉了。希望帮到你。
本文还有配套的精品资源,点击获取