├── LICENSE ├── README.md ├── esp32-rmt-ir.cpp ├── esp32-rmt-ir.h ├── examples └── esp32-rmt-ir-txrx │ └── esp32-rmt-ir-txrx.ino └── library.properties /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 junkfix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp32-rmt-ir 2 | Arduino Library for ESP32 using RMT peripherals for common IR protocols NEC, Sony, Samsung and RC5 transmit and receive 3 | 4 | The example is provided and tested using Arduino 3.0.0 based on ESP-IDF 5.1.4 5 | -------------------------------------------------------------------------------- /esp32-rmt-ir.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | https://github.com/junkfix/esp32-rmt-ir 3 | */ 4 | #include "esp32-rmt-ir.h" 5 | 6 | uint8_t irRxPin = 34; 7 | uint8_t irTxPin = 4; 8 | 9 | const ir_protocol_t proto[PROTO_COUNT] = { 10 | [UNK] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, "UNK" }, 11 | [NEC] = { 9000, 4500, 560, 1690, 560, 560, 560, 0, 38000, "NEC" }, 12 | [SONY] = { 2400, 600, 1200, 600, 600, 600, 0, 0, 40000, "SONY" }, 13 | [SAM] = { 4500, 4500, 560, 1690, 560, 560, 560, 0, 38000, "SAM" }, 14 | [RC5] = { 0, 0, 889, 889, 889, 889, 0, 0, 38000, "RC5" }, 15 | }; 16 | 17 | const uint8_t bitMargin = 120; 18 | 19 | volatile uint8_t irTX = 0; 20 | volatile uint8_t irRX = 0; 21 | 22 | void recvIR(void* param){ 23 | rmt_rx_done_event_data_t rx_data; 24 | QueueHandle_t rx_queue = xQueueCreate(1, sizeof(rx_data)); 25 | 26 | for(;;){ 27 | if(irTX){vTaskDelay( 300 / portTICK_PERIOD_MS ); continue;} 28 | irRX = 1; 29 | rmt_channel_handle_t rx_channel = NULL; 30 | rmt_symbol_word_t symbols[64]; 31 | 32 | rmt_receive_config_t rx_config = { 33 | .signal_range_min_ns = 1250, 34 | .signal_range_max_ns = 12000000, 35 | }; 36 | 37 | rmt_rx_channel_config_t rx_ch_conf = { 38 | .gpio_num = static_cast(irRxPin), 39 | .clk_src = RMT_CLK_SRC_DEFAULT, 40 | .resolution_hz = 1000000, 41 | .mem_block_symbols = 64, 42 | }; 43 | 44 | rmt_new_rx_channel(&rx_ch_conf, &rx_channel); 45 | rmt_rx_event_callbacks_t cbs = { 46 | .on_recv_done = irrx_done, 47 | }; 48 | 49 | rmt_rx_register_event_callbacks(rx_channel, &cbs, rx_queue); 50 | rmt_enable(rx_channel); 51 | rmt_receive(rx_channel, symbols, sizeof(symbols), &rx_config); 52 | for(;;){ 53 | if(irTX){break;} 54 | 55 | if (xQueueReceive(rx_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdPASS){ 56 | 57 | size_t len = rx_data.num_symbols; 58 | rmt_symbol_word_t *rx_items = rx_data.received_symbols; 59 | 60 | if(len > 11){ 61 | uint32_t rcode = 0; irproto rproto = UNK; 62 | if( rcode = nec_check(rx_items, len) ){ 63 | rproto = NEC; 64 | }else if( rcode = sony_check(rx_items, len) ){ 65 | rproto = SONY; 66 | }else if( rcode = sam_check(rx_items, len) ){ 67 | rproto = SAM; 68 | }else if( rcode = rc5_check(rx_items, len) ){ 69 | rproto = RC5; 70 | } 71 | irReceived(rproto, rcode, len, rx_items); 72 | } 73 | 74 | rmt_receive(rx_channel, symbols, sizeof(symbols), &rx_config); 75 | } 76 | } 77 | rmt_disable(rx_channel); 78 | rmt_del_channel(rx_channel); 79 | irRX = 0; 80 | } 81 | vQueueDelete(rx_queue); 82 | vTaskDelete(NULL); 83 | } 84 | 85 | 86 | 87 | void sendIR(irproto brand, uint32_t code, uint8_t bits, uint8_t burst, uint8_t repeat) { 88 | 89 | irTX = 1; 90 | for(;;){ 91 | if(irRX == 0){break;} 92 | vTaskDelay( 2 / portTICK_PERIOD_MS ); 93 | } 94 | 95 | sendir_t codetx = {brand, code, bits}; 96 | 97 | rmt_channel_handle_t tx_channel = NULL; 98 | 99 | rmt_tx_channel_config_t txconf = { 100 | .gpio_num = static_cast(irTxPin), 101 | .clk_src = RMT_CLK_SRC_DEFAULT, 102 | .resolution_hz = 1000000, // 1MHz resolution, 1 tick = 1us 103 | .mem_block_symbols = 64, 104 | .trans_queue_depth = 4, 105 | }; 106 | 107 | if(rmt_new_tx_channel(&txconf, &tx_channel) != ESP_OK) { 108 | return; 109 | } 110 | float duty = 0.50; 111 | uint8_t rptgap = 100; if(brand == SONY){rptgap = 24; duty = 0.40;} 112 | 113 | rmt_carrier_config_t carrier_cfg = { 114 | .frequency_hz = proto[brand].frequency, 115 | .duty_cycle = duty, 116 | //.flags = { 117 | // .polarity_active_low = 0 118 | //} 119 | }; 120 | 121 | rmt_apply_carrier(tx_channel, &carrier_cfg); 122 | 123 | rmt_ir_encoder_t *ir_encoder = (rmt_ir_encoder_t *)calloc(1, sizeof(rmt_ir_encoder_t)); 124 | ir_encoder->base.encode = rmt_encode_ir; 125 | ir_encoder->base.del = rmt_del_ir_encoder; 126 | ir_encoder->base.reset = rmt_ir_encoder_reset; 127 | 128 | rmt_copy_encoder_config_t copy_encoder_config = {}; 129 | rmt_new_copy_encoder(©_encoder_config, &ir_encoder->copy_encoder); 130 | 131 | rmt_encoder_handle_t encoder_handle = &ir_encoder->base; 132 | 133 | rmt_enable(tx_channel); 134 | 135 | rmt_transmit_config_t tx_config = { 136 | .loop_count = 0, 137 | //.flags = { 138 | // .eot_level = 0 139 | //} 140 | }; 141 | for(uint8_t j = 0; j < repeat; j++){ 142 | for(uint8_t i = 0; i < burst; i++){ 143 | rmt_transmit(tx_channel, encoder_handle, &codetx, sizeof(codetx), &tx_config); 144 | rmt_tx_wait_all_done(tx_channel, portMAX_DELAY); 145 | vTaskDelay( rptgap / portTICK_PERIOD_MS ); 146 | } 147 | vTaskDelay( 100 / portTICK_PERIOD_MS ); 148 | } 149 | 150 | rmt_disable(tx_channel); 151 | rmt_del_channel(tx_channel); 152 | rmt_del_encoder(encoder_handle); 153 | irTX = 0; 154 | } 155 | 156 | 157 | uint32_t nec_check(rmt_symbol_word_t *item, size_t &len){ 158 | const uint8_t totalData = 34; 159 | if(len < totalData ){ 160 | return 0; 161 | } 162 | const uint32_t m = 0x80000000; 163 | uint32_t code = 0; 164 | for(uint8_t i = 0; i < totalData; i++){ 165 | if(i == 0){//header 166 | if(!checkbit(item[i], proto[NEC].header_high, proto[NEC].header_low)){return 0;} 167 | }else if(i == 33){//footer 168 | if(!checkbit(item[i], proto[NEC].footer_high, proto[NEC].footer_low)){return 0;} 169 | }else if(checkbit(item[i], proto[NEC].one_high, proto[NEC].one_low)){ 170 | code |= (m >> (i - 1) ); 171 | }else if(!checkbit(item[i], proto[NEC].zero_high, proto[NEC].zero_low)){ 172 | //Serial.printf("BitError i:%d\n",i); 173 | return 0; 174 | } 175 | } 176 | return code; 177 | } 178 | 179 | 180 | uint32_t sam_check(rmt_symbol_word_t *item, size_t &len){ 181 | const uint8_t totalData = 34; 182 | if(len < totalData ){ 183 | return 0; 184 | } 185 | const uint32_t m = 0x80000000; 186 | uint32_t code = 0; 187 | for(uint8_t i = 0; i < totalData; i++){ 188 | if(i == 0){//header 189 | if(!checkbit(item[i], proto[SAM].header_high, proto[SAM].header_low)){return 0;} 190 | }else if(i == 33){//footer 191 | if(!checkbit(item[i], proto[SAM].footer_high, proto[SAM].footer_low)){return 0;} 192 | }else if(checkbit(item[i], proto[SAM].one_high, proto[SAM].one_low)){ 193 | code |= (m >> (i - 1) ); 194 | }else if(!checkbit(item[i], proto[SAM].zero_high, proto[SAM].zero_low)){ 195 | //Serial.printf("BitError i:%d\n",i); 196 | return 0; 197 | } 198 | } 199 | return code; 200 | } 201 | 202 | 203 | uint32_t sony_check(rmt_symbol_word_t *item, size_t &len){ 204 | const uint8_t totalMin = 12; 205 | uint8_t i = 0; 206 | if(len < totalMin || !checkbit(item[i], proto[SONY].header_high, proto[SONY].header_low)){ 207 | return 0; 208 | } 209 | i++; 210 | uint32_t m = 0x80000000; 211 | uint32_t code = 0; 212 | uint8_t maxData = 20; 213 | if(len < maxData){maxData = 15;} 214 | if(len < maxData){maxData = 12;} 215 | for(int j = 0; j < maxData - 1; j++){ 216 | if(checkbit(item[i], proto[SONY].one_high, proto[SONY].one_low)){ 217 | code |= (m >> j); 218 | }else if(checkbit(item[i], proto[SONY].zero_high, proto[SONY].zero_low)){ 219 | code |= 0 & (m >> j); 220 | }else if(item[i].duration1 > 15000){ //space repeats 221 | //len = i+1; 222 | break; 223 | }else{ 224 | //Serial.printf("BitError %d, max:%d, j:%d\n",i, maxData, j); Serial.printf("d0:%d, d1:%d, L0:%d L1:%d\n", item[i].duration0, item[i].duration1, item[i].level0, item[i].level1); 225 | return 0; 226 | } 227 | i++; 228 | } 229 | code = code >> (32 - i); 230 | len = i+1; 231 | return code; 232 | } 233 | 234 | 235 | uint32_t rc5_check(rmt_symbol_word_t *item, size_t &len){ 236 | if(len < 13 || len > 30 ){ 237 | return 0; 238 | } 239 | const uint16_t RC5_High = proto[RC5].one_high + proto[RC5].one_low; 240 | uint32_t code = 0; bool c = false; 241 | for(uint8_t i = 0; i < len; i++){ 242 | uint32_t d0 = item[i].duration0; 243 | uint32_t d1 = item[i].duration1; 244 | if (rc5_bit(d0, proto[RC5].one_low)) { 245 | code = (code << 1) | c; 246 | c = rc5_bit(d1, RC5_High) ? !c : c; 247 | } else if (rc5_bit(d0, RC5_High)) { 248 | code = (code << 2) | (item[i].level0 << 1) | !item[i].level0; 249 | c = rc5_bit(d1, proto[RC5].one_low) ? !c : c; 250 | }else{ 251 | //Serial.printf("BitError i:%d\n",i); 252 | return 0; 253 | } 254 | } 255 | return code; 256 | } 257 | 258 | bool rc5_bit(uint32_t d, uint32_t v) { 259 | return (d < (v + bitMargin)) && (d > (v - bitMargin)); 260 | } 261 | 262 | 263 | bool checkbit(rmt_symbol_word_t &item, uint16_t high, uint16_t low){ 264 | return item.level0 == 0 && item.level1 != 0 && 265 | item.duration0 < (high + bitMargin) && item.duration0 > (high - bitMargin) && 266 | item.duration1 < (low + bitMargin) && item.duration1 > (low - bitMargin); 267 | } 268 | bool irrx_done(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *udata){ 269 | BaseType_t h = pdFALSE; 270 | QueueHandle_t q = (QueueHandle_t)udata; 271 | xQueueSendFromISR(q, edata, &h); 272 | return h == pdTRUE; 273 | } 274 | 275 | 276 | 277 | size_t rmt_encode_ir(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) { 278 | rmt_ir_encoder_t *ir_encoder = __containerof(encoder, rmt_ir_encoder_t, base); 279 | 280 | rmt_encode_state_t fstate = RMT_ENCODING_RESET; 281 | 282 | size_t encoded_symbols = 0; 283 | rmt_encoder_handle_t copy_encoder = ir_encoder->copy_encoder; 284 | 285 | const sendir_t *send_data = (const sendir_t *)primary_data; 286 | ir_protocol_t protocol = proto[send_data->irtype]; 287 | 288 | 289 | if (ir_encoder->state == 0) { 290 | if(protocol.header_high>0){ 291 | rmt_symbol_word_t header_symbol; 292 | fill_item(header_symbol, protocol.header_high, protocol.header_low, 0); 293 | encoded_symbols += copy_encoder->encode(copy_encoder, channel, &header_symbol, sizeof(rmt_symbol_word_t), &fstate); 294 | }else{ 295 | fstate = RMT_ENCODING_COMPLETE; 296 | } 297 | 298 | if (fstate & RMT_ENCODING_COMPLETE) { 299 | ir_encoder->state = 1; 300 | ir_encoder->bit_index = 0; 301 | } 302 | if (fstate & RMT_ENCODING_MEM_FULL) { 303 | fstate = RMT_ENCODING_MEM_FULL; 304 | *ret_state = fstate; 305 | return encoded_symbols; 306 | } 307 | } 308 | 309 | if (ir_encoder->state == 1) { 310 | 311 | uint8_t rcspecial = 0; 312 | if(send_data->irtype == RC5){rcspecial = 1;} 313 | 314 | rmt_symbol_word_t one_symbol; 315 | fill_item(one_symbol, protocol.one_high, protocol.one_low, rcspecial); 316 | 317 | rmt_symbol_word_t zero_symbol; 318 | fill_item(zero_symbol, protocol.zero_high, protocol.zero_low, 0); 319 | 320 | for (uint8_t i = ir_encoder->bit_index; i < send_data->bits; i++) { 321 | if (send_data->ircode & (1 << (send_data->bits - 1 - i))) { 322 | encoded_symbols += copy_encoder->encode(copy_encoder, channel, &one_symbol, sizeof(rmt_symbol_word_t), &fstate); 323 | } else { 324 | encoded_symbols += copy_encoder->encode(copy_encoder, channel, &zero_symbol, sizeof(rmt_symbol_word_t), &fstate); 325 | } 326 | if (fstate & RMT_ENCODING_MEM_FULL) { 327 | fstate = RMT_ENCODING_MEM_FULL; 328 | ir_encoder->bit_index = i + 1; 329 | *ret_state = fstate; 330 | return encoded_symbols; 331 | } 332 | } 333 | ir_encoder->state = 2; 334 | } 335 | 336 | if (ir_encoder->state == 2) { 337 | if(protocol.footer_high>0){ 338 | rmt_symbol_word_t end_symbol; 339 | fill_item(end_symbol, protocol.footer_high, protocol.footer_low, 0); 340 | encoded_symbols += copy_encoder->encode(copy_encoder, channel, &end_symbol, sizeof(rmt_symbol_word_t), &fstate); 341 | }else{ 342 | fstate = (rmt_encode_state_t)((int)fstate | RMT_ENCODING_COMPLETE); 343 | } 344 | if (fstate & RMT_ENCODING_COMPLETE) { 345 | ir_encoder->state = 0; 346 | *ret_state = RMT_ENCODING_COMPLETE; 347 | } 348 | } 349 | 350 | return encoded_symbols; 351 | } 352 | 353 | void fill_item(rmt_symbol_word_t &item, uint16_t high, uint16_t low, bool bit){ 354 | item.level0 = !bit; 355 | item.duration0 = high; 356 | item.level1 = bit; 357 | item.duration1 = low; 358 | } 359 | 360 | esp_err_t rmt_del_ir_encoder(rmt_encoder_t *encoder) { 361 | rmt_ir_encoder_t *ir_encoder = __containerof(encoder, rmt_ir_encoder_t, base); 362 | rmt_del_encoder(ir_encoder->copy_encoder); 363 | free(ir_encoder); 364 | return ESP_OK; 365 | } 366 | 367 | esp_err_t rmt_ir_encoder_reset(rmt_encoder_t *encoder) { 368 | rmt_ir_encoder_t *ir_encoder = __containerof(encoder, rmt_ir_encoder_t, base); 369 | rmt_encoder_reset(ir_encoder->copy_encoder); 370 | ir_encoder->state = 0; 371 | ir_encoder->bit_index = 0; 372 | return ESP_OK; 373 | } 374 | -------------------------------------------------------------------------------- /esp32-rmt-ir.h: -------------------------------------------------------------------------------- 1 | /* 2 | https://github.com/junkfix/esp32-rmt-ir 3 | */ 4 | 5 | #ifndef ir_rmt_esp32 6 | #define ir_rmt_esp32 7 | 8 | #include 9 | #include "freertos/FreeRTOS.h" 10 | #include "freertos/task.h" 11 | #include "driver/rmt_rx.h" 12 | #include "driver/rmt_tx.h" 13 | #include "driver/rmt_encoder.h" 14 | 15 | extern uint8_t irRxPin; 16 | extern uint8_t irTxPin; 17 | 18 | enum irproto { UNK, NEC, SONY, SAM, RC5, PROTO_COUNT }; 19 | 20 | 21 | typedef struct { 22 | rmt_encoder_t base; 23 | rmt_encoder_t *copy_encoder; 24 | uint8_t bit_index; 25 | int state; 26 | } rmt_ir_encoder_t; 27 | 28 | typedef struct { 29 | irproto irtype; 30 | uint32_t ircode; 31 | uint8_t bits; 32 | } sendir_t; 33 | 34 | typedef struct { 35 | uint16_t header_high; 36 | uint16_t header_low; 37 | uint16_t one_high; 38 | uint16_t one_low; 39 | uint16_t zero_high; 40 | uint16_t zero_low; 41 | uint16_t footer_high; 42 | uint8_t footer_low; 43 | uint16_t frequency; 44 | const char* name; 45 | } ir_protocol_t; 46 | 47 | extern const ir_protocol_t proto[PROTO_COUNT]; 48 | 49 | extern void irReceived(irproto brand, uint32_t code, size_t len, rmt_symbol_word_t *item); 50 | void sendIR(irproto brand, uint32_t code, uint8_t bits = 32, uint8_t burst = 1, uint8_t repeat = 1); 51 | 52 | IRAM_ATTR bool irrx_done(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *udata); 53 | 54 | void recvIR(void* param); 55 | uint32_t nec_check(rmt_symbol_word_t *item, size_t &len); 56 | uint32_t sam_check(rmt_symbol_word_t *item, size_t &len); 57 | uint32_t sony_check(rmt_symbol_word_t *item, size_t &len); 58 | uint32_t rc5_check(rmt_symbol_word_t *item, size_t &len); 59 | bool rc5_bit(uint32_t d, uint32_t v); 60 | bool checkbit(rmt_symbol_word_t &item, uint16_t high, uint16_t low); 61 | void fill_item(rmt_symbol_word_t &item, uint16_t high, uint16_t low, bool bit); 62 | 63 | static esp_err_t rmt_ir_encoder_reset(rmt_encoder_t *encoder); 64 | static esp_err_t rmt_del_ir_encoder(rmt_encoder_t *encoder); 65 | static size_t rmt_encode_ir(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state); 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /examples/esp32-rmt-ir-txrx/esp32-rmt-ir-txrx.ino: -------------------------------------------------------------------------------- 1 | /* 2 | https://github.com/junkfix/esp32-rmt-ir 3 | */ 4 | #include "esp32-rmt-ir.h" 5 | 6 | void irReceived(irproto brand, uint32_t code, size_t len, rmt_symbol_word_t *item){ 7 | if( code ){ 8 | Serial.printf("IR %s, code: %#x, bits: %d\n", proto[brand].name, code, len); 9 | } 10 | 11 | if(true){//debug 12 | Serial.printf("Rx%d: ", len); 13 | for (uint8_t i=0; i < len ; i++ ) { 14 | int d0 = item[i].duration0; if(!item[i].level0){d0 *= -1;} 15 | int d1 = item[i].duration1; if(!item[i].level1){d1 *= -1;} 16 | Serial.printf("%d,%d ", d0, d1); 17 | } 18 | Serial.println(); 19 | } 20 | 21 | } 22 | 23 | void setup() { 24 | Serial.begin(115200); 25 | irRxPin = 34; 26 | irTxPin = 4; 27 | xTaskCreatePinnedToCore(recvIR, "recvIR", 2048, NULL, 10, NULL, 1); 28 | } 29 | 30 | void loop() { 31 | sendIR(NEC, 0xC1AAFC03, 32, 1, 1); //protocol, code, bits, bursts, repeats 32 | delay(1000); 33 | sendIR(SONY, 0x3e108, 20, 1, 1); //protocol, code, bits, bursts, repeats 34 | delay(1000); 35 | } 36 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=esp32-rmt-ir 2 | version=1.0.2 3 | author=junkfix 4 | maintainer=junkfix 5 | sentence=Minimal, non-blocking, IR library for ESP32 using RMT pheripheral, supports NEC, Sony, Samsung and RC5 transmit and receive, lightweight, no dependencies, will need Arduino esp32 3.x based on IDF 5.X 6 | paragraph= 7 | category=Sensors 8 | url=https://github.com/junkfix/esp32-rmt-ir 9 | architectures=esp32 10 | --------------------------------------------------------------------------------