紅外模組詳解

良许發表於2024-02-04

和紅外有關的模組有很多,比如紅外循跡,紅外感應,紅外發射,紅外接收,紅外對射,紅外編解碼等等。

今天我們要介紹的是紅外編解碼模組,它最常見的應用就是我們家裡的電視、空調,當我們按下遙控器上的按鈕時,紅外訊號從遙控器上的紅外編解碼模組發射,操作電視音量增大,空調溫度降低等等。

1. 原始碼下載及前置閱讀

本文首發 良許嵌入式網https://www.lxlinux.net/e/ ,歡迎關注!

本文所涉及的原始碼及安裝包如下(由於平臺限制,請點選以下連結閱讀原文下載):

https://www.lxlinux.net/e/stm32/infrared-sensor-tutorial.html

如果你是嵌入式開發小白,那麼建議你先讀讀下面幾篇文章。

  • 瞭解不同的下載程式方法,為你的嵌入式開發提供更多選擇:【STM32下載程式的五種方法:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to...
  • 手把手讓你掌握MDK的使用方式和技巧,助你更高效地進行開發:【一文教你使用MDK開發工具:https://www.lxlinux.net/e/stm32/mdk-development-tool-tutorial...
  • 從零開始輕鬆掌握STM32開發的必備指南:【零基礎快速上手STM32開發(手把手保姆級教程):https://www.lxlinux.net/e/stm32/stm32-quick-start-for-beginne...

前期教程,沒看過的小夥伴可以先看下。

  • 嵌入式基本功,為後續學習打下堅實的基礎:【STM32串列埠接收不定長資料(接收中斷+超時判斷):https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-usin...

2. 紅外編解碼模組介紹

2.1 型號介紹

紅外編解碼模組使用特定的紅外協議來確保裝置之間的通訊準確性和相容性。常見的紅外編解碼協議包括 NEC、RC5、RC6 等。

我們今天介紹的是 NEC 紅外編解碼模組,型號是 YS-IRTM。

  • 紅外發射頭: 用於發射紅外訊號,波長為 940nm,頻率為 38k,協議為 NEC 編碼的紅外訊號。
  • 紅外接收頭:用於接收 NEC 紅外訊號,進而微控制器進行分析解碼操作。
  • 紅外頭擴充套件:該介面為紅外發射頭的擴充套件,可以連線多個紅外發射頭(常稱紅外發射模組),用於安放到不同的位置,實現多方位控制。

2.2 工作引數及引腳介紹

預設波特率是 9600。

YS-IRTMSTM32
GNDGND
RXDA2(串列埠2)/B10(串列埠3)
TXDA3(串列埠2)/B11(串列埠3)
5V5V

3. 紅外編解碼原理

我們今天介紹的紅外編解碼模組採用 NEC 編碼,由引導碼、使用者碼高位、使用者碼低位、資料碼、資料反碼五部分組成。

NEC 編碼格式如下:

  1. 使用 38kHz 的載波頻率。
  2. 引導碼間隔為 9ms+4.5ms,用於同步傳送方和接收方的時鐘。
  3. 使用者編碼用於識別裝置型別,比如識別不同的遙控器。
  4. 透過脈衝串之間的時間間隔來實現訊號的調製(PWM)。

    • 邏輯「0」由 0.56ms 的 38kHz 載波和 0.565ms 的無載波間隔組成,週期1.125ms。
    • 邏輯「1」由 0.56ms 的 38kHz 載波和 1.69ms 的無載波間隔組成,週期2.25ms。

  5. 結束位由 0.56ms 的 38kHz 載波組成。

學習完原理,就進行我們的實踐吧。

4. 通訊示意圖

實現目標是我們有一個三色 LED 燈,三個燈各自有特定的訊號,遙控器/手機傳送紅外訊號,紅外編解碼模組收到資料,若含綠燈訊號,綠燈亮;再次傳送綠燈訊號,綠燈滅,黃燈和紅燈設定和效果一樣。

5. 程式設計準備

我們知道,NEC 紅外訊號編碼由 1 個 16 位使用者碼(分為高、低 8 位)、1 個 8 位資料碼和 1 個 8 位資料碼的反碼組成。格式如下:

「使用者碼高位、使用者碼低位、資料碼、資料反碼」

