写在前言
此为博主自学江科大51单片机(B站)的笔记,方便后续重温知识
在后面的章节中,为了防止篇幅过长和易于查找,我把一个小节分成两部分来发,上章节主要是关于本节课的硬件介绍、电路图、原理图等理论知识,主要是为下章节的代码部分打基础。
我的单片机是24年12月在tb普中买的,型号是STC89C52,在原视频中引脚或接口不对应的我都会改正,保证在我的机子上能运行才发上来的,还有一些文字部分是我的理解,并非照搬,所以可能有理解不到位的现象。
如有误或交流,敬请指点提问
红外遥控有一个小拨片把电池隔开,用的时候要拿出来
课程目标:
1.LCD显示遥控的地址码,遥控码和+-控制自定义数字
2.红外遥控电机调速
一、红外遥控
新建工程,导入Dealy和LCD模块
1.外部中断
测试一下Web外部中断和下降沿能不能触发

下降沿触发IT0给1,IEO不用管给0,EX0使能中断和EA总中断给1,高优先级精度高防止打断PX0给1
再给一个中断函数,接着在这个函数里对Num++,然后把这个Num显示出来

#include <REGX52.H>
#include " Delay.h"
#include " LCD1602.h"
unsigned char Num;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"A");
IT0=1;
IE0=0;
EX0=1;
EA=1;
PX0=1;
while(1)
{
LCD_ShowNum(2,1,Num,3);
}
}
void Int0_Routine(void) interrupt 0
{
Num++;
}
烧录后我们会在LCD上看到000,然后我们怎么触发外部中断呢,要给一个下降沿,怎么给呢,可以看到P32这个引脚接到K3独立按键上,所以按下K3就可以触发外部中断,而且下降沿只触发一次(在这里因为没交消抖,如果按住晃两下还会+多次1)

接下来我们试一下低电平触发,把IT0置0,再运行后发现只要一直按着K3,数字就会一直跳动,直到松手,这是因为低电平触发在低电平时间会一直触发,这就是两种触发方式的区别
在这里我们要用到下降沿触发,接下来把这个中断函数封装一下,因为中断函数一般是放在要用的地方,所以在这里作为一个模版放在这里注释掉
//Int0.c
#include <REGX52.H>
void Int0_Init(void)
{
IT0=1;
IE0=0;
EX0=1;
EA=1;
PX0=1;
}
/*
void Int0_Routine(void) interrupt 0
{
Num++;
}
*/
//Int0.h
#ifndef __INT0_H__
#define __INT0_H__
void Int0_Init(void);
#endif
这样就把外部中断函数封装起来了,要用直接调用就可以了
2.红外解码
思路:IR.c继承Int0和定时器T0(只用来计时),然后main里调用IR.c
定义一个变量表示当前状态,0代表空闲,1代表寻找开始或重复的头部信号,2代表开始解码数据;
当0收到下降沿,把0置1,打开计数器,读取定时器的值,判断中间的时间,
如果是重发信号,就给一个重发标志位置1重发回去;如果是起始信号,就把1置2
然后2状态会连续进行32次,每进行一次2状态,就计算0和1的时间,然后存到变量缓存区,在里面再定义一个变量记录进行了多少次2状态,如果存完32次就认为数据已经收到
然后对数据进行论证,然后再置一个收到数据的标志位,然后把状态切回0
首先来改造定时器0,让他可以计时
第一个函数是初始化,第二个是设置计时值,第三个是得到计时值
第四个是控制启动和停止
//Timer0.c
#include <REGX52.H>
void Timer0_Init(void) //1毫秒@12.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0; //设置定时初值
TH0 = 0; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 0; //定时器0不计时
}
void Timer0_SetCounter(unsigned int Value)
{
TH0=Value/256;
TL0=Value%256;
}
unsigned int Timer0_GetCounter(void)
{
return (TH0<<8)|TL0;
}
void Timer0_Run(unsigned char Flag)
{
TR0=Flag;
}
然后我们来验证一下
//main
#include <REGX52.H>
#include " Delay.h"
#include " LCD1602.h"
#include " Int0.h"
#include " Timer0.h"
unsigned char Num;
unsigned int Time;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"A");
Timer0_Init();
Timer0_SetCounter(0);
Timer0_Run(1);
Delay(10);
Time=Timer0_GetCounter();
LCD_ShowNum(2,1,Time,5);
while(1)
{
}
}

可以看到成功计算了,这里显示的是10000us,也就是10ms,只有一点误差很正常
接下来正式开始写红外编码的函数
前面说过了这个函数继承Timer0和Int0,所以导入进来
先把底层给初始化,就是把Timer0和Int0给初始化
void IR_Init(void)
{
Timer0_Init();
Int0_Init();
}
再定义一些变量,计时,状态,数据区,指向当前存到第几位,数据收到位,重发标志位,IR的地址用于存储数据,一个IR的命令码
左右移有距离限制,最高只有16位有效,所以数据区(32位)不能用long型,只能定义char型,分成4个字节
unsigned int IR_Time;
unsigned char IR_State;
unsigned char IR_Data[4];
unsigned char IR_pData;
unsigne char IR_DataFlag;
unsigne char IR_RepeatFlag;
unsigne char IR_Address;
unsigne char IR_Command;
然后把Int0的中断函数复制过来,在里面进行编码
用if判断,if IR状态默认是0,当信号来下降沿后,把计时值清零,计时器开始,状态置为1;else if 状态是1,读取计时值,然后再把计时值清零,
然后对计时值进行判断,嵌套if,if 这个时间在13500正负500范围内,就是start信号(下红图),此时把状态置2;else if 这个时间在11250正负500范围,就是重发信号(下绿图),此时把重复标识位置1,然后把计时器停止,把状态置0;再次else,如果出现两个范围都不是,即解码错误等情况,状态继续置1
以上就是状态1的功能
上面的数值是12mhz的,但我们的开发板是11.0592mhz所以开始值是12442,重复值是10368
到这里可以去验证一下,记得在.h文件声明初始化函数,然后在main文件导入,在主函数里写初始化
然后在代码中有三个地方有P2=0;即让LED全亮,逐次测试每个地方,如果按下遥控器任一个键,LED就会全亮,这样就是成功了
#include <REGX52.H>
#include " Int0.h"
#include " Timer0.h"
unsigned int IR_Time;
unsigned char IR_State;
unsigned char IR_Data[4];
unsigned char IR_pData;
unsigned char IR_DataFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Address;
unsigned char IR_Command;
void IR_Init(void)
{
Timer0_Init();
Int0_Init();
}
void Int0_Routine(void) interrupt 0
{
if(IR_State==0)
{
P2=0;
Timer0_SetCounter(0);
Timer0_Run(1);
IR_State=1;
}
else if(IR_State==1)
{
P2=0;
IR_Time=Timer0_GetCounter();
Timer0_SetCounter(0);
if(IR_Time>12442-500 && IR_Time<12442+500)
{
P2=0;
IR_State=2;
}
else if(IR_Time>10368-500 && IR_Time<10368+500)
{
IR_RepeatFlag=1;
Timer0_Run(0);
IR_State=0;
}
else
{
IR_State=1;
}
}
}
接下来写关于状态2的功能
先把时间拿出来,然后清零,
紧接着判断时间是否在0的范围里,即if 收到0,就把data的对应位清零(利用"&=~"),把0x01左移pdata位,但是pdata范围是0-31,所以要拆分成4个部分
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8))
这里巧妙的只用一句代码就把pdata拆分成4个部分,还把每一组的每一位都对应上,思路非常牛
假设pData是10,对第十位取零,也就是第[1]个数组的第2位取零,对应10/8=1和10%8=2

然后把IR_pData++,当下一位来存到下一位
接下来判断时间是否在1的范围里,即else if 收到1,就把data的对应位置1(|=),照样要IR_pData++;
接着要else 特殊情况,把R_pData清零,这一次的数据就作废了,把状态置1
接下来再对IR_pData进行判断,如果大于等于32就是收数据收完了,就清零,然后对数据进行验证,如果第一位等于第二位,第二位也等于第三位,就代表验证通过,成功收到一个数据,然后把DataFlag置1,把数据转移到地址码和命令码上
收到数据后把定时器停止,再把状态置0
void Int0_Routine(void) interrupt 0
{
if(IR_State==0)
{
Timer0_SetCounter(0);
Timer0_Run(1);
IR_State=1;
}
else if(IR_State==1)
{
IR_Time=Timer0_GetCounter();
Timer0_SetCounter(0);
if(IR_Time>12442-500 && IR_Time<12442+500)
{
IR_State=2;
}
else if(IR_Time>10368-500 && IR_Time<10368+500)
{
IR_RepeatFlag=1;
Timer0_Run(0);
IR_State=0;
}
else
{
IR_State=1;
}
}
else if(IR_State==2)
{
IR_Time=Timer0_GetCounter();
Timer0_SetCounter(0);
if(IR_Time>1120-500 && IR_Time<1120+500)
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8))
IR_pData++;
}
else if(IR_Time>2250-500 && IR_Time<2250+500)
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8))
IR_pData++;
}
else
{
IR_pData=0;
IR_State=1;
}
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;
}
}
}
接下来再写一个获取数据标志位的函数
if DataFlag为1,就清零然后返回代表收到,否则就直接返回0
还有获取重复标志位的函数,思路一样的
if RepeatFlag为1,就清零然后返回代表收到,否则就直接返回0
再写两个获取地址和命令的函数
直接返回地址,命令;相当于封装起来
接着把这些函数都放.h文件里声明
unsigned char IR_GetDataFlag(void)
{
if(IR_DataFlag)
{
IR_DataFlag=0;
return 1;
}
return 0;
}
unsigned char IR_GetRepeatFlag(void)
{
if(IR_RepeatFlag)
{
IR_RepeatFlag=0;
return 1;
}
return 0;
}
unsigned char IR_GetAddress(void)
{
return IR_Address;
}
unsigned char IR_GetCommand(void)
{
return IR_Command;
}
然后去主函数里验证一下
定义两个变量地址和命令
接下来的代码放到while里,如果收到数据标识位,把地址和命令拿出来,用LCD显示,因为地址和命令都是十六进制,所以要显示HEX
#include " Delay.h"
#include " LCD1602.h"
#include " Int0.h"
#include " Timer0.h"
#include " IR.h"
unsigned char Num;
unsigned char Address;
unsigned char Command;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"A");
IR_Init();
while(1)
{
if(IR_GetDataFlag())
{
Address=IR_GetAddress();
Command=IR_GetCommand();
LCD_ShowHexNum(2,1,Address,2);
LCD_ShowHexNum(2,5,Command,2);
}
}
}
此时就可以在LCD上显示遥控按下的键码值
接下来再写连发功能
一旦收到重发标志位,重发之前肯定收到了一个数据,这个数据已经放在地址里,此时这个标志位已经置1
if(IR_GetDataFlag() || IR_GetRepeatFlag())
这样就可以实现按下遥控上的VOL-和VOL+就可以在LCD上显示加减并且可以连发
#include <REGX52.H>
#include " Delay.h"
#include " LCD1602.h"
#include " Int0.h"
#include " Timer0.h"
#include " IR.h"
unsigned char Num;
unsigned char Address;
unsigned char Command;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"A");
IR_Init();
while(1)
{
if(IR_GetDataFlag() || IR_GetRepeatFlag())
{
Address=IR_GetAddress();
Command=IR_GetCommand();
LCD_ShowHexNum(2,1,Address,2);
LCD_ShowHexNum(2,5,Command,2);
if(Command==0x15)
{
Num--;
}
if(Command==0x09)
{
Num++;
}
LCD_ShowNum(2,10,Num,3);
}
}
}
接下来我们把引脚宏定义,方便调用,因为要外部调用,所以写在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_RPT 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

