第二十一章 红外遥控器实验
本章,我们将介绍MicroPython remote库对红外遥控器的信号解码。ESP32-S3板子上标配的红外接收头和一个小巧的红外遥控器。我们将利用管脚输入功能,解码开发板标配的红外遥控器的编码信号,并将编码后的键值在SPILCD模块中显示出来。
本章分为如下几个小节:
21.1 红外遥控简介
21.2 remote类
21.3 硬件设计
21.4 程序设计
21.5 下载验证
21.1 红外遥控简介
21.1.1 红外遥控技术介绍
红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机系统中。 由于红外线遥控不具有像无线电遥控那样穿过障碍物去控制被控对象的能力,所以,在设计红外线遥控器时,不必要像无线电遥控器那样,每套(发 射 器和接收器)要有不同的遥控频率或编码(否则,就会隔墙控制或干扰邻居的家用电器),所以同类产品的红外线遥控器,可以有相同的遥控频率或编码,而不会出现遥控信号“串门”的情况。这对于大批量生产以及在家用电器上普及红外线遥控提供了极大的方便。由于红外线为不可见光,因此对环境影响很小,再由红外光波动波长远小于无线电波的波长,所以红外线遥控不会影响其他家用电器,也不会影响临近的无线电设备。
21.1.2 红外器件特性
红外遥控的情景中,必定会有一个红外发射端和红外接收端。在本实验中,正点原子的红外遥控器作为红外发射端,红外接收端就是板载的红外接收器,实物图可以查看21.2.3小节原理图部分。要使两者通信成功,收/发红外波长与载波频率需一致,在这里波长就是940nm,载波频率就是38kHz。
红外发射管也是属于二极管类,红外发射电路通常使用三极管控制红外发 射 器的导通或者截至,在导通的时候,红外发射管会发射出红外光,反之,就不会发射出红外光。虽然我们用肉眼看不到红外光,但是我们借助手机摄像头就能看到红外光。但是红外接收管的特性是当接收到红外载波信号时,OUT引脚输出低电平;假如没有接收到红外载波信号时,OUT引脚输出高电平。
红外载波信号其实就是由一个个红外载波周期组成。在频率为38KHz下,红外载波周期约等于26.3us(1s / 38KHz ≈ 26.3us)。在一个红外载波发射周期里,发射红外光时间8.77us和不发射红外光17.53us,发射红外光的占空比一般为1/3。相对的,整个周期内不发射红外光,就是载波不发射周期。在红外遥控器内已经把载波和不载波信号处理好,我们需要做的就是识别遥控器按键发射出的信号,信号也是遵循某种协议的。
21.1.3 红外编解码协议介绍
红外遥控的编码方式目前广泛使用的是:PWM(脉冲宽度调制)的NEC协议和Philips PPM(脉冲位置调制)的RC-5协议的。开发板配套的遥控器使用的是NEC协议,其特征如下:
1,8 位地址和 8 位指令长度;
2,地址和命令 2 次传输(确保可靠性);
3,PWM 脉冲位置调制,以发射红外载波的占空比代表“0”和“1”;
4,载波频率为 38Khz;
5,位时间为 1.125ms 或 2.25ms;
在NEC协议中,如何为协议中的数据‘0’或者‘1’?这里分开红外接收器和红外发 射 器。
红外发 射 器:发送协议数据‘0’ = 发射载波信号560us + 不发射载波信号560us
发送协议数据‘1’ = 发射载波信号560us + 不发射载波信号1680us
红外发 射 器的位定义如下图所示。
图21.1.3.1 红外发 射 器位定义图
红外接收器:接收到协议数据‘0’ = 560us低电平 + 560us高电平
接收到协议数据‘1’ = 560us低电平 + 1680us高电平
红外接收器的位定义如下图所示。
图21.1.3.2 红外接收器位定义图
NEC遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步码由一个9ms的低电平和一个4.5ms的高电平组成,地址码、地址反码、控制码、控制反码均是8位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性(可用于校验)。
我们遥控器的按键▽按下时,从红外接收头端收到的波形如下图所示。
图21.1.3.3 按键▽所对应的红外波形
从上图中可以看到,其地址码为0,控制码为21(正确解码后00010101)。可以看到在100ms之后,我们还收到了几个脉冲,这是NEC码规定的连发码(由9ms低电平+2.25ms高电平+0.56ms低电平+97.94ms高电平组成),如果在一帧数据发送完毕之后,按键仍然没有放开,则发射重复码,即连发码可以通过统计连发码的次数来标记按键按下的长短/次数。
21.2 remote类
根据上小节的红外编码协议的原理解析,作者在工程中新建remote.py文件,用来接收和解析红外编码数据。红外解码如下。
""" * 定义红外接收类 """ class REMOTE_IR(object): # 定义键值及内容 REMOTE_CODE = { 0: "ERROR", 162: "POWER", 98: "UP", 2: "PLAY", 226: "ALIENTEK", 194: "RIGHT", 34: "LEFT", 224: "VOL-", 168: "DOWN", 144: "VOL+", 104: "1", 152: "2", 176: "3", 48: "4", 24: "5", 122: "6", 16: "7", 56: "8", 90: "9", 66: "0", 82: "DELETE" } # 红外接收初始化 def __init__(self, gpio_num): self.irRecv = Pin(gpio_num, Pin.IN, Pin.PULL_UP) self.irRecv.irq(trigger = Pin.IRQ_RISING | Pin.IRQ_FALLING, handler = self.ex_handler) # 配置中断信息 # 声明变量 self.ir_step = 0 self.ir_count = 0 self.rx_buf = [0 for i in range(64)] self.rx_ok = False self.cmd = None self.repeat = 0 self.t_ok = None self.start = 0 self.start_last = 0 # 红外接收回调函数 def ex_handler(self, source): """ 中断回调函数 """ thisComeInTime = time.ticks_us() # 更新时间 curtime = time.ticks_diff(thisComeInTime, self.start) self.start = thisComeInTime if curtime >= 8500 and curtime <= 9500: self.ir_step = 1 return if self.ir_step == 1: if curtime >= 4000 and curtime <= 5000: self.ir_step = 2 self.rx_ok = False self.ir_count = 0 elif curtime >= 2000 and curtime <= 3000: # 长按重复接收 self.ir_step = 3 self.repeat += 1 elif self.ir_step == 2: # 接收4个字节 self.rx_buf[self.ir_count] = curtime self.ir_count += 1 if self.ir_count >= 64: self.rx_ok = True self.t_ok = self.start # 记录最后ok的时间 self.ir_step = 0 elif self.ir_step == 3: # 重复 if curtime >= 500 and curtime <= 650: self.repeat += 1 # 检测命令 def check_cmd(self): one_byte = 0 for i in range(32): x = i * 2 t = self.rx_buf[x] + self.rx_buf[x + 1] one_byte <<= 1 if t >= 1800 and t <= 2800: one_byte += 1 cmd_data = (one_byte & 0x0000ff00) >> 8 self.cmd = cmd_data # 红外解码 def remote_scan(self): # 接收到数据 if self.rx_ok: self.check_cmd() self.rx_ok = False # 获取对应按钮字符 s = self.REMOTE_CODE.get(self.cmd) return self.cmd,s
在Python脚本中,作者定义了一个名为REMOTE_IR的类,用于创建红外解码对象的实例。该类中,作者使用字典定义了正点原子红外遥控器的按键值(控制码)和按键名称。
类中接着定义了红外接收初始化方法,用于实例化对象、初始化GPIO引脚、设置中断模式(上升沿和下降沿)以及声明变量。此外,还定义了红外接收回调方法,用于计算逻辑1和逻辑0的时间(请参考图21.1.3.2 红外接收器位定义图)。
除此之外,还定义了检测命令方法,用于检测接收的按键值(控制码)。最后,作者还定义了红外解码方法,该方法根据按键值(控制码)查询REMOTE_CODE字典的对应内容,并将结果返回给用户。
1,remote类的构造方法
remote的构造对象方法如下:
class remote.init(gpio_num) 使用示例:remote = remote.init(gpio_num)
该构造方法的参数描述,如下表所示。
表21.2.1 remote.init构造方法参数描述
返回值: remote对象。
2,remote类的方法
①:获取红外解码的数据。
其函数原型如下:
remote.remote_scan()
返回值:红外命令和数值。
21.3 硬件设计
1. 例程功能
本章实验功能简介:本实验开机在LCD上显示一些信息之后,即进入等待红外触发,如果接收到正确的红外信号,则解码,并在LCD上显示键值和所代表的意义,以及按键次数等信息。LED闪烁用于提示程序正在运行。
2. 硬件资源
1)XL9555
IIC_INT-IO0(需在P5连接IO0)
IIC_SDA-IO41
IIC_SCL-IO42
2)SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在P5端口,使用跳线帽将IO_SET和LCD_DC相连)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555)
3)红外接收头
REMOTE_IN-IO2
4)正点原子红外遥控器
3. 原理图
红外遥控接收头与ESP32-S3的连接关系,如下图所示。
图21.3.1 红外遥控接收头与ESP32-S3的连接电路图
红外遥控接收头连接在ESP32-S3的IO2上。硬件上不需要变动,只要程序将IO2设计为输入模式且开启外部中断,然后将收到的脉冲信号解码就可以了。不过需要注意:REMOTE_IN和SD卡片选共用了IO2,所以它们不可以同时使用。
开发板配套的红外遥控器外观如图21.3.2所示:
图21.3.2 红外遥控器
开发板上接收红外遥控器信号的红外管外观如图21.3.2所示。使用时需要遥控器有红外管的一端对准开发板上的红外管才能正确收到信号。
21.4 程序设计
21.4.1 程序流程图
程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。下面看看本实验的程序流程图。
图21.4.1.1 程序流程图
21.4.2 程序解析
本书籍的代码都在main.py脚本下编写的,读者可在光盘资料下找到对应的源码。红外遥控器实验main.py源码如下:
from machine import Pin,SPI,I2C import atk_xl9555 as io_ex import atk_lcd as lcd import remote import time """ * @brief 程序入口 * @param 无 * @retval 无 """ if __name__ == "__main__": # 初始化LED led = Pin(1,Pin.OUT, value = 1) # IIC初始化 i2c0 = I2C(0, scl = Pin(42), sda = Pin(41), freq = 400000) # XL9555初始化 xl9555 = io_ex.init(i2c0) # 复位LCD xl9555.write_bit(io_ex.SLCD_RST,0) time.sleep_ms(100) xl9555.write_bit(io_ex.SLCD_RST,1) time.sleep_ms(100) # 初始化SPI spi = SPI(2,baudrate = 80000000, sck = Pin(12), mosi=Pin(11), miso=Pin(13)) # 初始化LCD,lcd = 0为正点原子2.4寸屏幕;lcd = 1为正点原子1.3寸SPILCD屏幕; display = lcd.init(spi,dc = Pin(40,Pin.OUT,Pin.PULL_UP,value=1), cs=Pin(21,Pin.OUT,Pin.PULL_UP,value = 1),dir = 1,lcd = 0) xl9555.write_bit(io_ex.SLCD_PWR,1) time.sleep_ms(100) # 实验信息 display.string(30, 50, 240, 16, 16, "ESP32-S3",lcd.RED) display.string(30, 70, 240, 16, 16, "REMOTE TEST",lcd.RED) display.string(30, 90, 240, 16, 16, "ATOM@ALIENTEK",lcd.RED) display.string(30, 120, 200, 16, 16, "CMD:", lcd.RED) display.string(30, 150, 200, 16, 16, "DATA:", lcd.RED) # 初始化红外接收 ir = remote.REMOTE_IR(2) while True : cmd,key_str = ir.remote_scan() display.fill(30 + 11 * 8 - 2,120,30 + 11 * 8 + 100,200,lcd.WHITE) display.string(30 + 11 * 8, 120, 200, 16, 16, str(cmd), lcd.BLUE) display.string(30 + 11 * 8, 150, 200, 16, 16, str(key_str), lcd.BLUE) led_state = led.value() led.value(not led_state) time.sleep_ms(500) # 延时500ms
main函数代码比较简单,主要是通过ir.remote_scan函数获得红外遥控输入的数据(控制码),然后显示在LCD上面。正点原子红外遥控器按键对应的控制码图如下图所示。
图21.4.2.1 红外遥控器按键对应的控制码图(十六进制数)
特别注意:上图中的控制码数值是十六进制的,而我们代码中使用的是十进制的表示方式。
此外,正点原子红外遥控器的地址码是0。
21.5 下载验证
将程序下载到开发板后,可以看到LED不停的闪烁,提示程序已经在运行了。LCD显示的内容如下图所示:
图21.5.1 程序运行效果图
此时我们通过遥控器按下不同的按键,则可以看到LCD上显示了不同按键的键值以及按键次数和对应的遥控器上的符号。如下图所示:
图25.5.2 解码成功