博客 / 詳情

返回

遙控器的秘密你知道嗎?

大家好,我是良許。

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

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

本文目錄如下,全文 4000 字,無廢話完全乾貨,請君閲讀。

1. 紅外編解碼模塊介紹

1.1 型號介紹

紅外編解碼模塊使用特定的紅外協議來確保設備之間的通信準確性和兼容性。常見的紅外編解碼協議包括 NEC、RC5、RC6 等。

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

  • 紅外發射頭: 用於發射紅外信號,波長為 940nm,頻率為 38k,協議為 NEC 編碼的紅外信號。
  • 紅外接收頭:用於接收 NEC 紅外信號,進而單片機進行分析解碼操作。
  • 紅外頭擴展:該接口為紅外發射頭的擴展,可以連接多個紅外發射頭(常稱紅外發射模塊),用於安放到不同的位置,實現多方位控制。

1.2 工作參數及引腳介紹

默認波特率是 9600。

YS-IRTM STM32
GND GND
RXD A2(串口2)/B10(串口3)
TXD A3(串口2)/B11(串口3)
5V 5V

2. 紅外編解碼原理

我們今天介紹的紅外編解碼模塊採用 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 載波組成。

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

3. 通信示意圖

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

4. 編程準備

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

「用户碼高位、用户碼低位、數據碼、數據反碼」

我們在做解碼操作時,只需要將遙控器對準紅外接收頭,按下需要解碼的按鍵,即可通過串口調試助手查看到解碼的結果,結果輸出為「用户碼高位+用户碼低位+命令碼」三位。

在做編碼發送時發送「地址+操作位+數據位1+數據位2+數據位3」即可。

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

4.1 硬件準備與連接

準備所需要的硬件如下:

  • 紅外編解碼模塊:YS-IRTM
  • 遙控器:紅外遙控器
  • 串口:USB 轉 TTL

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

4.2 紅外接收,查看編碼

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

接線如下:

YS-IRTM USB 轉 TTL
VCC 5V
RXD TX
TXD RX
GND GND

接好效果如下:

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

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

4.3 紅外發射

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

地址 操作位 數據位1 數據位2 數據位3
A1(FA) XX XX XX XX
  • 地址:A1為默認地址(可改),FA 為通用地址 (不可改)。
  • 操作位:該位的數據用於代表當前的工作狀態。

    • F1:紅外發射狀態
    • F2:進入修改串口通信地址狀態
    • F3:進入修改波特率狀態
  • 數據位:不同的操作位(工作狀態)有不同的數據內容,具體可看下錶。
操作位 數據位1 數據位2 數據位3 説明
F1 用户碼高位 用户碼低位 命令碼
F2 1-FF 00 00 數據位1代表需要修改的地址值
F3 1-4 00 00 01 - 4800bps02 - 9600bps03 - 19200bps04 - 57600bps

比如:

目的 編碼
發射 NEC 信號編碼為 1C 2F 33 A1 F1 1C 2F 33
修改串口通信地址為 0xA5 A1 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」。

5. 紅外對射實驗

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

本實驗使用的硬件如下:

  • 兩個紅外編解碼模塊:YS-IRTM
  • 兩個串口:USB 轉 TTL

兩對接線如下:

YS-IRTM USB 轉 TTL
5V 5V
RXD TXD
TXD RXD
GND GND

接好效果如下:

電腦打開兩個串口調試助手,發送編碼效果如下,紅框和綠框各是一次發送結果。

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

6. 編程實戰

6.1 硬件接線

本教程使用的硬件如下:

  • 單片機:STM32F103C8T6
  • 紅外編解碼模塊:YS-IRTM
  • 遙控器:紅外遙控器
  • 小燈:三色 LED 燈模塊
  • 串口:USB 轉 TTL
  • 燒錄器:ST-LINK V2

接線如下:

YS-IRTM LED STM32 USB 轉 TTL
5V 5V
RXD A2
TXD A3
GND G
R A5
Y A6
G A7
GND G
A10 TX
A9 RX
G GND

燒錄的時候接線如下表,如果不會燒錄的話可以看我之前的文章【STM32下載程序的五種方法】。

ST-Link V2 STM32
SWCLK SWCLK
SWDIO SWDIO
GND GND
3.3V 3V3

接好如下圖:

6.2 串口接收數據

串口接收數據在【STM32串口接收不定長數據(接收中斷+超時判斷)】有詳細介紹,沒看過的小夥伴可以看看。

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總線空閒中斷
    }
}

6.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

6.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;
      }
    }
}

6.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);
    }
}

6.6 運行過程

燒錄後,打開串口,按下遙控器1、2、3,效果如下。

紅外編解碼模塊(串口2)波特率是9600,串口調試助手接收的是單片機(串口1)的數據,波特率115200,大家不要弄混啦。

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

7. 總結

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

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.