0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 程式開發
  3. 正文

ESP32 紅外線遙控 IR Remote 完整教學:NEC 協定解碼與 RMT 硬體實作

2026 年 6 月 13 日 27點熱度 0人點贊 0條評論

紅外線遙控技術簡介

ESP32 紅外線遙控(IR Remote)是物聯網專案中最實用的無線控制方式之一,幾乎所有家電(電視、冷氣、投影機、音響)都支援紅外線遙控。ESP32 內建的 RMT(Remote Control)外設能精確產生和解析紅外線訊號,實現智慧家電控制。

紅外線通訊原理

紅外線遙控使用 940nm 波長的紅外光,透過 38kHz(或其他頻率)載波調變來傳輸資料:

  • 載波(Carrier):38kHz 方波(duty cycle 1/3),LED 以此頻率閃爍
  • 調變:載波的存在(burst)代表邏輯訊號
  • 協定:不同廠商使用不同協定(NEC、Sony、RC-5、RC-6 等),最常見的是 NEC 協定
  • 接收端:整合式紅外線接收頭(如 VS1838B)解調載波,輸出 TTL 電平訊號

ESP32 紅外線接收與發送電路

NEC 協定完全解析

NEC 協定是最廣泛使用的紅外線遙控協定,由 NEC 開發,被大量 Android TV 盒子、風扇、燈具採用。

NEC 紅外線通訊協定訊框格式

完整的 NEC 訊框包含:

  1. Leader code(引導碼):9ms HIGH + 4.5ms LOW,標示訊框開始
  2. Address(位址,8 bits):裝置識別碼(如 0x00)
  3. ~Address(反相位址,8 bits):用於錯誤檢測
  4. Command(命令,8 bits):按鍵識別碼(如 0x45 = CH-)
  5. ~Command(反相命令,8 bits):用於錯誤檢測
  6. 總長:32 bits + Leader = 約 67.5ms

NEC 位元編碼與重複碼

位元編碼規則非常直觀:

  • 邏輯 '0':560μs HIGH + 560μs LOW(總長 1.12ms)
  • 邏輯 '1':560μs HIGH + 1.69ms LOW(總長 2.25ms)
  • 重複碼(Repeat):若按鍵持續按住,每 110ms 發送一組重複碼(9ms HIGH + 2.25ms LOW + 560μs HIGH)

ESP32 RMT 外設

ESP32 的 RMT(Remote Control) 外設是專為紅外線/無線遙控設計的硬體模組,不需要 CPU 干預就能產生或解析精確的脈衝序列:

  • 8 個通道(可任意映射到 GPIO)
  • 發送模式:硬體自動輸出脈衝序列,CPU 可同時做其他事
  • 接收模式:硬體自動測量脈衝寬度,存入 RAM
  • 解析度:12.5ns(80MHz 時脈 ÷ 1 分頻)
  • 最大脈衝數:256 組 per transaction

ESP32 RMT 外設 IR 載波脈衝序列

IR 接收:使用 RMT 解碼 NEC 協定

// ESP32 RMT IR 接收 — NEC 協定解碼
#include <driver/rmt.h>
#include   // 或使用 Arduino IRremote 函式庫

#define IR_RX_PIN   15   // RMT 通道 0 → GPIO 15
#define RMT_RX_CH   0

// NEC 協定解碼結果
typedef struct {
    uint16_t address;
    uint16_t command;
    bool     repeat;
} nec_decode_t;

// RMT 接收配置
rmt_config_t rmt_rx = {
    .rmt_mode = RMT_MODE_RX,
    .channel  = (rmt_channel_t)RMT_RX_CH,
    .gpio_num = (gpio_num_t)IR_RX_PIN,
    .clk_div  = 80,  // 80MHz / 80 = 1MHz → 1μs 解析度
    .mem_block_num = 1,
    .rx_config = {
        .idle_threshold = 10000,  // 10ms 無訊號視為空閒
        .filter_ticks_thresh = 100,  // 濾除 < 100μs 的雜訊
        .filter_en = true,
    }
};

// NEC 協定解碼
bool decodeNEC(rmt_item32_t* items, size_t len, nec_decode_t* result) {
    if (len < 34) return false; // Leader(2) + 32 bits // 檢查 Leader code (9ms HIGH + 4.5ms LOW) if (abs((int)items[0].duration0 - 9000) > 1000) return false;
    if (abs((int)items[0].duration1 - 4500) > 1000) return false;

    // 檢查是否為重複碼
    if (len == 2 && abs((int)items[0].duration0 - 9000) < 1000 &&
        abs((int)items[0].duration1 - 2250) < 500) { result->repeat = true;
        return true;
    }

    // 解碼 32 bits (items[1] ~ items[32])
    uint32_t code = 0;
    for (int i = 0; i < 32; i++) { code >>= 1;
        if (items[i+1].duration1 > 1000) {  // > 1ms = logic '1'
            code |= 0x80000000;
        }
    }

    result->address = (code >> 24) & 0xFF;
    result->command = (code >> 8) & 0xFF;
    result->repeat = false;
    return true;
}

