当前位置: 首页 > news >正文

【自学嵌入式:51单片机】红外遥控控制电机调速

注:LCD1602用的是我这篇博客的版本:https://www.cnblogs.com/qinruiqian/p/19020925
江科大这版51最后一节课用红外遥控控制电机调速,因为普中的开发板没做电机供电和USB接口的隔离,如果连着电脑去用红外线遥控,非常不灵敏,如果接5V1A手机充电器,马上正常工作!

目录
  • 红外遥控
    • 硬件电路
    • 基本发送与接收
    • NEC通信协议
    • 51单片机外部中断
      • 外部中断寄存器
    • 红外遥控器键码
    • 代码实现LCD1602显示红外遥控器的键码值并且音量加减控制NUM+-
      • Timer0.h
      • Timer0.c
      • INT0.h
      • INT0.c
      • IR.h
      • IR.c
      • main.c
    • 实现红外遥控电机调速(红外不灵敏的可以看过来)

红外遥控

image

红外遥控是利用红外光进行通信的设备,由红外LED将调制后的信号发出,由专用的红外接收头进行解调输出

  • 通信方式:单工,异步
  • 红外LED波长:940nm
  • 通信协议标准:NEC标准

硬件电路

image
image

调制过程:低电平导通三极管两个,用38kHz闪烁,滤掉自然界自然光中的红外光,它传递信息很快,没法用if else判断,将out引脚接在外部中断引脚上,通过外部中断进行

基本发送与接收

image

NEC通信协议

image

红外遥控器,在传送数据时,传送哪些数,这些数哪个先发,哪个后发灯,这些规则统称为IR协议,IR协议最常用的就是NEC协议
NEC通信协议包括引导码,用户码,用户反码,键码和键码反码,取反码是为了验证传输的对不对
image
最后一个是停止位,停止位起到隔离作用,一般不用判断

引导码是要发9ms的38k载波信号和4.5ms的空闲才称为是引导码
image

红外接收器接收规则:有载波输出低电平0,无载波即空闲时输出高电平1,本文用到的红外接收器是HS0-38
image

用户码、用户反码、键码和键码反码每个部分一个字节
用户码代表这个遥控器的大类是什么,用户反码就是用户码取反,键码是遥控器上每个键都有一个唯一值用于区分,键码反码就是键码取反

红外遥控器的逻辑0和1的定义:
image

对应的红外接收端HS0038转换后的信号:
image

假设传输0000 0000,过程如下:
image

Repeat信号是为了按下连续的按键准备的
image
9ms低电平和2.25ms高点哦组成

51单片机外部中断

image
外部中断是INT0 和 INT1引脚

外部中断寄存器

image

红外遥控器键码

image

代码实现LCD1602显示红外遥控器的键码值并且音量加减控制NUM+-

Timer0.h

#ifndef __TIMER0_H__
#define __TIMER0_H__void Timer0_Init(void); //初始化定时器0
void Timer0_SetCounter(unsigned int Value); //定时器0设置计数器
unsigned int Timer0_GetCounter(void); //获取定时器0的计数器
void Timer0_Run(unsigned char Flag); //启动定时器0,Flag表示是否启动#endif

Timer0.c

#include <REGX52.H>//定时器0中断初始化
void Timer0_Init(void)
{TMOD &= 0xF0;		//设置定时器模式TMOD |= 0x01;		//设置定时器模式TL0 = 0;		//设置定时初值TH0 = 0;		//设置定时初值TF0 = 0;		//清除TF0标志TR0 = 0;		//定时器0暂时不计时
}//定时器0设置计数器
void Timer0_SetCounter(unsigned int Value)
{TH0 = Value/256;TL0 = Value%256;
}//获取定时器0的计数器
unsigned int Timer0_GetCounter(void)
{return (TH0<<8) | TL0; //把TH0和TL0拼成一个16位无符号整数
}//启动定时器0,Flag表示是否启动
void Timer0_Run(unsigned char Flag)
{TR0 = Flag;
}

INT0.h

#ifndef __INT0_H__
#define __INT0_H__void initINT0(); //初始化外部中断#endif

INT0.c

#include <REGX52.H>//初始化外部中断
void initINT0()
{IT0 = 1; //1-下降沿触发;0-低电平触发IE0 = 0; //中断标志位清零EX0 = 1; //中断打开EA = 1; //允许中断PX0 = 1; //给外部中断高优先级
}

IR.h