我們在做解碼操作時,只需要將遙控器對準紅外接收頭,按下需要解碼的按鍵,即可透過串列埠除錯助手檢視到解碼的結果,結果輸出為「使用者碼高位+使用者碼低位+命令碼」三位。

在做編碼傳送時傳送「地址+操作位+資料位1+資料位2+資料位3」即可。

所以在正式開始前,我們需要知道我們的遙控器/手機會發出怎樣的紅外訊號。

5.1 硬體準備與連線

準備所需要的硬體如下:

  • 紅外編解碼模組:YS-IRTM
  • 遙控器:紅外遙控器
  • 串列埠:USB 轉 TTL

我紅外遙控器用的是正點原子的,不一定要用同款,甚至有的手機也可以當作紅外遙控器用。

5.2 紅外接收,檢視編碼

先將紅外編解碼模組與 USB 轉 TTL 模組連線,插到電腦,用串列埠看看遙控器會發出怎樣的編碼。

接線如下:

YS-IRTMUSB 轉 TTL
VCC5V
RXDTX
TXDRX
GNDGND

接好效果如下:

開啟串列埠助手,選擇你的串列埠號,波特率選擇 9600;勾選顯示接收時間,將換行輸出,看的更清楚;勾選十六進位制顯示。

然後就可以按遙控器檢視編碼啦,以下是我的遙控器 1~9 的編碼。我們選擇 1 的 00 FF 16 為綠燈碼,2 的 00 FF 19 為黃燈碼,3 的 00 FF 10D 為紅燈碼。

5.3 紅外發射

紅外的發射指令格式如下:

地址操作位資料位1資料位2資料位3
A1(FA)XXXXXXXX
  • 地址:A1為預設地址(可改),FA 為通用地址 (不可改)。
  • 操作位:該位的資料用於代表當前的工作狀態。

    • F1:紅外發射狀態
    • F2:進入修改串列埠通訊地址狀態
    • F3:進入修改波特率狀態
  • 資料位:不同的操作位(工作狀態)有不同的資料內容,具體可看下錶。
操作位資料位1資料位2資料位3說明
F1使用者碼高位使用者碼低位命令碼
F21-FF0000資料位1代表需要修改的地址值
F31-4000001 - 4800bps
02 - 9600bps
03 - 19200bps
04 - 57600bps

比如:

目的編碼
發射 NEC 訊號編碼為 1C 2F 33A1 F1 1C 2F 33
修改串列埠通訊地址為 0xA5A1 F2 A5 00 00
修改波特率為9600bps(對應序號2)A1 F3 02 00 00

我們發射訊號後會收到如下結果:

編碼意義
F1發射成功
F2串列埠地址修改成功
F3波特率設定成功
無返回指令接收錯誤、操作不成功、重啟才有效

串列埠效果如下:

A1是串列埠通訊預設地址,修改串列埠通訊地址為A5後,再傳送「A1 F1 00 FF 16」就收不到了,要傳送「A5 F1 00 FF 16」才可以得到發射成功的「F1」。

6. 紅外對射實驗

我們來試試紅外對射,兩個紅外編解碼模組傳送、接收。

本實驗使用的硬體如下:

  • 兩個紅外編解碼模組:YS-IRTM
  • 兩個串列埠:USB 轉 TTL

兩對接線如下:

YS-IRTMUSB 轉 TTL
5V5V
RXDTXD
TXDRXD
GNDGND

接好效果如下:

電腦開啟兩個串列埠除錯助手,傳送編碼效果如下,紅框和綠框各是一次傳送結果。

紅外對射的互動方式雖然簡單,但是有很多應用場景。例如利用紅外對射進行無線控制和互動,實現遙控車輛、飛行器、電子遊戲等的操作和反饋。

7. 程式設計實戰

7.1 硬體接線

本教程使用的硬體如下:

  • 微控制器:STM32F103C8T6
  • 紅外編解碼模組:YS-IRTM
  • 遙控器:紅外遙控器
  • 小燈:三色 LED 燈模組
  • 串列埠:USB 轉 TTL
  • 燒錄器:ST-LINK V2

接線如下:

YS-IRTMLEDSTM32USB 轉 TTL
5V 5V
RXD A2
TXD A3
GND G
RA5
YA6
GA7
GNDG
A10TX
A9RX
GGND

