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

ops/2025/3/15 16:49:53/

写在前言


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


http://www.ppmy.cn/ops/165987.html

相关文章

【病毒分析】熊猫烧香病毒分析及其查杀修复

目录 前言 一、样本概况 1.1 样本信息 1.2 测试环境及工具 1.3 分析目标 二、具体行为分析 2.1 主要行为 2.1.1 恶意程序对用户造成的危害 2.2 恶意代码分析 2.2.1 加固后的恶意代码树结构图(是否有加固) 2.2.2 恶意程序的代码分析片段 三、解决方案(或总结) 3.1 …

基于NXP+FPGA轨道交通人机交互(HMI)和健康管理单元(PHM)解决方案

人机接口 (HMI) 是交互式显示设备&#xff0c;可用于司机和乘务员的交互式信息显示。也可以用于CCTV监视。满足多种接口设备连接的同时&#xff0c;搭载的Linux系统&#xff0c;可以满足客户的定制化需求。 关键特性 触摸式按键位于显示区域周围&#xff0c;耐用性好&#xff0…

Blackbox.Ai体验:AI编程插件如何提升开发效率

文章目录 一、引言二、特色功能2.1 VSCode插件安装2.2 自动化网页生成功能2.3 自动化测试2.4 MCP服务器 三、编程功能评测3.1 测试一&#xff1a;代码生成3.2 测试二&#xff1a;代码翻译3.3 测试三&#xff1a;代码审查 四、总结 一、引言 最近&#xff0c;AI的热潮已经席卷各…

CentOS 系统安装 docker 以及常用插件

博主用的的是WindTerm软件链接的服务器&#xff0c;因为好用 1.链接上服务器登入后&#xff0c;在/root/目录下 2.执行以下命令安装docker sudo yum install -y yum-utilssudo yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.reposudo…

YOLOv8模型改进 第三十二讲 添加Transformer Self Attention TSA 解决CNN过程中特征丢失的问题

在医学图像分割过程中&#xff0c;卷积操作的局部性导致全局信息缺失&#xff0c;连续下采样导致细节丢失&#xff0c;以及跳跃连接未能有效融合多尺度特征。TSA通过自注意力机制捕捉全局上下文&#xff0c;结合位置编码保留空间信息&#xff0c;同时多头机制增强特征表达能力。…

LeetCode - #227 基于 Swift 实现基本计算器

摘要 在这篇文章中&#xff0c;我们将实现一个基于 Swift 语言的基本计算器。该计算器能够解析和计算包含 、-、* 和 / 的数学表达式&#xff0c;并且遵循运算符的优先级规则。整数除法仅保留整数部分&#xff0c;不能使用 eval() 这样的内置解析方法。 描述 给你一个字符串表…

C++蓝桥杯基础篇(十一)

片头 嗨~小伙伴们&#xff0c;大家好&#xff01;今天我们来学习C蓝桥杯基础篇&#xff08;十一&#xff09;&#xff0c;学习类&#xff0c;结构体&#xff0c;指针相关知识&#xff0c;准备好了吗&#xff1f;咱们开始咯~ 一、类与结构体 类的定义&#xff1a;在C中&#x…

使用Arduino、ESP8266和GPS在Google地图上追踪车辆

使用 ESP8266、GPS 和 Google 地图的 Arduino Vehicle Tracker 如今,车辆跟踪系统变得非常重要,尤其是在车辆被盗的情况下。如果您的车辆安装了 GPS 系统,您可以跟踪您的车辆位置,它可以帮助警方追踪被盗车辆。 在这里,我们正在构建更高级版本的车辆跟踪系统,您可以在其…