#ifndef __IR_H__
#define __IR_H__//宏定义每个键码
#define IR_POWER 0x45
#define IR_MODE 0X46
#define IR_MUTE 0x47
#define IR_START_STOP 0x44
#define IR_PREVIOUS 0x40
#define IR_NEXT 0x43
#define IR_EQ 0x07
#define IR_VOL_MINUS 0x15
#define IR_VOL_ADD 0x09
#define IR_0 0x16
#define IR_PRT 0x19
#define IR_USD 0x0D
#define IR_1 0x0C
#define IR_2 0x18
#define IR_3 0x5E
#define IR_4 0x08
#define IR_5 0x1C
#define IR_6 0x5A
#define IR_7 0x42
#define IR_8 0x52
#define IR_9 0x4Aextern unsigned int IR_Time; //计时变量
void IR_Init(void); //红外传输初始化
unsigned char IR_GetDataFlag(void); //获取数据传输标志位
unsigned char IR_GetRepeatFlag(void); //获取是否重发标志
unsigned char IR_GetAddress(void); //获取地址码
unsigned char IR_GetCommand(void); //获取指令#endif

IR.c

#include <REGX52.H>
#include "INT0.h"
#include "Timer0.h"unsigned int IR_Time; //计时变量
unsigned char IR_State; //状态0-默认,1-启动信号,2-解码数据
unsigned char IR_Data[4]; //用长整型移位超过16会出问题,所以用4个8位二进制的数组
unsigned char IR_pData; //当前收到了第几位的指针,相当于栈顶指针
unsigned char IR_DataFlag; //数据传输结束了为1,否则为0
unsigned char IR_RepeatFlag; //是否重发标志
unsigned char IR_Address; //地址码
unsigned char IR_Command; //IR的命令//红外传输初始化
void IR_Init(void)
{Timer0_Init();initINT0(); //下降沿触发,检测引导信号
}//获取数据传输标志位
unsigned char IR_GetDataFlag(void)
{//传输结束,清零,返回1,代表收到if(IR_DataFlag){IR_DataFlag = 0;return 1;}//否则返回0return 0;
}//获取是否重发标志
unsigned char IR_GetRepeatFlag(void)
{//传输结束,清零,返回1,代表收到if(IR_RepeatFlag){IR_RepeatFlag = 0;return 1;}//否则返回0return 0;
}//获取地址码
unsigned char IR_GetAddress(void)
{return IR_Address;
}//获取指令
unsigned char IR_GetCommand(void)
{return IR_Command;
}//外部中断函数,连着红外模块,如果信号过来自动中断
void Int0_Rountine(void) interrupt 0
{switch(IR_State){case 0: //默认起始状态//P2=0;//用LED灯全亮测试是否有信号过来Timer0_SetCounter(0); //把计数器清零Timer0_Run(1); //开始启动,需要解码IR_State = 1;break;case 1: //状态为1,识别起始信号IR_Time = Timer0_GetCounter(); //获取中断时间Timer0_SetCounter(0); //计时器清零//比较时间,看是启动信号还是什么//允许误差500微秒//启动信号//引导码9ms低电平+4.5ms空闲//我的开发板是11.0592晶振,一个机器周期是1.085微秒//为了精确计算,引导码是13500微秒//要除1.082得到一个更精确的值//13500 / 1.085 = 12442//然后给500微秒的范围进行判断//500 / 1.085 = 461//但是还是接不到信号,于是我试了试1000微秒,也就是1ms//1000 / 1.085 = 922if(IR_Time > 12442 - 922 && IR_Time < 12442 + 922){//P2_1=0;//用LED灯测试是否是起始信号IR_State = 2; //状态2解码数据}//重发Repeat信号,9ms+2.25ms//还是11250 / 1.085 = 10369//范围没重复,这个最大值11291,那个最大值11500多,不重复else if(IR_Time >10369 - 922 && IR_Time < 10369 + 922){IR_RepeatFlag = 1; //置重发信号为1Timer0_Run(0); //计数器停止IR_State = 0; //回到初始状态}//上面都不是,可能是解码错误else{IR_State = 1; //继续搜寻起始信号}break;case 2: //状态2,解码数据//继续读取计数器的数,然后清零计数器IR_Time = Timer0_GetCounter();Timer0_SetCounter(0);//逻辑0,560us低电平。560us高电平//1120us / 1.085 = 1032us//逻辑1,560us低电平,1690us高电平//2250us / 1.085 = 2074//1032 + 922 = 1954 < 2074 - 922 = 1152,不重叠//收到逻辑0if(IR_Time > 1032 - 922 && IR_Time < 1032 + 922){//8个二进制位是一个字节,IR_pData / 8取的是当前是第几个字节//IR_pData % 8是指左移多少位//0x01确定0的位置//按位取反再做与运算//因为传的是逻辑0,要把0准确写进去,所以要取反然后取与IR_Data[IR_pData / 8] &= ~(0x01 << (IR_pData % 8));IR_pData++;}//收到逻辑1else if(IR_Time > 2074 - 922 && IR_Time < 2074 + 922){//传逻辑1,不用取反,直接或就行IR_Data[IR_pData / 8] |= (0x01 << (IR_pData % 8));IR_pData++;}//收到错误else{//重新搜寻起始信号,栈顶指针清零IR_pData = 0;IR_State = 1;}//如果接收4个字节完成if(IR_pData >= 32){IR_pData = 0;//验证取反是否正确if((IR_Data[0] == ~IR_Data[1]) && (IR_Data[2] == ~ IR_Data[3])){IR_Address = IR_Data[0]; //地址码转存过来IR_Command = IR_Data[2]; //命令码IR_DataFlag = 1; //传输结束}//然后切换回搜寻起始信号的空闲状态并停止计时器Timer0_Run(0);IR_State = 0; //数据发送完成/*当IR_State=1(等待读取状态)时,外部中断触发后会直接进入 “判断引导码 / 重复码” 的逻辑,但此时定时器已停止(Timer0_Run(0)),且状态机未回到初始状态,无法重新启动定时器计时。后续按键时,红外模块发送的引导码下降沿无法正确触发 “空闲状态(0)” 的初始化操作(定时器清零 + 启动),导致无法识别新的键码。*/}break;}
}

main.c

#include <REGX52.H>
#include "LCD1602.h"
#include "IR.h"unsigned char Num; //记录音量键加减的数值
unsigned char Address; //地址码
unsigned char Command; //命令void main()
{//initINT0();LCD1602_Init();LCD1602_ShowString(0,0,"ADDR CMD NUM");LCD1602_ShowString(1,0,"00   00  000");IR_Init();while(1){//如果收到32位数据//按住不放发送重发标志,所以要检测一下重发if(IR_GetDataFlag() || IR_GetRepeatFlag()){Address = IR_GetAddress();Command = IR_GetCommand();LCD1602_ShowHexNum(1,0,Address,2);LCD1602_ShowHexNum(1,5,Command,2);if(Command == IR_VOL_MINUS) //红外遥控器的音量--模块{Num--;}if(Command == IR_VOL_ADD) //红外遥控器的音量++模块{Num++;}LCD1602_ShowNum(1, 9, Num, 3);}}
}

实现红外遥控电机调速(红外不灵敏的可以看过来)

我一开始以为江科大的代码,用红外遥控不好使,结果是连着电脑端做实验,电脑端USB口有信号干扰,我用一个5V1A的手机充电器连接开发板,马上这个红外调速就变得非常灵敏,一般来说,那个无线四项步进电机是三极管驱动的,那个VCC应该是单独引出一个电源,这样能保护单片机,但是普中的板子并没有,而且电机转的时候,自身也有信号干扰,总而言之,接个手机充电器就好用了,代码太多了,直接开源了:https://gitee.com/qin-ruiqian/51-infrared-ray-dcm

http://www.sczhlp.com/news/6880/

相关文章:

  • Android Camera性能分析 – 通过Perfetto的PivotTable查看调用栈
  • 河南萌新联赛2025第(四)A
  • 2025 暑假集训 Day3
  • Linux 下查看超大文件(比如大日志) - Higurashi
  • Day36
  • Windows OpenGL 学习一(OpenGL 环境搭建)
  • 完整教程:2025年信创政策解读:如何应对国产化替代挑战?(附禅道/飞书多维表格/华为云DevCloud实战指南)
  • 18
  • 2.变量于应用
  • OI集训 Day21
  • STL的五大组件
  • 完整教程:吴恩达【prompt提示词工程】学习笔记
  • ArKTS: McPieChart
  • 2025.8.6总结 - A
  • 【fuse】struct fuse_lowlevel_ops解析-①
  • Policy Gradient原理和Python实现
  • 记一些oi啸寄巧
  • 25.8.6模拟赛
  • 考前建议
  • RS232与RS485通信协议深度对比
  • Linux系统入门第四章 --磁盘管理和LVM
  • 部落冲突coc到5000杯后如何快速掉杯
  • [河南萌新联赛2025第(四)场]H (DP 图论)
  • WinForm 实现火绒杀毒界面
  • 【通信模型】Actors with Tokio
  • 开此侧门(25夏收集)
  • 《硅谷甄选》项目笔记
  • 腾讯游戏安全2023安卓初赛题解
  • Linux系统入门指南第二章 -- 安装及管理程序
  • 8.6总结