下面再完善一下整个代码
显示更完善的信息,并且让他在不按下时会显示000,再微调一下坐标值
#include <REGX52.H>
#include " Delay.h"
#include " LCD1602.h"
#include " Int0.h"
#include " Timer0.h"
#include " IR.h"
unsigned char Num;
unsigned char Address;
unsigned char Command;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"ADDR CMD NUm");
LCD_ShowString(2,1,"00 00 000");
IR_Init();
while(1)
{
if(IR_GetDataFlag() || IR_GetRepeatFlag())
{
Address=IR_GetAddress();
Command=IR_GetCommand();
LCD_ShowHexNum(2,1,Address,2);
LCD_ShowHexNum(2,7,Command,2);
if(Command==IR_VOL_MINUS)
{
Num--;
}
if(Command==IR_VOL_ADD)
{
Num++;
}
LCD_ShowNum(2,12,Num,3);
}
}
}
二、红外遥控电机调速
下面的代码是在直流电机调速的基础上进行的
下面把PWM模块化,把定时0改成定时1,因为遥控用了定时1
因为高四位是定时1,低四位是定时0,所以只需换一下
然后把其他关于定时0的部分都换成定时1
然后把电机模块化
首先是初始化,然后是设置速度,再把定时1拿过来
#include <REGX52.H>
#include "Timer1.h"
//引脚定义
sbit Motor=P1^0;
unsigned char Counter,Compare;
/**
* @brief 电机初始化
* @param 无
* @retval 无
*/
void Motor_Init(void)
{
Timer1_Init();
}
/**
* @brief 电机设置速度
* @param Speed 要设置的速度,范围0~100
* @retval 无
*/
void Motor_SetSpeed(unsigned char Speed)
{
Compare=Speed;
}
//定时器1中断函数
void Timer1_Routine() interrupt 3
{
TL1 = 0x9C; //设置定时初值
TH1 = 0xFF; //设置定时初值
Counter++;
Counter%=100; //计数值变化范围限制在0~99
if(Counter<Compare) //计数值小于比较值
{
Motor=1; //输出1
}
else //计数值大于比较值
{
Motor=0; //输出0
}
}
#ifndef __MOTOR_H__
#define __MOTOR_H__
void Motor_Init(void);
void Motor_SetSpeed(unsigned char Speed);
#endif
#include <REGX52.H>
#include " Delay.h"
#include " Key.h"
#include " Nixie.h"
#include " Motor.h"
unsigned char KeyNum,Speed;
void main()
{
Motor_Init();
while(1)
{
KeyNum=Key();
if(KeyNum==1)
{
Speed++;
Speed%=4;
if(Speed==0){Motor_SetSpeed(0);}
if(Speed==1){Motor_SetSpeed(50);}
if(Speed==2){Motor_SetSpeed(75);}
if(Speed==3){Motor_SetSpeed(100);}
}
Nixie(1,Speed);
}
}
这样就实现了封装,并且功能跟之前一样,更加简洁
最后再改改主函数
导入IR头文件,然后把IR初始化
把按键部分删掉,我们这里也不需要连发功能
如果收到数据,就把命令取出来,嵌套if,如果命令等于IR0,速度=0,然后依次延伸,后面的速度也不变
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "Motor.h"
#include "IR.h"
unsigned char Command,Speed;
void main()
{
Motor_Init();
IR_Init();
while(1)
{
if(IR_GetDataFlag()) //如果收到数据帧
{
Command=IR_GetCommand(); //获取遥控器命令码
if(Command==IR_0){Speed=0;} //根据遥控器命令码设置速度
if(Command==IR_1){Speed=1;}
if(Command==IR_2){Speed=2;}
if(Command==IR_3){Speed=3;}
if(Speed==0){Motor_SetSpeed(0);} //速度输出
if(Speed==1){Motor_SetSpeed(50);}
if(Speed==2){Motor_SetSpeed(75);}
if(Speed==3){Motor_SetSpeed(100);}
}
Nixie(1,Speed); //数码管显示速度
}
}
到这里也就实现了用遥控器的0和123控制直流电机的暂停和123档位