GPIO模拟时序控制外设4——红外发射管

news2024/11/19 3:49:53

文章目录

  • 前言
  • 红外发射管简介
    • NEC协议
    • HS0038
    • NEC 的逻辑“1”与逻辑“0”
    • NEC的数据帧格式
  • 编程思路
    • 1. GPIO管脚
    • 2. 模拟同步头
    • 3.发送逻辑“0”与逻辑“1”
    • 发送一个字节数据
    • 发送一帧数据
    • 结束码
    • 现象
  • 总结

前言

上一篇介绍了使用GPIO模拟时序实现I2C协议的功能,本文继续使用GPIO模拟的方式来实现一个平时生活中常用的模块——红外发射管的驱动,红外在家电的应用中十分广泛,空调、电视、投影等都是通过红外遥控来实现的。

红外发射管简介

红外发射管实物就是下图中左边的红框内的器件,右侧的是红外接收管。
在这里插入图片描述
大家拿到红外发射管后,第一反应可能就是这玩儿意和LED灯长得一模一样啊,其实,不光长得一样,其的工作原理也是一样,就是通过一定的规律给电平,点“亮”和熄灭红外发射管来产生红外光信号,这个红外光信号被接收管接受处理后,就会产生对应的电信号,然后用户接收和处理这个电信号用来控制不同的外设即可。只不过红外发射管的波长超过了我们肉眼可见的波段,我们平常事无法观察到的。如下图:我们肉眼可见的光波段就是380nm----760nm,而这个红外发射管的波长是940nm。
在这里插入图片描述
在这里插入图片描述
当然,如果实在是想看见点亮效果,也可以把周围的灯都关了,找一个比较黑的环境,用手机摄像头就可以看见红光。
在这里插入图片描述

NEC协议

上面提到了,红外发射管用的是一种特殊的方式进行数据发送,具体的发送格式需要参考红外接收管的芯片手册以及使用到的具体通信协议了,这里接收管使用的是HS0038,通信协议是NEC。

HS0038

先来看看HS0038的手册是怎么描述的,如下图所示,可以知道,当接收到的输入红外光是一个频率为38khz,占空比为50%,持续时间为500us-700us的PWM波形时,HS0038的输出端就会产生一个低电平,而当输入的红外光不是此规格的信号时,HS0038就会输出一个高电平。
在这里插入图片描述
来个实际波形分析一下:
如下图所示:D1是红外发射管发射出的光信号,D0是红外接收管根据输入信号产生的输出信号,可以看出,当D1的波形变成了PWM时,D0输出低电平,而D1输出不是PWM时,D0输出就是高电平。
在这里插入图片描述

NEC 的逻辑“1”与逻辑“0”

为啥我上面一直说的HS0038的输出是高低电平而不是逻辑0和逻辑1呢?
还记得之前驱动WS2812B与DHT11的时候,他们的逻辑0与逻辑1并不是简单的对应高低电平,这里也是如此,NEC协议中的逻辑0与逻辑1也不是简单的高低电平,而是有不同时间段的高低电平组合而成,其中:
逻辑“0”是由560us的低电平加560us的高电平组成,
逻辑“1”是由560us的低电平加1690us的高电平组成,
再结合下面的这个逻辑分析仪的图大家就可以看懂了,注意最下面绿色的解码,第一段低电平与第一段高电平组成了第一个逻辑0,
第二段低电平与第二段高电平又组成了一个逻辑0,
而第三段的低电平与第三段高电平组成的是逻辑1,
在这里插入图片描述
因为红外传输的工程中大部分设备都是支持NEC格式的,所以我们发射管端也要对应这个NEC码的时序做出调整,当需要输出逻辑1时,就要使用到下图的前半段时序,而输出逻辑“0”时,就需要使用到下图的后半段时序:
原文链接——http://t.csdn.cn/LoC2r
在这里插入图片描述
整个数据传输动态过程可以看下面的动图:
单片机控制开关管按照逻辑“0”与逻辑“1”的时序来控制红外发射管的点亮与关闭,而红外接收管接收到红外发射管的信号后就会输出对应的高低电平信息,使用MCU进行捕获处理这部分信号就可以了。
在这里插入图片描述

NEC的数据帧格式

再弄清楚了NEC协议的逻辑“0”与逻辑“1”后,就要知道其数据帧的具体格式了,为了进一步提高数据的稳定性,NEC码在传输的过程中对于数据帧也有着很高的要求,具体帧格式如下:
在这里插入图片描述
需要注意一点,前面的数据通信都是高位在前,高位先发,此处的NEC是低位在前,低位先发。

