简介:针对STC 51单片机(以STC8H1K28为例)驱动三相无刷电机的完整工程资料,面向单片机与电机控制爱好者,可帮助解决从脉宽调制调速、换相逻辑到驱动电路设计的常见难点。压缩包共17个文件,以C语言源码、十六进制固件、Keil工程配置、调试辅助文件为主,附带一份三相无刷电机驱动电路PDF,整体仅145KB,结构紧凑。目前已有2129人学习查看。资料内包含无感电位器调速与带霍尔传感器电位器调速(捕捉中断换相)两套独立实例,分别演示六步换相法和反电动势检测的工作方式,便于对照两种控制策略的异同;结合电路原理图与可烧录的固件,可在学习脉宽调制定时器配置、电压换相时序的同时,直接移植到自己的开发板中验证。对于希望入门无刷电机驱动或深入研究STC高性能51系列电机控制的读者,是轻量而完整的参考素材。
1. STC8H1K28 驱动三相无刷电机:51 内核为什么还能打
拿到这套资料的时候,我第一反应是:都什么年代了,还有人用 51 内核折腾 BLDC?但翻完motor_Opt.c和motor_Uv2工程之后,我改变了看法。STC8H1K28 这颗芯片虽然挂着 51 的名头,但主频跑到 24MHz 以上、内置 15 路 PWM、还带模拟比较器和 ADC,做六步换相的无感驱动绰绰有余。资料里同时给了两套完整工程——带霍尔传感器的捕捉中断换相方案,和无感反电动势过零检测方案,都是电位器调速,hex 和 OBJ 文件齐全,烧进去就能转。这套东西适合三类人:刚接触 BLDC 控制想看懂换相逻辑的学生,用 STM32 做惯了 FOC 想回头梳理六步换相底层原理的工程师,以及需要低成本量产电调、预算卡死在几块钱 MCU 上的产品开发者。51 的价值从来不在于算力,而在于外设直接、中断响应可控、代码裸奔也能把时序拿捏住。
2. 带霍尔方案:捕捉中断换相的工程实现
2.1 霍尔传感器与六步换相的基本逻辑
三相无刷电机的定子绕组是三相对称的,转子是永磁体。要让它持续转下去,必须让定子磁场始终领先转子磁场一个角度,这就是换相的本质。带霍尔传感器的电机在内部装了三个开关型霍尔元件,间隔 60° 电角度安装,转子转一圈会输出 6 种组合状态,每种状态对应一个确定的转子位置区间。
六步换相法的核心就是:每一时刻只有两相绕组通电,第三相悬空,每 60° 电角度换一次相,一个电气周期内完成 6 次换相。霍尔状态和换相之间的关系用一张表就能列清楚:
| 霍尔状态 (H1,H2,H3) | 通电相序(假设 A 相悬空为例) | 转子电角度区间 |
|---|---|---|
| 001 | C+ / B- | 0° ~ 60° |
| 101 | A+ / C- | 60° ~ 120° |
| 100 | A+ / B- | 120° ~ 180° |
| 110 | B+ / A- | 180° ~ 240° |
| 010 | B+ / C- | 240° ~ 300° |
| 011 | C+ / A- | 300° ~ 360° |
2.2 捕捉中断换相的代码结构
资料里那套STC8H1K28-带霍尔传感器-电位器调速-捕捉中断换相.c,换相的核心不是轮询霍尔电平,而是利用 STC8H1K28 的外部中断或 PCA 捕获功能,在霍尔信号跳变沿触发中断,中断里读取完整霍尔状态再决定下一步怎么换。这种方式的好处是:电机转速变化时,霍尔信号频率跟着变,但中断总能及时响应,不会因为主循环里的其他任务而错过换相时机。
// 霍尔状态到换相输出映射表(共 8 种,含非法状态) // 每项对应:A相上桥, A相下桥, B相上桥, B相下桥, C相上桥, C相下桥 // 1 表示打开,0 表示关闭 unsigned char code hall_table[8][6] = { {0, 0, 0, 0, 0, 0}, // 000 非法或停止态 {0, 0, 0, 0, 1, 1}, // 001: C+ B- {0, 0, 1, 1, 0, 0}, // 010: B+ C- {1, 1, 0, 0, 0, 0}, // 011: C+ A-(此项依实际接线修正) {1, 1, 0, 0, 0, 0}, // 100: A+ B- {0, 0, 1, 1, 0, 0}, // 101: A+ C- {0, 0, 0, 0, 1, 1}, // 110: B+ A- {0, 0, 0, 0, 0, 0}, // 111 非法或停止态 }; // 霍尔跳变沿触发的中断服务函数 void hall_isr() interrupt 0 { unsigned char hall_state; hall_state = P3 & 0x07; // 读取霍尔引脚:P3.0, P3.1, P3.2 if (hall_state == 0 || hall_state == 7) { motor_stop(); // 非法状态,立即关断 return; } commutation_output(hall_table[hall_state]); }这是一个典型的查表法换相。hall_table里存的不是 PWM 占空比,而是 6 个功率管的导通组合。实际工程里还有一点需要处理:霍尔信号跳变和换相输出之间存在机械角度偏差,也就是所谓的超前换相角。资料里直接用中断立刻换相,对低速起步够用,但高速时建议在表里预置提前量,或者用定时器做延迟换相。
2.3 驱动电路与上下桥控制策略
驱动电路部分,资料里的三相无刷电机驱动电路.pdf画的是经典的半桥驱动结构:STC8H1K28 的 PWM 输出引脚接栅极驱动芯片(常见的是 IR2101 或 EG2131),再驱动 6 个 N-MOSFET 组成三相全桥。注意,上桥要自举电容供电,下桥直接由单片机 IO 或驱动芯片输出驱动。
调速方面,资料用电位器分压后进 ADC,然后改 PWM 占空比。有一种常见的控制策略是:上桥 PWM 斩波,下桥恒通。也就是换相表里被点亮的两个桥臂里,一个桥臂走 PWM 信号,另一个桥臂一直导通。这种做法的好处是开关损耗集中在一对管子上,控制逻辑简单。缺点也明显——6 个管子发热不均,长时间满负荷跑要留意上桥管温升。
void set_pwm_duty(unsigned int duty) { // STC8H1K28 的 PWM 寄存器是 16 位,周期值由 PWMA_ARR 决定 // duty 范围 0 ~ PWMA_ARR,对应占空比 0% ~ 100% PWMA_CCR1 = duty; // 通道1(A相上桥) PWMA_CCR2 = duty; // 通道2(B相上桥) PWMA_CCR3 = duty; // 通道3(C相上桥) }这里只更新了上桥的占空比,下桥在换相表里已经固定为常通或常闭。电位器的 ADC 采样值需要先做线性映射,把 0~4095 的采样范围映射到 PWM 死区之外的占空比区间。我一般会限制最小占空比在 5% 左右,避免低速时三相电流太小,转子在换相间隙里丢失位置,尤其是带霍尔方案里霍尔安装误差会造成低速转矩脉动。
3. 无感方案:反电动势过零检测与三段式启动
3.1 为什么去掉霍尔还能知道转子位置
无感方案去掉霍尔传感器之后,转子的位置信息只能从电机的电气量里提取。最常用的手段是检测反电动势(Back EMF)。原理不复杂:转子永磁体转动时,会在定子绕组里感应出电压,这个电压的幅值和转子角速度成正比。六步换相中有一相是悬空的,这一相在导通区间内会暴露出反电动势的波形,反电动势穿过零点的时刻,恰好对应转子的一个关键位置点。
STC8H1K28 内部有一个模拟比较器,正端接三相绕组的星形中点电压或虚拟中点,负端接悬空相的分压信号。当悬空相的反电动势电压穿过中点时,比较器翻转,捕获这个翻转沿就知道该换相了。资料里的STC8H1K28-无感-电位器调速.c就是用比较器中断做换相触发的,代码结构上比霍尔方案多了一层启动流程。
// 比较器中断服务函数:检测到反电动势过零事件 void cmp_isr() interrupt 21 { // 清中断标志 CMPCR1 &= ~0x80; // 记录本次过零时间戳 last_zero_crossing_time = timer_get_count(); // 延时 30° 电角度后执行换相 // 常见做法:开启一个定时器,延时 = 上次换相周期的一半(近似 30°) set_timer_delay(last_commutation_period / 2); } // 定时器延时时长到了之后执行真正换相 void timer_delay_isr() interrupt 3 { commutation_output(next_com_table_entry()); last_commutation_period = last_zero_crossing_time - prev_zero_crossing_time; prev_zero_crossing_time = last_zero_crossing_time; }比较器只负责捕捉过零点,换相还要再等 30° 电角度,这一步必须用定时器做延迟,不能直接在比较器中断里换相。注释里写得很清楚:延时时间取上次换相周期的一半,这是一种近似估算,实际中因为反电动势波形不是理想正弦,30° 延时会有几度误差,但工程上完全可用。
3.2 三段式启动:定位、强制换相、闭环切入
无感电机最头疼的是启动。静止状态下反电动势为零,比较器根本等不到过零事件,必须靠外力把转子先转起来。资料里的无感方案采用的是一般项目里最常见的三段式:
第一段,定位。随便选一个换相状态给电机通电,定子磁场会把转子拉到某个固定位置,这个过程叫预定位。通常拉 200~500ms,电流不宜太大,否则转子会过冲。第二段,强制换相。按固定的时间间隔依次切换换相状态,不管转子是否真的到位,强迫电机转起来。时间间隔从大往小缩,比如从 30ms 起步逐渐减到 5ms,相当于开环升频。第三段,闭环切入。当强制换相频率达到某个阈值,反电动势已经明显可测,此时把控制权交给比较器中断,检测到过零就换相,系统进入闭环。
void motor_start() { // 第一步:预定位 300ms commutation_output(align_state); delay_ms(300); // 第二步:强制换相启动,换相间隔逐步缩短 for (comm_period = 30; comm_period > 5; comm_period -= 2) { commutation_output(next_com_table_entry()); delay_ms(comm_period); } // 第三步:启动比较器中断,切换到反电动势闭环 CMPCR1 |= 0x80; // 使能比较器中断 motor_state = CLOSED_LOOP; }注意,强制换相阶段的换相时间表没有反馈校正,如果电机负载很重,转子跟不上换相速度,就会失步。一个常见的补救措施是:把启动电流上限设高一点,或者在强制换相阶段用更大的占空比提供额外的加速力矩。资料里电位器旋到最大时启动会顺畅很多,就是这个原因——电压裕量补偿了开环阶段的盲目性。
3.3 比较器配置与分压网络参数
无感方案的硬件电路上,三相绕组输出要经过一个无源分压网络接到比较器输入。原因很简单:单片机的比较器输入范围是 GND 到 VCC,而绕组节点处的电压在 PWM 开关过程中会大幅波动,直接接入会把比较器打坏。分压电阻取 10kΩ/3.3kΩ 的分压比比较稳妥,把最高 24V 或 36V 的母线电压折算到 5V 以内。
STC8H1K28 比较器有两个输入引脚,正端建议接三个 10kΩ 电阻组成的虚拟中点网络,负端接三相反电动势检测选通网络。软件里还要设置比较器的滤波时间,因为在 PWM 开关边沿会有尖峰干扰,可能造成比较器误翻转。资料里虽然没有写滤波参数,但一般做法是把比较器的数字滤波设为 1μs 左右,滤掉功率管开关瞬间的毛刺,保留真实过零点。
4. 电位器调速与 STC8H1K28 关键外设配置
4.1 ADC 采样与占空比映射
电位器调速的本质是电压模拟量到数字量再到物理输出的一个链路:电位器分压 → ADC 采样 → 计算占空比 → PWM 输出 → 改变电机端电压 → 转速变化。STC8H1K28 的 ADC 是 12 位的,参考电压默认是 VCC,电位器接在 VCC 和 GND 之间,中间抽头进 ADC 引脚。
unsigned int adc_read(unsigned char channel) { ADC_CONTR = 0x80 | (channel << 4); // 启动 ADC,选择通道 ADC_CONTR |= 0x40; // 开始转换 while (!(ADC_CONTR & 0x20)); // 等待 ADC 完成标志 ADC_CONTR &= ~0x20; // 清标志 return (ADC_RES << 8) | ADC_RESL; // 12 位结果合成 } void speed_loop() { unsigned int adc_val = adc_read(1); // 读电位器所在 ADC 通道 unsigned int duty; // 映射到 5%~95% 占空比区间,避免死区和满占空比直通 duty = 50 + (adc_val * 900) / 4095; set_pwm_duty(duty); }这段代码有一个容易被忽略的点:把 0~4095 的 ADC 值映射到 50~950 的 PWM 计数值,用的是先乘法后除法,避免浮点运算。adc_val * 900在 4095 量级下最大乘积是 368 万,刚好落在 STC8H1K28 的 16 位乘法范围内吗?不是,368 万已经超过 16 位了,所以这里要用无符号整型的自动类型提升,编译器会扩展为 32 位计算。如果代码里用了unsigned int强制压缩,结果会溢出,转速会毫无规律地跳变,这是实际调车时最容易踩的坑。
4.2 PWM 输出模式与互补死区设置
STC8H1K28 的高级 PWM 模块(PWMA)支持互补输出和死区插入,这是驱动半桥电路的必要功能。即使你只是在换相表里开某些管子,每个管子也不能直接给纯逻辑电平,因为 MOSFET 的栅极充电需要时间,上下桥切换瞬间可能出现直通短路。死区时间通常设在 300ns~1μs 之间,具体取多少取决于管子型号和栅极驱动电阻。
// PWMA 初始化:边沿对齐模式,设置死区 500ns // // PWMA_PSCRH / PWMA_PSCRL:预分频 // PWMA_ARRH / PWMA_ARRL:自动重装载值,决定 PWM 周期 void pwm_init() { PWMA_PSCRH = 0; PWMA_PSCRL = 23; // 24MHz / 24 = 1MHz 计数频率 PWMA_ARRH = 0x03; // 周期 1000 计数值 PWMA_ARRL = 0xE8; // 即 1000,实际 PWM 频率 1kHz PWMA_CCMR1 |= 0x60; // PWM 模式 1 PWMA_CCER1 |= 0x03; // 使能输出 + 使能互补输出 PWMA_CCER1 &= ~0x02; // 极性配置:高电平有效 PWMA_DTR = 0x1E; // 死区寄存器,30 / 1MHz = 500ns PWMA_BKR |= 0x80; // 使能主输出 }这里的预分频 24、计数周期 1000,对应 PWM 频率是 1kHz。BLDC 驱动一般建议开关频率在 16kHz~20kHz,超出人耳可听范围避免噪音。把预分频改成 1(不分频),ARR 改成 1200,就是 24MHz / 1200 = 20kHz,死区时间要用周期除以频率换算:1 / 24MHz × 30 = 1.25μs,适合大多数图腾柱驱动的 MOSFET。
4.3 Keil 工程结构与 STC-ISP 烧录要点
资料里的工程文件是用 Keil uVision 建的,.Uv2是老版本 Keil 的工程文件,直接用 Keil 5 打开会提示版本转换,正常选是就行。工程里同时存在STC8H1K28-无感-电位器调速.c和STC8H1K28-带霍尔传感器-电位器调速-捕捉中断换相.c两个源码文件,但同一时间只能有一个参与编译。我打开motor.Uv2看了一下,资料默认的是无感方案,如果你要看霍尔方案,把无感的那个源文件右键从工程中移除,再把霍尔方案的加进来即可。
烧录用的是 STC-ISP,接好串口后选对芯片型号 STC8H1K28,加载 hex 文件,点下载。STC 系列需要在冷启动状态下才能进入 ISP 模式,所以操作顺序是:先点“下载/编程”,再给板子重新上电。STC-ISP 还提供硬件选项配置,比如内部晶振频率、复位脚功能等。注意不要勾选“下次下载时重新上电”以外的选项,否则可能把 PWM 引脚占用成普通 IO,导致电机不动。
5. 换相时序与启动失败的关键调试点
无感方案调试时建议先接示波器,探头夹在悬空相和虚拟中点之间,看反电动势波形是否完整穿过零位。如果波形有严重削顶或噪声,先检查分压网络和比较器滤波。启动失败大概率出现在强制换相阶段频率收太快,转子还没跟上就进入闭环,看到的现象是电机抖两下就停了。最直接的排查方法是:把强制换相间隔的最小值从 5ms 改成 10ms,损失一点启动时间,换回的是稳定切入闭环的把握。
霍尔方案调试相对省事,但要注意三个霍尔信号不能直接测量换相是否正确。我一般先只接电机的两根线,用手慢慢转动转子,一边转一边看六个霍尔状态是否正确循环到了 1~6。状态缺失或者顺序错乱,先调整霍尔线序而不是改代码——大多数时候是表里的顺序和实际接线不匹配。霍尔信号线上串一个 1kΩ 电阻、对地并 100nF 电容,能有效消除长线引入的高频干扰。
电位器调速范围如果线性度差,大概率是 ADC 参考电压不稳。STC8H1K28 的 VREF 脚接了滤波电容之后,ADC 采样稳定性会明显改善。另外一个细节是:PWM 占空比突变不能太大,每次调速循环最多调 1%~2%,直接跳变会造成电流冲击,表现在电机上是“咯噔”一下的顿挫,严重点会让过流保护动作。电机高速运行时注意听声音,换相噪声如果突然变大,说明换相时序偏移了——带霍尔方案要查霍尔安装角度,无感方案要检查过零延时是否恒定。整套资料里最有价值的不是某一个代码文件,而是两套方案能放在同一个芯片上对比:同样的硬件平台,带霍尔和无感在启动策略、中断响应、调试工具上的差异,比单纯看书理解得透彻得多。
本文还有配套的精品资源,点击获取