单片机学习笔记---红外遥控红外遥控电机调速(完结篇)

目录

低电平触发中断和下降沿触发中断的区别

红外遥控

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;//取出高八位赋值给TH0TL0=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();//初始化定时器0Int0_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);	//定时计数器清0Timer0_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;	//置收到连发帧标志位为1Timer0_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));	//数据对应位置1IR_pData++;			//数据位置指针自增}else					//接收出错{IR_pData=0;			//数据位置指针清0IR_State=1;			//置状态为1}//32位数据解码完成后,开始验证if(IR_pData>=32)		//如果接收到了32位数据{IR_pData=0;			//数据位置指针清0if((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			0x4Avoid 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~99if(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两种源码),如有问题可评论区留言!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/479463.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“丑女”上春晚:任素汐获赞,黄绮珊遭网暴?

♥ 为方便您进行讨论和分享&#xff0c;同时也为能带给您不一样的参与感。请您在阅读本文之前&#xff0c;点击一下“关注”&#xff0c;非常感谢您的支持&#xff01; 文 |猴哥聊娱乐 编 辑|徐 婷 校 对|侯欢庭 在这个光怪陆离的人间舞台&#xff0c;我们常被绚烂的表象所迷…

Vue3 学习笔记(Day1)

「写在前面」 本文为尚硅谷禹神 Vue3 教程的学习笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。 目录 0 课程介绍 1 Vue3 简介 2 创建 Vue3 工程 2.1 基于 vue-cli 创建 2.2 基于 vite 创建&#xff08;推荐&#xff09; 2.3 …

ChatGPT-01 用ChatGPT指令,自学任何领域的系统知识

1. 指令位置 Github仓库&#xff1a;Mr Ranedeer AI Tutor 但是需要开通chatgtp plus版本&#xff0c;并且打开代码解释器 2 使用 学习内容 开始学习 GPT甚至可以给你思考题&#xff0c;给出的答案还能进行评价 配置 通过配置表修改 深度 学习风格 沟通风格 语气风格 …

【Linux】主机搭建 Linux服务器环境 笔记

目录 前言选择系统软件1. 用U盘装系统2. 安装 Centos7.93. 网络套件 应用软件1. ngnix2. 防火墙配置3. nodejs 后记 前言 过年买了个 mini 主机当玩具玩一下&#xff0c;这里记录下。 选择 已有主力机 (windows) 的情况下&#xff0c;使用过如下四种 Linux宿主环境。这里总…

【Vuforia+Unity】AR01实现单张多张图片识别产生对应数字内容

1.官网注册 Home | Engine Developer Portal 2.下载插件SDK&#xff0c;导入Unity 3.官网创建数据库上传图片&#xff0c;官网处理成数据 下载好导入Unity&#xff01; 下载好导入Unity&#xff01; 下载好导入Unity&#xff01; 下载好导入Unity&#xff01; 4.在Unity设…

微服务—RabbitMQ高级(延迟消息)

本博客为个人学习笔记&#xff0c;学习网站&#xff1a;2023黑马程序员RabbitMQ入门到实战教程 高级篇章节 目录 延迟消息 死信交换机 延迟消息插件 下载安装 延迟交换机声明 ​编辑 发送延迟消息 订单状态同步问题 延迟消息 在电商的支付业务中&#xff0c;对于一些库…

区块链革命:Web3如何改变我们的生活

随着技术的不断发展&#xff0c;区块链技术作为一种去中心化的分布式账本技术&#xff0c;正逐渐成为数字世界的核心。Web3作为区块链技术的重要组成部分&#xff0c;正在引领着数字化时代的变革&#xff0c;其影响已经开始渗透到我们生活的方方面面。本文将深入探讨区块链革命…

Sora一出 哪里又要裁员了?

上班前夕迎来大新闻&#xff0c;那就是Sora了&#xff0c;Sora是什么&#xff0c;有什么牛逼之处&#xff0c;怎么实现的&#xff0c;我们跟着官方文档透露出来的一点点信息&#xff0c;简单的捋一捋。 一、Sora是什么 官方给出的定义是&#xff1a;世界模拟器。这很明显有夸大…

Java的Lock(二)

自旋锁 VS 适应性自旋锁 堵塞或者notify一个Java线程需要操作系统切换CPU状态来完成(详情请参考11408)。这种状态切换需要耗费CPU时间。如果同步代码块种的内容过于简单。状态切换消耗的时间可能比用户代码执行的时间还要长。 在许多场景中,同步资源的锁定时间很短,为了这一…

【.NET Core】C#编程规范

【.NET Core】C#编程规范 文章目录 【.NET Core】C#编程规范一、概述1.1 结构清晰第一1.2 简洁之风1.3 代码风格保持一致性 二、命名约定三、类型参数命名指南3.1 请使用描述性名称命名泛型类型参数&#xff0c;除非单个字面名称完全具有自我说明性且描述性名称不会增加任何作用…

当Diffusion遇到Transformer

Diffusion与Tranformer结合的代表性文章 一、DiT 题目: Scalable Diffusion Models with Transformers 机构&#xff1a;UC Berkeley&#xff0c;纽约大学 论文: https://arxiv.org/pdf/2212.09748.pdf 代码&#xff1a;https://github.com/facebookresearch/DiT 任务: 图像生…

SQL Developer 小贴士:显示RAC配置

前提&#xff1a; 已建立2节点RAC已在SQL Developer中建立了2个连接&#xff0c;分别到RAC的两个节点 然后单击菜单View>DBA&#xff0c;分别连接RAC节点1和节点2&#xff0c;并组织成目录&#xff08;不必须&#xff0c;但建议&#xff09;。 在两处可以体现为RAC配置。第…