void setup() {
    Serial.begin(115200);

    // 安裝 RMT 接收
    rmt_config(&rmt_rx);
    rmt_driver_install((rmt_channel_t)RMT_RX_CH, 1000, 0);

    Serial.println("IR 接收器就緒,等待紅外線訊號...");
}

void loop() {
    RingbufHandle_t rb = NULL;
    rmt_get_ringbuf_handle((rmt_channel_t)RMT_RX_CH, &rb);

    size_t len = 0;
    rmt_item32_t* items = (rmt_item32_t*)xRingbufferReceive(rb, &len, portMAX_DELAY);

    if (items && len > 0) {
        nec_decode_t result = {0};
        if (decodeNEC(items, len / sizeof(rmt_item32_t), &result)) {
            if (result.repeat) {
                Serial.println("重複碼 (按鍵持續按住)");
            } else {
                Serial.printf("NEC 解碼成功 | 位址: 0x%02X | 命令: 0x%02X\n",
                              result.address, result.command);
            }
        }
        vRingbufferReturnItem(rb, (void*)items);
    }
}

使用 IRremote 函式庫(更簡單)

// ESP32 IR 接收 — 使用 IRremote 函式庫
#include 

#define IR_RX_PIN 15
IRrecv irrecv(IR_RX_PIN);
decode_results results;

void setup() {
    Serial.begin(115200);
    irrecv.enableIRIn();
    Serial.println("IRremote 就緒,按下遙控器...");
}

void loop() {
    if (irrecv.decode(&results)) {
        Serial.printf("協定: %s | 值: 0x%08X | 位元數: %d\n",
                      results.decode_type == NEC ? "NEC" :
                      results.decode_type == SONY ? "SONY" :
                      results.decode_type == PANASONIC ? "PANASONIC" :
                      results.decode_type == RC5 ? "RC5" :
                      results.decode_type == RC6 ? "RC6" : "UNKNOWN",
                      results.value,
                      results.bits);
        irrecv.resume();
    }
}

IR 發送:模擬遙控器

// ESP32 IR 發送 — 使用 IRremote 函式庫
#include 

#define IR_TX_PIN 4
IRsend irsend(IR_TX_PIN);

// NEC 命令對照表(以電視為例)
#define NEC_CH_MINUS   0x45
#define NEC_CH         0x46
#define NEC_CH_PLUS    0x47
#define NEC_VOL_DOWN   0x15
#define NEC_VOL_UP     0x16
#define NEC_MUTE       0x0D
#define NEC_POWER      0x44
#define NEC_INPUT      0x40

void setup() {
    Serial.begin(115200);
    irsend.begin();
    Serial.println("IR 發送器就緒");
}

void loop() {
    if (Serial.available()) {
        char c = Serial.read();
        switch (c) {
            case 'v': irsend.sendNEC(NEC_VOL_UP, 32);   break;
            case 'V': irsend.sendNEC(NEC_VOL_DOWN, 32); break;
            case 'm': irsend.sendNEC(NEC_MUTE, 32);     break;
            case 'p': irsend.sendNEC(NEC_POWER, 32);    break;
            case '0'...'9':
                // 以 CH+ 範例,實際需查遙控器編碼
                irsend.sendNEC(NEC_CH, 32);
                break;
        }
    }
}

RMT 原生發送(精確控制脈衝)

// ESP32 RMT 原生發送 — 自行組裝 NEC 脈衝
#include <driver/rmt.h>

#define IR_TX_PIN   4
#define RMT_TX_CH   1

rmt_item32_t necItems[34];  // Leader(2) + 32 bits

void buildNECFrame(uint16_t addr, uint16_t cmd, rmt_item32_t* items) {
    // Leader code: 9ms HIGH + 4.5ms LOW
    items[0].duration0 = 9000;
    items[0].level0 = 1;
    items[0].duration1 = 4500;
    items[0].level1 = 0;

    // 32 bits data
    uint32_t code = addr | ((uint32_t)~addr << 8) | (cmd << 16) | ((uint32_t)~cmd << 24);
    for (int i = 0; i < 32; i++) {
        items[i+1].duration0 = 560;
        items[i+1].level0 = 1;
        if (code & (1 << 31)) {
            items[i+1].duration1 = 1690;  // logic '1'
        } else {
            items[i+1].duration1 = 560;   // logic '0'
        }
        items[i+1].level1 = 0;
        code <<= 1;
    }
}

void setup() {
    rmt_config_t rmt_tx = {
        .rmt_mode = RMT_MODE_TX,
        .channel  = (rmt_channel_t)RMT_TX_CH,
        .gpio_num = (gpio_num_t)IR_TX_PIN,
        .clk_div  = 80,
        .mem_block_num = 1,
        .tx_config = {
            .carrier_freq_hz = 38000,  // 38kHz 載波
            .carrier_level = 1,
            .carrier_en = true,         // 自動產生 38kHz 載波!
            .idle_level = 0,
            .loop_en = false,
        }
    };
    rmt_config(&rmt_tx);
    rmt_driver_install((rmt_channel_t)RMT_TX_CH, 0, 0);
}

