十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用STC15F104W实现舵机控制:定时器中断PWM方案详解

用STC15F104W实现舵机控制:定时器中断PWM方案详解 简介基于15F104W微控制器的舵机控制程序是一份可直接编译烧录的Keil工程源码包面向嵌入式初学者、机器人/无人机开发者和需要学习PWM电机控制的工程师解决舵机角度精确控制中波形生成与单片机底层配置问题。压缩包共30个文件、约55KB包含两个完整demo工程一个在12M晶振下通过IO口高低电平变化控制舵机另一个在11.0592M晶振下实现串口十六进制转十进制数据发送文件类型涵盖C源码、A51启动文件、hex烧录文件、uvproj工程文件以及obj、lst、m51、lnp等编译链接辅助文件目录结构清晰适合直接打开工程阅读。目前已有390人学习下载。通过源码可掌握15F104W定时器/IO口初始化、PWM脉宽与舵机角度0.5~2.5ms对应转动范围的对应关系以及串口收发、进制转换的具体写法hex文件可直接烧录验证配合示波器观察输出波形能加深对嵌入式底层控制、软硬件联调和常见通信协议的理解。 有朋友问过我一个很实在的问题做舵机控制有必要一上来就上STM32吗我的回答是真没必要。我手头有个跑了大半年的小项目核心就是一颗8脚的STC15F104W输出PWM信号控制舵机角度成本几块钱代码加起来不到一百行运行稳定、响应及时。这篇文章就把这个15F104W舵机控制程序的完整思路拆开讲透从PWM原理、定时器中断设计到按键调角度、限位保护再到硬件接线和调试踩坑一次说清。这块芯片的资源其实非常有限2KB Flash、256字节RAM、6个IO口但恰恰是这种“资源紧张”逼着你用最简洁的方案解决问题反而是学习单片机底层控制思路的好素材。适合刚入门想做小项目的学生也适合想把手头舵机方案做到够用就好的创客。1. 为什么8脚的单片机能干这种“脏活”15F104W的选型逻辑1.1 这颗芯片到底强在哪STC15F104W是STC15系列里非常经典的一颗8脚单片机SOP8封装引脚不多但五脏俱全。很多人一看8脚就觉得“这能干啥”但舵机控制这个场景它真的就是为这种活儿准备的。先说它最核心的几点优势1T 8051内核同频下比传统STC89C52快8~12倍意味着同样的定时器精度它可以做更细腻的控制。内置高精度RC时钟误差通常在1%以内跑舵机这种对周期精度要求不苛刻的场景完全够用省掉外部晶振省掉两个晶振引脚。工作电压范围3.3V~5.5V可以直接和5V舵机系统共用一个电源域不用做电平转换。6个IO口对于单路舵机控制加两个按键的应用刚好够用。价格便宜拆机料几毛钱全新料也就一两块钱做产品有成本优势。选这颗芯片的时候有个细节容易被忽略它内部有4路PCA/PWM模块看起来可以“硬件输出PWM”控制舵机但这4路PCA的PWM是8位的周期和分辨率往往顾此失彼。真正要做细腻的舵机脉宽控制反而要用定时器中断来模拟这就是文章后面要展开的核心。1.2 什么时候该选它什么时候别硬上15F104W适合做单路到两三路舵机的控制比如航模舵机测试器、小型云台、机械臂单关节控制、智能小车转向舵机、翻页器/推拉机构等。这些场景的特点是控制逻辑简单、实时性要求单一、不需要复杂通信。但如果你要做四路以上的舵机同步控制或者要同时跑OLED显示、串口通信、传感器采集这颗芯片的256字节RAM和2KB Flash会非常吃力。此时更建议选STC8系列或者STM32。选型这件事最忌讳“一颗芯片从一而终”该换就得换。另外要注意15F104W没有硬件看门狗外的很多外设P3.0/P3.1还兼任串口下载一旦这两个引脚被占满会直接影响程序烧录。所以画板子之前一定要把引脚规划好给下载留出余地。2. 舵机到底听谁的话PWM信号与20ms周期的控制逻辑2.1 从一根信号线看懂舵机原理标准模拟舵机内部是一个“直流电机减速齿轮电位器反馈控制电路”的闭环系统。它对外只需要三根线电源正、电源负、信号线。控制电路通过比较信号线上高电平的持续时间脉宽和电位器当前反馈的角度位置来决定电机往哪个方向转、转多久。控制信号的“官方标准”是周期20ms即50Hz频率高电平脉宽在0.5ms到2.5ms之间。0.5ms对应舵机转到一端极限通常标0°1.5ms对应中位90°2.5ms对应另一端极限180°。有些舵机是1ms到2ms对应0°到180°不同品牌略有差异用之前要查一下舵机手册或者用示波器实测中间值。我习惯用一个类比来理解信号线就像领导给下属发指令“这一小段时间内保持高电平”下属通过这段高电平持续了多久来判断“领导要我转多少度”。20ms周期是发送指令的节奏脉宽才是指令内容本身。2.2 周期、脉宽、死区这几个参数别搞混很多初学者容易把“周期”和“脉宽”混在一起。周期是固定的20ms脉宽是变化的。周期决定了舵机的刷新频率脉宽决定了舵机角度。如果你把周期弄成了15ms或者25ms舵机内部电路会按照期望的50Hz去比对结果往往是舵机抖动、嗡嗡叫甚至朝一个方向不停转。另一个概念是“死区”。舵机控制电路对脉宽的分辨率有限当两个脉宽之间的差值小于它的分辨率时舵机不会做出反应。这个不反应的范围就叫死区常见在1~2us左右。所以在写程序时如果你把PWM精度做到1us级别舵机实际角度分辨已经足够平滑。还有一点容易踩坑数字舵机和模拟舵机对PWM频率的容忍度不一样。模拟舵机最好按50Hz来数字舵机往往支持更高频率100Hz~300Hz以获得更快的响应。用15F104W做单路控制时直接用20ms周期50Hz最稳妥数字舵机也能兼容只是发挥不出它更高的刷新率优势而已。3. 定时器中断方案用两个重载值捏出精确PWM3.1 为什么不用阻塞延时最直觉的舵机PWM写法是拉高引脚、延时目标脉宽、拉低引脚、延时剩余周期如此循环。这确实能转但在实际项目中非常难受。阻塞延时期间CPU被完全占死按键扫描、串口通信、传感器读取全都要停下来。更麻烦的是C语言里的软延时函数高度依赖编译优化选项和时钟频率换一颗芯片或者改个优化等级延时时间就可能偏掉舵机角度会跟着跑偏。把PWM生成放到定时器中断里CPU在中断之外可以干别的事PWM波形只依赖定时器硬件计数精度更高、行为可预测。这是舵机控制程序“正规军”和“游击队”的分水岭。3.2 双阶段计数法的核心思路所谓双阶段计数法就是把一个20ms周期拆成两段第一段是高电平阶段定时器的目标计数值就是目标脉宽第二段是低电平阶段目标计数值是“20ms减去目标脉宽”。两个阶段交替进行中断里只做一件事翻转引脚装载下一阶段的重载值。这样设计的巧妙之处在于舵机角度实际上只是脉宽这个变量你只需要在中断外修改这个变量下一个周期自然就会按照新脉宽输出。不必重新配置定时器不必停中断非常干净。16位定时器最长计数65536个脉冲能不能装下20ms取决于计数时钟频率。这就是下面要算的一笔账。3.3 12MHz、12分频和微秒级换算的账我用12MHz时钟定时器0选择12分频模式也就是传统8051的工作方式。此时定时器计数频率是12MHz/121MHz也就是1个计数等于1微秒。这样20ms周期 20000个计数1.5ms中位脉宽 1500个计数0.5ms到2.5ms极限范围 500到2500个计数所有数值直接以微秒为单位代码可读性极高不需要乘除换算。那为什么不用STC15的1T模式如果定时器按1T跑12MHz下1us就是12个计数20ms需要240000个计数超过16位定时器65535的上限还得做分频或者软件计数反而画蛇添足。记住一个原则要让定时器计数值尽量落在65535以内并且最好保证“1个计数等于一个有意义的时间单位”调试的时候会非常舒服。4. 完整程序按键调角度顺手做个限位保护4.1 可编译的完整代码下面这段代码是完整的Keil C51工程芯片选STC15F104W两个按键接P3.2和P3.3P3.4输出PWM信号。代码包含注释可以直接抄。#include STC15F104W.h sbit SERVO P3^4; // 舵机信号线 sbit KEY_UP P3^2; // 角度加按键 sbit KEY_DOWN P3^3; // 角度减按键 #define PERIOD_US 20000 // 20ms周期单位us #define MIN_PULSE 500 // 0.5ms对应0度 #define MAX_PULSE 2500 // 2.5ms对应180度 #define INIT_PULSE 1500 // 上电中位对应90度 #define STEP 10 // 每次按键调整10us约0.9度 unsigned int pulse INIT_PULSE; // 当前目标脉宽 void delay_ms(unsigned int ms) { unsigned int i; unsigned char j; for (i 0; i ms; i) for (j 0; j 122; j); } void Timer0_Init(void) { AUXR ~0x80; // 定时器0选择12T模式保证1us计数 TMOD 0x01; // 16位不可重载模式 SERVO 0; // 上电先保持低电平 TH0 (unsigned char)((65536 - (PERIOD_US - INIT_PULSE)) 8); TL0 (unsigned char)(65536 - (PERIOD_US - INIT_PULSE)); ET0 1; // 开定时器0中断 EA 1; // 开总中断 TR0 1; // 启动定时器 } void Timer0_ISR(void) interrupt 1 { if (SERVO 1) { // 高电平阶段结束拉低装载剩余周期 SERVO 0; TH0 (unsigned char)((65536 - (PERIOD_US - pulse)) 8); TL0 (unsigned char)(65536 - (PERIOD_US - pulse)); } else { // 低电平阶段结束拉高装载目标脉宽 SERVO 1; TH0 (unsigned char)((65536 - pulse) 8); TL0 (unsigned char)(65536 - pulse); } } void main(void) { P3M1 0x00; P3M0 0x10; // P3.4设为推挽输出提高驱动能力 Timer0_Init(); while (1) { if (KEY_UP 0) { delay_ms(10); // 消抖 if (KEY_UP 0) { while (KEY_UP 0); // 等待松手 EA 0; // 关中断安全修改变量 if (pulse STEP MAX_PULSE) pulse STEP; EA 1; } } if (KEY_DOWN 0) { delay_ms(10); if (KEY_DOWN 0) { while (KEY_DOWN 0); EA 0; if (pulse MIN_PULSE STEP) pulse - STEP; EA 1; } } } }4.2 角度步进、限位和边界处理按键每次调整10us如果按常见的舵机“0°~180°对应0.5ms~2.5ms”来算总共2000us对应180度10us大约等于0.9度。这个步进手感比较合适舵机转动平滑不会出现一步跳好几度的问题。限位逻辑很多人觉得多余但非常必要。舵机一旦转到机械极限还继续收到指令电机就会堵转电流急剧上升发热严重轻则舵机寿命缩短重则烧毁电机驱动管。代码里的上下限判断虽然简单却是保护舵机的第一道防线。修改pulse时用EA0关一下中断是为了防止主循环和中断同时读写16位变量pulse时出现高低字节错位的瞬间值。这种概率很小但一个偶然的2000us跳变就可能让舵机猛抽一下养成关中断再改共享变量的习惯能省很多排查时间。5. 硬件连接和上电调试从示波器到舵机转起来5.1 供电、共地和信号线硬件连接是舵机控制里最容易翻车的地方。舵机三根线红色接5V电源正棕色或黑色接GND橙色或黄色接信号线。15F104W这边P3.4接信号线芯片VCC接3.3V或5VGND必须和舵机电源共地。这里有个非常关键的坑很多新手用开发板上的LDO给舵机供电结果一上电单片机就复位。原因是舵机启动瞬间电流很大小容量LDO瞬间被拉垮电压跌落单片机跟着掉电重启。正确做法是舵机单独用5V电源或电池供电电源端并联一个100uF到470uF的电解电容滤掉启动尖峰。还有一个容易被忽略的点共地。如果舵机电源和单片机电源各自独立比如舵机用4节电池、单片机用USB供电那两组电源的GND必须连在一起。否则信号线上的参考地不一致PWM信号可能完全乱掉。5.2 逐级上电调试顺序我调试这类板子有个习惯先空载、再带载、最后联调。第一步不接舵机只给单片机供电用示波器或逻辑分析仪看P3.4输出波形确认周期在20ms附近脉宽随按键在0.5ms到2.5ms之间变化。如果没有示波器可以用一个LED串电阻接在P3.4上按键时LED亮度有明显变化说明脉宽在变。第二步接舵机信号线但不给舵机供电先确认信号线上有正常PWM。第三步才给舵机通电。此时如果一切正常舵机会先转到中位附近然后按键可以左右调整。如果一通电舵机就抖或者转个不停大概率是电源问题先按5.1节排查而不是怀疑程序。5.3 下载烧录的冷启动问题STC系列单片机下载不是传统的ISP自动复位而是“冷启动”先用STC-ISP软件选择程序点击下载然后给目标板重新上电。道理是先让芯片进Bootloader再烧程序。接线是USB转TTL的TX接P3.0RXDRX接P3.1TXDGND共地。如果你把P3.0或P3.1复用做其他功能烧录时就得断开。另外STC-ISP软件里有个“下次冷启动后进入用户程序”的选项如果选错可能会出现烧完程序不跑的情况。6. 我实际调试中踩过的坑给同样玩15F104W的人提个醒6.1 舵机抖动的真凶多半是电源而不是程序我最初做这个项目时舵机在某个角度附近一直“吱吱”响还伴随轻微抖动。第一反应是PWM精度不够在代码里反复调步进值、改中断优先级折腾一晚上毫无进展。后来用示波器看波形PWM本身是干干净净的问题出在舵机电压上——舵机转动时电池电压跌落控制电路工作不稳定才导致抖动。换上独立5V稳压电源并在舵机电源端并联470uF电容后抖动立刻消失。舵机抖动这种事90%是供电问题真正程序问题反而少见。遇到抖动先看电源再查信号最后才怀疑代码。6.2 上电瞬间舵机猛转一下的解决方案另一个典型现象每次上电舵机会猛地朝一个方向甩一下然后再回中位。原因是单片机IO口上电瞬间处于高阻或准双向状态可能输出短暂的高电平舵机把这一小段高电平当成一个异常脉宽就会猛动一下。解决思路很简单上电初始化时先把SERVO引脚拉低再接一个10K下拉电阻到GND双保险。程序里我给定时器初值装载的是“周期减脉宽”也就是先走完一个低电平阶段再进入正常PWM这样舵机收到的第一个信息就是一个完整周期内的正常信号不会乱甩。6.3 下载失败和P3.0/P3.1的占用问题15F104W因为引脚少很多人喜欢把P3.0和P3.1也拿来控制灯、按键甚至直接驱动小电机。结果就是下载器连上芯片完全没反应或者下载到一半报错。我的经验是只要板子上还有调试需求P3.0/P3.1就尽量保留给下载或者做成跳线调试模式断开负载烧完再恢复。如果你已经画好板子且P3.0/P3.1被占用了也未必完全没救——可以把负载断开或者在上电瞬间短接复位脚让芯片进Bootloader但这个操作极其恼人属于下策。6.4 一个省钱的扩展思路单定时器软PWM带多路回到15F104W只有6个IO口的现实有没有可能控制多个舵机当然可以。思路是把定时器中断周期改小比如100us产生一次中断在中断里维护一个软件计数器然后轮流扫描每路舵机如果当前累计计数小于该路目标脉宽对应引脚输出高否则输出低。这就是多路软PWM。这种方案的代价是中断频率升高CPU占用率变大但控制在两三路舵机时完全可行。实际调试时要注意把每个通道的脉宽存成独立变量修改时有条理别把通道搞混。比如两个舵机分别存pulse1和pulse2按键同时控制时独立限位最后在中断里统一按计数比较输出。这个扩展思路更适合用来做简易舵机测试台或者两轴云台成本还是一颗8脚芯片量大了优势非常明显。本文还有配套的精品资源点击获取
返回列表