ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从舵机测试到蓝牙小车:C51定时器PWM与串口控制完整改造

从舵机测试到蓝牙小车:C51定时器PWM与串口控制完整改造 简介一份面向电子爱好者和嵌入式初学者的舵机控制代码包基于51系列单片机实现蓝牙无线指令解析与舵机PWM驱动解决小车舵机远程控制中的信号生成与调试问题。压缩包共3个文件均为C语言源程序总大小仅3KB分别演示使用PCA16位软件定时器、T0单一定时器以及T0/T1双定时器产生PWM信号的实现方法便于对比不同定时方案的精度与资源占用。已有277人学习下载适合正在学习51单片机、蓝牙通信及PWM控制原理的开发者参考。通过分析这些代码读者可以快速掌握舵机角度控制的核心思路并将蓝牙模块与单片机串口对接的流程迁移至类似项目实现手机或遥控器对舵机的无线操控。1. 从舵机测试到蓝牙小车这套C51程序在解决什么问题很多人第一次接触“舵机测试C51程序”是在电子设计竞赛、毕业设计或者课设的智能小车任务里。下载下来的压缩包往往是一个Keil C51工程里面有main.c、接线图还有一句“用按键调舵机角度”的注释。初学者照着接线舵机能转但往上加蓝牙模块和电机驱动后程序就乱套了。真正的问题不是舵机不会转而是PWM、串口和主循环之间怎么分配时间。下面以STC89C52和SG90为例讲清楚舵机测试程序到蓝牙控制小车的完整改造先解决硬件供电和电平再给出可运行的定时器PWM加上HC-05串口指令解析最后教你在没有示波器的情况下验证脉宽并排查复位。阅读对象是正在用C51做小车、舵机云台或机械臂的工程师和爱好者也适合刚从Arduino转过来、对寄存器还不太熟的人。2. 硬件选型与接线C51、舵机、蓝牙模块和电机驱动的连接顺序2.1 为什么是C51而不是STM32把手头材料用起来很多人看到“STM32F103ZET6智能小车”或“stm32cubemx舵机”方案会怀疑C51是不是落伍了。这里要区分场景舵机测试C51程序的目标是低成本验证不是高性能控制。C51只有2个定时器和1个UART做一路舵机PWM加一路蓝牙串口加一路电机驱动刚好够用。而且Keil5兼容C51和STM32的工程可以在同一个uVision5窗口里管理只是C51工具链需要单独安装。你在网络上下载的旧Keil C51工程如果打开后芯片型号是AT89C52但编译报错通常不是代码问题而是Keil的C51包没有装好或者电脑装了Keil MDK后没有切换到C51设备数据库。遇到编译地址溢出时也不需要手工去改C51链接器控制文件。在Keil的Options for Target里把Memory Model改成Small或Large或者把XDATA相关的段声明移出多数情况可以解决。用C51开发还有一个习惯性门槛寄存器直接操作。P1^0、TMOD、SBUF这些名字对做过Arduino智能小车的人来说确实陌生但换来的是每个引脚状态都能看得见调试小车电机和舵机时反而直观。这个项目里定时器0只负责PWM定时器1只负责波特率两个中断逻辑上互不干扰。2.2 舵机选型PWM模拟舵机和总线舵机的区别标题里带“舵机测试”就应该先确认舵机类型。大多数网上下载的C51测试程序是为SG90、MG996R这类3线模拟舵机写的橙色信号线、红色电源线、棕色地线。SG90在4.8V工作电压下扭力约1.8kg·cm空载电流几十毫安堵转电流约200到700mA。MG996R完全不同堵转电流经常超过1A绝对不能用单片机最小系统的5V引脚直接供电。还有一种“总线舵机机械臂”上常见的串行总线舵机信号线上传的不是PWM而是串口字节码需要对应厂家的驱动库普通C51的定时器翻转程序驱动不了它。因此拿到舵机测试程序先看引脚如果只有3根线且方向由脉宽决定就是PWM模拟舵机如果是4根线GND/VCC/RXD/TXD或者标注了BusServo就得换驱动方式。数字舵机如DS3218也是PWM控制的只是推荐PWM频率更高常见为300Hz。C51也能模拟只是中断周期要从20ms改成3.33ms后面的duty换算范围要按舵机标称脉宽重新算。对于常见的MG90S、SG90标准的20ms周期方案可以直接套用。2.3 HC-05蓝牙模块和C51连接电平、波特率、AT模式手机控制小车比较省事的蓝牙模块是HC-05支持主从一体。买来一般默认从机模式上电后指示灯快闪手机能搜索到类似HC-05的设备配对码常见为1234或0000。HC-05的串口电平是3.3V而STC89C52的IO输出高电平是5V直接接会带来风险。稳妥做法是蓝牙的TXD经过两个电阻分压例如R11kΩ、R22kΩ高点平拉到3.3V附近后送C51的P3.0蓝牙的RXD通过一个100Ω限流电阻接P3.1。另外蓝牙模块的VCC接3.3V还是5V要看模块上的稳压芯片。很多HC-05模块带AMS1117-3.3可以直接接5V有些不带稳压的只能接3.3V接错会发热甚至烧掉。遇到HC05蓝牙模块连接不上先处理波特率问题。默认可能是9600或38400最好用AT指令重新配置。进入AT模式的步骤是先把EN脚有的板子标KEY接3.3V再上电用USB-TTL连接模块的TXD/RXD交叉连接并共地打开串口助手发送AT如果收到OK再按第4章的表配置。注意用USB-TTL调试时不要和C51同时连接蓝牙模块否则会出现多个主机争抢通信的冲突。2.4 L298N电机驱动和舵机的分工电动机接线真值表小车底盘一般有两个减速直流电机使用L298N模块时每个电机需要两个输入引脚例如IN1和IN2决定正反转ENA使能脚接高电平或PWM调速。C51的P2口接驱动模块输入即可。信号C51引脚默认状态作用舵机信号P1.0输出PWM控制前轮或云台角度蓝牙TXDP3.0输入接收手机数据蓝牙RXDP3.1输出发送状态回传电机IN1P2.00左电机控制A电机IN2P2.10左电机控制B电机IN3P2.20右电机控制A电机IN4P2.30右电机控制B如果不调速ENA和ENB接5VIN11、IN20时电机正转IN10、IN21时反转两脚相同时刹车。如果使用PWM调速ENA接C51的P2.4或P2.5占空比由定时器或比较器产生。注意L298N的逻辑电源和电机电源分开逻辑电源从5V降压模块取电机电源接6到12V电池但所有模块的地必须接到同一个公共地。2.5 电源分配舵机、电机、单片机三路供电注意事项舵机测试程序单独跑时USB给开发板供电就够。但加上小车电机后供电变得关键。L298N的电源输入来自一组7.4V锂电池驱动板内部有稳压5V输出但这个5V电流有限接舵机容易导致电压跌落。常见做法是把L298N的5V输出只作为逻辑电源舵机从另一路5V/3A降压模块供电单片机和蓝牙再从一片AMS1117-5V降压后取电三路电源的负极都连在一起。如果只有一个电源可以在舵机电源脚并联一个470uF电解电容和一个104瓷片电容缓解舵机启动瞬间大电流对单片机的干扰。花两块钱多并两个电容能省掉不少“蓝牙连不上但重启就好”的诡异问题。3. 用定时器写舵机驱动C51产生20ms周期PWM的代码与参数3.1 舵机角度和PWM脉宽的关系舵机内部比较电路需要的是信号线高电平的绝对时间。以SG90为例周期20ms时高电平0.5ms为0°1.5ms为90°2.5ms为180°。计算公式为脉宽(ms) 0.5 (角度/180)*2.0。有些舵机的起始脉宽不是0.5ms而是0.9ms或1.1ms属于民用舵机里的误差建议先手动测试得到左右极限。如果给舵机发送超出极限的脉宽比如3ms舵机会一直嗡嗡响时间长了会发热损坏所以代码里要做限幅处理。C51没有独立PWM外设最常见做法是用定时器中断产生一个固定周期然后在周期内某个点翻转电平。这个方案的优点是定时器还能同时做其他事例如后面蓝牙串口的波特率由定时器1负责缺点是中断频率高单片机没有多少时间干重活但对这个小项目足够。3.2 11.0592MHz晶振下的定时器初值计算为了和蓝牙串口波特率兼容选用11.0592MHz晶振。舵机PWM的20ms周期由定时器0产生。将20ms拆成200个100us每个100us中断一次。11.0592MHz下每个机器周期是12/11.0592约等于1.085us100us对应的机器周期数约为92.16取整为92。定时器0初值为65536减92等于65444即0xFFA4。因为取整少了约0.16us累计到200次就是32us的误差相对于20ms周期可以忽略。如果手里只有12MHz晶振初值应设为65536减100等于65436即0xFF9C但串口9600会有约8%的误差接收端不一定能识别。因此建议优先使用11.0592MHz晶振或者用STC芯片内部高精度IRC时钟由下载软件设置频率为11.0592MHz这样串口和定时器都能稳定工作。3.3 舵机测试C51程序完整代码下面代码基于STC89C52晶振11.0592MHzP1.0输出舵机PWMP3.2和P3.3为两个按键用来加减角度。#include reg52.h sbit servo P1^0; // 舵机信号线 sbit key_add P3^2; // 角度增加按键 sbit key_sub P3^3; // 角度减小按键 unsigned char duty 15; // 默认1.5ms对应90度 unsigned char cnt 0; // 中断计数 void timer0_init(void) { TMOD 0xF0; // 只改低四位保留定时器1设置 TMOD | 0x01; // 定时器016位定时模式 TH0 0xFF; TL0 0xA4; // 92个机器周期约100us 11.0592MHz ET0 1; TR0 1; EA 1; } void timer0_isr(void) interrupt 1 { TH0 0xFF; TL0 0xA4; cnt; if (cnt 200) { // 200 * 100us 20ms周期起点 cnt 0; servo 1; } if (cnt duty) { // duty个100us后拉低 servo 0; } } void delay_ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) for (j 0; j 120; j); } void main(void) { timer0_init(); while (1) { if (key_add 0) { // 按键按下为低电平 delay_ms(10); // 消抖 if (key_add 0) { if (duty 25) duty; while (key_add 0); // 等待松手 } } if (key_sub 0) { delay_ms(10); if (key_sub 0) { if (duty 5) duty--; while (key_sub 0); } } } }代码逻辑说明定时器0每100us进入一次中断cnt从0增加到199。在cnt等于0时把P1.0置高之后每过一个100us中断如果cnt等于duty就把P1.0置低。这样P1.0输出的高电平时间正好是duty乘以100us。主循环的按键检测中duty最小为5即0.5ms最大为25即2.5ms对应的理论角度为0°到180°。因为按键没有硬件去抖这里用10ms软件延时消抖延时结束后还等待按键释放防止一次按下连续执行多次duty自增。这种轮询方式在纯舵机测试时没问题当引入蓝牙控制后按键部分可以删掉只保留中断和duty变量。关于参数duty每增加1高电平增加0.1ms对应大约9°。如果想要更精细的角度可以把20ms分成400份每份50usduty范围变成10到50角度精度接近0.45°。相应的定时器初值在11.0592MHz下约为65536减46等于65490。不过对普通舵机来说0.4°级别的精度已经超过舵机本身的重复精度实际使用不必再细分。3.4 角度、脉宽与变量对照表目标角度高电平脉宽duty100us步进对应操作0°0.5ms5左极限45°1.0ms10左转45°90°1.5ms15中位135°2.0ms20右转45°180°2.5ms25右极限3.5 为什么不用delay延时模拟PWM很多初学C51的人喜欢这样写先拉高delay 1.5ms再拉低delay 18.5ms循环。这个写法在单独测舵机时能工作但当程序里加入蓝牙串口中断、电机控制、超声波测距等逻辑后一次长达18ms的死循环会阻塞主流程。而定时器中断方案里主循环即使正在执行一个耗时2ms的指令中断仍然会准时翻转舵机信号所以舵机不会卡顿。这也是“舵机测试C51程序”变成“蓝牙控制小车”时必须跨过的一步把所有的控制放到公共状态变量中外设通过状态变量获取最新值。4. 蓝牙控制通道搭建HC-05串口配置与指令解析4.1 C51串口模式1初始化定时器1的复用C51的UART使用SBUF寄存器接收完成自动置位RI。需要在中断里读取SBUF同时由定时器1作为波特率发生器。在11.0592MHz晶振下设置定时器1工作在8位自动重载模式TH1和TL1都等于0xFDSMOD设为0即可得到9600波特率。这组数值是C51串口编程里的经典组合误差只有0.16%足以支持长字符串发送。初始化代码如下#include reg52.h void uart_init(void) { SCON 0x50; // 串口模式18位异步REN1使能接收 TMOD 0x0F; // 清除定时器1的高4位 TMOD | 0x20; // 定时器1方式2自动重载8位 TH1 0xFD; TL1 0xFD; // 波特率9600 11.0592MHz PCON 0x7F; // SMOD0 ES 1; TR1 1; EA 1; }逻辑说明SCON0x50表示串口工作在8位UART模式允许接收。TMOD的高4位原本没有被使用这里先清零再置位避免覆盖定时器0的低4位配置。因为定时器0已经设置为模式1所以最终的TMOD值会是0x21。初始化顺序要先设置TH1/TL1再打开TR1避免波特率发生器在定时器初值未写入时就开始计数。4.2 HC-05的AT指令配置步骤新买的HC-05模块通常没有配对需要先配置。以下是一个可靠的操作顺序断开蓝牙模块的VCC保留GND。将EN/KEY引脚接3.3V。给VCC上电此时模块进入AT模式指示灯慢闪。用USB-TTL模块连接蓝牙。USB-TTL的TXD接蓝牙的RXDUSB-TTL的RXD接蓝牙的TXDGND接GND。串口助手选择波特率38400部分旧版本是9600发送AT返回OK。按需发送ATUART9600,0,0设置波特率ATROLE0设置为从机ATNAMECar设置蓝牙名称。设置完成后断开EN引脚重新上电。常用AT指令表如下AT指令说明返回AT检查通信OKATROLE0从机模式OKATUART9600,0,0波特率96008位无校验1停止位OKATPSWD1234配对密码OKATNAMECar蓝牙名称OK注意AT指令返回结果可能包含末尾的回车换行不要把它当成协议的一部分。如果发送AT没有任何反应检查USB-TTL的TXD/RXD是否接反或者波特率不是38400而是9600。很多人HC05蓝牙模块连接不上就是因为上一步设置波特率后没有重新上电模块仍停留在AT模式手机搜索不到设备。4.3 蓝牙控制小车的指令解析单字节状态机这里给出一个适合C51小车的指令集全部为单字节大写字符。指令小车动作对应状态F前进RUN_FORWARDB后退RUN_BACKWARDL左转舵机偏转电机前进TURN_LEFTR右转TURN_RIGHTG舵机回正电机前进RUN_FORWARDS停车舵机保持当前位置STOP解析函数放在串口接收中断里直接修改全局变量。实现如下volatile unsigned char run_state 0; // 0停止1前进2后退3左转4右转 volatile unsigned char duty 15; // 舵机脉宽变量 void process_cmd(unsigned char ch) { switch (ch) { case F: run_state 1; break; case B: run_state 2; break; case L: run_state 3; duty 20; break; case R: run_state 4; duty 10; break; case G: run_state 1; duty 15; break; case S: run_state 0; break; default: break; // 忽略未知字符防止误码产生抖动 } } void uart_isr(void) interrupt 4 { if (RI) { unsigned char ch SBUF; RI 0; process_cmd(ch); } }逻辑说明串口中断里读取SBUF后立即清RI然后调用process_cmd。这里没有使用环形缓冲区是因为指令只有单字节即使手机连续发来多个字符单片机逐个处理最多在中断里多花十几个机器周期不影响舵机PWM。duty被声明为volatile因为它在定时器中断和串口中断里都被修改防止编译器优化时把变量放到寄存器里导致主循环看不到最新值。参数说明run_state等于3或4时舵机角度固定为duty等于20或10对应约135°或45°。实际要根据小车机械结构改。如果舵机装在云台上用来控制摄像头L和R应该只改duty、不改变电机状态可以把run_state设置为1放在case外让主循环统一控制电机前进如果舵机是前轮转向那么左转动作就是电机前进加舵机偏转上面这种方式可以直接使用。4.4 粘包和乱码的处理手机蓝牙APP发送数据时有的会附带回车换行process_cmd的default分支会忽略掉它们。有些APP会连续发送两个相同指令导致小车转向后立刻又执行一次此时可以加一个同一指令去抖变量记录上一次指令如果相同则跳过直到收到其他指令才更新。不过大多数场景不需要这么复杂只要发送字符时不要勾选“发送新行”或者只发送一次即可。如果使用“蓝牙串口”类APP建议把按键的发送方式设为按下发送而不是切换发送这样松开时发送一个停止指令S小车急停更安全。这个习惯可以避免小车路试时手忙脚乱找不到停止键。5. 小车联动控制与调试验证供电、抗干扰和波形检查5.1 主循环统一刷新电机和舵机把串口解析和PWM放在中断里主循环只负责根据run_state输出电机控制。代码片段如下sbit IN1 P2^0; sbit IN2 P2^1; sbit IN3 P2^2; sbit IN4 P2^3; void motor_control(unsigned char state) { switch (state) { case 0: IN10; IN20; IN30; IN40; break; case 1: IN11; IN20; IN31; IN40; break; case 2: IN10; IN21; IN30; IN41; break; case 3: IN11; IN20; IN30; IN40; break; // 左轮转右轮停 case 4: IN10; IN20; IN31; IN40; break; // 右轮转左轮停 default: break; } }逻辑说明case 3模拟左转采用左侧电机停转、右侧电机推进的差速方式比单纯让两个电机反转更接近小车实际转弯。如果舵机做前轮转向case 3和4里的电机状态可以改成case 1让两个电机都前进只靠前轮偏转转弯。选择哪种策略取决于底盘结构建议在程序里用宏定义切换。5.2 用串口回显duty和状态无示波器验证一个很实用的调试技巧是把duty和run_state通过串口回发到手机每次收到指令后发送类似D:15,S:1这样的文本。这样蓝牙串口APP上能看到当前角度变量和电机状态能验证链路是否通畅。注意不要在串口中断里直接调用printf会占用大量时间。建议在主循环中设置一个发送标志接收中断只置位标志主循环检测到标志后再用查询方式发送unsigned char tx_flag 0; void uart_isr(void) interrupt 4 { if (RI) { unsigned char ch SBUF; RI 0; process_cmd(ch); tx_flag 1; } } void main(void) { uart_init(); timer0_init(); while (1) { if (tx_flag) { tx_flag 0; SBUF duty 0; while (!TI); TI 0; } } }这个技巧的价值是当手机收到数值且舵机角度和数值对应时说明从蓝牙到C51再到舵机整个链路都正常如果角度变化但数值不变问题在舵机机械部分如果数值变化但舵机不动就去看PWM波形。5.3 逻辑分析仪抓舵机波形与最后检查有条件的话用逻辑分析仪抓舵机信号线。正常波形应该是20ms一个小周期高电平宽度在0.5ms到2.5ms之间。采样率调成10MHz以上可以准确测量高电平宽度。如果看到周期不是20ms而是40ms说明中断重装初值有误如果脉宽是2.6ms且逐渐增大说明duty越界检查按键程序里if(duty25)duty25的限幅有没有生效。最后还要检查硬件把万用表打到直流电压档测量HC-05和STC89C52的供电引脚发送指令瞬间如果电压跌落超过0.3V就说明电源分配有问题优先处理供电而不是继续去调程序。确认这几项后这辆蓝牙小车就可以从单机调试进入路试了。本文还有配套的精品资源点击获取
返回列表