mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
360 lines
11 KiB
C
360 lines
11 KiB
C
/* NEC remote infrared RMT example
|
|
|
|
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 <stdio.h>
|
|
#include <string.h>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/queue.h"
|
|
#include "freertos/semphr.h"
|
|
#include "esp_err.h"
|
|
#include "esp_log.h"
|
|
#include "driver/rmt.h"
|
|
#include "driver/periph_ctrl.h"
|
|
#include "soc/rmt_reg.h"
|
|
|
|
static const char* NEC_TAG = "NEC";
|
|
|
|
//CHOOSE SELF TEST OR NORMAL TEST
|
|
#define RMT_RX_SELF_TEST 1
|
|
|
|
/******************************************************/
|
|
/***** SELF TEST: *****/
|
|
/*Connect RMT_TX_GPIO_NUM with RMT_RX_GPIO_NUM */
|
|
/*TX task will send NEC data with carrier disabled */
|
|
/*RX task will print NEC data it receives. */
|
|
/******************************************************/
|
|
#if RMT_RX_SELF_TEST
|
|
#define RMT_RX_ACTIVE_LEVEL 1 /*!< Data bit is active high for self test mode */
|
|
#define RMT_TX_CARRIER_EN 0 /*!< Disable carrier for self test mode */
|
|
#else
|
|
//Test with infrared LED, we have to enable carrier for transmitter
|
|
//When testing via IR led, the receiver waveform is usually active-low.
|
|
#define RMT_RX_ACTIVE_LEVEL 0 /*!< If we connect with a IR receiver, the data is active low */
|
|
#define RMT_TX_CARRIER_EN 1 /*!< Enable carrier for IR transmitter test with IR led */
|
|
#endif
|
|
|
|
#define RMT_TX_CHANNEL 1 /*!< RMT channel for transmitter */
|
|
#define RMT_TX_GPIO_NUM 16 /*!< GPIO number for transmitter signal */
|
|
#define RMT_RX_CHANNEL 0 /*!< RMT channel for receiver */
|
|
#define RMT_RX_GPIO_NUM 19 /*!< GPIO number for receiver */
|
|
#define RMT_INTR_NUM 19 /*!< RMT interrupt number, select from soc.h */
|
|
#define RMT_CLK_DIV 100 /*!< RMT counter clock divider */
|
|
#define RMT_TICK_10_US (80000000/RMT_CLK_DIV/100000) /*!< RMT counter value for 10 us.(Source clock is APB clock) */
|
|
|
|
#define NEC_HEADER_HIGH_US 9000 /*!< NEC protocol header: positive 9ms */
|
|
#define NEC_HEADER_LOW_US 4500 /*!< NEC protocol header: negative 4.5ms*/
|
|
#define NEC_BIT_ONE_HIGH_US 560 /*!< NEC protocol data bit 1: positive 0.56ms */
|
|
#define NEC_BIT_ONE_LOW_US (2250-NEC_BIT_ONE_HIGH_US) /*!< NEC protocol data bit 1: negative 1.69ms */
|
|
#define NEC_BIT_ZERO_HIGH_US 560 /*!< NEC protocol data bit 0: positive 0.56ms */
|
|
#define NEC_BIT_ZERO_LOW_US (1120-NEC_BIT_ZERO_HIGH_US) /*!< NEC protocol data bit 0: negative 0.56ms */
|
|
#define NEC_BIT_END 560 /*!< NEC protocol end: positive 0.56ms */
|
|
#define NEC_BIT_MARGIN 20 /*!< NEC parse margin time */
|
|
|
|
#define NEC_ITEM_DURATION(d) ((d & 0x7fff)*10/RMT_TICK_10_US) /*!< Parse duration time from memory register value */
|
|
#define NEC_DATA_ITEM_NUM 34 /*!< NEC code item number: header + 32bit data + end */
|
|
#define RMT_TX_DATA_NUM 100 /*!< NEC tx test data number */
|
|
#define rmt_item32_tIMEOUT_US 9500 /*!< RMT receiver timeout value(us) */
|
|
|
|
/*
|
|
* @brief Build register value of waveform for NEC one data bit
|
|
*/
|
|
inline void nec_fill_item_level(rmt_item32_t* item, int high_us, int low_us)
|
|
{
|
|
item->level0 = 1;
|
|
item->duration0 = (high_us) / 10 * RMT_TICK_10_US;
|
|
item->level1 = 0;
|
|
item->duration1 = (low_us) / 10 * RMT_TICK_10_US;
|
|
}
|
|
|
|
/*
|
|
* @brief Generate NEC header value: active 9ms + negative 4.5ms
|
|
*/
|
|
static void nec_fill_item_header(rmt_item32_t* item)
|
|
{
|
|
nec_fill_item_level(item, NEC_HEADER_HIGH_US, NEC_HEADER_LOW_US);
|
|
}
|
|
|
|
/*
|
|
* @brief Generate NEC data bit 1: positive 0.56ms + negative 1.69ms
|
|
*/
|
|
static void nec_fill_item_bit_one(rmt_item32_t* item)
|
|
{
|
|
nec_fill_item_level(item, NEC_BIT_ONE_HIGH_US, NEC_BIT_ONE_LOW_US);
|
|
}
|
|
|
|
/*
|
|
* @brief Generate NEC data bit 0: positive 0.56ms + negative 0.56ms
|
|
*/
|
|
static void nec_fill_item_bit_zero(rmt_item32_t* item)
|
|
{
|
|
nec_fill_item_level(item, NEC_BIT_ZERO_HIGH_US, NEC_BIT_ZERO_LOW_US);
|
|
}
|
|
|
|
/*
|
|
* @brief Generate NEC end signal: positive 0.56ms
|
|
*/
|
|
static void nec_fill_item_end(rmt_item32_t* item)
|
|
{
|
|
nec_fill_item_level(item, NEC_BIT_END, 0x7fff);
|
|
}
|
|
|
|
/*
|
|
* @brief Check whether duration is around target_us
|
|
*/
|
|
inline bool nec_check_in_range(int duration_ticks, int target_us, int margin_us)
|
|
{
|
|
if(( NEC_ITEM_DURATION(duration_ticks) < (target_us + margin_us))
|
|
&& ( NEC_ITEM_DURATION(duration_ticks) > (target_us - margin_us))) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @brief Check whether this value represents an NEC header
|
|
*/
|
|
static bool nec_header_if(rmt_item32_t* item)
|
|
{
|
|
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
|
|
&& nec_check_in_range(item->duration0, NEC_HEADER_HIGH_US, NEC_BIT_MARGIN)
|
|
&& nec_check_in_range(item->duration1, NEC_HEADER_LOW_US, NEC_BIT_MARGIN)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* @brief Check whether this value represents an NEC data bit 1
|
|
*/
|
|
static bool nec_bit_one_if(rmt_item32_t* item)
|
|
{
|
|
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
|
|
&& nec_check_in_range(item->duration0, NEC_BIT_ONE_HIGH_US, NEC_BIT_MARGIN)
|
|
&& nec_check_in_range(item->duration1, NEC_BIT_ONE_LOW_US, NEC_BIT_MARGIN)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* @brief Check whether this value represents an NEC data bit 0
|
|
*/
|
|
static bool nec_bit_zero_if(rmt_item32_t* item)
|
|
{
|
|
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
|
|
&& nec_check_in_range(item->duration0, NEC_BIT_ZERO_HIGH_US, NEC_BIT_MARGIN)
|
|
&& nec_check_in_range(item->duration1, NEC_BIT_ZERO_LOW_US, NEC_BIT_MARGIN)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* @brief Parse NEC 32 bit waveform to address and command.
|
|
*/
|
|
static int nec_parse_items(rmt_item32_t* item, int item_num, uint16_t* addr, uint16_t* data)
|
|
{
|
|
int w_len = item_num;
|
|
if(w_len < NEC_DATA_ITEM_NUM) {
|
|
return -1;
|
|
}
|
|
int i = 0, j = 0;
|
|
if(!nec_header_if(item++)) {
|
|
return -1;
|
|
}
|
|
uint16_t addr_t = 0;
|
|
for(j = 0; j < 16; j++) {
|
|
if(nec_bit_one_if(item)) {
|
|
addr_t |= (1 << j);
|
|
} else if(nec_bit_zero_if(item)) {
|
|
addr_t |= (0 << j);
|
|
} else {
|
|
return -1;
|
|
}
|
|
item++;
|
|
i++;
|
|
}
|
|
uint16_t data_t = 0;
|
|
for(j = 0; j < 16; j++) {
|
|
if(nec_bit_one_if(item)) {
|
|
data_t |= (1 << j);
|
|
} else if(nec_bit_zero_if(item)) {
|
|
data_t |= (0 << j);
|
|
} else {
|
|
return -1;
|
|
}
|
|
item++;
|
|
i++;
|
|
}
|
|
*addr = addr_t;
|
|
*data = data_t;
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* @brief Build NEC 32bit waveform.
|
|
*/
|
|
static int nec_build_items(int channel, rmt_item32_t* item, int item_num, uint16_t addr, uint16_t cmd_data)
|
|
{
|
|
int i = 0, j = 0;
|
|
if(item_num < NEC_DATA_ITEM_NUM) {
|
|
return -1;
|
|
}
|
|
nec_fill_item_header(item++);
|
|
i++;
|
|
for(j = 0; j < 16; j++) {
|
|
if(addr & 0x1) {
|
|
nec_fill_item_bit_one(item);
|
|
} else {
|
|
nec_fill_item_bit_zero(item);
|
|
}
|
|
item++;
|
|
i++;
|
|
addr >>= 1;
|
|
}
|
|
for(j = 0; j < 16; j++) {
|
|
if(cmd_data & 0x1) {
|
|
nec_fill_item_bit_one(item);
|
|
} else {
|
|
nec_fill_item_bit_zero(item);
|
|
}
|
|
item++;
|
|
i++;
|
|
cmd_data >>= 1;
|
|
}
|
|
nec_fill_item_end(item);
|
|
i++;
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* @brief RMT transmitter initialization
|
|
*/
|
|
static void rmt_tx_init()
|
|
{
|
|
rmt_config_t rmt_tx;
|
|
rmt_tx.channel = RMT_TX_CHANNEL;
|
|
rmt_tx.gpio_num = RMT_TX_GPIO_NUM;
|
|
rmt_tx.mem_block_num = 1;
|
|
rmt_tx.clk_div = RMT_CLK_DIV;
|
|
rmt_tx.tx_config.loop_en = false;
|
|
rmt_tx.tx_config.carrier_duty_percent = 50;
|
|
rmt_tx.tx_config.carrier_freq_hz = 38000;
|
|
rmt_tx.tx_config.carrier_level = 1;
|
|
rmt_tx.tx_config.carrier_en = RMT_TX_CARRIER_EN;
|
|
rmt_tx.tx_config.idle_level = 0;
|
|
rmt_tx.tx_config.idle_output_en = true;
|
|
rmt_tx.rmt_mode = 0;
|
|
rmt_config(&rmt_tx);
|
|
rmt_driver_install(rmt_tx.channel, 0, RMT_INTR_NUM);
|
|
}
|
|
|
|
/*
|
|
* @brief RMT receiver initialization
|
|
*/
|
|
void rmt_rx_init()
|
|
{
|
|
rmt_config_t rmt_rx;
|
|
rmt_rx.channel = RMT_RX_CHANNEL;
|
|
rmt_rx.gpio_num = RMT_RX_GPIO_NUM;
|
|
rmt_rx.clk_div = RMT_CLK_DIV;
|
|
rmt_rx.mem_block_num = 1;
|
|
rmt_rx.rmt_mode = RMT_MODE_RX;
|
|
rmt_rx.rx_config.filter_en = true;
|
|
rmt_rx.rx_config.filter_ticks_thresh = 100;
|
|
rmt_rx.rx_config.idle_threshold = rmt_item32_tIMEOUT_US / 10 * (RMT_TICK_10_US);
|
|
rmt_config(&rmt_rx);
|
|
rmt_driver_install(rmt_rx.channel, 1000, RMT_INTR_NUM);
|
|
}
|
|
|
|
/**
|
|
* @brief RMT receiver demo, this task will print each received NEC data.
|
|
*
|
|
*/
|
|
void rmt_nec_rx_task()
|
|
{
|
|
int channel = RMT_RX_CHANNEL;
|
|
rmt_rx_init();
|
|
RingbufHandle_t rb = NULL;
|
|
//get RMT RX ringbuffer
|
|
rmt_get_ringbuf_handler(channel, &rb);
|
|
rmt_rx_start(channel, 1);
|
|
while(rb) {
|
|
size_t rx_size = 0;
|
|
//try to receive data from ringbuffer.
|
|
//RMT driver will push all the data it receives to its ringbuffer.
|
|
//We just need to parse the value and return the spaces of ringbuffer.
|
|
rmt_item32_t* item = (rmt_item32_t*) xRingbufferReceive(rb, &rx_size, 1000);
|
|
if(item) {
|
|
uint16_t rmt_addr;
|
|
uint16_t rmt_cmd;
|
|
int offset = 0;
|
|
while(1) {
|
|
//parse data value from ringbuffer.
|
|
int res = nec_parse_items(item + offset, rx_size / 4 - offset, &rmt_addr, &rmt_cmd);
|
|
if(res > 0) {
|
|
offset += res + 1;
|
|
ESP_LOGI(NEC_TAG, "RMT RCV --- addr: 0x%04x cmd: 0x%04x", rmt_addr, rmt_cmd);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
//after parsing the data, return spaces to ringbuffer.
|
|
vRingbufferReturnItem(rb, (void*) item);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
/**
|
|
* @brief RMT transmitter demo, this task will periodically send NEC data. (100 * 32 bits each time.)
|
|
*
|
|
*/
|
|
void rmt_nec_tx_task()
|
|
{
|
|
vTaskDelay(10);
|
|
rmt_tx_init();
|
|
esp_log_level_set(NEC_TAG, ESP_LOG_INFO);
|
|
int channel = RMT_TX_CHANNEL;
|
|
uint16_t cmd = 0x0;
|
|
uint16_t addr = 0x11;
|
|
int nec_tx_num = RMT_TX_DATA_NUM;
|
|
for(;;) {
|
|
ESP_LOGI(NEC_TAG, "RMT TX DATA");
|
|
size_t size = (sizeof(rmt_item32_t) * NEC_DATA_ITEM_NUM * nec_tx_num);
|
|
//each item represent a cycle of waveform.
|
|
rmt_item32_t* item = (rmt_item32_t*) malloc(size);
|
|
int item_num = NEC_DATA_ITEM_NUM * nec_tx_num;
|
|
memset((void*) item, 0, size);
|
|
int i, offset = 0;
|
|
while(1) {
|
|
//To build a series of waveforms.
|
|
i = nec_build_items(channel, item + offset, item_num - offset, ((~addr) << 8) | addr, cmd);
|
|
if(i < 0) {
|
|
break;
|
|
}
|
|
cmd++;
|
|
addr++;
|
|
offset += i;
|
|
}
|
|
//To send data according to the waveform items.
|
|
rmt_write_items(channel, item, item_num, true);
|
|
//Wait until sending is done.
|
|
rmt_wait_tx_done(channel);
|
|
//before we free the data, make sure sending is already done.
|
|
free(item);
|
|
vTaskDelay(2000 / portTICK_RATE_MS);
|
|
}
|
|
vTaskDelete(NULL);
|
|
}
|