StickS3 IR 红外发送 & 接收相关 API 与案例程序。
#include "M5Unified.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_encoder.h"
#define IR_SEND_PIN 46 // GPIO pin connected to IR LED transmitter
// NEC protocol parameters
uint16_t address = 0x0000; // NEC address (8-bit or 16-bit)
uint8_t command = 0x55; // NEC command byte
uint8_t repeats = 0; // Number of repeat frames (0 = no repeat)
rmt_channel_handle_t tx_chan = NULL;
rmt_encoder_handle_t copy_encoder = NULL;
// NEC protocol timing constants (microseconds)
#define NEC_HEADER_MARK 9000
#define NEC_HEADER_SPACE 4500
#define NEC_BIT_MARK 560
#define NEC_BIT_0_SPACE 560
#define NEC_BIT_1_SPACE 1690
#define NEC_REPEAT_MARK 9000
#define NEC_REPEAT_SPACE 2250
// IR carrier configuration
#define IR_CARRIER_FREQ_HZ 38000
#define IR_DUTY_CYCLE 0.33
// Function prototypes
void setup_rmt_tx();
bool sendNEC(uint16_t address, uint8_t command, uint8_t repeats);
void encodeNEC(uint32_t raw_data, rmt_symbol_word_t *symbols, size_t *symbol_count);
uint32_t NECRaw(uint16_t address, uint8_t command);
void setup() {
M5.begin();
Serial.begin(115200);
// Display initialization
M5.Display.setRotation(3);
M5.Display.setTextFont(&fonts::FreeMonoBold9pt7b);
M5.Display.clear();
M5.Display.setCursor(0, 0);
M5.Display.printf("StickS3 IR example");
Serial.println("StickS3 IR example");
// Initialize RMT TX channel
setup_rmt_tx();
Serial.printf("IR Send Pin: %d\n", IR_SEND_PIN);
// Enable external power output for IR LED module
M5.Power.setExtOutput(true, m5::ext_none);
delay(100);
}
void loop() {
// Build 32-bit NEC frame data
uint32_t raw = NECRaw(address, command);
Serial.printf("Send NEC: addr=0x%04X, cmd=0x%02X, raw=0x%08X\n", address, command, raw);
// -------- Send NEC frame --------
sendNEC(address, command, repeats);
M5.Display.fillRect(0, 30, 240, 105, TFT_BLACK);
M5.Display.setCursor(0, 30);
M5.Display.printf("Send NEC:\n");
M5.Display.printf(" addr=0x%04X\n", address);
M5.Display.printf(" cmd =0x%02X\n", command);
M5.Display.printf(" raw =0x%08X\n", raw);
address += 0x0001;
command += 0x01;
repeats = 0;
delay(2000);
}
// Initialize RMT TX channel
void setup_rmt_tx() {
// Configure RMT TX channel
rmt_tx_channel_config_t tx_chan_config = {
.gpio_num = (gpio_num_t)IR_SEND_PIN,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 1000000, // 1 us per tick
.mem_block_symbols = 64,
.trans_queue_depth = 4,
.flags = {
.invert_out = false,
.with_dma = false,
}
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_chan));
// Configure 38kHz carrier wave for IR transmission
rmt_carrier_config_t carrier_cfg = {
.frequency_hz = IR_CARRIER_FREQ_HZ,
.duty_cycle = IR_DUTY_CYCLE,
.flags = {
.polarity_active_low = false,
}
};
ESP_ERROR_CHECK(rmt_apply_carrier(tx_chan, &carrier_cfg));
// Create copy encoder for pre-encoded symbols
rmt_copy_encoder_config_t encoder_config = {};
ESP_ERROR_CHECK(rmt_new_copy_encoder(&encoder_config, ©_encoder));
// Enable TX channel
ESP_ERROR_CHECK(rmt_enable(tx_chan));
}
/*
* Send NEC IR frame via RMT.
*
* @param address NEC address (8-bit or 16-bit)
* @param command NEC command byte
* @param repeats Number of repeat frames to send
*
* @return true if transmission successful
*/
bool sendNEC(uint16_t address, uint8_t command, uint8_t repeats) {
// Build 32-bit NEC raw data
uint32_t raw = NECRaw(address, command);
// Buffer for RMT symbols
rmt_symbol_word_t symbols[68]; // Header + 32 bits + ending mark
size_t symbol_count = 0;
encodeNEC(raw, symbols, &symbol_count);
// RMT transmit configuration
rmt_transmit_config_t tx_config = {
.loop_count = 0,
.flags = {
.eot_level = 0,
}
};
// Transmit the frame
esp_err_t ret = rmt_transmit(tx_chan, copy_encoder, symbols,
symbol_count * sizeof(rmt_symbol_word_t),
&tx_config);
if (ret == ESP_OK) {
// Wait for transmission completion
ret = rmt_tx_wait_all_done(tx_chan, 1000);
}
// Send repeat frames if requested
for (int i = 0; i < repeats; i++) {
delay(108); // NEC repeat frame interval
// TODO: Implement repeat frame
// Repeat frame: 9ms mark + 2.25ms space + 560us mark
}
return (ret == ESP_OK);
}
/*
* Encode NEC protocol data into RMT symbols.
*
* @param raw_data 32-bit NEC frame data
* @param symbols Output buffer for RMT symbols
* @param symbol_count Number of symbols generated
*/
void encodeNEC(uint32_t raw_data, rmt_symbol_word_t *symbols, size_t *symbol_count) {
size_t idx = 0;
// NEC header: ~9 ms mark + ~4.5 ms space
symbols[idx].duration0 = NEC_HEADER_MARK;
symbols[idx].level0 = 1;
symbols[idx].duration1 = NEC_HEADER_SPACE;
symbols[idx].level1 = 0;
idx++;
// Encode 32 data bits (LSB first)
for (int i = 0; i < 32; i++) {
// Mark duration: always 560 us
symbols[idx].duration0 = NEC_BIT_MARK;
symbols[idx].level0 = 1;
// Space duration distinguishes logic 0 and logic 1
if (raw_data & (1UL << i)) {
symbols[idx].duration1 = NEC_BIT_1_SPACE; // Logic 1: 1690 us
} else {
symbols[idx].duration1 = NEC_BIT_0_SPACE; // Logic 0: 560 us
}
symbols[idx].level1 = 0;
idx++;
}
// Ending mark: 560 us
symbols[idx].duration0 = NEC_BIT_MARK;
symbols[idx].level0 = 1;
symbols[idx].duration1 = 0;
symbols[idx].level1 = 0;
idx++;
*symbol_count = idx;
}
/*
* Build 32-bit NEC raw data from address and command.
*
* NEC frame format (LSB first):
* bit 0-15 : Address field (8-bit + inverse, or full 16-bit)
* bit 16-23 : Command byte
* bit 24-31 : Inverse of command byte
*
* @param address NEC address (8-bit with auto-inverse, or 16-bit extended)
* @param command NEC command byte
*
* @return 32-bit NEC raw data ready for encoding
*/
uint32_t NECRaw(uint16_t address, uint8_t command) {
uint16_t nec_addr;
// Standard NEC: 8-bit address + inverse byte
if (address <= 0x00FF) {
uint8_t addr8 = address & 0xFF;
nec_addr = ((uint16_t)(~addr8) << 8) | addr8;
}
// Extended NEC: full 16-bit address
else {
nec_addr = address;
}
// Assemble 32-bit NEC frame
uint32_t raw = 0;
raw |= (uint32_t)nec_addr; // Address field
raw |= (uint32_t)command << 16; // Command byte
raw |= (uint32_t)(~command) << 24; // Inverted command byte
return raw;
}cfg.internal_spk = false;),否则无法正常接收。#include "M5Unified.h"
#include "driver/rmt_rx.h"
#define IR_RECEIVE_PIN 42
rmt_channel_handle_t rx_chan = NULL;
bool decodeNEC(rmt_symbol_word_t *rx_raw_symbols, uint32_t *out_raw, bool *out_repeat);
// Initialize RMT RX channel
void setup_rmt_rx() {
rmt_rx_channel_config_t rx_chan_config = {
.gpio_num = (gpio_num_t)IR_RECEIVE_PIN,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 1000000, // 1 us per tick
.mem_block_symbols = 128,
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan));
ESP_ERROR_CHECK(rmt_enable(rx_chan));
}
void setup() {
auto cfg = M5.config();
cfg.internal_spk = false; // Disable speaker amp to avoid IR RX interference
M5.begin(cfg);
Serial.begin(115200);
// Display initialization
M5.Display.setRotation(3);
M5.Display.setFont(&fonts::FreeMonoBold9pt7b);
M5.Display.clear();
M5.Display.println("StickS3 IR example");
M5.Display.setCursor(0, 30);
M5.Display.println("Waiting for NEC...");
setup_rmt_rx();
// Enable external power output for IR receiver module
M5.Power.setExtOutput(true, m5::ext_none);
}
void loop() {
M5.update();
// Buffer for received RMT symbols
rmt_symbol_word_t rx_raw_symbols[64];
// RMT receive configuration
rmt_receive_config_t receive_config = {
.signal_range_min_ns = 1000,
.signal_range_max_ns = 20000000
};
if (rmt_receive(rx_chan, rx_raw_symbols, sizeof(rx_raw_symbols), &receive_config) == ESP_OK)
{
delay(100); // Allow DMA buffer to be fully populated
uint32_t rx_data = 0;
bool repeat_frame = false;
// -------- Decode NEC frame --------
bool valid = decodeNEC(rx_raw_symbols, &rx_data, &repeat_frame);
if (repeat_frame) {
Serial.println("NEC Repeat Frame");
M5.Display.fillRect(0, 30, 240, 105, TFT_BLACK);
M5.Display.setCursor(0, 30);
M5.Display.setTextColor(YELLOW);
M5.Display.println("NEC Repeat");
}
else if (valid) {
uint16_t rx_addr = rx_data & 0xFFFF;
uint8_t rx_cmd = (rx_data >> 16) & 0xFF;
Serial.printf( "Received NEC: Addr: 0x%04X, Cmd: 0x%02X, Raw: 0x%08X\n", rx_addr, rx_cmd, rx_data);
M5.Display.fillRect(0, 30, 240, 105, TFT_BLACK);
M5.Display.setCursor(0, 30);
M5.Display.setTextColor(GREEN);
M5.Display.printf("Received NEC:\n");
M5.Display.printf("Addr: 0x%04X\n", rx_addr);
M5.Display.printf("Cmd: 0x%02X\n", rx_cmd);
M5.Display.printf("Raw: 0x%08X\n", rx_data);
}
else {
Serial.println("Signal received, but not a valid NEC frame.");
}
}
delay(10);
M5.Display.setTextColor(WHITE);
}
/*
* Decode a NEC IR frame from RMT symbols.
*
* @param rx_raw_symbols Pointer to RMT RX symbol buffer
* @param out_raw Decoded 32-bit NEC raw data (LSB first)
* @param out_repeat Set to true if a NEC repeat frame is detected
*
* @return true if a valid NEC data frame is decoded
*/
bool decodeNEC(rmt_symbol_word_t *rx_raw_symbols, uint32_t *out_raw, bool *out_repeat) {
*out_raw = 0;
*out_repeat = false;
uint32_t header_low = rx_raw_symbols[0].duration0;
uint32_t header_high = rx_raw_symbols[0].duration1;
// Standard NEC header: ~9 ms LOW + ~4.5 ms HIGH
if (header_low > 8000 && header_high > 4000) {
// Valid NEC header, continue decoding
}
// NEC repeat frame: ~9 ms LOW + ~2.25 ms HIGH
else if (header_low > 8000 &&
header_high > 2000 &&
header_high < 3000) {
*out_repeat = true;
return false;
}
else {
return false;
}
// Decode 32 NEC data bits (LSB first)
for (int i = 0; i < 32; i++) {
uint32_t mark = rx_raw_symbols[i + 1].duration0;
uint32_t space = rx_raw_symbols[i + 1].duration1;
// NEC mark duration should be ~560 us
if (mark < 300 || mark > 800) {
return false;
}
// Space duration distinguishes logic 0 and logic 1
if (space > 1000) {
*out_raw |= (1UL << i);
}
}
// Verify command byte and its inverse
uint8_t cmd = (*out_raw >> 16) & 0xFF;
uint8_t cmd_inv = (*out_raw >> 24) & 0xFF;
if ((cmd ^ cmd_inv) != 0xFF) {
return false;
}
return true;
}发送端与接收端分别运行在两台 StickS3 设备上,发送端每两秒发送一次 NEC 红外信号,接收端接收到信号后会在串口与屏幕上显示接收到的地址与命令。
