nodemcu-firmware/components/modules/rmt.c

397 lines
10 KiB
C
Raw Normal View History

// Module for working with the rmt driver
#include <string.h>
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "platform_rmt.h"
#include "task/task.h"
#include "driver/rmt.h"
#include "common.h"
typedef struct {
bool tx;
int channel;
int cb_ref;
struct _lrmt_cb_params *rx_params;
} *lrmt_channel_t;
typedef struct _lrmt_cb_params {
bool dont_call;
bool rx_shutting_down;
rmt_channel_t channel;
int cb_ref;
int data_ref;
int rc;
size_t len;
rmt_item32_t *data;
} lrmt_cb_params;
static task_handle_t cb_task_id;
static int get_divisor(lua_State *L, int index) {
int bittime = luaL_checkinteger(L, index);
int divisor = bittime / 12500; // 80MHz clock
luaL_argcheck(L, divisor >= 1 && divisor <= 255, index, "Bit time out of range");
return divisor;
}
static int configure_channel(lua_State *L, rmt_config_t *config, rmt_mode_t mode) {
lrmt_channel_t ud = (lrmt_channel_t)lua_newuserdata(L, sizeof(*ud));
if (!ud) return luaL_error(L, "not enough memory");
memset(ud, 0, sizeof(*ud));
luaL_getmetatable(L, "rmt.channel");
lua_setmetatable(L, -2);
// We have allocated the channel -- must free it if the rest of this method fails
int channel = platform_rmt_allocate(config->mem_block_num, mode);
if (channel < 0) {
return luaL_error(L, "no spare RMT channel");
}
config->channel = channel;
ud->channel = channel;
ud->tx = mode == RMT_MODE_TX;
esp_err_t rc = rmt_config(config);
if (rc) {
platform_rmt_release(config->channel);
return luaL_error(L, "Failed to configure RMT");
}
rc = rmt_driver_install(config->channel, config->mem_block_num * 600, 0);
if (rc) {
platform_rmt_release(config->channel);
return luaL_error(L, "Failed to install RMT driver");
}
return 1;
}
static int lrmt_txsetup(lua_State *L) {
int gpio = luaL_checkinteger(L, 1);
int divisor = get_divisor(L, 2);
// We will set the channel later
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(gpio, 0);
config.clk_div = divisor;
if (lua_type(L, 3) == LUA_TTABLE) {
lua_getfield(L, 3, "carrier_hz");
int hz = lua_tointeger(L, -1);
if (hz) {
config.tx_config.carrier_freq_hz = hz;
config.tx_config.carrier_en = true;
}
lua_pop(L, 1);
lua_getfield(L, 3, "carrier_duty");
int duty = lua_tointeger(L, -1);
if (duty) {
config.tx_config.carrier_duty_percent = duty;
}
lua_pop(L, 1);
lua_getfield(L, 3, "idle_level");
if (!lua_isnil(L, -1)) {
int level = lua_tointeger(L, -1);
config.tx_config.idle_level = level;
config.tx_config.idle_output_en = true;
}
lua_pop(L, 1);
lua_getfield(L, 3, "invert");
if (lua_toboolean(L, -1)) {
config.flags |= RMT_CHANNEL_FLAGS_INVERT_SIG;
}
lua_pop(L, 1);
lua_getfield(L, 3, "slots");
int slots = lua_tointeger(L, -1);
if (slots > 0 && slots <= 8) {
config.mem_block_num = slots;
}
lua_pop(L, 1);
}
configure_channel(L, &config, RMT_MODE_TX);
lua_pushinteger(L, divisor * 12500);
return 2;
}
static int lrmt_rxsetup(lua_State *L) {
int gpio = luaL_checkinteger(L, 1);
int divisor = get_divisor(L, 2);
// We will set the channel later
rmt_config_t config = RMT_DEFAULT_CONFIG_RX(gpio, 0);
config.clk_div = divisor;
config.rx_config.idle_threshold = 65535;
if (lua_type(L, 3) == LUA_TTABLE) {
lua_getfield(L, 3, "invert");
if (lua_toboolean(L, -1)) {
config.flags |= RMT_CHANNEL_FLAGS_INVERT_SIG;
}
lua_pop(L, 1);
lua_getfield(L, 3, "filter_ticks");
if (!lua_isnil(L, -1)) {
int ticks = lua_tointeger(L, -1);
if (ticks < 0 || ticks > 255) {
return luaL_error(L, "filter_ticks must be in the range 0 - 255");
}
config.rx_config.filter_ticks_thresh = ticks;
config.rx_config.filter_en = true;
}
lua_pop(L, 1);
lua_getfield(L, 3, "idle_threshold");
if (!lua_isnil(L, -1)) {
int threshold = lua_tointeger(L, -1);
if (threshold < 0 || threshold > 65535) {
return luaL_error(L, "idle_threshold must be in the range 0 - 65535");
}
config.rx_config.idle_threshold = threshold;
}
lua_pop(L, 1);
lua_getfield(L, 3, "slots");
int slots = lua_tointeger(L, -1);
if (slots > 0 && slots <= 8) {
config.mem_block_num = slots;
}
lua_pop(L, 1);
}
configure_channel(L, &config, RMT_MODE_RX);
lua_pushinteger(L, divisor * 12500);
return 2;
}
static void free_transmit_wait_params(lua_State *L, lrmt_cb_params *p) {
if (!p->data) {
luaL_unref(L, LUA_REGISTRYINDEX, p->cb_ref);
luaL_unref(L, LUA_REGISTRYINDEX, p->data_ref);
}
free(p);
}
static void handle_receive(void *param) {
lrmt_cb_params *p = (lrmt_cb_params *) param;
RingbufHandle_t rb = NULL;
//get RMT RX ringbuffer
rmt_get_ringbuf_handle(p->channel, &rb);
// Start receive
rmt_rx_start(p->channel, true);
while (!p->rx_shutting_down) {
size_t length = 0;
rmt_item32_t *items = NULL;
items = (rmt_item32_t *) xRingbufferReceive(rb, &length, 50 / portTICK_PERIOD_MS);
if (items && length) {
lrmt_cb_params *rx_params = malloc(sizeof(lrmt_cb_params) + length);
if (rx_params) {
memset(rx_params, 0, sizeof(*rx_params));
memcpy(rx_params + 1, items, length);
rx_params->cb_ref = p->cb_ref;
rx_params->data = (void *) (rx_params + 1);
rx_params->len = length;
rx_params->channel = p->channel;
task_post_high(cb_task_id, (task_param_t) rx_params);
} else {
printf("Unable allocate receive data memory\n");
}
}
if (items) {
vRingbufferReturnItem(rb, (void *) items);
}
}
rmt_rx_stop(p->channel);
p->dont_call = true;
task_post_high(cb_task_id, (task_param_t) p);
/* Destroy this task */
vTaskDelete(NULL);
}
static int lrmt_on(lua_State *L) {
lrmt_channel_t ud = (lrmt_channel_t)luaL_checkudata(L, 1, "rmt.channel");
if (ud->tx) {
return luaL_error(L, "Cannot receive on a TX channel");
}
luaL_argcheck(L, !strcmp(lua_tostring(L, 2), "data") , 2, "Must be 'data'");
luaL_argcheck(L, lua_type(L, 3) == LUA_TFUNCTION, 3, "Must be a function");
if (ud->rx_params) {
return luaL_error(L, "Can only call 'on' once");
}
// We have a callback
lrmt_cb_params *params = (lrmt_cb_params *) malloc(sizeof(*params));
if (!params) {
return luaL_error(L, "Cannot allocate memory");
}
memset(params, 0, sizeof(*params));
lua_pushvalue(L, 3);
params->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
ud->rx_params = params;
params->channel = ud->channel;
xTaskCreate(handle_receive, "rmt-rx-receiver", 3000, params, 2, NULL);
return 0;
}
static void wait_for_transmit(void *param) {
lrmt_cb_params *p = (lrmt_cb_params *) param;
esp_err_t rc = rmt_wait_tx_done(p->channel, 10000 / portTICK_PERIOD_MS);
p->rc = rc;
task_post_high(cb_task_id, (task_param_t) p);
/* Destroy this task */
vTaskDelete(NULL);
}
static int lrmt_send(lua_State *L) {
lrmt_channel_t ud = (lrmt_channel_t)luaL_checkudata(L, 1, "rmt.channel");
if (!ud->tx) {
return luaL_error(L, "Cannot send on an RX channel");
}
int string_index = 2;
if (lua_type(L, 2) == LUA_TTABLE) {
lua_getfield(L, 2, "concat");
lua_pushvalue(L, 2);
lua_pushstring(L, "");
lua_call(L, 2, 1);
string_index = -1;
}
size_t len;
const char *data = lua_tolstring(L, string_index, &len);
if (!data || !len) {
return 0;
}
if (len & 1) {
return luaL_error(L, "Length must be a multiple of 2");
}
if (len & 3) {
// Just tack on a "\0\0" -- this is needed as the hardware can
// only deal with multiple of 4 bytes.
luaL_Buffer b;
luaL_buffinit(L, &b);
luaL_addlstring(&b, data, len);
luaL_addlstring(&b, "\0\0", 2);
luaL_pushresult(&b);
data = lua_tolstring(L, -1, &len);
string_index = -1;
}
bool wait_for_done = true;
if (lua_type(L, 3) == LUA_TFUNCTION) {
// We have a callback
lrmt_cb_params *params = (lrmt_cb_params *) malloc(sizeof(*params));
if (!params) {
return luaL_error(L, "Cannot allocate memory");
}
memset(params, 0, sizeof(*params));
params->channel = ud->channel;
lua_pushvalue(L, 3);
params->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
lua_pushvalue(L, string_index);
params->data_ref = luaL_ref(L, LUA_REGISTRYINDEX);
xTaskCreate(wait_for_transmit, "rmt-tx-waiter", 1024, params, 2, NULL);
wait_for_done = false;
}
// We want to transmit it
rmt_write_items(ud->channel, (rmt_item32_t *) data, len / sizeof(rmt_item32_t), wait_for_done);
return 0;
}
static int lrmt_close(lua_State *L) {
lrmt_channel_t ud = (lrmt_channel_t)luaL_checkudata(L, 1, "rmt.channel");
if (ud->channel >= 0) {
if (ud->rx_params) {
// We need to stop the listening task
ud->rx_params->rx_shutting_down = true;
} else {
rmt_driver_uninstall(ud->channel);
platform_rmt_release(ud->channel);
}
ud->channel = -1;
}
return 0;
}
static void cb_task(task_param_t param, task_prio_t prio) {
lrmt_cb_params *p = (lrmt_cb_params *) param;
lua_State *L = lua_getstate();
if (!p->dont_call) {
lua_rawgeti (L, LUA_REGISTRYINDEX, p->cb_ref);
if (p->data) {
lua_pushlstring(L, (char *) p->data, p->len);
} else {
lua_pushinteger(L, p->rc);
}
int res = luaL_pcallx(L, 1, 0);
if (res) {
printf("rmt callback threw an error\n");
}
}
if (p->rx_shutting_down) {
rmt_driver_uninstall(p->channel);
platform_rmt_release(p->channel);
}
free_transmit_wait_params(L, p);
}
// Module function map
LROT_BEGIN(rmt_channel, NULL, LROT_MASK_GC_INDEX)
LROT_FUNCENTRY( __gc, lrmt_close )
LROT_TABENTRY ( __index, rmt_channel )
LROT_FUNCENTRY( on, lrmt_on )
LROT_FUNCENTRY( close, lrmt_close )
LROT_FUNCENTRY( send, lrmt_send )
LROT_END(rmt_channel, NULL, LROT_MASK_GC_INDEX)
LROT_BEGIN(rmt, NULL, LROT_MASK_INDEX)
LROT_TABENTRY ( __index, rmt )
LROT_FUNCENTRY( txsetup, lrmt_txsetup )
LROT_FUNCENTRY( rxsetup, lrmt_rxsetup )
LROT_END(rmt, NULL, LROT_MASK_INDEX)
int luaopen_rmt(lua_State *L) {
luaL_rometatable(L, "rmt.channel", LROT_TABLEREF(rmt_channel)); // create metatable
cb_task_id = task_get_id(cb_task);
return 0;
}
NODEMCU_MODULE(RMT, "rmt", rmt, luaopen_rmt);