同步码头(引导码/起始码)、地址码(遥控ID)、地址反码、控制码(键值)、控制反码。
同步码、地址码(遥控ID)、地址反码、控制码(键值)、控制反码
同步码由一个 9ms 的低电平和一个 4.5ms 的高电平组成
如果接收到同步,说明有红外数据进来

同步码 + 8位地址码+ 8位地址码反码+ 8位控制码 + 8位控制码反码
红外接收每一次接收到数据都是32位

实际的数据:

帧头地址码地址码反码命令命令反码
9ms 的低电平+ 4.5ms 的高电平8位8位8位8位
9ms 的低电平+ 4.5ms 的高电平1011 01110100 10000110 01101001 1001

在这里插入图片描述
除此之外还有一个连续发送的数据帧格式,这里就不做介绍了,有需要的自己去研究一下吧。

编程思路

搞清楚了红外发射管与接收管的通信过程以及其数据帧的格式后,接下来就可以开始编程了,本文先解决红外发射管的代码,接收管的使用留到下一篇介绍GPIO复用功能的时候再说。

1. GPIO管脚

首先第一步,二话不说,直接看原理图,找到GPIO是PB7,前面分析了,在发送红外光的时候,需要发送一段频率为38khz,占空比位50%,持续时间长度为560us的PWM,来使得红外接收管输出低电平,一般来说最好的解决方案就是使用定时器来产生PWM来实现低电平,当时间到了后再将GPIO口拉低不输出PWM即可实现
这里选择PB7管脚是可以作为定时器4的通道2来使用的,但是有一个问题,在实际使用过程中定时器4另有它用;所以没法了,只能使用GPIO模拟的办法来实现红外发射管的功能了,这里就需要模拟出一段PWM波形,当然是使用推挽模式配合延时来实现。
在这里插入图片描述
对于PWM实现的方案,这里给大吉留几个链接,供大家参考。
STM32 NEC红外遥控器解码——
基于STM32f103c8t6的红外接收发送
除此之外还有几篇写的很好地博客也给大家贴在这
STM32入门开发: 制作红外线遥控器(智能居家-万能遥控器)
在这里插入图片描述
GPIO初始化代码:

/************************************************
函数功能:红外发送控制码
函数名:Infrared_Send_Init
函数形参:None
函数返回值:
备注:  PB7
**************************************************/
void Infrared_Send_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;                       // 定义一个GPIO_InitTypeDef类型的变量
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);   // 允许GPIOB时钟
	
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;                  // GPIO_Pin_7
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;           // 通用推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;          // 50MHz速度
	GPIO_Init(GPIOB, &GPIO_InitStruct);
	
	GPIO_ResetBits(GPIOB, GPIO_Pin_7);                      //PB7拉低
}

找到实现方案后,就该具体执行了,对于这类数据帧比较复杂的通信方式,在模拟其时序的时候一定记得从下往上来写,先搞定各个部分,然后再拼接起来组成一整个时序。
整个数据帧无非就是以下几个内容组成,
1.同步头,2.逻辑“0”与逻辑“1”;而且逻辑0与逻辑1还可以进一步细分为低电平和高电平,也就是一个PWM,一个非PWM。那么接下来就一个一个的实现。

2. 模拟同步头

由于接收管的同步头是9ms的低电平加上4.5ms的高电平,那么根据转换方式可以知道,发射管的同步头是9ms的38khz、50%占空比的PWM加上4.5ms的低电平。
在这里插入图片描述
38khz的PWM,一个脉冲的时长为1/38 000=26.3us;
占空比50%,则输出高电平的时间:26.3/2=13.15us由于STM32F103C8T6的系统滴答延时最多支持到us级别,所以这里把小数点后的给省略,直接高低电平各自延时13us。
而9ms的PWM一共有9ms/26=346个。
于是代码就有了:

/************************************************
函数功能:红外发送同步头
函数名:NEC_IE_Start
函数形参:None
函数返回值:
备注:  
// ①引导码:载波发射9ms,不发射4.5ms
**************************************************/
//------------------------------------------------------------
void NEC_IE_Start(void)
{
	u16 i;
// 载波发射 9ms ≈ 26.3us * 346
	for(i=0;i<346;i++) 
	{
		PBout(7)=1;	// IE抬高,发射红外光
		Systick_Delay_us(13);	// 延时13us
		PBout(7)=0;;	// IE拉低,不发射红外光
		Systick_Delay_us(13);	// 延时13us
	}
	// 载波不发射 4.5ms ≈ 26.3us * 171
	for(i=0;i<171;i++)
	{
		PBout(7)=0;	// IE拉低,不发射红外光
		Systick_Delay_us(26);	// 延时26us
	}
}

