简介:面向电子开发与单片机学习者的STC15F104W步进电机脉冲控制方案,覆盖从核心板编程输出方波到驱动步进电机的完整链路,解决脉冲信号发生与掉电参数记忆等实际需求。资源共22个文件,以C语言源程序、Keil工程文件、PDF原理图、Word说明文档为主,并附hex烧录文件、备份及列表文件;其中源码与工程文件用于程序修改,原理图与说明文档用于硬件搭建和调试,hex文件可直接烧录使用。压缩包约1.85MB,轻量易取,已有467人学习下载。读者可参考定时器方式2产生精确脉冲的代码写法,结合按键调速、IAP读写实现掉电保存速度值;原理图清晰标注接口,说明手册兼顾产品使用手册与快速说明,能帮助快速搭建PLC控制步进电机的扩展应用,也适合单片机方波输出实验入门。整体目录简洁,适合作为项目模板复用或二次开发。
1. STC15F104W 做步进电机方波脉冲发生器:先解决频率源再谈控制
STC15F104W 是 STC 单片机里最不起眼的一种 8 脚封装,但把“编程输出方波”和“步进电机脉冲信号发生器”放在一起,这个项目的重点就不是“会不会写方波”了,而是如何用一颗只有几个 IO 口、两颗定时器的增强型 51 单片机,把频率算准、把脉冲数数清、把方向时序留够。很多人一拿到这类资料就盯着步进电机驱动电路看,实际上普通定位、送料、点胶平台需要的只是一个稳定精确的方波源,不是 FOC 那种磁场角运算。STC15F104W 内置 IRC 振荡器,免费掉了外部晶振和两个负载电容,SOP-8 封装适合直接焊在驱动器信号端附近,常见做法是把它当作 PLC 或上位机之外的“本地脉冲发生器”。下面的内容按照定时器计算、程序实现、原理图接线、下载参数和验证调试这条线展开,每一步都能直接抄到工程里。
2. 方波脉冲的发生原理与 STC15F104W 定时器资源分配
2.1 步进电机驱动器输入接口到底要什么波形
步进电机驱动器通常给出 STEP 和 DIR 两个输入口,A4988、DRV8825、TB6600,五相步进电机驱动器的接口本质上是一路光耦或者差分比较器。单片机只要给 STEP 脚送一串 TTL 方波,驱动器在上升沿执行一拍,电机转子就走一步;DIR 引脚上的电平决定旋转方向。所以从单片机侧看,这不叫复杂的电机控制,而叫作“脉冲信号发生器”,真正难的是方波参数要满足驱动器的输入规范。
光耦输入型驱动器对脉冲宽度比较敏感,我见过很多 TB6600 类的板卡要求高电平时间不小于 2 微秒,而直接 TTL 输入的驱动芯片可以到 1 微秒以内。另一个硬指标是频率上限,42 步进电机和 57 步进电机配一般驱动板,实际机械系统跑到 40 kHz 以上力矩衰减就很明显了,所以工程里最常用的速度区间是 1 kHz 到 20 kHz。DIR 信号还要比 STEP 提前建立,驱动器数据手册里通常写着方向建立时间 5 微秒或 10 微秒,如果方向引脚和脉冲引脚同时翻转,电机会在半个脉冲周期内走错方向。
2.2 用定时器中断翻转 IO,还是用死循环延时翻转
常见新手做法是写一个while(1),里面PULSE = 1; delay; PULSE = 0; delay;,这种方案在调试器里跑一跑看起来没问题,但换一颗芯片、改一下优化等级、电压波动一点,频率就变了。更致命的是死循环没有计数能力,要“走到 500 步停一下”时,必须在循环里嵌计数器,稍微被按键扫描打断一下,方波周期就出现毛刺。
我一般用定时器溢出中断翻转 IO。STC15F104W 的定时器工作在 1T 模式时,计数时钟等于系统时钟,24 MHz 主频下最高可以产生几百 kHz 级别的中断,足够覆盖步进驱动器的常用范围。每次中断只做一件事:把引脚电平翻转一次。这样两个中断才产生一个完整方波周期,所以脉冲频率计算公式是:
f_pulse = MAIN_Fosc / (2 * (65536 - reload))
以 24 MHz 系统时钟为例,用 16 位手动重载模式,常用频率对应的重载值如下表:
| 目标脉冲频率 | 半周期计数值 | 16位重载值 |
|---|---|---|
| 1 kHz | 12000 | 0xD120 / 53536 |
| 10 kHz | 1200 | 0xFB50 / 64336 |
| 20 kHz | 600 | 0xFD98 / 64936 |
| 50 kHz | 240 | 0xFF10 / 65296 |
| 100 kHz | 120 | 0xFF88 / 65416 |
注意低速时半周期计数值可能超过 65535,比如 1 Hz 脉冲,24 MHz 的半周期要 12,000,000 个计数,16 位定时器根本放不下。此时把 T0 切到 12T 模式,计数时钟降低到 2 MHz,最低可产生约 15 Hz;还想更低就每隔 N 次中断再翻转一次,用软件分频。
2.3 初始化代码与频率设置函数的最小框架
推荐把重载值做成全局变量,中断里每次都重新装载TH0/TL0。这样外部修改重载值时,不会立刻打断当前半周期,而是在下个溢出点生效,天然适合做加减速。
#include "stc15.h" #define MAIN_Fosc 24000000UL sbit PULSE = P3^0; sbit DIR = P3^1; volatile unsigned int reload_value; void Timer0_Isr(void) interrupt 1 { TH0 = (unsigned char)(reload_value >> 8); TL0 = (unsigned char)(reload_value & 0xFF); PULSE = !PULSE; } void Timer0_Init(void) { AUXR |= 0x80; // T0 时钟切换到 1T,计数时钟=24MHz TMOD &= 0xF0; TMOD |= 0x01; // T0 16 位手动重载模式 TR0 = 0; ET0 = 1; EA = 1; } void SetPulseFreq(unsigned long hz) { unsigned long half_ticks; if(hz < 200UL) // 低速自动换 12T,避免溢出 { AUXR &= 0x7F; // T0 时钟变为 MAIN_Fosc/12 half_ticks = MAIN_Fosc / 12UL / hz / 2UL; } else { AUXR |= 0x80; // T0 时钟切回 1T half_ticks = MAIN_Fosc / hz / 2UL; } if(half_ticks > 65535UL) half_ticks = 65535UL; if(half_ticks < 2UL) half_ticks = 2UL; reload_value = (unsigned int)(65536UL - half_ticks); }AUXR |= 0x80是 STC15 系列定时器 T0 的 1T 控制位,置 1 后定时器按系统时钟计数;AUXR &= 0x7F恢复 12T。代码里加hz < 200的判断,是避免低速时 16 位计数值溢出。这个函数可以在主循环里被按键、串口命令或上位机随时调用。
3. 用 STC15F104W 写一个可调速、可定脉冲数的方波发生器
3.1 在定时器中断里维护“步数计数器”,而不是只翻转电平
连续方波只能让电机一直转,步进电机的点位控制必须知道“走了多少步”。第二步是在中断里增加一个上升沿计数器,达到目标步数后关断定时器,并让脉冲脚停在低电平。
volatile unsigned int step_target; volatile unsigned int step_done; volatile unsigned char pulse_level; volatile unsigned char running; void Timer0_Isr(void) interrupt 1 { TH0 = (unsigned char)(reload_value >> 8); TL0 = (unsigned char)(reload_value & 0xFF); if(!running) { return; } pulse_level = !pulse_level; PULSE = pulse_level; if(pulse_level) // 只在上升沿计数一次,避免双倍步数 { step_done++; if(step_done >= step_target) { running = 0; pulse_level = 0; PULSE = 0; TR0 = 0; } } }判断“上升沿”不要用if(PULSE),因为那是读引脚状态;外部电路如果对地短路或者经过光耦反相,读回来会误导。这里用内存变量pulse_level保存当前输出电平,每次中断取反后再判断,速度更快也不会被外部电路绑架。
启动电机时,方向信号要先建立,再开脉冲。对 STC15F104W 这种没有专用运动控制外设的芯片,方向建立时间可以在StartMove里用手动延时顶一下,至少给 20 微秒:
void StartMove(unsigned int steps, unsigned long hz, bit dir) { if(0 == steps) return; DIR = dir; TR0 = 0; step_target = steps; step_done = 0; pulse_level = 0; PULSE = 0; SetPulseFreq(hz); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); // 再补 20 微秒延时 running = 1; TR0 = 1; }TR0 = 1启动定时器后,第一个中断发生在半周期后,而不是立即翻转。这样电机起步动作是完整的方波上升沿,驱动器不会在使能瞬间误动作。
3.2 频率调整是立即改重载值,不用重启定时器
工程里经常遇到运行中调速的需求,比如手动模式下按“快/慢”键把 2 kHz 改成 6 kHz。正确做法是只改reload_value,然后等待下一个中断自动加载新值。
void SetRunFreq(unsigned long hz) { unsigned long half_ticks; half_ticks = MAIN_Fosc / 12UL / hz / 2UL; if(half_ticks > 65535UL) half_ticks = 65535UL; if(half_ticks < 2UL) half_ticks = 2UL; reload_value = (unsigned int)(65536UL - half_ticks); }不要在这个函数里写TR0 = 0再去重设TH0/TL0。一旦关闭定时器,当前半周期被打断,驱动器收到一个极短的毛刺,严重时会让电机原地发热。修改reload_value的方案等价于“下次中断换挡”,当前半周期长度保持不变,输出波形连续。
3.3 最简梯形加减速:用频率分档表控制换挡点
步进电机从静止直接跳到 20 kHz 一定会丢步,所以要让频率从小到大爬上去。梯形加减速的标准算法需要实时计算加速度积分,对 51 单片机负载偏重,我常用一张压缩过的分档表:
code unsigned long speed_lv[8] = { 500, 1000, 2000, 4000, 8000, 12000, 16000, 20000 }; code unsigned int ramp_pos[8] = { 0, 30, 100, 250, 600, 1200, 2200, 4000 };ramp_pos表示步数达到多少时切到下一档速度。比如步进数走到 30 步时,速度从 500 Hz 升到 1 kHz;走到 100 步时再升到 2 kHz。在定时器中断里加一段判断:
unsigned char speed_level; if(step_done == ramp_pos[speed_level] && speed_level < 7) { speed_level++; SetRunFreq(speed_lv[speed_level]); }减速部分同理,判断剩余步数,当step_target - step_done == 800时把档位降下来。注意降速时的档位值不是严格镜像,建议减速起点比加速终点提前一点,否则高速段还没完全降到安全值就到了目标位置。
4. 原理图接线和 STC-ISP 下载:STC15F104W 的电源、光耦和串口怎么安排
4.1 最小系统原理图:两个电容加一个下载插座就够了
STC15F104W 是 SOP-8 封装,内部 IRC 振荡器出厂已校准,不需要外部晶振,也不需要传统 8051 的 30pF 负载电容。很多 PDF 原理图里会多画一个复位电路,但对 STC15 来说上电复位是内部的,外部复位引脚在多数封装里也没引出。最小系统实际只需要:
| 引脚功能 | 推荐接法 | 说明 |
|---|---|---|
| VCC | 5V 电源,并联 100nF 陶瓷电容和 10uF 电解电容 | 去耦必须紧靠芯片引脚 |
| GND | 电源地,与驱动器信号地单点连接 | 避免形成地环路干扰 |
| P3.0 / P3.1 | 同时接串口下载座和驱动器信号 | 中间加 1k 电阻隔离 |
| 其他 IO | 按键、方向选择、使能输出 | 不用则悬空或上拉 |
驱动器的 PUL+ 和 DIR+ 如果接 5V,单片机 P3.0 和 P3.1 低电平时光耦导通,属于共阳接法;如果驱动器要求 MCU 引脚输出高电平触发,则把限流电阻串在脉冲正端。我一般选 1k 作为默认限流电阻,5V 供电时光耦输入电流约 5mA,绝大多数驱动器都认这个电平。不要用 10k,有些光耦的发光管在 1mA 以下导通不彻底,偶尔转有时会丢步。
4.2 STC 下载电路:P3.0/P3.1 交叉接线,点击下载后再上电
STC15 系列下载使用串口,P3.0 是 RXD,P3.1 是 TXD。USB 转 TTL 模块和单片机之间要交叉连接:CH340 的 TXD 接 STC15F104W 的 P3.0/RXD,CH340 的 RXD 接 STC15F104W 的 P3.1/TXD,两边 GND 必须共地。
下载时先打开 STC-ISP,选中正确的单片机型号,点击“下载/编程”,然后给目标板断电再上电。STC 单片机只有在上电复位瞬间才进入 ISP 引导区,这也是很多新手点“下载”点了半天没反应的原因。如果目标板上已经接了步进驱动器,建议在 P3.0/P3.1 到驱动器的线路上加跳线帽,下载固件时断开驱动信号,避免拉低下载握手电平。
4.3 STC-ISP 里的 IRC 频率必须和源码里的 MAIN_Fosc 一致
STC15F104W 的时钟在 STC-ISP 烧录软件里配置,不同批次芯片写入的 IRC 频率可能不同。烧录时要把“输入用户程序运行时的 IRC 频率”选成 24.000MHz,源码里#define MAIN_Fosc 24000000UL也必须是 24MHz。两边不匹配时,定时器算出来的重载值是准的,但实际计数时钟不同,输出方波频率会整体偏掉几个百分点。
下载软件里还有一个“低电压复位”和“看门狗”选项,做信号发生器这类长时间运行的设备,看门狗可以开,但要在主循环里定期喂狗;如果系统简单到只有一个while(1),干脆关闭看门狗,否则喂狗位置写错会让单片机不断重启,方波输出变成短促的间断波形。
5. 用这几个办法验证 STC15F104W 方波发生器是否真的合格
5.1 示波器看单周期,别只看占空比
方波信号发生器的第一项验证是上升沿是否干净、半周期是否等宽。把示波器探头打到 10 倍衰减,接在 P3.0 输出端,地线夹要尽量短。重点看下降沿和上升沿之间有没有毛刺,特别是在加减速换挡点附近。CPU 在中断里执行多条指令时,翻转位置会相对理论时刻偏移几个时钟周期,24MHz 下几个时钟对应不到 0.5 微秒,不会影响光耦型驱动器,但如果用逻辑分析仪看到缺口超过 1 微秒,就要检查中断里是不是加了过长指令。
5.2 用一块普通 STC15 或者 STM32 做秒脉冲闸门计数
更可靠的办法是测 1 秒内的脉冲数而不是某个周期的宽度。把输出接到另一块单片机的 INT0 引脚,开启外部中断计数,同时用主循环里的定时器产生 1 秒闸门。实际输出频率和目标值偏差在 ±1Hz 以内,说明定时器重载值正确,中断没有因为按键扫描或串口任务被阻塞。
这一步对运行中调速尤其重要。如果实测 20kHz 档位只有 19.2kHz,先查 STC-ISP 里的 IRC 频率是不是 24MHz,再查SetPulseFreq里hz < 200的 12T 分支有没有被误触发。
5.3 低速和加减速段各拍一次波形
把目标步数设成 4000 步,速度峰值设成 20kHz,用示波器在加速段前 100 步、中间稳定段、最后减速段各保存一段波形。低速段脉冲宽度必须明显大于驱动器最小脉宽;减速段最后一个脉冲结束后,PULSE 电平要稳定在低电平,不能出现半个残余方波。这个测试能同时暴露三个问题:加速太快丢步、减速不到位、停止时脉冲脚没有关断。全部通过后,再把这套 STC15F104W 方波源接到步进电机驱动器上,才算真正闭环。
本文还有配套的精品资源,点击获取