├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── component.mk ├── examples ├── raw_dumper │ ├── Makefile │ └── main │ │ ├── component.mk │ │ └── raw_dumper.c └── raw_sender │ ├── Makefile │ └── main │ ├── component.mk │ └── raw_sender.c └── ir ├── debug.h ├── generic.c ├── generic.h ├── ir.h ├── raw.c ├── raw.h ├── rx.c ├── rx.h ├── tx.c └── tx.h /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | firmware/ 3 | sdkconfig 4 | sdkconfig.old -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCDIRS 2 | "ir" 3 | ) 4 | 5 | set(COMPONENT_ADD_INCLUDEDIRS 6 | "." 7 | ) 8 | 9 | register_component() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Maxim Kulkin 4 | Copyright (c) 2019 Fonger 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP8266-RTOS-IR 2 | 3 | Infrared rx/tx library for [espressif/ESP8266_RTOS_SDK](https://github.com/espressif/ESP8266_RTOS_SDK) (v3.2+, esp-idf style) to send and receive IR commands. 4 | 5 | Receiving IR codes can be done on arbitrary pin (which supports GPIO mode and pin change interrupts), 6 | **transmission though can only be done on `GPIO14`**. 7 | 8 | This library use i2s WS pin to send accurate 38KHz IR signals with 50% duty cycle without blocking CPU like other libraries do. 9 | 10 | Recently, espressif has released official IR feature (`driver/ir-rx.h` `driver/ir-tx.h`) in master branch but this library is more lightweight. 11 | 12 | ## Compatibility 13 | 14 | ESP8266_RTOS_SDK v3.2+ 15 | 16 | ## Installation 17 | 18 | Because of [this issue #663](https://github.com/espressif/ESP8266_RTOS_SDK/issues/663) you should add the following line to `$IDF_PATH/components/freertos/port/esp8266/include/freertos/FreeRTOSConfig.h` manually. 19 | 20 | ```c 21 | #define INCLUDE_xTimerPendFunctionCall 1 22 | ``` 23 | 24 | Then you can clone this project inside your components folder. 25 | 26 | ## Usage 27 | 28 | Example sending command: 29 | 30 | ```c 31 | #include 32 | #include 33 | 34 | static int16_t[] command1 = { 35 | 3291, -1611, 36 | 443, -370, 425, -421, 421, -1185, 424, -422, 37 | 421, -1185, 425, -421, 421, -370, 424, -392, 38 | 448, -1188, 423, -1214, 444, -372, 422, -395, 39 | 447, -397, 420, -1186, 449, -1185, 424, -423, 40 | 419, -375, 441, -372, 423, -422, 420, -372, 41 | 444, -370, 424, -422, 420, -372, 421, -393, 42 | 424, -421, 421, -371, 422, -392, 449, -398, 43 | 420, -1185, 450, -396, 421, -370, 422, -423, 44 | }; 45 | 46 | ir_tx_init(); 47 | ir_raw_send(command1, sizeof(command1) / sizeof(*command1)); 48 | ``` 49 | 50 | Example receiving NEC-like command: 51 | 52 | ```c 53 | #include 54 | #include 55 | 56 | #define IR_RX_GPIO 12 57 | 58 | static ir_generic_config_t my_protocol_config = { 59 | .header_mark = 3200, 60 | .header_space = -1600, 61 | 62 | .bit1_mark = 400, 63 | .bit1_space = -1200, 64 | 65 | .bit0_mark = 400, 66 | .bit0_space = -400, 67 | 68 | .footer_mark = 400, 69 | .footer_space = -8000, 70 | 71 | .tolerance = 10, 72 | }; 73 | 74 | ir_rx_init(IR_RX_GPIO, 1024); 75 | ir_decoder_t *generic_decoder = ir_generic_make_decoder(&my_protocol_config); 76 | 77 | uint8_t buffer[32]; 78 | while (1) { 79 | uint16_t size = ir_recv(generic_decoder, 0, buffer, sizeof(buffer)); 80 | if (size <= 0) 81 | continue; 82 | 83 | printf("Decoded packet (size = %d): ", size); 84 | for (int i=0; i < size; i++) { 85 | printf("0x%02x ", buffer[i]); 86 | if (i % 16 == 15) 87 | // newline after every 16 bytes of packet data 88 | printf("\n"); 89 | } 90 | 91 | if (size % 16) 92 | // print final newline unless packet size is multiple of 16 and newline 93 | // was printed inside of loop 94 | printf("\n"); 95 | } 96 | ``` 97 | 98 | ## License 99 | 100 | MIT licensed. See the bundled [LICENSE](https://github.com/Fonger/ESP8266-RTOS-IR/blob/master/LICENSE) file for more details. 101 | 102 | This library is based on [maximkulkin/esp-ir](https://github.com/maximkulkin/esp-ir) which works with [esp-open-rtos](https://github.com/SuperHouse/esp-open-rtos) instead of the official espressif SDK. 103 | -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | COMPONENT_ADD_INCLUDEDIRS = . 2 | COMPONENT_SRCDIRS = ir -------------------------------------------------------------------------------- /examples/raw_dumper/Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME := raw_dumper 2 | 3 | EXTRA_COMPONENT_DIRS = ../.. 4 | include $(IDF_PATH)/make/project.mk -------------------------------------------------------------------------------- /examples/raw_dumper/main/component.mk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fonger/ESP8266-RTOS-IR/c28693bf1ead57bf4be6957aa16a4f61511e3cc4/examples/raw_dumper/main/component.mk -------------------------------------------------------------------------------- /examples/raw_dumper/main/raw_dumper.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | 10 | #define IR_RX_GPIO 12 11 | 12 | void ir_dump_task(void *arg) { 13 | ir_rx_init(IR_RX_GPIO, 1024); 14 | ir_decoder_t *raw_decoder = ir_raw_make_decoder(); 15 | 16 | uint16_t buffer_size = sizeof(int16_t) * 1024; 17 | int16_t *buffer = malloc(buffer_size); 18 | while (1) { 19 | int size = ir_recv(raw_decoder, 0, buffer, buffer_size); 20 | if (size <= 0) 21 | continue; 22 | 23 | printf("Decoded packet (size = %d):\n", size); 24 | for (int i=0; i < size; i++) { 25 | printf("%5d ", buffer[i]); 26 | if (i % 16 == 15) 27 | printf("\n"); 28 | } 29 | 30 | if (size % 16) 31 | printf("\n"); 32 | } 33 | } 34 | void app_main() 35 | { 36 | xTaskCreate(ir_dump_task, "IR dump", 2048, NULL, tskIDLE_PRIORITY, NULL); 37 | } -------------------------------------------------------------------------------- /examples/raw_sender/Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME := raw_sender 2 | 3 | EXTRA_COMPONENT_DIRS = ../.. 4 | include $(IDF_PATH)/make/project.mk -------------------------------------------------------------------------------- /examples/raw_sender/main/component.mk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fonger/ESP8266-RTOS-IR/c28693bf1ead57bf4be6957aa16a4f61511e3cc4/examples/raw_sender/main/component.mk -------------------------------------------------------------------------------- /examples/raw_sender/main/raw_sender.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | static const int16_t AC_POWER[] = { 10 | 9000, -4500, 562, -562, 562, -562, 562, -562, 562, -562, 562, -562, 11 | 562, -562, 562, -562, 562, -1688, 562, -1688, 562, -1688, 562, -1688, 12 | 562, -1688, 562, -1688, 562, -1688, 562, -1688, 562, -1688, 562, -562, 13 | 562, -562, 562, -562, 562, -562, 562, -562, 562, -562, 562, -562, 14 | 562, -562, 562, -1688, 562, -1688, 562, -1688, 562, -1688, 562, -1688, 15 | 562, -1688, 562, -1688, 562, -1688, 562}; 16 | 17 | // IR signal pin must be GPIO14 (D5 pin) 18 | 19 | void ir_tx_task(void *arg) 20 | { 21 | ir_tx_init(); 22 | while (1) 23 | { 24 | ir_raw_send(AC_POWER, sizeof(AC_POWER) / sizeof(*AC_POWER)); 25 | vTaskDelay(2000 / portTICK_RATE_MS); 26 | } 27 | } 28 | void app_main() 29 | { 30 | xTaskCreate(ir_tx_task, "IR TX", 2048, NULL, tskIDLE_PRIORITY, NULL); 31 | } -------------------------------------------------------------------------------- /ir/debug.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef IR_DEBUG 4 | #include 5 | #define ir_debug(message, ...) printf("IR: " message, ## __VA_ARGS__) 6 | #define ir_debug1(message, ...) printf(message, ## __VA_ARGS__) 7 | #else 8 | #define ir_debug(message, ...) 9 | #define ir_debug1(message, ...) 10 | #endif 11 | -------------------------------------------------------------------------------- /ir/generic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef enum { 12 | ir_state_idle, 13 | 14 | ir_state_header_mark, 15 | ir_state_header_space, 16 | 17 | ir_state_bit_mark, 18 | ir_state_bit_space, 19 | 20 | ir_state_footer_mark, 21 | ir_state_footer_space, 22 | } ir_generic_fsm_state_t; 23 | 24 | 25 | typedef struct { 26 | ir_encoder_t encoder; 27 | 28 | ir_generic_config_t *config; 29 | 30 | ir_generic_fsm_state_t fsm_state; 31 | 32 | size_t bit_count; 33 | size_t bits_left; 34 | 35 | size_t byte_pos; 36 | uint8_t bit_pos; 37 | 38 | uint8_t data[]; 39 | } ir_generic_encoder_t; 40 | 41 | 42 | static int16_t ir_generic_get_next_pulse(ir_generic_encoder_t *encoder) { 43 | switch(encoder->fsm_state) { 44 | case ir_state_idle: 45 | return 0; 46 | 47 | case ir_state_header_mark: 48 | encoder->fsm_state = ir_state_header_space; 49 | return encoder->config->header_mark; 50 | 51 | case ir_state_header_space: 52 | encoder->fsm_state = ir_state_bit_mark; 53 | return encoder->config->header_space; 54 | 55 | case ir_state_bit_mark: { 56 | encoder->fsm_state = ir_state_bit_space; 57 | 58 | uint8_t bit = (encoder->data[encoder->byte_pos] >> encoder->bit_pos) & 0x1; 59 | 60 | return (bit ? encoder->config->bit1_mark : encoder->config->bit0_mark); 61 | } 62 | 63 | case ir_state_bit_space: { 64 | encoder->bits_left--; 65 | if (!encoder->bits_left) { 66 | if (encoder->config->footer_mark) { 67 | encoder->fsm_state = ir_state_footer_mark; 68 | } else if (encoder->config->footer_space) { 69 | encoder->fsm_state = ir_state_footer_space; 70 | } else { 71 | encoder->fsm_state = ir_state_idle; 72 | } 73 | } else { 74 | encoder->fsm_state = ir_state_bit_mark; 75 | } 76 | 77 | uint8_t bit = (encoder->data[encoder->byte_pos] >> encoder->bit_pos) & 0x1; 78 | 79 | encoder->bit_pos++; 80 | if (encoder->bit_pos >= 8) { 81 | encoder->bit_pos = 0; 82 | encoder->byte_pos++; 83 | } 84 | 85 | return (bit ? encoder->config->bit1_space : encoder->config->bit0_space); 86 | } 87 | 88 | case ir_state_footer_mark: 89 | encoder->fsm_state = ir_state_footer_space; 90 | return encoder->config->footer_mark; 91 | 92 | case ir_state_footer_space: 93 | encoder->fsm_state = ir_state_idle; 94 | return encoder->config->footer_space; 95 | } 96 | 97 | return 0; 98 | } 99 | 100 | 101 | static void ir_generic_free(ir_generic_encoder_t *encoder) { 102 | if (encoder) 103 | free(encoder); 104 | } 105 | 106 | 107 | static ir_encoder_t *ir_generic_make_encoder(ir_generic_config_t *config, uint8_t *data, uint16_t data_size) { 108 | ir_generic_encoder_t *encoder = 109 | malloc(sizeof(ir_generic_encoder_t) + sizeof(uint8_t) * data_size); 110 | if (!encoder) 111 | return NULL; 112 | 113 | encoder->encoder.get_next_pulse = (ir_get_next_pulse_t)ir_generic_get_next_pulse; 114 | encoder->encoder.free = (ir_free_t)ir_generic_free; 115 | 116 | encoder->config = config; 117 | memcpy(encoder->data, data, sizeof(uint8_t) * data_size); 118 | 119 | encoder->bit_count = encoder->bits_left = data_size * 8; 120 | encoder->byte_pos = encoder->bit_pos = 0; 121 | 122 | if (config->header_mark) { 123 | encoder->fsm_state = ir_state_header_mark; 124 | } else if (config->header_space) { 125 | encoder->fsm_state = ir_state_header_space; 126 | } else { 127 | encoder->fsm_state = ir_state_bit_mark; 128 | } 129 | 130 | return (ir_encoder_t*)encoder; 131 | } 132 | 133 | 134 | int ir_generic_send(ir_generic_config_t *config, uint8_t *data, uint16_t data_size) { 135 | ir_encoder_t *encoder = ir_generic_make_encoder(config, data, data_size); 136 | if (!encoder) 137 | return -1; 138 | 139 | int result = ir_tx_send(encoder); 140 | if (result) { 141 | encoder->free(encoder); 142 | } 143 | return result; 144 | } 145 | 146 | 147 | typedef struct { 148 | ir_decoder_t decoder; 149 | 150 | ir_generic_config_t *config; 151 | } ir_generic_decoder_t; 152 | 153 | 154 | static int match(int16_t actual, int16_t expected, uint8_t tolerance) { 155 | if (actual < 0) { 156 | if (expected > 0) { 157 | return 0; 158 | } 159 | actual = -actual + 50; 160 | expected = -expected; 161 | } else { 162 | if (expected < 0) { 163 | return 0; 164 | } 165 | 166 | actual -= 50; 167 | } 168 | 169 | uint16_t delta = ((uint32_t)expected) * tolerance / 100; 170 | if ((actual < expected - delta) || (expected + delta < actual)) { 171 | return 0; 172 | } 173 | 174 | return 1; 175 | } 176 | 177 | 178 | static int ir_generic_decode(ir_generic_decoder_t *decoder, 179 | int16_t *pulses, uint16_t count, 180 | void *data, uint16_t data_size) 181 | { 182 | if (!data_size) { 183 | ir_debug("generic: invalid buffer size\n"); 184 | return -1; 185 | } 186 | 187 | ir_generic_config_t *c = decoder->config; 188 | 189 | if (!match(pulses[0], c->header_mark, c->tolerance) || 190 | !match(pulses[1], c->header_space, c->tolerance)) 191 | { 192 | ir_debug("generic: header does not match\n"); 193 | return 0; 194 | } 195 | 196 | uint8_t *bits = data; 197 | uint8_t *bits_end = data + data_size; 198 | 199 | *bits = 0; 200 | 201 | uint8_t bit_count = 0; 202 | for (int i=2; i + 1 < count; i+=2, bit_count++) { 203 | if (bit_count >= 8) { 204 | bits++; 205 | *bits = 0; 206 | 207 | bit_count = 0; 208 | } 209 | 210 | if (match(pulses[i], c->bit1_mark, c->tolerance) && 211 | match(pulses[i+1], c->bit1_space, c->tolerance)) { 212 | 213 | if (bits == bits_end) { 214 | ir_debug("generic: data overflow\n"); 215 | return -1; 216 | } 217 | 218 | *bits |= 1 << bit_count; 219 | } else if (match(pulses[i], c->bit0_mark, c->tolerance) && 220 | match(pulses[i+1], c->bit0_space, c->tolerance)) { 221 | 222 | if (bits == bits_end) { 223 | ir_debug("generic: data overflow\n"); 224 | return -1; 225 | } 226 | 227 | // *bits |= 0 << bit_count; 228 | } else { 229 | ir_debug("generic: pulses at %d does not match: %d %d\n", 230 | i, pulses[i], pulses[i+1]); 231 | return (bits - (uint8_t*)data + (bit_count ? 1 : 0)); 232 | } 233 | } 234 | 235 | int decoded_size = bits - (uint8_t*)data + (bit_count ? 1 : 0); 236 | ir_debug("generic: decoded %d bytes\n", decoded_size); 237 | return decoded_size; 238 | } 239 | 240 | 241 | ir_decoder_t *ir_generic_make_decoder(ir_generic_config_t *config) { 242 | ir_generic_decoder_t *decoder = malloc(sizeof(ir_generic_decoder_t)); 243 | if (!decoder) 244 | return NULL; 245 | 246 | decoder->decoder.decode = (ir_decoder_decode_t)ir_generic_decode; 247 | decoder->decoder.free = (ir_decoder_free_t) free; 248 | decoder->config = config; 249 | 250 | return (ir_decoder_t*) decoder; 251 | } 252 | -------------------------------------------------------------------------------- /ir/generic.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | 12 | // NEC protocol decoder 13 | // 14 | // NEC protocol command consists of 15 | // - header (long carrier pulse (mark) + long silence (space)) 16 | // - sequence of pulses encoding bits 17 | // - zeros are encoded as short carrier pulse (mark) + short silence (space) 18 | // - ones are encoded as short carrier pulse (mark) + long silence (space) 19 | // - footer (short carrier pulse + long silence) 20 | // 21 | // Different devices have different periods for NEC protocol components, this 22 | // structure allows to configure all those periods. 23 | typedef struct { 24 | int16_t header_mark; 25 | int16_t header_space; 26 | 27 | int16_t bit1_mark; 28 | int16_t bit1_space; 29 | 30 | int16_t bit0_mark; 31 | int16_t bit0_space; 32 | 33 | int16_t footer_mark; 34 | int16_t footer_space; 35 | 36 | // Number of percents by which real value can differ +/- to be still considered a 37 | // match. E.g. for a mark of 565us 10% tolerance would allow 38 | // 565+/-56us -> 509 to 630us range 39 | uint8_t tolerance; 40 | } ir_generic_config_t; 41 | 42 | 43 | // Send NEC-like IR command 44 | // 45 | // Args: 46 | // - config - NEC protocol config 47 | // - data - data bytes to send 48 | // - data_size - number of bytes to send 49 | // 50 | // Returns: 51 | // 0 on success; negative value - error code 52 | int ir_generic_send(ir_generic_config_t *config, uint8_t *data, uint16_t data_size); 53 | ir_decoder_t *ir_generic_make_decoder(ir_generic_config_t *config); 54 | 55 | 56 | #ifdef __cplusplus 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /ir/ir.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | -------------------------------------------------------------------------------- /ir/raw.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | 12 | typedef struct { 13 | ir_encoder_t encoder; 14 | 15 | size_t count; 16 | size_t pos; 17 | int16_t data[]; 18 | } ir_raw_encoder_t; 19 | 20 | 21 | static int16_t IRAM_ATTR ir_raw_get_next_pulse(ir_raw_encoder_t *encoder) { 22 | if (encoder->pos >= encoder->count) 23 | return 0; 24 | 25 | return encoder->data[encoder->pos++]; 26 | } 27 | 28 | 29 | static void ir_raw_free(ir_raw_encoder_t *encoder) { 30 | if (encoder) 31 | free(encoder); 32 | } 33 | 34 | 35 | int ir_raw_send(int16_t *widths, uint16_t count) { 36 | ir_raw_encoder_t *encoder = malloc(sizeof(ir_raw_encoder_t) + sizeof(int16_t) * count); 37 | encoder->encoder.get_next_pulse = (ir_get_next_pulse_t)ir_raw_get_next_pulse; 38 | encoder->encoder.free = (ir_free_t)ir_raw_free; 39 | 40 | encoder->count = count; 41 | encoder->pos = 0; 42 | 43 | memcpy(encoder->data, widths, sizeof(int16_t) * count); 44 | 45 | int result = ir_tx_send((ir_encoder_t*) encoder); 46 | if (result) { 47 | ir_raw_free(encoder); 48 | } 49 | return result; 50 | } 51 | 52 | 53 | static int ir_raw_decode(ir_decoder_t *decoder, int16_t *pulses, uint16_t count, 54 | void *decoded_data, uint16_t decoded_size) 55 | { 56 | if (decoded_size < count * sizeof(int16_t)) 57 | return -1; 58 | 59 | memcpy(decoded_data, pulses, count * sizeof(int16_t)); 60 | 61 | return count; 62 | } 63 | 64 | 65 | ir_decoder_t *ir_raw_make_decoder() { 66 | ir_decoder_t *decoder = malloc(sizeof(ir_decoder_t)); 67 | decoder->decode = (ir_decoder_decode_t) ir_raw_decode; 68 | decoder->free = (ir_decoder_free_t) free; 69 | return decoder; 70 | } 71 | -------------------------------------------------------------------------------- /ir/raw.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | 10 | // API to send raw commands 11 | // 12 | // Args: 13 | // - widths - pulse periods: positive values - period of carrier, 14 | // negative - silence 15 | // - count - number of pulses to send 16 | // 17 | // Returns: 18 | // 0 on success; negative value - error code 19 | int ir_raw_send(int16_t *widths, uint16_t count); 20 | 21 | // RAW decoder: uses decoded buffer as an array of int16_t to store pulses 22 | ir_decoder_t *ir_raw_make_decoder(); 23 | 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /ir/rx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | typedef enum { 16 | ir_rx_state_idle, 17 | ir_rx_state_mark, 18 | ir_rx_state_space, 19 | ir_rx_state_overflow, 20 | } ir_rx_state_t; 21 | 22 | typedef struct { 23 | uint8_t gpio; 24 | int16_t excess; 25 | 26 | uint32_t timeout; 27 | os_timer_t timeout_timer; 28 | 29 | ir_rx_state_t state; 30 | uint32_t last_time; 31 | 32 | volatile QueueHandle_t receive_queue; 33 | 34 | int16_t *buffer; 35 | uint16_t buffer_size; 36 | uint16_t buffer_pos; 37 | } ir_rx_context_t; 38 | 39 | 40 | static ir_rx_context_t ir_rx_context; 41 | 42 | // from https://github.com/espressif/ESP8266_RTOS_SDK/issues/454 43 | extern uint32_t esp_get_time(void); 44 | 45 | static void ir_rx_timeout(void *arg) { 46 | if ((ir_rx_context.state == ir_rx_state_idle) || 47 | (esp_get_time() - ir_rx_context.last_time < ir_rx_context.timeout)) 48 | return; 49 | 50 | if (ir_rx_context.buffer_pos && (ir_rx_context.state != ir_rx_state_overflow)) { 51 | int16_t *pulses = malloc(sizeof(int16_t) * (ir_rx_context.buffer_pos + 1)); 52 | for (int i=0; i < ir_rx_context.buffer_pos; i++) 53 | pulses[i] = ir_rx_context.buffer[i]; 54 | pulses[ir_rx_context.buffer_pos] = 0; 55 | 56 | #ifdef IR_DEBUG 57 | ir_debug("Received raw pulses:\n"); 58 | for (int16_t i = 0, *p = pulses; *p; i++, p++) { 59 | ir_debug1("%5d ", *p); 60 | if (i % 16 == 15) 61 | ir_debug1("\n"); 62 | } 63 | ir_debug1("\n"); 64 | 65 | ir_debug("Queue length: %d\n", 66 | (int)uxQueueMessagesWaiting(ir_rx_context.receive_queue)); 67 | #endif 68 | 69 | xQueueSendToBack(ir_rx_context.receive_queue, &pulses, 10); 70 | } 71 | 72 | ir_rx_context.state = ir_rx_state_idle; 73 | ir_rx_context.buffer_pos = 0; 74 | } 75 | 76 | static void IRAM_ATTR ir_rx_interrupt_handler(uint8_t gpio_num) { 77 | uint32_t now = esp_get_time(); 78 | 79 | switch (ir_rx_context.state) { 80 | case ir_rx_state_idle: 81 | break; 82 | 83 | case ir_rx_state_mark: { 84 | uint32_t us = now - ir_rx_context.last_time; 85 | ir_rx_context.buffer[ir_rx_context.buffer_pos++] = us - ir_rx_context.excess; 86 | break; 87 | } 88 | 89 | case ir_rx_state_space: { 90 | uint32_t us = now - ir_rx_context.last_time; 91 | ir_rx_context.buffer[ir_rx_context.buffer_pos++] = -(us + ir_rx_context.excess); 92 | break; 93 | } 94 | 95 | case ir_rx_state_overflow: 96 | break; 97 | } 98 | 99 | if (ir_rx_context.buffer_pos >= ir_rx_context.buffer_size) { 100 | ir_rx_context.state = ir_rx_state_overflow; 101 | } else { 102 | ir_rx_context.state = !gpio_get_level(ir_rx_context.gpio) ? ir_rx_state_mark : ir_rx_state_space; 103 | } 104 | 105 | ir_rx_context.last_time = now; 106 | } 107 | 108 | 109 | void ir_rx_init(uint8_t gpio, uint16_t rx_buffer_size) { 110 | ir_rx_context.gpio = gpio; 111 | ir_rx_context.excess = 0; 112 | ir_rx_context.buffer = malloc(sizeof(int16_t) * rx_buffer_size); 113 | ir_rx_context.buffer_size = rx_buffer_size; 114 | ir_rx_context.buffer_pos = 0; 115 | 116 | ir_rx_context.state = ir_rx_state_idle; 117 | ir_rx_context.timeout = 20000; 118 | os_timer_setfn(&ir_rx_context.timeout_timer, ir_rx_timeout, NULL); 119 | os_timer_arm(&ir_rx_context.timeout_timer, 10, 1); 120 | 121 | ir_rx_context.receive_queue = xQueueCreate(10, sizeof(int16_t *)); 122 | if (!ir_rx_context.receive_queue) { 123 | printf("Failed to create IR receive queue\n"); 124 | } 125 | 126 | gpio_config_t io_conf; 127 | io_conf.intr_type = GPIO_INTR_ANYEDGE; 128 | io_conf.mode = GPIO_MODE_INPUT; 129 | io_conf.pin_bit_mask = 1ULL << ir_rx_context.gpio; 130 | io_conf.pull_down_en = 0; 131 | io_conf.pull_up_en = 0; 132 | gpio_config(&io_conf); 133 | 134 | //install gpio isr service 135 | gpio_install_isr_service(0); 136 | //hook isr handler for specific gpio pin 137 | gpio_isr_handler_add(ir_rx_context.gpio, ir_rx_interrupt_handler, (void *)ir_rx_context.gpio); 138 | } 139 | 140 | void ir_rx_set_excess(int16_t excess) { 141 | ir_rx_context.excess = excess; 142 | } 143 | 144 | int ir_recv(ir_decoder_t *decoder, uint32_t timeout, void *receive_buffer, uint16_t receive_buffer_size) { 145 | uint32_t start_time = esp_get_time(); 146 | while (!timeout || (esp_get_time() - start_time) * portTICK_PERIOD_MS < timeout) 147 | { 148 | int16_t *pulses = NULL; 149 | 150 | uint32_t time_left = portMAX_DELAY; 151 | if (timeout) 152 | time_left = (esp_get_time() - start_time) * portTICK_PERIOD_MS; 153 | 154 | if (xQueueReceive(ir_rx_context.receive_queue, &pulses, time_left) != pdTRUE) { 155 | break; 156 | } 157 | 158 | int pulse_count = 0; 159 | while (pulses[pulse_count]) 160 | pulse_count++; 161 | 162 | int r = decoder->decode(decoder, pulses, pulse_count, receive_buffer, receive_buffer_size); 163 | free(pulses); 164 | if (r > 0) { 165 | return r; 166 | } 167 | ir_debug("recv: got %d error code\n", r); 168 | } 169 | 170 | // timed out 171 | return -1; 172 | } 173 | -------------------------------------------------------------------------------- /ir/rx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | 10 | typedef struct ir_decoder ir_decoder_t; 11 | 12 | // Decode function: takes decoder and pulses and if decoding is successful should 13 | // populate decoded_data and return decoded data size as result. In case of pulse 14 | // pattern was not recognized, should return 0. Should return negative value if 15 | // error occured. 16 | // 17 | // Args: 18 | // - decoder - decoder instance (can be used to access decoder configuration) 19 | // - pulses - an array of detected pulses. Positive values correspond to length of 20 | // periods of carrier data, negative values - periods of silence. 21 | // - pulse_count - number of entries in `pulses` array 22 | // - decoded_data - buffer to receive decoded data. Usually - array of bytes, but 23 | // can be used to represent high level state encoded in IR transmission (e.g. 24 | // AC state) 25 | // - decoded_size - size of `decoded_data` buffer (usually in bytes, but 26 | // interpretation is up to particular decoder implementation) 27 | // 28 | // Returns: 29 | // size of decoded data on success; 0 if IR command is not recognized; negative - 30 | // in case of error 31 | typedef int(*ir_decoder_decode_t)(ir_decoder_t *decoder, int16_t *pulses, uint16_t pulse_count, 32 | void *decoded_data, uint16_t decoded_size); 33 | 34 | 35 | // Frees decoder 36 | typedef void(*ir_decoder_free_t)(ir_decoder_t *decoder); 37 | 38 | // IR decoder 39 | struct ir_decoder { 40 | ir_decoder_decode_t decode; 41 | ir_decoder_free_t free; 42 | }; 43 | 44 | 45 | // Initialize IR receiving infra on given GPIO pin, with buffer to capture number of 46 | // pulses given by `buffer_size`. Pulse is period of either carrier or silence. 47 | // 48 | // Args: 49 | // gpio - GPIO number to receive on 50 | // buffer_size - size of pulse buffer (maximum number of pulses allowed in one IR 51 | // command) 52 | void ir_rx_init(uint8_t gpio, uint16_t buffer_size); 53 | 54 | // Some sensors have lag and thus marks tend to be slightly longer 55 | // and spaces - slightly shorter. Excess amount (in microseconds) is subtracted from 56 | // mark pulse lengths and added to space lengths. 57 | void ir_rx_set_excess(int16_t excess); 58 | 59 | // Start listening for IR transmission until either transmission is matched by 60 | // given decoder (it's decode() function returns positive value) or timeout 61 | // milliseconds has passed. 62 | // 63 | // Args: 64 | // decoder - decoder to use for decoding IR commands 65 | // timeout - maximum number of milliseconds to wait for a valid IR command 66 | // receive_buffer - buffer to store received IR command 67 | // receive_buffer_size - size of receive buffer (interpretation is up to decoder 68 | // implementation) 69 | int ir_recv(ir_decoder_t *decoder, uint32_t timeout, void *receive_buffer, uint16_t receive_buffer_size); 70 | 71 | 72 | #ifdef __cplusplus 73 | } 74 | #endif 75 | -------------------------------------------------------------------------------- /ir/tx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | // The following definitions is taken from ESP8266_MP3_DECODER demo 20 | // https://github.com/espressif/ESP8266_RTOS_SDK/blob/master/components/esp8266/driver/i2s.c 21 | // It is required to set clock to I2S subsystem 22 | void rom_i2c_writeReg_Mask(uint8_t block, uint8_t host_id, 23 | uint8_t reg_add, uint8_t msb, uint8_t lsb, uint8_t data); 24 | 25 | #ifndef i2c_bbpll 26 | #define i2c_bbpll 0x67 27 | #define i2c_bbpll_en_audio_clock_out 4 28 | #define i2c_bbpll_en_audio_clock_out_msb 7 29 | #define i2c_bbpll_en_audio_clock_out_lsb 7 30 | #define i2c_bbpll_hostid 4 31 | #endif 32 | 33 | #define IR_GPIO_NUM 14 // Must be GPIO14 MTMS pin (I2S CLK pin) 34 | 35 | EventGroupHandle_t tx_flags; 36 | #define TX_FLAG_READY (1 << 0) 37 | 38 | #define ALWAYS_INLINE __attribute__((always_inline)) inline 39 | 40 | static void ALWAYS_INLINE gen_carrier() { 41 | PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_I2SI_WS); 42 | I2S0.conf.rx_start = 1; 43 | } 44 | 45 | 46 | static void ALWAYS_INLINE clr_carrier() { 47 | PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14); 48 | GPIO.out_w1tc |= (0x1 << IR_GPIO_NUM); // equivalent to gpio_set_level(IR_GPIO_NUM, 0); 49 | I2S0.conf.rx_start = 0; 50 | } 51 | 52 | 53 | static void ALWAYS_INLINE hw_timer_pause() { 54 | frc1.ctrl.en = 0; 55 | } 56 | 57 | 58 | static void ALWAYS_INLINE hw_timer_arm(uint32_t us) { 59 | // equivalent to hw_timer_alarm_us() with less overhead 60 | frc1.ctrl.reload = 0; 61 | frc1.ctrl.div = TIMER_CLKDIV_16; 62 | frc1.ctrl.intr_type = TIMER_EDGE_INT; 63 | frc1.load.data = ((TIMER_BASE_CLK >> frc1.ctrl.div) / 1000000) * us; 64 | frc1.ctrl.en = 1; 65 | } 66 | 67 | 68 | static void IRAM_ATTR ir_tx_timer_handler(ir_encoder_t *encoder) { 69 | hw_timer_pause(); 70 | int16_t pulse = encoder->get_next_pulse(encoder); 71 | if (pulse == 0) { 72 | // Done with transmission 73 | clr_carrier(); 74 | encoder->free(encoder); 75 | 76 | hw_timer_deinit(); 77 | BaseType_t task_woken = 0; 78 | xEventGroupSetBitsFromISR(tx_flags, TX_FLAG_READY, &task_woken); 79 | 80 | portEND_SWITCHING_ISR(task_woken); 81 | return; 82 | } 83 | 84 | if (pulse > 0) { 85 | gen_carrier(); 86 | } else { 87 | clr_carrier(); 88 | } 89 | hw_timer_arm(abs(pulse)); 90 | } 91 | 92 | 93 | void ir_tx_init() { 94 | // Start I2C clock for I2S subsystem 95 | rom_i2c_writeReg_Mask( 96 | i2c_bbpll, i2c_bbpll_hostid, 97 | i2c_bbpll_en_audio_clock_out, 98 | i2c_bbpll_en_audio_clock_out_msb, 99 | i2c_bbpll_en_audio_clock_out_lsb, 100 | 1 101 | ); 102 | 103 | // Clear I2S subsystem 104 | I2S0.conf.val &= ~I2S_I2S_RESET_MASK; 105 | I2S0.conf.val |= I2S_I2S_RESET_MASK; 106 | I2S0.conf.val &= ~I2S_I2S_RESET_MASK; 107 | 108 | // Set i2s clk freq for IR pulse 109 | I2S0.conf.bck_div_num = 62 & I2S_BCK_DIV_NUM; 110 | I2S0.conf.clkm_div_num = 2 & I2S_CLKM_DIV_NUM; 111 | I2S0.conf.bits_mod = 1 & I2S_BITS_MOD; 112 | 113 | // Set normal GPIO mode for IR space 114 | gpio_config_t io_conf; 115 | io_conf.intr_type = GPIO_INTR_DISABLE; 116 | io_conf.mode = GPIO_MODE_OUTPUT; 117 | io_conf.pin_bit_mask = 1ULL << IR_GPIO_NUM; 118 | io_conf.pull_down_en = 0; 119 | io_conf.pull_up_en = 0; 120 | gpio_config(&io_conf); 121 | gpio_set_level(IR_GPIO_NUM, 0); 122 | 123 | tx_flags = xEventGroupCreate(); 124 | xEventGroupSetBits(tx_flags, TX_FLAG_READY); 125 | } 126 | 127 | 128 | int ir_tx_send(ir_encoder_t *encoder) { 129 | if (xEventGroupWaitBits(tx_flags, TX_FLAG_READY, pdTRUE, pdTRUE, portMAX_DELAY) == 0) 130 | return -1; 131 | 132 | hw_timer_init((void (*)(void *))ir_tx_timer_handler, encoder); 133 | 134 | ir_tx_timer_handler(encoder); 135 | 136 | return 0; 137 | } 138 | 139 | -------------------------------------------------------------------------------- /ir/tx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | 10 | typedef struct ir_encoder ir_encoder_t; 11 | 12 | // Function to get next pulse to transmit. Pulse is period (in microseconds) of 13 | // either carrier transmission (positive value) or silence (negative value). 14 | typedef int16_t(*ir_get_next_pulse_t)(ir_encoder_t *); 15 | 16 | // Free given encoder 17 | typedef void(*ir_free_t)(ir_encoder_t *); 18 | 19 | // IR encoder - sending state + functions to access it. 20 | // Usually, an instance of encoder is created for every IR transmission and freed 21 | // when all data is transmitted. 22 | // 23 | // Encoders is a way to represent transmitted IR command in a more compact way, 24 | // rather than storing every pulse in an array (effective memory usage). 25 | struct ir_encoder { 26 | ir_get_next_pulse_t get_next_pulse; 27 | ir_free_t free; 28 | }; 29 | 30 | 31 | // Initialize IR transmission system. Always uses GPIO14 for transmission. 32 | void ir_tx_init(); 33 | 34 | // Send IR command represented by given encoder 35 | // 36 | // Args: 37 | // - encoder - encoder representing IR command 38 | // 39 | // Returns: 40 | // 0 on success; negative value - error code 41 | int ir_tx_send(ir_encoder_t *encoder); 42 | 43 | 44 | #ifdef __cplusplus 45 | } 46 | #endif 47 | --------------------------------------------------------------------------------