Hendrik Langer
5 years ago
5 changed files with 384 additions and 4 deletions
@ -0,0 +1,314 @@ |
|||
// https://github.com/espressif/esp-idf/blob/master/examples/peripherals/rmt_nec_tx_rx/main/infrared_nec_main.c
|
|||
|
|||
#include <Arduino.h> |
|||
|
|||
#include <driver/rmt.h> |
|||
|
|||
#include "IR_RMT.h" |
|||
|
|||
static constexpr uint8_t CLK_DIV = 100; // Clock divisor (base clock is 80MHz)
|
|||
static constexpr uint16_t TICK_10_US = (80000000 / CLK_DIV / 100000); // Number of clock ticks that represent 10us. 10 us = 1/100th msec.
|
|||
|
|||
static constexpr uint8_t NEC_DATA_ITEM_NUM = 34; // NEC code item number: header + 32bit data + end
|
|||
|
|||
#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_REPEAT_LOW_US 2250 /*!< NEC repeat header: negative 2.25ms*/ |
|||
#define NEC_BIT_REPEAT_HIGH_US 560 |
|||
#define NEC_BIT_MARGIN 40 /*!< NEC parse margin time */ |
|||
|
|||
namespace { |
|||
|
|||
bool isInRange(rmt_item32_t item, int lowDuration, int highDuration, int tolerance) { |
|||
uint32_t lowValue = item.duration0 * 10 / TICK_10_US; |
|||
uint32_t highValue = item.duration1 * 10 / TICK_10_US; |
|||
/*
|
|||
ESP_LOGD(tag, "lowValue=%d, highValue=%d, lowDuration=%d, highDuration=%d", |
|||
lowValue, highValue, lowDuration, highDuration); |
|||
*/ |
|||
if (lowValue < (lowDuration - tolerance) || lowValue > (lowDuration + tolerance) || |
|||
(highValue != 0 && |
|||
(highValue < (highDuration - tolerance) || highValue > (highDuration + tolerance)))) { |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
bool NEC_is0(rmt_item32_t item) { |
|||
return isInRange(item, NEC_BIT_ZERO_HIGH_US, NEC_BIT_ZERO_LOW_US, NEC_BIT_MARGIN); |
|||
} |
|||
|
|||
bool NEC_is1(rmt_item32_t item) { |
|||
return isInRange(item, NEC_BIT_ONE_HIGH_US, NEC_BIT_ONE_LOW_US, NEC_BIT_MARGIN); |
|||
} |
|||
|
|||
ir_data_t decodeNEC(rmt_item32_t *data, int numItems) { |
|||
ir_data_t irData = {0,0}; |
|||
|
|||
// check for repeat
|
|||
if (numItems == 2) { |
|||
if (isInRange(data[0], NEC_HEADER_HIGH_US, NEC_REPEAT_LOW_US, NEC_BIT_MARGIN)) { |
|||
Serial.println("repeat header"); |
|||
if (isInRange(data[1], NEC_BIT_REPEAT_HIGH_US, 0, NEC_BIT_MARGIN)) { |
|||
irData = {REPEAT, REPEAT}; |
|||
return irData; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!isInRange(data[0], NEC_HEADER_HIGH_US, NEC_HEADER_LOW_US, NEC_BIT_MARGIN) || numItems != 34) { |
|||
ESP_LOGD(tag, "Not an NEC"); |
|||
irData = {INVALID_PROTOCOL, INVALID_PROTOCOL}; |
|||
return irData; // 0
|
|||
} |
|||
int i; |
|||
uint8_t address = 0, notAddress = 0, command = 0, notCommand = 0; |
|||
int accumCounter = 0; |
|||
uint8_t accumValue = 0; |
|||
for (i=1; i<numItems; i++) { |
|||
if (NEC_is0(data[i])) { |
|||
ESP_LOGD(tag, "%d: 0", i); |
|||
accumValue = accumValue >> 1; |
|||
} else if (NEC_is1(data[i])) { |
|||
ESP_LOGD(tag, "%d: 1", i); |
|||
accumValue = (accumValue >> 1) | 0x80; |
|||
} else { |
|||
ESP_LOGD(tag, "Unknown"); |
|||
} |
|||
if (accumCounter == 7) { |
|||
accumCounter = 0; |
|||
ESP_LOGD(tag, "Byte: 0x%.2x", accumValue); |
|||
if (i==8) { |
|||
address = accumValue; |
|||
} else if (i==16) { |
|||
notAddress = accumValue; |
|||
} else if (i==24) { |
|||
command = accumValue; |
|||
} else if (i==32) { |
|||
notCommand = accumValue; |
|||
} |
|||
accumValue = 0; |
|||
} else { |
|||
accumCounter++; |
|||
} |
|||
} |
|||
ESP_LOGD(tag, "Address: 0x%.2x, NotAddress: 0x%.2x", address, notAddress ^ 0xff); |
|||
if (address != (notAddress ^ 0xff) || command != (notCommand ^ 0xff)) { |
|||
ESP_LOGD(tag, "Data mis match"); |
|||
irData = {INVALID_CHECKSUM, INVALID_CHECKSUM}; |
|||
return irData; // 0
|
|||
} |
|||
ESP_LOGD(tag, "Address: 0x%.2x, Command: 0x%.2x", address, command); |
|||
|
|||
irData.address = address; |
|||
irData.command = command; |
|||
|
|||
return irData; |
|||
} |
|||
|
|||
|
|||
|
|||
} /* anonymous namespace */ |
|||
|
|||
|
|||
/*
|
|||
* @brief Build register value of waveform for NEC one data bit |
|||
*/ |
|||
inline void IR_RMT::nec_fill_item_level(rmt_item32_t* item, int high_us, int low_us) |
|||
{ |
|||
item->level0 = inverted ? 0 : 1; |
|||
item->duration0 = (high_us) / 10 * TICK_10_US; |
|||
item->level1 = inverted ? 1 : 0; |
|||
item->duration1 = (low_us) / 10 * TICK_10_US; |
|||
} |
|||
|
|||
/*
|
|||
* @brief Generate NEC header value: active 9ms + negative 4.5ms |
|||
*/ |
|||
void IR_RMT::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 |
|||
*/ |
|||
void IR_RMT::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 |
|||
*/ |
|||
void IR_RMT::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 |
|||
*/ |
|||
void IR_RMT::nec_fill_item_end(rmt_item32_t* item) |
|||
{ |
|||
nec_fill_item_level(item, NEC_BIT_END, 0x7fff); |
|||
} |
|||
|
|||
/*
|
|||
* @brief Build NEC 32bit waveform. |
|||
*/ |
|||
void IR_RMT::nec_build_items(rmt_item32_t* item, uint16_t addr, uint16_t cmd_data) |
|||
{ |
|||
nec_fill_item_header(item++); |
|||
for(int j = 0; j < 16; j++) { |
|||
if(addr & 0x1) { |
|||
nec_fill_item_bit_one(item); |
|||
} else { |
|||
nec_fill_item_bit_zero(item); |
|||
} |
|||
item++; |
|||
addr >>= 1; |
|||
} |
|||
for(int j = 0; j < 16; j++) { |
|||
if(cmd_data & 0x1) { |
|||
nec_fill_item_bit_one(item); |
|||
} else { |
|||
nec_fill_item_bit_zero(item); |
|||
} |
|||
item++; |
|||
cmd_data >>= 1; |
|||
} |
|||
nec_fill_item_end(item); |
|||
} |
|||
|
|||
bool IR_RMT::send(uint16_t address, uint16_t command) { |
|||
int item_num = NEC_DATA_ITEM_NUM; |
|||
size_t size = (sizeof(rmt_item32_t) * item_num); |
|||
|
|||
rmt_item32_t* item = (rmt_item32_t*)heap_caps_malloc(size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); |
|||
memset((void*) item, 0, size); |
|||
|
|||
nec_build_items(item, ((~address) << 8) | address, ((~command) << 8) | command); |
|||
|
|||
//To send data according to the waveform items.
|
|||
rmt_write_items(tx_channel, item, item_num, true); |
|||
//Wait until sending is done.
|
|||
//rmt_wait_tx_done(tx_channel, portMAX_DELAY);
|
|||
//before we free the data, make sure sending is already done.
|
|||
free(item); |
|||
return true; |
|||
} |
|||
|
|||
bool IR_RMT::sendNEC32(uint32_t data) { |
|||
int item_num = NEC_DATA_ITEM_NUM; |
|||
size_t size = (sizeof(rmt_item32_t) * item_num); |
|||
|
|||
rmt_item32_t* item = (rmt_item32_t*)heap_caps_malloc(size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); |
|||
memset((void*) item, 0, size); |
|||
|
|||
nec_build_items(item, (data >> 16), data); |
|||
|
|||
//To send data according to the waveform items.
|
|||
rmt_write_items(tx_channel, item, item_num, true); |
|||
//Wait until sending is done.
|
|||
//rmt_wait_tx_done(tx_channel, portMAX_DELAY);
|
|||
//before we free the data, make sure sending is already done.
|
|||
free(item); |
|||
return true; |
|||
} |
|||
|
|||
IR_RMT::IR_RMT(gpio_num_t rx_pin, gpio_num_t tx_pin, bool inverted) { |
|||
this->rx_pin = rx_pin; |
|||
this->tx_pin = tx_pin; |
|||
this->rx_channel = RMT_CHANNEL_7; |
|||
this->tx_channel = RMT_CHANNEL_6; |
|||
this->running = false; |
|||
this->inverted = inverted; |
|||
} |
|||
|
|||
bool IR_RMT::begin() { |
|||
if (rx_pin >= 0) { |
|||
rmt_config_t config; |
|||
config.rmt_mode = RMT_MODE_RX; |
|||
config.channel = rx_channel; |
|||
config.gpio_num = rx_pin; |
|||
config.mem_block_num = 1; |
|||
config.rx_config.filter_en = true; |
|||
config.rx_config.filter_ticks_thresh = 100; // 80000000/100 -> 800000 / 100 = 8000 = 125us
|
|||
config.rx_config.idle_threshold = TICK_10_US * 100 * 20; |
|||
config.clk_div = CLK_DIV; |
|||
|
|||
ESP_ERROR_CHECK(rmt_config(&config)); |
|||
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 1000, 0)); |
|||
rmt_get_ringbuf_handle(rx_channel, &ringBuf); |
|||
rmt_rx_start(rx_channel, 1); |
|||
} |
|||
|
|||
if (tx_pin >= 0) { |
|||
rmt_config_t rmt_tx; |
|||
rmt_tx.rmt_mode = RMT_MODE_TX; |
|||
rmt_tx.channel = tx_channel; |
|||
rmt_tx.gpio_num = tx_pin; |
|||
rmt_tx.mem_block_num = 1; |
|||
rmt_tx.clk_div = 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 = inverted ? RMT_CARRIER_LEVEL_LOW : RMT_CARRIER_LEVEL_HIGH; |
|||
rmt_tx.tx_config.carrier_en = true; |
|||
rmt_tx.tx_config.idle_level = inverted ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW; |
|||
rmt_tx.tx_config.idle_output_en = true; |
|||
ESP_ERROR_CHECK(rmt_config(&rmt_tx)); |
|||
ESP_ERROR_CHECK(rmt_driver_install(rmt_tx.channel, 0, 0)); |
|||
//rmt_tx_start(tx_channel, true);
|
|||
} |
|||
} |
|||
|
|||
bool IR_RMT::stop() { |
|||
running = false; |
|||
rmt_tx_stop(tx_channel); |
|||
rmt_rx_stop(rx_channel); |
|||
} |
|||
|
|||
IR_RMT::~IR_RMT() { |
|||
rmt_driver_uninstall(tx_channel); |
|||
rmt_driver_uninstall(rx_channel); |
|||
} |
|||
|
|||
bool IR_RMT::register_callback(const ir_cb_t &cb) { |
|||
callbacks_.push_back(cb); |
|||
if (!rx_task_handle) { |
|||
xTaskCreate(&cTaskWrapper, "watch_ringbuf", 3072, this, tskIDLE_PRIORITY+2, &rx_task_handle); |
|||
} |
|||
} |
|||
|
|||
void IR_RMT::task_watch_ringbuf(void* pvParameters) { |
|||
size_t itemSize; |
|||
|
|||
running = true; |
|||
while(running && ringBuf) { |
|||
rmt_item32_t* data = (rmt_item32_t*) xRingbufferReceive(ringBuf, &itemSize, portMAX_DELAY); |
|||
int numItems = itemSize / sizeof(rmt_item32_t); |
|||
|
|||
ir_data_t val = decodeNEC(data, numItems); |
|||
|
|||
if (val.address != INVALID_CHECKSUM && val.address != INVALID_PROTOCOL) { |
|||
for (const auto &cb : callbacks_) { |
|||
cb(val); |
|||
} |
|||
} |
|||
|
|||
vRingbufferReturnItem(ringBuf, (void*) data); |
|||
} |
|||
rx_task_handle = NULL; |
|||
vTaskDelete(NULL); |
|||
} |
|||
|
|||
void IR_RMT::cTaskWrapper(void* parameters) { |
|||
static_cast<IR_RMT*>(parameters)->task_watch_ringbuf(NULL); |
|||
} |
@ -0,0 +1,53 @@ |
|||
#ifndef _IR_RMT_H |
|||
#define _IR_RMT_H |
|||
|
|||
#include <Arduino.h> |
|||
#include <functional> |
|||
#include <vector> |
|||
|
|||
#include <driver/rmt.h> |
|||
|
|||
|
|||
struct ir_data_t { |
|||
uint16_t address; |
|||
uint16_t command; |
|||
}; |
|||
|
|||
typedef std::function<void(ir_data_t)> ir_cb_t; |
|||
|
|||
class IR_RMT { |
|||
public: |
|||
IR_RMT(gpio_num_t rx_pin, gpio_num_t tx_pin, bool inverted = false); |
|||
~IR_RMT(); |
|||
bool begin(void); |
|||
bool stop(void); |
|||
bool register_callback(const ir_cb_t &cb); |
|||
bool send(uint16_t address, uint16_t command); |
|||
bool sendNEC32(uint32_t data); |
|||
private: |
|||
gpio_num_t rx_pin; |
|||
gpio_num_t tx_pin; |
|||
rmt_channel_t rx_channel; |
|||
rmt_channel_t tx_channel; |
|||
RingbufHandle_t ringBuf; |
|||
TaskHandle_t rx_task_handle; |
|||
bool running; |
|||
bool inverted; |
|||
void task_watch_ringbuf(void*); |
|||
static void cTaskWrapper(void*); |
|||
std::vector<ir_cb_t> callbacks_; |
|||
protected: |
|||
void nec_fill_item_level(rmt_item32_t* item, int high_us, int low_us); |
|||
void nec_fill_item_header(rmt_item32_t* item); |
|||
void nec_fill_item_bit_one(rmt_item32_t* item); |
|||
void nec_fill_item_bit_zero(rmt_item32_t* item); |
|||
void nec_fill_item_end(rmt_item32_t* item); |
|||
void nec_build_items(rmt_item32_t* item, uint16_t addr, uint16_t cmd_data); |
|||
}; |
|||
|
|||
enum command_type { |
|||
REPEAT = 0x100, |
|||
INVALID_CHECKSUM = UINT16_MAX-1, |
|||
INVALID_PROTOCOL = UINT16_MAX }; |
|||
|
|||
#endif /* _IR_RMT_H */ |
Loading…
Reference in new issue