燒錄的時候接線如下表,如果不會燒錄的話可以看我之前的文章【STM32下載程式的五種方法:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to...】。

ST-Link V2STM32
SWCLKSWCLK
SWDIOSWDIO
GNDGND
3.3V3V3

接好如下圖:

7.2 串列埠接收資料

串列埠接收資料在【STM32串列埠接收不定長資料(接收中斷+超時判斷):https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-usin...】有詳細介紹,沒看過的小夥伴可以看看。

UART_HandleTypeDef ys_uart_handle;

uint8_t ys_uart_rx_buf[YS_RX_BUF_SIZE];
uint8_t ys_uart_tx_buf[YS_TX_BUF_SIZE];
uint16_t ys_uart_rx_len = 0;

void ys_init(uint32_t baudrate)
{
    ys_uart_handle.Instance          = YS_INTERFACE;                 /* BT */
    ys_uart_handle.Init.BaudRate     = baudrate;                     /* 波特率 */
    ys_uart_handle.Init.WordLength   = UART_WORDLENGTH_8B;           /* 資料位 */
    ys_uart_handle.Init.StopBits     = UART_STOPBITS_1;              /* 停止位 */
    ys_uart_handle.Init.Parity       = UART_PARITY_NONE;             /* 校驗位 */
    ys_uart_handle.Init.Mode         = UART_MODE_TX_RX;              /* 收發模式 */
    ys_uart_handle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;          /* 無硬體流控 */
    ys_uart_handle.Init.OverSampling = UART_OVERSAMPLING_16;         /* 過取樣 */
    HAL_UART_Init(&ys_uart_handle);                                  /* 使能BT */
}

void ys_rx_clear(void)
{
    memset(ys_uart_rx_buf, 0, sizeof(ys_uart_rx_buf));              //清空接收緩衝區
    ys_uart_rx_len = 0;                                             //接收計數器清零
}

void YS_IRQHandler(void)
{
    uint8_t receive_data = 0;   
    if(__HAL_UART_GET_FLAG(&ys_uart_handle, UART_FLAG_RXNE) != RESET){      //獲取接收RXNE標誌位是否被置位
        if(ys_uart_rx_len >= sizeof(ys_uart_rx_buf))                        //如果接收的字元數大於接收緩衝區大小,
            ys_uart_rx_len = 0;                                             //則將接收計數器清零
        HAL_UART_Receive(&ys_uart_handle, &receive_data, 1, 1000);          //接收一個字元
        ys_uart_rx_buf[ys_uart_rx_len++] = receive_data;                    //將接收到的字元儲存在接收緩衝區
    }

    if (__HAL_UART_GET_FLAG(&ys_uart_handle, UART_FLAG_IDLE) != RESET)      //獲取接收空閒中斷標誌位是否被置位
    {
        int i = 0;
        printf("receive: \r\n");
        for(i = 0; i < ys_uart_rx_len; i++ )
            printf("%02X ", ys_uart_rx_buf[i]);                             //將接收到的資料列印出來
        printf("\r\n");
        control_led();
        ys_rx_clear();
        __HAL_UART_CLEAR_IDLEFLAG(&ys_uart_handle);                         //清除UART匯流排空閒中斷
    }
}

7.3 LED初始化

LED 燈的程式碼簡簡單單,只要進行一下三個燈的初始化就行。

void led_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    LED1_GPIO_CLK_ENABLE();                                 /* LED1時鐘使能 */
    LED2_GPIO_CLK_ENABLE();                                 /* LED2時鐘使能 */
    LED3_GPIO_CLK_ENABLE();                                 /* LED3時鐘使能 */

    gpio_init_struct.Pin = LED1_GPIO_PIN;                   /* LED1引腳 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推輓輸出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
    HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);       /* 初始化LED1引腳 */

    gpio_init_struct.Pin = LED2_GPIO_PIN;                   /* LED2引腳 */
    HAL_GPIO_Init(LED2_GPIO_PORT, &gpio_init_struct);       /* 初始化LED2引腳 */
    
    gpio_init_struct.Pin = LED3_GPIO_PIN;                   /* LED3引腳 */
    HAL_GPIO_Init(LED3_GPIO_PORT, &gpio_init_struct);       /* 初始化LED3引腳 */

    LED1(0);                                                /* 關閉 LED1 */
    LED2(0);                                                /* 關閉 LED2 */
    LED3(0);                                                /* 關閉 LED3 */
}