void sendNEC(uint16_t addr, uint16_t cmd) {
    buildNECFrame(addr, cmd, necItems);
    rmt_write_items((rmt_channel_t)RMT_TX_CH, necItems, 34, true);
    Serial.printf("已發送 NEC: 0x%02X 0x%02X\n", addr, cmd);
}

紅外線遙控協定比較

紅外線遙控協定比較

特性 NEC Sony SIRC Philips RC-5 Philips RC-6
載波頻率 38 kHz 40 kHz 36 kHz 36 kHz
資料長度 32 bits 12/15/20 bits 14 bits 36 bits
引導碼 9ms + 4.5ms 2.4ms + 0.6ms 無 3ms + 0.89ms
位元 '0' 560μs + 560μs 1.2ms + 0.6ms 889μs + 889μs 444μs + 444μs
位元 '1' 560μs + 1.69ms 2.4ms + 0.6ms 889μs + 889μs 444μs + 444μs
重複碼 Leader + 2.25ms 無 Toggle bit Toggle bit
錯誤檢測 反相 + 固定長度 固定長度 無(Toggle 防重複) CRC (RC-6 MCE)

支援多協定的萬用遙控器

// ESP32 萬用遙控器 — 學習 + 發送
#include 
#include 


#define IR_RX_PIN 15
#define IR_TX_PIN 4
#define BTN_LEARN 0   // GPIO 0 (BOOT 按鍵)

IRrecv irrecv(IR_RX_PIN);
IRsend irsend(IR_TX_PIN);
decode_results results;

// 儲存已學習的編碼
std::map<String, decode_results> learnedCodes;

void setup() {
    Serial.begin(115200);
    pinMode(BTN_LEARN, INPUT_PULLUP);
    irrecv.enableIRIn();
    irsend.begin();
    Serial.println("=== 萬用遙控器 ===");
    Serial.println("按住 BOOT 按鍵 + 對準遙控器 = 學習");
    Serial.println("輸入 's <命名>' 來發送已學習的指令");
}

void learnCode(String name) {
    Serial.printf("等待接收 %s 的紅外線訊號...\n", name.c_str());
    while (!irrecv.decode(&results)) delay(10);
    learnedCodes[name] = results;
    irrecv.resume();
    Serial.printf("已學習: %s (協定=%d, 值=0x%08X)\n",
                  name.c_str(), results.decode_type, results.value);
}

void sendCode(String name) {
    if (learnedCodes.find(name) == learnedCodes.end()) {
        Serial.printf("未找到: %s\n", name.c_str());
        return;
    }
    auto& code = learnedCodes[name];
    irsend.send(code.decode_type, code.value, code.bits);
    Serial.printf("已發送: %s\n", name.c_str());
}

void loop() {
    if (digitalRead(BTN_LEARN) == LOW) {
        delay(50);
        if (digitalRead(BTN_LEARN) == LOW) {
            while (digitalRead(BTN_LEARN) == LOW) delay(10);
            Serial.println("輸入指令名稱...");
            while (!Serial.available()) delay(10);
            String name = Serial.readStringUntil('\n');
            name.trim();
            if (name.length() > 0) learnCode(name);
        }
    }

    if (Serial.available()) {
        String cmd = Serial.readStringUntil('\n');
        cmd.trim();
        if (cmd.startsWith("s ")) {
            sendCode(cmd.substring(2));
        }
    }
}

實務注意事項

  • 接收頭擺放:VS1838B 對正前方 ±45° 靈敏度最佳,側面幾乎無法接收
  • NPN 驅動電路:IR LED 需要 NPN 電晶體(如 2N2222)驅動,ESP32 GPIO 無法直接驅動 LED 到足夠亮度
  • 限流電阻:IR LED 串聯 100~220Ω 限流電阻,避免燒毀
  • 載波頻率匹配:發送端的載波頻率須與接收頭匹配(多數為 38kHz)
  • 環境光干擾:陽光或節能燈可能含紅外線成分,導致誤觸發—使用 RMT 的 filter 功能濾除短脈衝
  • 多個家電:不同品牌常使用不同協定,萬用遙控器需逐一學習

總結

ESP32 的 RMT 外設讓紅外線遙控的實作變得簡單而精確。從 NEC 協定的位元時序解析,到 RMT 硬體的自動載波產生,ESP32 能同時勝任 IR 接收和發送的角色。

結合 WiFi 和 MQTT,可以做出遠端遙控家電的 IoT 系統;配合之前介紹的 ESP32 Web Server,用手機瀏覽器就能控制電視和冷氣。

標籤: 教學
最後更新:2026 年 6 月 13 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇
下一篇 >

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang