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

红外遥控是利用红外光进行通信的设备,由红外LED将调制后的信号发出,由专用的红外接收头进行解调输出
- 通信方式:单工,异步
- 红外LED波长:940nm
- 通信协议标准:NEC标准
硬件电路


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

NEC通信协议

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

最后一个是停止位,停止位起到隔离作用,一般不用判断
引导码是要发9ms的38k载波信号和4.5ms的空闲才称为是引导码

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

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

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

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

Repeat信号是为了按下连续的按键准备的

9ms低电平和2.25ms高点哦组成
51单片机外部中断

外部中断是INT0 和 INT1引脚
外部中断寄存器

红外遥控器键码

代码实现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 0x4A
extern 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;
}
//否则返回0
return 0;
}
//获取是否重发标志
unsigned char IR_GetRepeatFlag(void)
{
//传输结束,清零,返回1,代表收到
if(IR_RepeatFlag)
{
IR_RepeatFlag = 0;
return 1;
}
//否则返回0
return 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 = 922
if(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; //置重发信号为1
Timer0_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,不重叠
//收到逻辑0
if(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++;
}
//收到逻辑1
else 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
浙公网安备 33010602011771号