3.发送逻辑“0”与逻辑“1”

逻辑“0”与逻辑“1”的共有部分是560us的PWM波形,不同之处在于后面的部分,
在这里插入图片描述
先看逻辑“1”,除了560us的PWM,它还需要2.25ms-560us=1690us的低电平,PWM还是参考上面的思路,于是发送逻辑“1”的代码就有了:

/************************************************
函数功能:红外发送逻辑“1”
函数名:NEC_IE_Send_one
函数形参:None
函数返回值:
备注:  
// NEC协议数据"1" = 载波发射0.56ms + 载波不发射1.68ms
**************************************************/
//-------------------------------------------------
void NEC_IE_Send_one(void)
{
	u8 i;
	// 载波发射0.56ms ≈ 26.3us * 21
	//-------------------------------
	for(i=0;i<22;i++)
	{
		//26.3us(载波发射周期)占空比50%
		//------------------------------------
		PBout(7)=1;    // IE抬高,发射红外光
		Systick_Delay_us(13);	// 延时13us
		
		PBout(7)=0;  // IE拉低,不发射红外光
		Systick_Delay_us(13);	// 延时13us
		//------------------------------------
	}
	
	// 载波不发射1.69ms ≈ 26us * 65
	//--------------------------------
	for(i=0;i<65;i++)
	{
		//26us(载波不发射周期)
		//------------------------------------
		PBout(7)=0;	// IE拉低,不发射红外光
		Systick_Delay_us(26);	// 延时26us
	}
}

同样的逻辑“0”的思路也差不多,直接给出代码:


/************************************************
函数功能:红外发送逻辑“0”
函数名:NEC_IE_Send_zero
函数形参:None
函数返回值:
备注:  
// NEC协议数据"0"= 载波发射0.56ms + 载波不发射0.56ms
**************************************************/
//-------------------------------------------------
void NEC_IE_Send_zero(void)     
{
	u8 i;
	
	// 载波发射0.56ms ≈ 26.3us * 21
	//-------------------------------
	for(i=0;i<22;i++)
	{
        //26.3us(载波发射周期)
		//------------------------------------
		PBout(7)=1;	// IE抬高,发射红外光
		Systick_Delay_us(13);	// 延时13us                       
											
		PBout(7)=0;	// IE拉低,不发射红外光
		Systick_Delay_us(13);	// 延时13us
		//------------------------------------
	}

	
	// 载波不发射0.56ms ≈ 26.3us * 21
	//-------------------------------
	for(i=0;i<21;i++)
	{
        //26.3us(载波不发射周期)
		//------------------------------------
		PBout(7)=0;	// IE拉低,不发射红外光
		Systick_Delay_us(26);	// 延时26us
		//------------------------------------
	}
}

发送一个字节数据

搞定了逻辑“0”与逻辑“1”之后,下一步自然是封装一个可以一次发送8个位也就是一字节的函数。这里需要注意的是低位先发。
在这里插入图片描述
于是代码如下:

/************************************************
函数功能:红外发送一个字节
函数名:NEC_IE_One_Data
函数形参:u8 IE_One_Data
函数返回值:
备注:  发送一个字节的数据,一次发8位,低位在前。
**************************************************/
//----------------------------------------------------------------------------------------------
void NEC_IE_One_Data(u8 IE_One_Data)
{
	u8 i;
	
	for(i=0;i<8;i++)
	{
		if( IE_One_Data & 0x01 )
			NEC_IE_Send_one();
		else
			NEC_IE_Send_zero();
			
		IE_One_Data>>=1;
	}
}

发送一帧数据

一帧数据格式在前面已经介绍了,这里根据帧格式配合上面的函数做一个组合就可以了。
代码如下:


/************************************************
函数功能:发送NEC 一帧数据
函数名:NEC_IE_code_message
函数形参:u8 IE_One_Data
函数返回值:
备注: 
// 将一帧数据调制为NEC协议规定的红外载波发射出去
// 一帧数据格式:低位在前,由低到高发送8位数据
// 这帧数据是:②8位用户码字节(8位) / ③8位用户码反码(8位) / ④8位数据码 / ⑤8位数据码的反码
**************************************************/
void NEC_IE_code_message(u8 user_code_8bit, u8 data_code_8bit)
{
	// ①引导码:载波发射9ms,不发射4.5ms
	NEC_IE_Start();
	//------------------------------------------------------------
	//------------------------------------------------------------
	// ②8位用户码	:8位数据
	NEC_IE_One_Data(user_code_8bit);
	
	// ③8位用户码的反码:8位数据
	NEC_IE_One_Data(~user_code_8bit);
	
	// ④8位数据码 :8位数据
	NEC_IE_One_Data(data_code_8bit);
	
	// ⑤8位数据码的反码:8位数据
	NEC_IE_One_Data(~data_code_8bit);
	//------------------------------------------------------------
	// ⑥结束码‘0’
	//--------------
	NEC_IE_Send_zero();
	//--------------
}

