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