LED 的 .h檔案:

#ifndef _LED_H
#define _LED_H
#include "sys.h"


/******************************************************************************************/
/* 引腳 定義 */

#define LED1_GPIO_PORT                  GPIOA
#define LED1_GPIO_PIN                   GPIO_PIN_7
#define LED1_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PA口時鐘使能 */

#define LED2_GPIO_PORT                  GPIOA
#define LED2_GPIO_PIN                   GPIO_PIN_6
#define LED2_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PA口時鐘使能 */

#define LED3_GPIO_PORT                  GPIOA
#define LED3_GPIO_PIN                   GPIO_PIN_5
#define LED3_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PB口時鐘使能 */

/******************************************************************************************/
/* LED埠定義 */
#define LED1(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

#define LED2(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED2_GPIO_PORT, LED2_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED2_GPIO_PORT, LED2_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

#define LED3(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED3_GPIO_PORT, LED3_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED3_GPIO_PORT, LED3_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

/* LED取反定義 */
#define LED1_TOGGLE()   do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0)        /* 翻轉LED1 */
#define LED2_TOGGLE()   do{ HAL_GPIO_TogglePin(LED2_GPIO_PORT, LED2_GPIO_PIN); }while(0)        /* 翻轉LED2 */
#define LED3_TOGGLE()   do{ HAL_GPIO_TogglePin(LED3_GPIO_PORT, LED3_GPIO_PIN); }while(0)        /* 翻轉LED3 */

/******************************************************************************************/
/* 外部介面函式*/
void led_init(void);                                                                            /* LED初始化 */

#endif

7.4 LED控制

我的遙控器前兩位都一樣,只需要判斷第三位是不是為綠/黃/紅燈碼即可。若前兩位都不正確,那就不是我的遙控器發出的紅外訊號,不用再往下判斷了。

void control_led(void)
{
    if(ys_uart_rx_buf[0] == 0x00 && ys_uart_rx_buf[1] == 0xFF)      //地址碼正確
    {
      switch(ys_uart_rx_buf[2])                                     //判斷資料碼
      {
        case 0x16:                                                  //綠燈碼
          LED1_TOGGLE();                                            //翻轉LED1
          break;
        case 0x19:                                                  //黃燈碼
          LED2_TOGGLE();                                            //翻轉LED2
          break;
        case 0x0D:                                                  //紅燈碼
          LED3_TOGGLE();                                            //翻轉LED3
          break;
      }
    }
}

7.5 主函式

主函式如下:

int main(void)
{
    HAL_Init();                                 /* 初始化HAL庫 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 設定時鐘,72M */
    delay_init(72);                             /* 初始化延時函式 */
    usart_init(115200);                         /* 串列埠1波特率設為115200 */
    ys_init(9600);                              /* 串列埠2波特率設為9600 */
    led_init();

    printf("紅外控制燈……\r\n");

    while(1)
    {
        delay_ms(1000);
    }
}

7.6 執行過程

燒錄後,開啟串列埠,按下遙控器1、2、3,效果如下。

紅外編解碼模組(串列埠2)波特率是9600,串列埠除錯助手接收的是微控制器(串列埠1)的資料,波特率115200,大家不要弄混啦。

我們的三個小燈也開啟了。(我的小綠燈不是很亮,用舊了,嘻嘻)

8. 總結

祝賀大家成功點燈!當然,除了控制燈的開關,紅外編解碼模組還可以應用於更廣泛的場景,如家庭娛樂、醫療保健、工業自動化等等。隨著技術的不斷進步,紅外技術將持續演進,並在更多領域發揮作用。希望本文能夠為你提供了一個初步的瞭解,並激發你進一步深入研究和應用紅外技術的興趣。感謝各位看官,love and peace!

另外,想進大廠的同學,一定要好好學演算法,這是面試必備的。這裡準備了一份 BAT 大佬總結的 LeetCode 刷題寶典,很多人靠它們進了大廠。

刷題 | LeetCode演算法刷題神器,看完 BAT 隨你挑!

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

推薦閱讀:

  • 程式設計師必備程式設計資料大全
  • 程式設計師必備軟體資源

歡迎關注我的部落格:良許嵌入式教程網,滿滿都是乾貨!

相關文章