结束码

关于结束码,由于32位数据发送完毕后,不管最后一位的逻辑是0还是1,末尾都是PBout(7)=0; // IE拉低,不发射红外光;理论来说,这种情况不额外添加结束码也是可行的;但是笔者尝试了一下,不再末尾加一个发送逻辑“0”的结束码的话,逻辑分析仪无法解析出数据,为了保险还是加上一句NEC_IE_Send_zero();在发送外32位数据后。
而对于连续码的时序,如下图,有兴趣的话可以自己尝试,调通后可以投食一下,我想白嫖。
在这里插入图片描述
既然搞定了红外发送的基本用法,最大的想法自然是控制空调之类的设备了,但是需要注意,各个厂家的空调的协议不太一样,需要在上面代码的基础上进一步修改才行。
具体的介绍大家参考这些大佬的——
STM32入门开发: 制作红外线遥控器(智能居家-万能遥控器)
格力空调红外编码解析
STM32解析美的空调红外遥控器
格力空调红外编码
在这里插入图片描述

现象

这里笔者还没去倒腾空调的编码,只是实现了简单的功能,最后捕捉到的实际波形和解析出来数据如下:
在这里插入图片描述

总结

关于使用GPIO模拟时序驱动红外发射管的介绍就先记录到这,至于空调的控制,这个先鸽了,或者有做出来的可以踢我一下。文中如有不足欢迎批评指正。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DELL戴尔笔记本电脑灵越Inspiron 14 54105418原厂Win10系统恢复原装OEM出厂状态系统

Dell戴尔笔记本电脑&#xff0c;灵越Inspiron 14 5410&5418原装出厂OEM系统镜像原厂系统文件 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件等预装程序 链接&#xff1a;https://pan.baidu.com/s/1Qj_dW5lj71e9d71-je4dXw?pwdz3z1 提取码&#xff1a;z3z1

Oracle Recovery Tools恢复csc higher than block scn---惜分飞

有客户强制关闭数据库,结果有数据块报坏块,dbv检查为:csc higher than block scn问题 该问题主要是由于scn异常导致通过Oracle Recovery工具进行修复 dbv再次验证数据块ok,Oracle Recovery完美代替bbed解决该问题 通过OraRecovery工具快速解决csc higher than block scn故障…

IntelliJ IDEA如何自动生成serialVersionUID

IntelliJ IDEA如何自动生成serialVersionUID&#xff1f; 实体对象在流中传输时&#xff0c;需要将其序列化。 对象的类型实现Serializable接口public class ClassName implements java.io.Serializable { } 生成版本号serialVersionUID单击类名&#xff0c;按Alt Enter,在出…

C语言的##使用

##在C/C中具有连接字符串的作用 #include<stdio.h> #define TEST(_name, _inst_alloc) \printf("token6 %d\n",_name##_inst_alloc); int main() {int token6 100;TEST(token, 6);return 0; }

[环境配置][原创]VS2019新建项目一直打转转圈很久才能正常显示模板项目

不知道什么时候VS2019新建一个C#或者C项目越来越慢&#xff0c;点击新建项目一直是下面状态 我记得以前最多等个几秒都是可以出来的&#xff0c;后面发现要一分钟以上。这个确实令人抓狂。于是找到一种方法可以明显改善这个问题&#xff0c;那就是卸载Nsight相关程序。一般都是…

软考高级系统架构设计师(四) 计算机网络3物联网云计算

目录 物联网 关键技术 云计算 物联网 PS&#xff1a;可能下午题 关键技术 射频识别、二维码&#xff08;感知层&#xff09; 云计算

为什么游戏总是闪退?游戏闪退的原因和解决方法

在玩游戏的过程中&#xff0c;会有游戏突然关闭的情况&#xff0c;游戏闪退后回到电脑桌面或游戏主界面&#xff0c;十分影响游戏体验。端午开黑在际&#xff01;驱动人生游戏助你攻克游戏闪退&#xff0c;带来游戏闪退的原因和解决方法。 **1、设备性能不足&#xff1a;**某些…

ElasticSearch-IK分词器介绍和下载

IK分词器 什么是IK分词器&#xff1f; 分词:把一段中文或者别的划分成一个一个的关键字,我们在搜索的时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词,比如"我爱魏一鹤"会被分成&quo…

