江科大51单片机笔记【17】红外遥控&电机调速

写在前言

此为博主自学江科大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档位

相关推荐
阿拉保1 小时前
C++复试笔记(四)
java·c++·笔记
promising-w2 小时前
硬件工程师入门教程(四)
嵌入式硬件
坏柠2 小时前
STM32与HAL库开发实战:深入探索ESP8266的多种工作模式
stm32·单片机·嵌入式硬件
电鱼智能的电小鱼3 小时前
SAIL-RK3576核心板应用方案——无人机视觉定位与地面无人设备通信控制方案
linux·嵌入式硬件·无人机·边缘计算
Amy.com3 小时前
嵌入式2-按键
stm32·单片机·嵌入式硬件
JANGHIGH3 小时前
c++ union使用笔记
c++·笔记
Ciderw4 小时前
侯捷 C++ 课程学习笔记:进阶语法之lambda表达式(二)
开发语言·c++·笔记·学习
printf_014 小时前
HAL库STM32常用外设—— CAN通信(一)
stm32·单片机·嵌入式硬件
小麦嵌入式4 小时前
Linux驱动开发实战(五):Qt应用程序点RGB灯(保姆级快速入门!)
linux·驱动开发·stm32·嵌入式硬件·mcu·qt·ubuntu
小禾苗_5 小时前
32单片机——BEEP
单片机·嵌入式硬件