diff --git a/components/esp8266/CMakeLists.txt b/components/esp8266/CMakeLists.txt index 2a6ba7bc..a146f047 100644 --- a/components/esp8266/CMakeLists.txt +++ b/components/esp8266/CMakeLists.txt @@ -44,7 +44,9 @@ else() "driver/i2s.c" "driver/pwm.c" "driver/spi.c" - "driver/uart.c") + "driver/uart.c" + "driver/ir_tx.c" + "driver/ir_rx.c") set(include_dirs "include" "include/driver") diff --git a/components/esp8266/driver/hw_timer.c b/components/esp8266/driver/hw_timer.c index 2416e0b4..e3eb26dd 100644 --- a/components/esp8266/driver/hw_timer.c +++ b/components/esp8266/driver/hw_timer.c @@ -159,7 +159,7 @@ uint32_t hw_timer_get_count_data() return frc1.count.data; } -static void hw_timer_isr_cb(void* arg) +static void IRAM_ATTR hw_timer_isr_cb(void* arg) { if (!frc1.ctrl.reload) { frc1.ctrl.en = 0; diff --git a/components/esp8266/driver/ir_rx.c b/components/esp8266/driver/ir_rx.c new file mode 100644 index 00000000..a606b990 --- /dev/null +++ b/components/esp8266/driver/ir_rx.c @@ -0,0 +1,271 @@ +// Copyright 2018-2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/ringbuf.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include "esp_attr.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/ir_rx.h" + +static const char *TAG = "ir rx"; + +#define IR_RX_CHECK(a, str, ret_val) \ + if (!(a)) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +typedef struct { + uint32_t io_num; + uint32_t buf_len; + SemaphoreHandle_t recv_mux; + RingbufHandle_t ring_buf; /*!< rx ring buffer handler*/ +} ir_rx_obj_t; + +ir_rx_obj_t *ir_rx_obj = NULL; + +typedef enum { + IR_RX_IDLE, + IR_RX_HEADER, + IR_RX_DATA, + IR_RX_REP, +} ir_rx_state_t; + +/** + * @brief ir rx state machine via gpio intr + */ +static void IRAM_ATTR ir_rx_intr_handler(void *arg) +{ + static int ir_state = IR_RX_IDLE; + static int ir_repeat = 0; + static ir_rx_nec_data_t ir_data = {0}; + static int cnt = 0; + static uint8_t rep_flg = 0; + static uint32_t time_last = 0; + + BaseType_t xHigherPriorityTaskWoken; + uint32_t time_escape, time_current; + struct timeval now; + + gettimeofday(&now, NULL); + time_current = now.tv_sec * 1000 * 1000 + now.tv_usec; + time_escape = time_current - time_last; + time_last = time_current; + + switch (ir_state) { + case IR_RX_IDLE: { + if (time_escape < IR_RX_NEC_HEADER_US + IR_RX_ERROR_US && time_escape > IR_RX_NEC_HEADER_US - IR_RX_ERROR_US) { + ir_state = IR_RX_DATA; + } + } + break; + + case IR_RX_DATA: { + if (time_escape < IR_RX_NEC_DATA1_US + IR_RX_ERROR_US && time_escape > IR_RX_NEC_DATA1_US - IR_RX_ERROR_US) { + ir_data.val = (ir_data.val >> 1) | (0x1 << (IR_RX_NEC_BIT_NUM * 4 - 1)); + cnt++; + } else if (time_escape < IR_RX_NEC_DATA0_US + IR_RX_ERROR_US && time_escape > IR_RX_NEC_DATA0_US - IR_RX_ERROR_US) { + ir_data.val = (ir_data.val >> 1) | (0x0 << (IR_RX_NEC_BIT_NUM * 4 - 1)); + cnt++; + } else { + goto reset_status; + } + + if (cnt == IR_RX_NEC_BIT_NUM * 4) { + // push rcv data to ringbuf + xRingbufferSendFromISR(ir_rx_obj->ring_buf, (void *) &ir_data, sizeof(ir_rx_nec_data_t) * 1, &xHigherPriorityTaskWoken); + ir_state = IR_RX_REP; + rep_flg = 0; + } + } + break; + + case IR_RX_REP: { + if (rep_flg == 0) { + if (time_escape > IR_RX_NEC_TM1_REP_US && time_escape < IR_RX_NEC_TM1_REP_US * 8) { + rep_flg = 1; + } else { + goto reset_status; + } + } else if (rep_flg == 1) { + if (time_escape < IR_RX_NEC_TM1_REP_US + IR_RX_ERROR_US && IR_RX_NEC_TM2_REP_US - IR_RX_ERROR_US) { + // push rcv data to ringbuf + xRingbufferSendFromISR(ir_rx_obj->ring_buf, (void *) &ir_data, sizeof(ir_rx_nec_data_t) * 1, &xHigherPriorityTaskWoken); + ir_repeat++; + rep_flg = 0; + } else { + goto reset_status; + } + } + } + break; + } + + if (xHigherPriorityTaskWoken == pdTRUE) { + taskYIELD(); + } + + return; + +reset_status: + ir_state = IR_RX_IDLE; + cnt = 0; + ir_data.val = 0; + ir_repeat = 0; + rep_flg = 0; +} + +esp_err_t ir_rx_disable() +{ + IR_RX_CHECK(ir_rx_obj, "ir rx not been initialized yet", ESP_FAIL); + gpio_isr_handler_remove(ir_rx_obj->io_num); + + return ESP_OK; +} + +esp_err_t ir_rx_enable() +{ + IR_RX_CHECK(ir_rx_obj, "ir rx not been initialized yet", ESP_FAIL); + gpio_isr_handler_add(ir_rx_obj->io_num, ir_rx_intr_handler, (void *) ir_rx_obj->io_num); + + return ESP_OK; +} + +int ir_rx_recv_data(ir_rx_nec_data_t *data, size_t len, uint32_t timeout_ticks) +{ + IR_RX_CHECK(ir_rx_obj, "ir rx not been initialized yet", ESP_FAIL); + int ret; + ir_rx_nec_data_t *buf = NULL; + size_t size = 0; + uint32_t ticks_escape = 0, ticks_last = 0; + struct timeval now; + + if (timeout_ticks != portMAX_DELAY) { + gettimeofday(&now, NULL); + ticks_last = (now.tv_sec * 1000 + now.tv_usec / 1000) / portTICK_RATE_MS; + } + + ret = xSemaphoreTake(ir_rx_obj->recv_mux, timeout_ticks); + + if (ret != pdTRUE) { + IR_RX_CHECK(false, "SemaphoreTake error", -1); + } + + if (timeout_ticks != portMAX_DELAY) { + gettimeofday(&now, NULL); + ticks_escape = (now.tv_sec * 1000 + now.tv_usec / 1000) / portTICK_RATE_MS - ticks_last; + + if (timeout_ticks <= ticks_escape) { + xSemaphoreGive(ir_rx_obj->recv_mux); + IR_RX_CHECK(false, "timeout", -1); + } else { + timeout_ticks -= ticks_escape; + } + } + + for (int x = 0; x < len;) { + buf = (ir_rx_nec_data_t *) xRingbufferReceive(ir_rx_obj->ring_buf, &size, timeout_ticks); + + if (buf == NULL) { + xSemaphoreGive(ir_rx_obj->recv_mux); + IR_RX_CHECK(false, "RingbufferReceive error", -1); + } + + memcpy(&data[x], buf, size); + vRingbufferReturnItem(ir_rx_obj->ring_buf, buf); + x += size; + + if (timeout_ticks != portMAX_DELAY) { + gettimeofday(&now, NULL); + ticks_escape = (now.tv_sec * 1000 + now.tv_usec / 1000) / portTICK_RATE_MS - ticks_last; + + if (timeout_ticks <= ticks_escape) { + xSemaphoreGive(ir_rx_obj->recv_mux); + IR_RX_CHECK(false, "timeout, return the actual accepted length", x); + } else { + timeout_ticks -= ticks_escape; + } + } + } + + xSemaphoreGive(ir_rx_obj->recv_mux); + + return len; +} + +static esp_err_t ir_rx_gpio_init(uint32_t io_num) +{ + gpio_config_t io_conf; + io_conf.intr_type = GPIO_INTR_NEGEDGE; + io_conf.pin_bit_mask = 1ULL << io_num; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + gpio_config(&io_conf); + gpio_install_isr_service(0); + + return ESP_OK; +} + +esp_err_t ir_rx_deinit() +{ + IR_RX_CHECK(ir_rx_obj, "ir rx has not been initialized yet.", ESP_FAIL); + + ir_rx_disable(); + + if (ir_rx_obj->ring_buf) { + vRingbufferDelete(ir_rx_obj->ring_buf); + ir_rx_obj->ring_buf = NULL; + } + + if (ir_rx_obj->recv_mux) { + vSemaphoreDelete(ir_rx_obj->recv_mux); + ir_rx_obj->recv_mux = NULL; + } + + heap_caps_free(ir_rx_obj); + ir_rx_obj = NULL; + return ESP_OK; +} + +esp_err_t ir_rx_init(ir_rx_config_t *config) +{ + IR_RX_CHECK(config, "config error", ESP_ERR_INVALID_ARG); + IR_RX_CHECK(NULL == ir_rx_obj, "ir rx has been initialized", ESP_FAIL); + + ir_rx_obj = heap_caps_malloc(sizeof(ir_rx_obj_t), MALLOC_CAP_8BIT); + IR_RX_CHECK(ir_rx_obj, "ir rx object malloc error", ESP_ERR_NO_MEM); + ir_rx_obj->io_num = config->io_num; + ir_rx_obj->buf_len = config->buf_len; + ir_rx_obj->ring_buf = xRingbufferCreate(sizeof(ir_rx_nec_data_t) * ir_rx_obj->buf_len, RINGBUF_TYPE_NOSPLIT); + ir_rx_obj->recv_mux = xSemaphoreCreateMutex(); + + if (NULL == ir_rx_obj->ring_buf || NULL == ir_rx_obj->recv_mux) { + ir_rx_deinit(); + IR_RX_CHECK(false, "Ringbuffer or Mutex create fail", ESP_ERR_NO_MEM); + } + + // gpio configure for IR rx pin + ir_rx_gpio_init(ir_rx_obj->io_num); + ir_rx_enable(); + + return ESP_OK; +} + diff --git a/components/esp8266/driver/ir_tx.c b/components/esp8266/driver/ir_tx.c new file mode 100644 index 00000000..274cc3df --- /dev/null +++ b/components/esp8266/driver/ir_tx.c @@ -0,0 +1,421 @@ +// Copyright 2018-2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include "esp_attr.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/ir_tx.h" +#include "driver/hw_timer.h" +#include "driver/i2s.h" +#include "esp8266/gpio_struct.h" + +static const char *TAG = "ir tx"; + +#define IR_TX_CHECK(a, str, ret_val) \ + if (!(a)) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +typedef enum { + TX_BIT_CARRIER, + TX_BIT_LOW, +} ir_tx_bit_state_t; + +typedef enum { + IR_TX_IDLE, + IR_TX_HEADER, + IR_TX_DATA, + IR_TX_REP, +} ir_tx_state_t; + +/** + * @brief IR TX transmission parameter structure type definition + */ +typedef struct { + ir_tx_nec_data_t data; + uint8_t repeat; +} ir_tx_trans_t; + +typedef struct { + uint32_t io_num; + uint32_t freq; + SemaphoreHandle_t done_sem; + SemaphoreHandle_t send_mux; + ir_tx_trans_t trans; +} ir_tx_obj_t; + +ir_tx_obj_t *ir_tx_obj = NULL; + +static ir_tx_state_t ir_tx_state = IR_TX_IDLE; + +static void inline ir_tx_clear_carrier() +{ + switch (ir_tx_obj->io_num) { + case 2: { + GPIO.out_w1tc |= 0x4; // GPIO 2 + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2); + } + break; + + case 14: { + GPIO.out_w1tc |= 0x4000; // GPIO 14 + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14); + } + break; + } +} + +static void inline ir_tx_gen_carrier() +{ + switch (ir_tx_obj->io_num) { + case 2: { + GPIO.out_w1ts |= 0x4; // GPIO 2 + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_I2SO_WS); + } + break; + + case 14: { + GPIO.out_w1ts |= 0x4000; // GPIO 14 + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_I2SI_WS); + } + break; + } +} + +static void inline ir_tx_timer_alarm(uint32_t val) +{ + hw_timer_alarm_us(val - IR_TX_ERROR_US, false); +} + +void IRAM_ATTR ir_tx_handler() +{ + uint32_t t_expire = 0; + static uint32_t rep_expire_us = IR_TX_NEC_REP_CYCLE; //for nec 32bit mode + static uint16_t data_tmp = 0; + static uint8_t ir_tx_bit_num = 0; + static uint8_t ir_bit_state = TX_BIT_CARRIER; + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + switch (ir_tx_state) { + case IR_TX_IDLE: { + ir_tx_gen_carrier(); + ir_tx_timer_alarm(IR_TX_NEC_HEADER_HIGH_US); + ir_tx_state = IR_TX_HEADER; + break; + } + + case IR_TX_HEADER: { + ir_tx_clear_carrier(); + ir_tx_timer_alarm(IR_TX_NEC_HEADER_LOW_US); + ir_tx_state = IR_TX_DATA; + ir_bit_state = TX_BIT_CARRIER; + data_tmp = ir_tx_obj->trans.data.addr1; + rep_expire_us -= (IR_TX_NEC_HEADER_HIGH_US + IR_TX_NEC_HEADER_LOW_US); + break; + } + + case IR_TX_DATA: { + if (ir_bit_state == TX_BIT_CARRIER) { + t_expire = IR_TX_NEC_DATA_HIGH_US; + ir_bit_state = TX_BIT_LOW; + ir_tx_gen_carrier(); + } else if (ir_bit_state == TX_BIT_LOW) { + ir_tx_clear_carrier(); + + if ((data_tmp >> (ir_tx_bit_num % IR_TX_NEC_BIT_NUM)) & 0x1) { + t_expire = IR_TX_NEC_DATA_LOW_1_US; + } else { + t_expire = IR_TX_NEC_DATA_LOW_0_US; + } + + ir_tx_bit_num++; + + if (ir_tx_bit_num == IR_TX_NEC_BIT_NUM) { + data_tmp = ir_tx_obj->trans.data.addr2; + } else if (ir_tx_bit_num == IR_TX_NEC_BIT_NUM * 2) { + data_tmp = ir_tx_obj->trans.data.cmd1; + } else if (ir_tx_bit_num == IR_TX_NEC_BIT_NUM * 3) { + data_tmp = ir_tx_obj->trans.data.cmd2; + } else if ((ir_tx_bit_num == (IR_TX_NEC_BIT_NUM * 4 + 1))) { + //clean up state for next or for repeat + ir_tx_bit_num = 0; + ir_bit_state = TX_BIT_CARRIER; + + if (ir_tx_obj->trans.repeat > 0) { + t_expire = (rep_expire_us - 5); + ir_tx_timer_alarm(t_expire); + rep_expire_us = IR_TX_NEC_REP_CYCLE; + ir_tx_state = IR_TX_REP; + } else { + rep_expire_us = IR_TX_NEC_REP_CYCLE; + ir_tx_state = IR_TX_IDLE; + xSemaphoreGiveFromISR(ir_tx_obj->done_sem, &xHigherPriorityTaskWoken); + } + + break; + + } + + ir_bit_state = TX_BIT_CARRIER; + } else { + } + + rep_expire_us -= t_expire; + ir_tx_timer_alarm(t_expire); + break; + } + + case IR_TX_REP: { + if (ir_tx_obj->trans.repeat > 0) { + if (ir_tx_bit_num == 0) { + ir_tx_gen_carrier(); + t_expire = IR_TX_NEC_REP_HIGH_US ; + } else if (ir_tx_bit_num == 1) { + ir_tx_clear_carrier(); + t_expire = IR_TX_NEC_REP_LOW_US ; + } else if (ir_tx_bit_num == 2) { + ir_tx_gen_carrier(); + t_expire = IR_TX_NEC_REP_STOP_US; + } else if (ir_tx_bit_num == 3) { + ir_tx_clear_carrier(); + ir_tx_obj->trans.repeat--; + + if (ir_tx_obj->trans.repeat > 0) { + t_expire = rep_expire_us ; + rep_expire_us = IR_TX_NEC_REP_CYCLE; + } else { + ir_tx_bit_num = 0; + rep_expire_us = IR_TX_NEC_REP_CYCLE; + ir_tx_state = IR_TX_IDLE; + ir_bit_state = TX_BIT_CARRIER; + xSemaphoreGiveFromISR(ir_tx_obj->done_sem, &xHigherPriorityTaskWoken); + break; + } + } else { + } + + ir_tx_bit_num++;//bit num reuse for repeat wave form + + if (ir_tx_bit_num == 4) { + ir_tx_bit_num = 0; + rep_expire_us = IR_TX_NEC_REP_CYCLE; + } else { + rep_expire_us -= t_expire; + } + + ir_tx_timer_alarm(t_expire); + } + + break; + } + + default: + break; + } + + if (xHigherPriorityTaskWoken == pdTRUE) { + taskYIELD(); + } +} + +static esp_err_t ir_tx_trans(ir_tx_nec_data_t data, uint8_t repeat, uint32_t *timeout_ticks) +{ + int ret; + uint32_t ticks_escape = 0, ticks_last = 0; + struct timeval now; + + if (*timeout_ticks != portMAX_DELAY) { + gettimeofday(&now, NULL); + ticks_last = (now.tv_sec * 1000 + now.tv_usec / 1000) / portTICK_RATE_MS; + } + + if (ir_tx_state != IR_TX_IDLE) { + IR_TX_CHECK(false, "When transmission begins, the state must be idle", ESP_FAIL); + } + + ir_tx_obj->trans.data = data; + ir_tx_obj->trans.repeat = repeat; + xSemaphoreTake(ir_tx_obj->done_sem, 0); // Clear possible semaphore + ir_tx_handler(); + + if (ir_tx_state != IR_TX_IDLE) { + ret = xSemaphoreTake(ir_tx_obj->done_sem, *timeout_ticks); + + if (ret != pdTRUE) { + IR_TX_CHECK(false, "Waiting for done_sem error", ESP_ERR_TIMEOUT); + } + } + + if (*timeout_ticks != portMAX_DELAY) { + gettimeofday(&now, NULL); + ticks_escape = (now.tv_sec * 1000 + now.tv_usec / 1000) / portTICK_RATE_MS - ticks_last; + + if (*timeout_ticks <= ticks_escape) { + IR_TX_CHECK(false, "timeout", ESP_ERR_TIMEOUT); + } else { + *timeout_ticks -= ticks_escape; + } + } + + return ESP_OK; +} + +int ir_tx_send_data(ir_tx_nec_data_t *data, size_t len, uint32_t timeout_ticks) +{ + IR_TX_CHECK(ir_tx_obj, "ir tx has not been initialized yet.", ESP_FAIL); + int ret; + int x, y; + uint32_t ticks_escape = 0, ticks_last = 0; + struct timeval now; + + if (timeout_ticks != portMAX_DELAY) { + gettimeofday(&now, NULL); + ticks_last = (now.tv_sec * 1000 + now.tv_usec / 1000) / portTICK_RATE_MS; + } + + ret = xSemaphoreTake(ir_tx_obj->send_mux, timeout_ticks); + + if (ret != pdTRUE) { + IR_TX_CHECK(false, "SemaphoreTake error", -1); + } + + if (timeout_ticks != portMAX_DELAY) { + gettimeofday(&now, NULL); + ticks_escape = (now.tv_sec * 1000 + now.tv_usec / 1000) / portTICK_RATE_MS - ticks_last; + + if (timeout_ticks <= ticks_escape) { + xSemaphoreGive(ir_tx_obj->send_mux); + IR_TX_CHECK(false, "timeout", -1); + } else { + timeout_ticks -= ticks_escape; + } + } + + for (x = 0; x < len;) { + for (y = 1; y < len - x; y++) { + if (data[y + x].val != data[x].val) { // search repeat + break; + } + } + + ret = ir_tx_trans(data[x], y - 1, &timeout_ticks); + + if (ret != ESP_OK) { + if (ret == ESP_ERR_TIMEOUT) { + x += y; + } + + xSemaphoreGive(ir_tx_obj->send_mux); + IR_TX_CHECK(false, "trans data error", x); + } + + x += y; + } + + xSemaphoreGive(ir_tx_obj->send_mux); + + return len; +} + +esp_err_t ir_tx_deinit() +{ + IR_TX_CHECK(ir_tx_obj, "ir tx has not been initialized yet.", ESP_FAIL); + + if (ir_tx_obj->done_sem) { + vSemaphoreDelete(ir_tx_obj->done_sem); + ir_tx_obj->done_sem = NULL; + } + + if (ir_tx_obj->send_mux) { + vSemaphoreDelete(ir_tx_obj->send_mux); + ir_tx_obj->send_mux = NULL; + } + + hw_timer_deinit(); + + i2s_driver_uninstall(I2S_NUM_0); + + heap_caps_free(ir_tx_obj); + ir_tx_obj = NULL; + return ESP_OK; +} + +esp_err_t ir_tx_init(ir_tx_config_t *config) +{ + IR_TX_CHECK(config, "config error", ESP_ERR_INVALID_ARG); + IR_TX_CHECK((config->io_num == 2) || (config->io_num == 14), "Only supports io2 and io14 as carrier outputs", ESP_ERR_INVALID_ARG); + IR_TX_CHECK(NULL == ir_tx_obj, "ir tx has been initialized", ESP_FAIL); + + ir_tx_obj = heap_caps_malloc(sizeof(ir_tx_obj_t), MALLOC_CAP_8BIT); + IR_TX_CHECK(ir_tx_obj, "ir tx object malloc error", ESP_ERR_NO_MEM); + ir_tx_obj->io_num = config->io_num; + ir_tx_obj->freq = config->freq; + ir_tx_obj->done_sem = xSemaphoreCreateBinary(); + ir_tx_obj->send_mux = xSemaphoreCreateMutex(); + + if (NULL == ir_tx_obj->done_sem || NULL == ir_tx_obj->send_mux) { + ir_tx_deinit(); + IR_TX_CHECK(false, "Semaphore create fail", ESP_ERR_NO_MEM); + } + + // init default data + ir_tx_obj->trans.data.addr1 = (uint8_t)0xee; //addr code + ir_tx_obj->trans.data.addr2 = (uint8_t)~0xee; + ir_tx_obj->trans.data.cmd1 = (uint8_t)0x5a; //cmd code + ir_tx_obj->trans.data.cmd2 = (uint8_t)~0x5a; + ir_tx_obj->trans.repeat = 5; //repeat number + + gpio_config_t io_conf; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1 << ir_tx_obj->io_num; + io_conf.pull_down_en = 0; + io_conf.pull_up_en = 0; + gpio_config(&io_conf); + + i2s_config_t i2s_config = { + .mode = I2S_MODE_MASTER, // Only carrier mode + .sample_rate = ir_tx_obj->freq, + .bits_per_sample = 16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // 2-channels + .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB, + .dma_buf_count = 2, // no use + .dma_buf_len = 8 // no use + }; + i2s_pin_config_t pin_config = { + .bck_o_en = -1, + .ws_o_en = (ir_tx_obj->io_num == 2) ? 1 : -1, + .bck_i_en = -1, + .ws_i_en = (ir_tx_obj->io_num == 14) ? 1 : -1, + .data_out_en = -1, + .data_in_en = -1 + }; + + i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); + i2s_set_pin(I2S_NUM_0, &pin_config); + hw_timer_init(ir_tx_handler, NULL); + hw_timer_disarm(); + ir_tx_clear_carrier(); + + return ESP_OK; +} diff --git a/components/esp8266/include/driver/ir_rx.h b/components/esp8266/include/driver/ir_rx.h new file mode 100644 index 00000000..7029f407 --- /dev/null +++ b/components/esp8266/include/driver/ir_rx.h @@ -0,0 +1,108 @@ +// Copyright 2018-2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Currently only supports infrared NEC code */ +/* NEC time parameter configuration */ +#define IR_RX_NEC_BIT_NUM 8 +#define IR_RX_NEC_HEADER_US 13500 +#define IR_RX_NEC_DATA0_US 1120 +#define IR_RX_NEC_DATA1_US 2250 +#define IR_RX_NEC_TM1_REP_US 20000 +#define IR_RX_NEC_TM2_REP_US 11250 +#define IR_RX_ERROR_US 200 // Used to compensate errors + +/** + * @brief ir rx initialization parameter structure type definition + */ +typedef struct { + uint32_t io_num; + uint32_t buf_len; +} ir_rx_config_t; + +/** + * @brief ir rx nec data union type definition + */ +typedef union { + struct { + uint32_t addr1: 8; + uint32_t addr2: 8; + uint32_t cmd1: 8; + uint32_t cmd2: 8; + }; + uint32_t val; /*!< union fill */ +} ir_rx_nec_data_t; + +/** + * @brief Disable the ir rx + * + * @return + * - ESP_OK Success + * - ESP_FAIL ir rx has not been initialized yet + */ +esp_err_t ir_rx_disable(); + +/** + * @brief Enable the ir rx + * + * @return + * - ESP_OK Success + * - ESP_FAIL ir rx has not been initialized yet + */ +esp_err_t ir_rx_enable(); + +/** + * @brief Receive infrared data + * + * @param data Pointer to the rx data buffer + * @param len Length of ir_rx_data, range: 0 < len < (uint16_t) + * @param timeout_ticks freertos timeout ticks + * + * @return + * - -1 error + * - length The actual length of data received + */ +int ir_rx_recv_data(ir_rx_nec_data_t *data, size_t len, uint32_t timeout_ticks); + +/** + * @brief Deinit the ir rx + * + * @return + * - ESP_OK Success + * - ESP_FAIL ir rx has not been initialized yet + */ +esp_err_t ir_rx_deinit(); + +/** + * @brief Initialize the ir rx + * + * @param config Pointer to deliver initialize configuration parameter + * + * @return + * - ESP_OK Success + * - ESP_ERR_NO_MEM malloc fail + * - ESP_FAIL ir rx has been initialized + */ +esp_err_t ir_rx_init(ir_rx_config_t *config); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp8266/include/driver/ir_tx.h b/components/esp8266/include/driver/ir_tx.h new file mode 100644 index 00000000..5c25ce7a --- /dev/null +++ b/components/esp8266/include/driver/ir_tx.h @@ -0,0 +1,97 @@ +// Copyright 2018-2025 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Currently only supports infrared NEC code */ +/* NEC time parameter configuration */ +#define IR_TX_NEC_BIT_NUM 8 +#define IR_TX_NEC_HEADER_HIGH_US 9000 +#define IR_TX_NEC_HEADER_LOW_US 4500 +#define IR_TX_NEC_DATA_HIGH_US 560 +#define IR_TX_NEC_DATA_LOW_1_US 1690 +#define IR_TX_NEC_DATA_LOW_0_US 560 +#define IR_TX_NEC_REP_HIGH_US 9000 +#define IR_TX_NEC_REP_LOW_US 2250 +#define IR_TX_NEC_REP_STOP_US 562 +#define IR_TX_NEC_REP_CYCLE 108000 +#define IR_TX_ERROR_US 40 // Timing in advance to reduce errors + +/** + * @brief ir tx initialization parameter structure type definition + */ +typedef struct { + uint32_t io_num; // 2 or 14, 2: I2SO_WS 14: I2SI_WS + uint32_t freq; +} ir_tx_config_t; + +/** + * @brief ir tx data union type definition + */ +typedef union { + struct { + uint32_t addr1: 8; + uint32_t addr2: 8; + uint32_t cmd1: 8; + uint32_t cmd2: 8; + }; + uint32_t val; /*!< union fill */ +} ir_tx_nec_data_t; + +/** + * @brief Send ir data + * + * @note If multiple data are identical, repeat signals will be used. + * Infrared data consumes more than 100 ms per transmission, so note the timeout_ticks parameter + * + * @param data Pointer to the tx data buffer + * @param len Length of ir_tx_data, range: 0 < len < (uint16_t) + * @param timeout_ticks freertos timeout ticks + * + * @return + * - -1 error + * - length The actual length of data sent + */ +int ir_tx_send_data(ir_tx_nec_data_t *data, size_t len, uint32_t timeout_ticks); + +/** + * @brief Deinit the ir tx + * + * @return + * - ESP_OK Success + * - ESP_FAIL ir tx has not been initialized yet + */ +esp_err_t ir_tx_deinit(); + +/** + * @brief Initialize the ir tx + * + * @param config Pointer to deliver initialize configuration parameter + * + * @return + * - ESP_OK Success + * - ESP_ERR_NO_MEM malloc fail + * - ESP_FAIL ir tx has been initialized + */ +esp_err_t ir_tx_init(ir_tx_config_t *config); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/ir_rx/CMakeLists.txt b/examples/peripherals/ir_rx/CMakeLists.txt new file mode 100644 index 00000000..778446ef --- /dev/null +++ b/examples/peripherals/ir_rx/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following four lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ir_rx) diff --git a/examples/peripherals/ir_rx/Makefile b/examples/peripherals/ir_rx/Makefile new file mode 100644 index 00000000..458d909c --- /dev/null +++ b/examples/peripherals/ir_rx/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ir_rx + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/peripherals/ir_rx/README.md b/examples/peripherals/ir_rx/README.md new file mode 100644 index 00000000..31568a09 --- /dev/null +++ b/examples/peripherals/ir_rx/README.md @@ -0,0 +1,83 @@ +# IR RX Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +In this example, we use IO5 to recv the nec infrared signal + +## How to Use Example + +### Hardware Required + +* A development board with ESP8266 SoC (e.g., ESP8266-DevKitC, etc.) +* A USB cable for power supply and programming + +### Configure the Project + +``` +make menuconfig +``` + +* Set serial port under Serial Flasher Options. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (471) gpio: GPIO[5]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:2 +I (19161) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x0, cmd2: 0xff +I (19171) main: ir rx nec data: 0x0 +I (19211) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x0, cmd2: 0xff +I (19221) main: ir rx nec data: 0x0 +I (19321) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x0, cmd2: 0xff +I (19331) main: ir rx nec data: 0x0 +I (19431) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x0, cmd2: 0xff +I (19431) main: ir rx nec data: 0x0 +I (19541) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x0, cmd2: 0xff +I (19541) main: ir rx nec data: 0x0 +I (20601) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x1, cmd2: 0xfe +I (20601) main: ir rx nec data: 0x1 +I (20651) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x1, cmd2: 0xfe +I (20661) main: ir rx nec data: 0x1 +I (20761) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x1, cmd2: 0xfe +I (20761) main: ir rx nec data: 0x1 +I (20871) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x1, cmd2: 0xfe +I (20871) main: ir rx nec data: 0x1 +I (20981) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x1, cmd2: 0xfe +I (20981) main: ir rx nec data: 0x1 +I (22041) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x2, cmd2: 0xfd +I (22041) main: ir rx nec data: 0x2 +I (22091) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x2, cmd2: 0xfd +I (22091) main: ir rx nec data: 0x2 +I (22201) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x2, cmd2: 0xfd +I (22201) main: ir rx nec data: 0x2 +I (22311) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x2, cmd2: 0xfd +I (22311) main: ir rx nec data: 0x2 +I (22411) main: addr1: 0x55, addr2: 0xaa, cmd1: 0x2, cmd2: 0xfd +I (22421) main: ir rx nec data: 0x2 +``` + +If you have a logic analyzer, you can use a logic analyzer to grab online data. The following table describes the pins we use by default (Note that you can also use other pins for the same purpose). + +| pin name| function | gpio_num | +|:---:|:---:|:---:| +| IR RX|Infrared reception | GPIO_NUM_5 | + +## Troubleshooting + +* Program upload failure + + * Hardware connection is not correct: run `make monitor`, and reboot your board to see if there are any output logs. + * The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again. + +For any technical queries, please open an [issue](https://github.com/espressif/ESP8266_RTOS_SDK/issues) on GitHub. We will get back to you soon. \ No newline at end of file diff --git a/examples/peripherals/ir_rx/main/CMakeLists.txt b/examples/peripherals/ir_rx/main/CMakeLists.txt new file mode 100644 index 00000000..b1576cbc --- /dev/null +++ b/examples/peripherals/ir_rx/main/CMakeLists.txt @@ -0,0 +1,3 @@ +set(COMPONENT_SRCS "ir_rx_example_main.c") + +register_component() diff --git a/examples/peripherals/ir_rx/main/component.mk b/examples/peripherals/ir_rx/main/component.mk new file mode 100644 index 00000000..44bd2b52 --- /dev/null +++ b/examples/peripherals/ir_rx/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/peripherals/ir_rx/main/ir_rx_example_main.c b/examples/peripherals/ir_rx/main/ir_rx_example_main.c new file mode 100644 index 00000000..24061db4 --- /dev/null +++ b/examples/peripherals/ir_rx/main/ir_rx_example_main.c @@ -0,0 +1,70 @@ +/* IR RX Example + This is an ir rx demo of ir function( NEC CODE ,32 BIT LENGTH) + This example code is in the Public Domain (or CC0 licensed, at your option.) + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/ir_rx.h" + +static const char *TAG = "main"; + +#define IR_RX_IO_NUM 5 +#define IR_RX_BUF_LEN 128 // The actual allocated memory size is sizeof(uint32_t)*BUF_LEN. If the allocation is too small, the old infrared data may be overwritten. + +/** + * @brief check whether the ir cmd and addr obey the protocol + * + * @param nec_code nec ir code that received + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + */ +static esp_err_t ir_rx_nec_code_check(ir_rx_nec_data_t nec_code) +{ + + if ((nec_code.addr1 != ((~nec_code.addr2) & 0xff))) { + return ESP_FAIL; + } + + if ((nec_code.cmd1 != ((~nec_code.cmd2) & 0xff))) { + return ESP_FAIL; + } + + return ESP_OK; +} + +void ir_rx_task(void *arg) +{ + ir_rx_nec_data_t ir_data; + ir_rx_config_t ir_rx_config = { + .io_num = IR_RX_IO_NUM, + .buf_len = IR_RX_BUF_LEN + }; + ir_rx_init(&ir_rx_config); + + while (1) { + ir_data.val = 0; + ir_rx_recv_data(&ir_data, 1, portMAX_DELAY); + ESP_LOGI(TAG, "addr1: 0x%x, addr2: 0x%x, cmd1: 0x%x, cmd2: 0x%x", ir_data.addr1, ir_data.addr2, ir_data.cmd1, ir_data.cmd2); + + if (ESP_OK == ir_rx_nec_code_check(ir_data)) { + ESP_LOGI(TAG, "ir rx nec data: 0x%x", ir_data.cmd1); + } else { + ESP_LOGI(TAG, "Non-standard nec infrared protocol"); + } + } + + vTaskDelete(NULL); +} + +void app_main() +{ + xTaskCreate(ir_rx_task, "ir_rx_task", 2048, NULL, 5, NULL); +} \ No newline at end of file diff --git a/examples/peripherals/ir_tx/CMakeLists.txt b/examples/peripherals/ir_tx/CMakeLists.txt new file mode 100644 index 00000000..909e28b9 --- /dev/null +++ b/examples/peripherals/ir_tx/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following four lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ir_tx) diff --git a/examples/peripherals/ir_tx/Makefile b/examples/peripherals/ir_tx/Makefile new file mode 100644 index 00000000..2cd171f8 --- /dev/null +++ b/examples/peripherals/ir_tx/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ir + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/peripherals/ir_tx/README.md b/examples/peripherals/ir_tx/README.md new file mode 100644 index 00000000..6d95d18e --- /dev/null +++ b/examples/peripherals/ir_tx/README.md @@ -0,0 +1,59 @@ +# IR TX Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +In this example, we use IO14 to transmit the nec infrared signal and i2s to generate the 38Khz carrier. + +## How to Use Example + +### Hardware Required + +* A development board with ESP8266 SoC (e.g., ESP8266-DevKitC, etc.) +* A USB cable for power supply and programming + +### Configure the Project + +``` +make menuconfig +``` + +* Set serial port under Serial Flasher Options. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (467) gpio: GPIO[14]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (937) main: ir tx nec: addr:55h;cmd:00h;repeat:4 +I (2377) main: ir tx nec: addr:55h;cmd:01h;repeat:4 +I (3817) main: ir tx nec: addr:55h;cmd:02h;repeat:4 +I (5257) main: ir tx nec: addr:55h;cmd:03h;repeat:4 +I (6697) main: ir tx nec: addr:55h;cmd:04h;repeat:4 +I (8137) main: ir tx nec: addr:55h;cmd:05h;repeat:4 +``` + +If you have a logic analyzer, you can use a logic analyzer to grab online data. The following table describes the pins we use by default (Note that you can also use other pins for the same purpose). + +| pin name| function | gpio_num | +|:---:|:---:|:---:| +| IR TX|Infrared emission | GPIO_NUM_14 | + +## Troubleshooting + +* Program upload failure + + * Hardware connection is not correct: run `make monitor`, and reboot your board to see if there are any output logs. + * The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again. + +For any technical queries, please open an [issue](https://github.com/espressif/ESP8266_RTOS_SDK/issues) on GitHub. We will get back to you soon. \ No newline at end of file diff --git a/examples/peripherals/ir_tx/main/CMakeLists.txt b/examples/peripherals/ir_tx/main/CMakeLists.txt new file mode 100644 index 00000000..6afcebd8 --- /dev/null +++ b/examples/peripherals/ir_tx/main/CMakeLists.txt @@ -0,0 +1,3 @@ +set(COMPONENT_SRCS "ir_tx_example_main.c") + +register_component() diff --git a/examples/peripherals/ir_tx/main/component.mk b/examples/peripherals/ir_tx/main/component.mk new file mode 100644 index 00000000..44bd2b52 --- /dev/null +++ b/examples/peripherals/ir_tx/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/peripherals/ir_tx/main/ir_tx_example_main.c b/examples/peripherals/ir_tx/main/ir_tx_example_main.c new file mode 100644 index 00000000..75dcb00f --- /dev/null +++ b/examples/peripherals/ir_tx/main/ir_tx_example_main.c @@ -0,0 +1,58 @@ +/* IR TX Example + This is an ir tx demo of ir function( NEC CODE ,32 BIT LENGTH) + This example code is in the Public Domain (or CC0 licensed, at your option.) + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_system.h" +#include "driver/ir_tx.h" + +static const char *TAG = "main"; + +#define IR_TX_IO_NUM 14 + +void ir_tx_task(void *arg) +{ + ir_tx_config_t ir_tx_config = { + .io_num = IR_TX_IO_NUM, + .freq = 38000 + }; + + ir_tx_init(&ir_tx_config); + + ir_tx_nec_data_t ir_data[5]; + /* + The standard NEC ir code is: + addr + ~addr + cmd + ~cmd + */ + ir_data[0].addr1 = 0x55; + ir_data[0].addr2 = ~0x55; + ir_data[0].cmd1 = 0x00; + ir_data[0].cmd2 = ~0x00; + + while (1) { + for (int x = 1; x < 5; x++) { // repeat 4 times + ir_data[x] = ir_data[0]; + } + + ir_tx_send_data(ir_data, 5, portMAX_DELAY); + ESP_LOGI(TAG, "ir tx nec: addr:%02xh;cmd:%02xh;repeat:%d", ir_data[0].addr1, ir_data[0].cmd1, 4); + ir_data[0].cmd1++; + ir_data[0].cmd2 = ~ir_data[0].cmd1; + vTaskDelay(1000 / portTICK_RATE_MS); + } + + vTaskDelete(NULL); +} + +void app_main() +{ + xTaskCreate(ir_tx_task, "ir_tx_task", 2048, NULL, 5, NULL); +} \ No newline at end of file