hive引入外部函数-java实现

引入依赖 <dependency><groupId>org.apache.hive</groupId><artifactId>hive-exec</artifactId><version>3.1.2</version> </dependency>编写函数 注意必须要继承GenericUDF 类 import org.apache.hadoop.hive.ql.exec.UDF…

5.按键输入

1.按键介绍&#xff1a; 按键WK_UP接在低电平&#xff0c;当按键按下检测到高电平&#xff1b;KEY0和KEY1接在高电平&#xff0c;当按键按下检测到低电平&#xff1b; 2.本次实验步骤&#xff1a; &#xff08;1&#xff09;GPIO输入操作&#xff1a; 读取IO口输入电平的库…

vue中实现将html导出为word文档

需求&#xff1a;将页面整成理想样式&#xff0c;将想要的那一部分页面导出成word,不用写模板&#xff0c;按照当前页面样式导出即可。&#xff08;简易版&#xff09; 保姆级别教程&#xff1a; 第一步&#xff1a;安装需要的依赖 npm install html-docx-js -Snpm install …

第11讲:使用ajax技术实现文件上传功能(jQuery)

软件项目开发过程中,文件上传功能是常用技术之一,经常需要上传文件过程中为了更好的体验,不允许刷新当前页面,这样就必须使用异步上传技术了,jQuery提供了异步上传的多种方法,下面跟大家一起探讨使用jQuery.form插件上传文件的功能,具体实现过程如下: 环境要求 序号资…

【单片机】MSP430 单片机 多路温度巡回检测仪的设计

多路温度巡回检测仪的设计 多路温度检测仪共有3个测温点&#xff0c;每个点连续检测8次&#xff0c;以平均值代表该点温度&#xff0c;并同时在 LCD 显示器&#xff0e;器任选&#xff0c;测量精度为1℃。系统每隔1秒完成一个点的测量。 设计以msp430g2553为核心的多路温度检测…

深度学习笔记之一看数据

最近在百度ai上跟了个“深度学习7日打卡营”的课程&#xff0c;目前看到人脸关键点检测章节&#xff0c;地址如下&#xff1a;飞桨AI Studio - 人工智能学习与实训社区 (baidu.com) 老师们讲解的很好&#xff0c;但是对于我这样的白菜来说&#xff0c;有些细节还是感觉略过去了…

Java-多线程

进程与线程 程序>进程>线程 程序是一段静止的代码&#xff0c;只有真正运行时的程序&#xff0c;才被称为进程。一个程序运行至少有一个进程 从操作系统底层来说&#xff0c;进程只是一个概念&#xff0c;真正执行的是线程。 进程是操作系统资源分配的基本单位&#x…

六、动画 - 旋转的应用例子,时钟

目录&#xff1a; 1.前期准备知识 2.实操 - 做时钟 3.完整代码 一、前期准备知识 因为旋转都是默认元素中心来旋转&#xff0c;所以&#xff0c;我们可以通过父元素包裹子元素。 通过父元素旋转&#xff0c;然后父带动子元素&#xff0c;到时候可以通过影藏父元素的背景颜色&a…

关于三元运算符强转的问题

1.int和char比较 public static void main(String[] args) {char x x;int i 10;char y 2;System.out.println(true? x: i);System.out.println(true? x: 1167);System.out.println(true?y:2);System.out.println((int)x);}JVM&#xff1a; public static void main(Stri…

CSS基础学习--23 CSS属性 选择器

一、定义 具有特定属性的HTML元素样式&#xff08;不仅仅是class和id&#xff09; 二、属性选择器 例子是把包含标题&#xff08;title&#xff09;的所有元素变为蓝色&#xff1a; <!DOCTYPE html> <html> <head> <style> [title] { color:blue; }…

Linux_CentOS_7.9修改更新默认时区

前言&#xff1a;近期一直在频繁部署虚拟机做系统测试发现Linux默认时区未做更改&#xff0c;这里做个记录留作参考。 查看服务器时区&#xff08;默认为纽约时间&#xff09; [rootorcl3 ~]# timedatectl 系统默认安装的所有时区&#xff0c;找到我们需要的时区 [rootor…

Proteus仿真之DAC(DAC0832)

1.数模/模数转换的目的&#xff1a;生活中常见的物理量如温度、流量、压力、位移、速度等都是一种模拟量。但是&#xff0c;计算机只能处理0、1的数字量&#xff0c;此时就需要将模拟量转为数字量。与此同时&#xff0c;MCU对模拟量设备进行控制时&#xff0c;如控制电动调节阀…