尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

51单片机红外遥控电机控制:NEC协议解码与PWM调速

51单片机红外遥控电机控制:NEC协议解码与PWM调速 简介这是一套面向单片机入门与进阶学习者的红外遥控电机控制实验例程围绕电机远程控制场景完整演示从红外信号采集、解码到电机启停与调速的实现流程。资源共10个文件约26KB包含C语言源码、头文件、Keil工程文件、编译生成的hex烧录文件及readme说明文档既适合课堂实验参考也便于自主修改验证。已有178人学习下载。例程中重点涵盖红外遥控编码与解码、PWM脉宽调制调速、LCD显示以及电机驱动接口等关键环节配套的readme文件还提供了实验流程与常见问题提示可帮助初学者快速理解单片机如何识别遥控指令并驱动电机。通过这份资源读者能直接获得一套可运行的基础工程框架并借此掌握单片机外部中断、定时器及信号解析等实用技能。1. 为什么把红外遥控和电机控制放在同一个实验里拿到“实验4. 单片机入门高级实验例程-电机远程控制--红外遥控控制.zip”这种名字的实验包直觉以为是两段独立代码打包在一起。实际玩过会发现红外遥控和电机驱动恰好踩中单片机入门的两道坎一个是时序判断另一个是驱动电流。红外遥控的难点不在接收头NEC协议帧里引导码、数据1和数据0的宽度判定都在百微秒级电机控制的难点也不在软件逻辑而是H桥、续流二极管和共地这些硬件细节。这里说的电机远程控制不是互联网远程而是用手边几米内的红外遥控器去控制电机启停、换向和调速。把两者合在一起等于要求你同时会看手册、会改参数、会处理外部干扰适合正在做课程设计、打算做智能小车、或者第一次接触直流电机驱动的读者。2. NEC协议、电机驱动选型与电路搭法2.1 红外遥控的NEC协议载波、引导码和1/0的时间差红外遥控看似无线性实际是基带脉冲信号调制在38kHz载波上发出去。接收模块常见VS1838B内部完成接收、放大、滤波和解调输出端直接输出解调后的高低电平有载波时为低电平无载波时为高电平。所以单片机要做的不是关心38kHz载波而是测量输出脚上的高、低脉冲宽度。NEC协议每个数据帧由下面的段组成9ms低电平加4.5ms高电平的引导码随后是32位数据。32位数据按“地址码、地址反码、命令码、命令反码”排列每段8位LSB在前。表示“0”时低电平560us高电平560us表示“1”时低电平560us高电平1.69ms。整个比特宽度很规律0位总时长约1.12ms1位总时长约2.25ms。接收头输出在空闲时保持高电平收到引导码时先跳低9ms再升回高4.5ms。波形段低电平时间高电平时间总时长引导码9ms4.5ms13.5ms重复码9ms2.25ms11.25ms数据0560us560us1.12ms数据1560us1.69ms2.25ms长按遥控器时NEC协议不会重新发送完整数据而是发一个重复码9ms低加2.25ms高。遥控器控制电机时如果你希望按住“加速”按键连续调快就要识别重复码并保持上一次的键值有效如果只是单次正转反转重复码可以忽略。2.2 为什么电机要用H桥而不是一个三极管直流电机的控制要求比LED复杂得多。单片机GPIO输出电流一般不到20mA而玩具电机空载电流往往上百毫安堵转时甚至超过1A直接用GPIO带不动。更要命的是电机要换向正转时电流从A流向B反转时要从B流向A单只三极管只能让一个方向通电所以在电机控制实验里最稳定的方案是H桥驱动。H桥由四个开关管组成对角导通形成回路。以L298N为例IN1和IN2输入决定电流方向ENA使能端可以接受PWM调速。使用L298N时有一个容易被忽视的点电机电源VM和信号电源VSS要分开接但两个GND必须共地否则控制逻辑无法形成回路。另外电机换向瞬间会产生反向电动势虽然L298N片内有续流二极管但为了保护单片机接近电机的地方最好再并一个104瓷片电容电源两端再放一个470uF电解电容。如果只控制单向用ULN2003更省焊点如果要做小车前进后退最少要一路H桥。我一般不会一上来就按模块推荐焊电路先看手头有什么。没有L298N的话用两个MOS管搭简易H桥也行但栅极驱动和死区保护对新手不太友好所以下面默认按L298N模块来讲。2.3 用51还是STM32解码的差异性没那么大红外遥控解码的本质是测量脉冲宽度51和STM32都能做。51通常用外部中断加定时器INT0在下降沿触发中断服务程序里读定时器当前值与上一次触发时刻相减得到时间间隔。这样测出来的时间精度受中断响应延迟影响但只要中断服务程序不长12MHz晶振下误差不会超过几条指令。STM32的做法是用定时器输入捕获边沿自动记录计数器值不需要频繁进中断在高频噪声多的环境里更稳。电机控制方面STM32有硬件定时器输出PWM而51多数靠定时器模拟PWM或者用带PCA/PWM的增强型51。对比项51单片机STC89C52STM32测脉宽外部中断软件计数定时器输入捕获PWM定时器模拟占CPU硬件PWM输出解码误差可能有1-2个计数抖动稳定适合场景课程实验、简单电机启停小车、带测速调速如果你必须按课程要求用51不用怕做不出来只是要把定时器中断写得干净利落如果自由选型用STM32会省不少调试时间。下面代码按51写方便直接对照实验包里的工程改。3. 硬件接线与固件实现红外解码、PWM调速一个都不能少3.1 最小系统连线接收头、L298N和电机的连接以STC89C52作为主控。红外接收头的OUT接P3.2也就是INT0引脚L298N的IN1、IN2接P1.0、P1.1ENA接P1.2用来做PWM调速。这套连接在51实验板上比较常见。接线时注意L298N的逻辑电源和电机电源最好分开两个GND必须连在一起。引脚模块接法P3.2VS1838B OUT接收解调后的信号P1.0L298N IN1方向控制1P1.1L298N IN2方向控制2P1.2L298N ENAPWM调速5VVS1838B VCC5V供电GNDVS1838B GND与L298N共地程序上建议先让红外解码跑通再上电机否则解码错误导致的误动作比电机本身还难查。3.2 NEC解码核心代码外部中断加100us定时器这里给出一段可以直接在Keil里跑的红外解码核心。思路是用定时器0产生一个100us计数基准每次INT0下降沿中断时读取计数值并与上一次作差得到间隔用间隔大小判断引导码、重复码、数据0和数据1。#include reg52.h volatile unsigned int t100us 0; /* 100us时基 */ volatile unsigned int last_t 0; volatile unsigned long ir_bits 0; /* 32位数据暂存 */ volatile unsigned char bit_cnt 0; volatile unsigned char ir_key 0; /* 命令码 */ volatile unsigned char repeat 0; volatile unsigned char frame_ok 0; /* 定时器0100us中断 */ void timer0_isr() interrupt 1 { TH0 0xFF; TL0 0x9C; /* 12MHz, 方式1, 100us */ t100us; } /* INT0下降沿触发 */ void ext0_isr() interrupt 0 { unsigned int now t100us; unsigned int dur now - last_t; /* 两次下降沿间隔 */ last_t now; if (dur 125 dur 145) { /* 引导码: 13.5ms */ ir_bits 0; bit_cnt 0; } else if (dur 105 dur 120) { /* 重复码: 11.25ms */ repeat 1; } else if (dur 9 dur 14) { /* 数据0: 1.12ms */ ir_bits 1; bit_cnt; } else if (dur 17 dur 26) { /* 数据1: 2.25ms */ ir_bits 1; ir_bits | 1; bit_cnt; } else { bit_cnt 0; /* 超范围丢弃 */ } if (bit_cnt 32) { ir_key (ir_bits 8) 0xFF; /* NEC帧第3字节是命令码 */ frame_ok 1; bit_cnt 0; } }注意TH00xFF; TL00x9C这个初值。在12MHz晶振和默认12分频下机器周期是1us方式1需要计数100次到溢出初值等于65536减100也就是0xFF9C。如果你用11.0592MHz晶振这个初值就不能照抄否则后面所有脉宽判断都会偏移。dur的单位是100us引导码约135个单位重复码约112个单位数据0约11个单位数据1约22个单位。阈值留了比较宽的范围是为了容忍遥控器老化、接收头波形边沿不够陡、以及中断响应耗时造成的偏差。环境干扰弱时可以把数据0的阈值收紧到10-12数据1收到20-24误码率会更低。3.3 用定时器1做PWM调速电机只做正转、反转、停止时ENA直接接高电平就够了。要“高级”一点就得加调速。51没有独立硬件PWM时可以用定时器1产生周期中断在中断里按占空比翻转IO。unsigned char duty 50; /* 0-100% */ void timer1_isr() interrupt 3 { static unsigned char pwm_cnt 0; if (pwm_cnt 100) pwm_cnt 0; ENA (pwm_cnt duty); }定时器1的初始化与定时器0类似中断号是3。这个办法的缺点是PWM频率受中断周期限制。如果定时器1也设成100usPWM频率只有100Hz电机运转时会听到啸叫。用STC15、STC12这类带PCA/PWM的增强型51会更好直接把比较值写到PCA捕获比较寄存器里不占CPU。对于课程实验先按上面的模拟PWM跑通功能没有问题只是量产和比赛时不要这么写。3.4 把遥控键码映射到电机控制指令红外解码完之后主循环只需要判断frame_ok然后按键码执行。试验包里的遥控器键码通常写在文档里但保险起见要用串口打印确认。你可以在串口初始化9600后在frame_ok1时用printf(KEY%02X\n, ir_key)打印。按键用途建议键码执行动作正转0x45IN11, IN20反转0x46IN10, IN21停止0x47IN10, IN20加速0x44duty 10减速0x40duty - 10键码只是建议值不同遥控器键值不一样。不要在你的代码里写死“0x45一定是正转”除非你拿手头遥控器确认过。我一般会在主循环里保留一个未映射表把连续三次相同的未知键码打印出来等逻辑调完再去固化映射关系。4. NEC时序容忍范围、供电共地与排错清单4.1 时序阈值调参以12MHz为基准计算可调范围红外解码的成败几乎全在时序阈值上。你可能会遇到一个现象红外解码本来正常换了一块实验板后就不正常了原因多半是晶振频率不一致。以12MHz、12分频为基准初值算出100us中断上面代码里的阈值范围如下波形段理论值代码阈值折合时间引导码13.5ms125-14512.5-14.5ms重复码11.25ms105-12010.5-12ms数据01.12ms9-140.9-1.4ms数据12.25ms17-261.7-2.6ms如果你用的晶振是11.0592MHz机器周期约1.085us同样初值下中断周期变成约108us。此时数据0约10.4个单位还在阈值内但引导码变成约124个单位会掉出125-145的下边界。所以当中断周期变大时引导码下界要改到115或120。这个坑在实验中最常见排查时先查晶振频率再查定时器初值。另一个坑是t100us变量溢出。它定义为unsigned int最大65535约6.5秒后归零。两个下降沿间隔最长不会超过40ms一般不会出问题。但如果主循环里读取这个变量做其他事C51对volatile的读取不是原子的外部中断触发时可能正读到高低字节更新的中间态。建议在外部中断里先关EA读取读完再开EA。4.2 复位、啸叫和驱动电流问题电机控制比红外解码更容易让单片机复位。常见原因有三个电机电源与单片机共用一套电源启动瞬间压降触发复位没有共地L298N的逻辑输入端没有参考点ENA引脚悬空或虚焊导致电机时转时停。第三个原因最容易误判模块上ENA悬空时内部逻辑可能直接关闭输出单片机程序却看起来完全正常。所以我在实际接线时会做以下处理给电机电源两端各加一个470uF电解电容吸收浪涌单片机与电机驱动板各自独立供电只把GND连在一起L298N输出端靠近电机的位置并一个0.1uF CBB电容吸收换向尖峰。如果实验包附带的是杜邦线还要考虑导线压降电机大功率启动时电压跌落可能会让红外接收头也跟着复位。4.3 红灯不闪、代码不走、电机不动怎么办排错顺序应该从红外接收头开始而不是先查电机。先用示波器或逻辑分析仪接P3.2按下遥控器按键观察是否出现一簇约20ms的方波。波形正常说明接收头与硬件通路没问题。如果什么都没有检查接收头引脚定义不同封装的OUT、GND、VCC顺序可能不同接反长时间会烧接收头。再用手机摄像头对准红外发射管按下按键屏幕里能看到紫色光点说明遥控器本身在发射。如果P3.2有波形但frame_ok一直为0就把每次中断里的dur值通过串口打印出来看是否落在判定区间的边界上。很多情况下是重复码阈值和引导码阈值靠得太近微调else if的判断顺序可以解决。电机不动时先短接ENA到5V手动给IN1、IN2加高低电平。如果电机能转说明问题在PWM初始化或duty赋值如果还不转检查VM电源、共地和电机本身。最后提醒一个很实际的问题下载程序时如果USB转串口和电机驱动板共用电源瞬间电流可能把驱动模块带崩。我一般下载时断掉电机电源跑起来后再给电机上电。5. 把解码与动作分离状态机、两帧确认和校验码过滤5.1 状态机比直接switch更适合做控制内核入门例程里常见这种写法if (ir_key KEY_FWD) { IN11; IN20; }按键少的时候没问题。一旦把换向、加速、停止做成连续操作问题就暴露了电机的“当前状态”和“目标状态”耦合在一起红外干扰导致误码时电机可能收到一个错误的停止码而停下。更好的做法是定义一个电机状态寄存器把解码结果作为事件交给状态机处理。typedef enum { MOTOR_STOP, MOTOR_FWD, MOTOR_REV } MotorState; MotorState motor_state MOTOR_STOP; void motor_event(unsigned char key) { switch (motor_state) { case MOTOR_STOP: if (key KEY_FWD) motor_state MOTOR_FWD; else if (key KEY_REV) motor_state MOTOR_REV; break; case MOTOR_FWD: if (key KEY_STOP) motor_state MOTOR_STOP; else if (key KEY_REV) motor_state MOTOR_REV; break; case MOTOR_REV: if (key KEY_STOP) motor_state MOTOR_STOP; else if (key KEY_FWD) motor_state MOTOR_FWD; break; } apply_state(motor_state); }这样即使遥控重复码连发状态机也只需要重新应用当前状态不会出现“正转时收到加速键却把方向改掉”这类问题。5.2 两帧确认防止环境光误触发红外接收头在太阳光或节能灯下偶尔会输出随机脉冲这些脉冲可能被误判成数据位。防误触发的可靠办法是在执行动作前要求连续收到两帧完全相同的键码。NEC一帧约几十毫秒两帧延迟察觉不到。static unsigned char last_key 0xFF; static unsigned char same_counter 0; if (frame_ok) { if (ir_key last_key) { if (same_counter 2) { same_counter 0; motor_event(ir_key); } } else { last_key ir_key; same_counter 0; } frame_ok 0; }注意重复码不能被当作“下一帧”因为它是长按的维持标志不是新按键。正确做法是在解码到repeat1时执行“长按保持”逻辑比如连续加速但不要把它送进motor_event。5.3 加地址码过滤一套代码控制多台电机NEC帧里有地址码和地址反码。实验包里如果只解命令码等于丢了一半协议。把ir_bits的高16位取出与预设地址比较可以区分不同遥控器或同一遥控器的不同地址模式。这个技巧在窗帘电机、升降桌这类多电机场景很实用。验证时不要只看电机转不转我通常检查三点第一按正转后松手电机应保持正转而不是停止第二在强光下连续按停止键10次不能出现一次误动作第三遥控器指向接收头视场范围外时电机保持原状态。这三点都通过说明时序阈值、状态机和地址过滤三个环节都已经到位。本文还有配套的精品资源点击获取
返回列表