目录
低电平触发中断和下降沿触发中断的区别
红外遥控
Int0.c
Int.h
Timer0.c
Timer0.h
IR.c
IR.h
main.c
红外遥控电机调速
Timer1.c
Timer.h
Motor.c
Motor.h
main.c
上一节讲了红外发送和接收的工作原理,这一节开始代码演示!
提前说明,本节代码演示中会涉及定时器和中断系统!
如果不懂定时器和中断系统的话,建议去看看我之前已经详细写过的中断系统和定时器的博客,也写的比较全面的了,看过后肯定能让你明白中断系统和定时器的工作原理!
单片机学习笔记---中断系统(含外部中断)-CSDN博客
单片机学习笔记---定时器/计数器(简述版!)_定时计数器ea-CSDN博客
单片机学习笔记---定时器和中断系统如何连起来工作-CSDN博客
低电平触发中断和下降沿触发中断的区别
在正式演示红外遥控的代码之前我们先来看看配置外部中断时,选择低电平触发和下降沿触发有什么区别?
我们用外部中断INT0来举个例子:
我们单片机的原理图上,INT0接的是单片机的P32口,
而独立按键K3正好也是P32口。
那么我们按下K3的时候就相当于给单片机的P32口一个下降沿
那么我们现在来写一个程序验证一下选择低电平触发和下降沿触发有什么区别:
先创建一个工程文件,把我之前的博客中讲过的LCD1602的程序文件添加进来
然后在主程序main.c中调用这个函数来显示中断触发的现象
首先是选择下降沿触发:
#include <REGX52.H>
#include "LCD1602.h"
unsigned char Num;
void main()
{
LCD_Init();//初始化液晶屏
//配置中断系统
IT0=1;//选择下降沿触发方式
IE0=0;//中断标志位清零
EX0=1;//打开中断
EA=1;//打开总中断
PX0=1;//一般都选择高优先级中断
//跳转到中断函数
//中断函数执行完进入while循环
while(1)
{
LCD_ShowNum(1,1,Num,3);
}
}
//中断函数
void Init0_Routine(void) interrupt 0
{
Num++;
}
下降沿触发的话,你按下的时候加一次,松开再按下的时候才再加一次。“松开状态再按下”这一个过程就是给一个下降沿的过程。
效果请看视频:
选择下降沿触发中断的结果现象
再来看看选择低电平触发:
#include <REGX52.H>
#include "LCD1602.h"
unsigned char Num;
void main()
{
LCD_Init();//初始化液晶屏
//配置中断系统
IT0=0;//选择低电平触发方式
IE0=0;//中断标志位清零
EX0=1;//打开中断
EA=1;//打开总中断
PX0=1;//一般都选择高优先级中断
//跳转到中断函数
//中断函数执行完进入while循环
while(1)
{
LCD_ShowNum(1,1,Num,3);
}
}
//中断函数
void Init0_Routine(void) interrupt 0
{
Num++;
}
而如果是低电平触发的话,只要按键按下这个中断会一直处于触发状态,当中断函数结束之后它会再次进入,直到变成高电平为止。那它的现象就是按下不松的时候,这个数值是一直加的,直到松手,数值才停止加。
效果请看视频:
选择低电平触发中断的结果现象
以上就是低电平触发中断和下降沿触发中断的区别
而本节红外遥控的示例代码要用的是下降沿触发中断的这种方式!
红外遥控
现在开始正式演示代码:
新创建一个工程文件:红外遥控
将我之前的博客讲过的程序模块直接添加进来
然后先创建Int0.c,Int.h,IR.c,IR.h和main.c文件
开始代码讲解:
首先写一个初始化中断系统的函数
Int0.c
#include <REGX52.H>
void Int0_Init(void)
{
IT0=1;//选择下降沿触发中断
IE0=0;//中断标志位清零
EX0=1;//把中断打开
EA=1;//打开总中断
PX0=1;//选择高优先级中断
}
声明一下这个函数
Int.h
#ifndef __INT0_H__
#define __INT0_H__
void Int0_Init(void);
#endif
Timer0.c
接下来我们把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设置初值的函数
//定时器0设置计数器值
void Timer0_SetCounter(unsigned int Value)
{
TH0=Value/256;//取出高八位赋值给TH0
TL0=Value%256;//取出低八位赋值给TH0
}
//Value,要设置的计数器值,范围:0~65535
定时器0设置好初值后,再写一个获取定时器0当时的计数器值的函数
//定时器0获取计数器值,范围:0~65535
unsigned int Timer0_GetCounter(void)
{
return (TH0<<8)|TL0;
//将TH0的8位数据左移8位,然后和TL0的8位数据组合成16位数据作为返回值
}
然后再写一个函数用来控制定时器的开始和结束
//定时器0启动停止控制
//Flag 启动停止标志,
//在TMOD寄存器中的GATE位等于0的情况下,TR0是定时器0在方式1的工作模式下的启停控制位
//1为启动,0为停止
void Timer0_Run(unsigned char Flag)
{
TR0=Flag;
}
将这几个函数声明一下
Timer0.h
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init(void);
void Timer0_SetCounter(unsigned int Value);
unsigned int Timer0_GetCounter(void);
void Timer0_Run(unsigned char Flag);
#endif
接下来写一下红外解码的程序函数
IR.c
先定义好一些变量,后面会用到
#include <REGX52.H>
#include "Timer0.h"
#include "Int0.h"
unsigned int IR_Time;//上一次中断到此次中断的时间
unsigned char IR_State;//接收的状态
unsigned char IR_Data[4];//数据,4个字节,32位
unsigned char IR_pData;//告知当前是在第几位,每次进来pData++,告诉告知现在收到第几位了,收满32位之后就结束了
unsigned char IR_DataFlag;//收到连发帧标志位
unsigned char IR_RepeatFlag;//重发标志
unsigned char IR_Address;//存的是地址
unsigned char IR_Command;//存的是命令码(就是遥控上的键码)
然后写一个红外遥控初始化函数
void IR_Init(void)
{
Timer0_Init();//初始化定时器0
Int0_Init();//初始化外部中断0
}
红外遥控获取收到数据帧标志位
返回值:是否收到数据帧,1为收到,0为未收到
unsigned char IR_GetDataFlag(void)
{
if(IR_DataFlag)
{
IR_DataFlag=0;//将收到连发帧标志位置0,方便下一次进行
return 1;//代表已经收到了
}
return 0;//如果IR_DataFlag=0,直接return 0
}
红外遥控获取收到连发帧标志位
返回值:是否收到连发帧,1为收到,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;
}
外部中断0中断函数,下降沿触发执行
这部分主要是根据这张图定义的函数
void Int0_Routine(void) interrupt 0
{
//第一次进入中断函数时
if(IR_State==0) //状态0,空闲状态
{
Timer0_SetCounter(0); //定时计数器清0
Timer0_Run(1); //定时器启动
IR_State=1; //置状态为1
}
//下一次进入中断函数时
else if(IR_State==1) //状态1,等待Start信号或Repeat信号
{
IR_Time=Timer0_GetCounter(); //获取上一次中断到此次中断的时间
Timer0_SetCounter(0); //定时计数器清0,方便下次计时
//计数器每加1就是1微秒,1000us=1ms
//如果计时为13.5ms即13500us,则接收到了Start信号(判定值在12MHz晶振下为13500,在11.0592MHz晶振下为12442)
//因为有一些误差,所以有可能不是13500us整,需允许波动的范围
//13500-11250=2250,允许波动的范围不能超过2250,可以给上下500的范围,上下500即1000的波动范围
//所以给个上下500波动的范围即13500-500~13500+500合理
if(IR_Time>13500-500 && IR_Time<13500+500)
{
IR_State=2; //置状态为2,下次再来中断就要开始解码数据
}
//如果计时为11.25ms,则接收到了Repeat信号(判定值在12MHz晶振下为11250,在11.0592MHz晶振下为10368)
else if(IR_Time>11250-500 && IR_Time<11250+500)
{
//如果接收到这个信号就说明一帧已经结束了,后面就不用数据了
IR_RepeatFlag=1; //置收到连发帧标志位为1
Timer0_Run(0); //定时器停止
IR_State=0; //置状态为0
}
else //接收出错
{
IR_State=1; //置状态为1,继续收Start信号或者repeat信号
}
}
//以上情况执行完就接收完了起始信号
//那再次进入中断函数来就要开始解码
else if(IR_State==2) //状态2,接收数据
{
IR_Time=Timer0_GetCounter(); //获取上一次中断到此次中断的时间
Timer0_SetCounter(0); //定时计数器清0,方便下次计时
//如果计时为1120us,则接收到了数据0(判定值在12MHz晶振下为1120,在11.0592MHz晶振下为1032)
if(IR_Time>1120-500 && IR_Time<1120+500)
{
//收到0之后就要把数据写进去
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //数据对应位清0
//假设pData的范围是0~7,即一个字节长度
//那么如果要将第一个字节的8位对应位清零
//可以写成IR_Data[0]&=~(0x01<<IR_pData);
//但是我们是要4个字节,把pData定义为告知32位数据中的哪一位数据,变化范围是0~31
//要将pData的范围0~31拆成4个字节,也就是4个数组元素,一个字节8位
//那么每8位就是一个数组元素,所以左移的范围要限制在一个字节中
//因此左移的范围要限制在0~7,则IR_pData%8
//4个数组元素范围是IR_Data[0]~IR_Data[3],则IR_pData/8,
//这样程序就可以自动跳到下一个元素的第0位
IR_pData++; //数据位置指针自增
}
//如果计时为2250us,则接收到了数据1(判定值在12MHz晶振下为2250,在11.0592MHz晶振下为2074)
else if(IR_Time>2250-500 && IR_Time<2250+500)
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //数据对应位置1
IR_pData++; //数据位置指针自增
}
else //接收出错
{
IR_pData=0; //数据位置指针清0
IR_State=1; //置状态为1
}
//32位数据解码完成后,开始验证
if(IR_pData>=32) //如果接收到了32位数据
{
IR_pData=0; //数据位置指针清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; //置收到连发帧标志位为1
}
Timer0_Run(0); //定时器停止
IR_State=0; //置状态为0
}
}
}
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_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
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
main.c
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.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) //如果遥控器VOL-按键按下
{
Num--; //Num自减
}
if(Command==IR_VOL_ADD) //如果遥控器VOL+按键按下
{
Num++; //Num自增
}
LCD_ShowNum(2,12,Num,3); //显示Num
}
}
}
效果请看视频:
红外遥控显示键码值
注意:如果程序没有出凑,但是没有结果反应的话,请查看一下自己的开发板的晶振到底是12MHz还是11.0592MHz,这两种晶振的机器周期是不一样的,要在IR.c文件中的程序对应修改,要修改的地方在IR.c文件程序中已经给出了注释,请认真查看并修改!
红外遥控电机调速
新创建一个工程:红外遥控电机调速
这个程序是建立在之前写的直流电机调速的那个程序的基础上改造的,之前是用独立按键用控制电机的运转的速度,这次改为用红外遥控来控制电机的运转速度。
复用到的程序:
Delay函数
数码管
中断系统配置,定时器0配置,红外解码
Timer1.c
由于红外解码部分用了定时器0,所以我们要加一个定时器1程序,即将Timer0.c复制一份并且对应的地方改成定时器
#include <REGX52.H>
void Timer1_Init(void)
{
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0x9C; //设置定时初值
TH1 = 0xFF; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1=1;
EA=1;
PT1=0;
}
Timer.h
#ifndef __TIMER1_H__
#define __TIMER1_H__
void Timer1_Init(void);
#endif
Motor.c
写一个驱动电机的模块
#include <REGX52.H>
#include "Timer1.h"
//引脚定义
sbit Motor=P1^0;
unsigned char Counter,Compare;
//电机初始化
void Motor_Init(void)
{
Timer1_Init();
}
//电机设置速度
//Speed 要设置的速度,范围0~100
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
}
}
Motor.h
#ifndef __MOTOR_H__
#define __MOTOR_H__
void Motor_Init(void);
void Motor_SetSpeed(unsigned char Speed);
#endif
main.c
#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();//初始化电机(定时器1初始化)
IR_Init();//红外遥控初始化(定时器0和中断系统初始化)
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;}
//Motor_SetSpeed相当于PWM输出中用到的比较值
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); //数码管显示速度
}
}
效果请看视频:
红外摇控调档速的小风扇
注意:如果程序没有出凑,但是没有结果反应的话,请查看一下自己的开发板的晶振到底是12MHz还是11.0592MHz,这两种晶振的机器周期是不一样的,要在IR.c文件中的程序对应修改,要修改的地方在IR.c文件程序中已经给出了注释,请认真查看并修改!
以上就是本篇的内容,源码会放在评论区(含12MHz和11.0592MHz两种源码),如有问题可评论区留言!