diff --git a/components/modules/Kconfig b/components/modules/Kconfig index da6a6cf3..a952cac3 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -105,6 +105,12 @@ config LUA_MODULE_I2C help Includes the I2C module (recommended). +config LUA_MODULE_I2S + bool "I2S module" + default "n" + help + Includes the I2S module. + config LUA_MODULE_LEDC bool "LEDC module" default "n" diff --git a/components/modules/i2s.c b/components/modules/i2s.c new file mode 100644 index 00000000..3916dd1c --- /dev/null +++ b/components/modules/i2s.c @@ -0,0 +1,231 @@ +// Module for interfacing with i2s hardware + +#include "module.h" +#include "lauxlib.h" +#include "lmem.h" +#include "platform.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" + +#include "esp_task.h" + +#include "task/task.h" + +#include "driver/i2s.h" + +#define MAX_I2C_NUM 2 +#define I2S_CHECK_ID(id) if(id >= MAX_I2C_NUM) luaL_error( L, "i2s not exists" ) + +typedef struct { + xTaskHandle taskHandle; + QueueHandle_t *event_queue; + int event_cb; +} i2s_status_t; + +typedef struct { + i2s_event_t event; + i2s_status_t* status; +} i2s_event_post_type; + +static task_handle_t i2s_event_task_id; + +static i2s_status_t i2s_status[MAX_I2C_NUM]; + +// LUA +static void i2s_event_task( task_param_t param, task_prio_t prio ) { + i2s_event_post_type *post = (i2s_event_post_type *)param; + + lua_State *L = lua_getstate(); + int event_cb = post->status->event_cb; + if(event_cb == LUA_NOREF) { + free( post ); + return; + } + lua_rawgeti(L, LUA_REGISTRYINDEX, event_cb); + if(post->event.type == I2S_EVENT_TX_DONE) + lua_pushstring(L, "sent"); + else if(post->event.type == I2S_EVENT_RX_DONE) + lua_pushstring(L, "data"); + else + lua_pushstring(L, "error"); + lua_pushinteger(L, post->event.size); + free( post ); + lua_call(L, 2, 0); +} + +// RTOS +static void task_I2S( void *pvParameters ){ + i2s_status_t* is = (i2s_status_t*)pvParameters; + + i2s_event_post_type *post = NULL; + + for (;;){ + if(post == NULL) { + post = (i2s_event_post_type *)malloc( sizeof( i2s_event_post_type ) ); + post->status = is; + } + + if( xQueueReceive( is->event_queue, &(post->event), 3 * portTICK_PERIOD_MS ) == pdTRUE ){ + task_post_high( i2s_event_task_id, (task_param_t)post ); + post = NULL; + } + } +} + +// Lua: start( i2s_id, {}, callback ) +static int node_i2s_start( lua_State *L ) +{ + int i2s_id = luaL_checkinteger( L, 1 ); + I2S_CHECK_ID( i2s_id ); + + luaL_checkanytable (L, 2); + + i2s_config_t i2s_config; + i2s_pin_config_t pin_config; + + lua_getfield (L, 2, "mode"); + i2s_config.mode = luaL_optint(L, -1, I2S_MODE_MASTER | I2S_MODE_TX); + lua_getfield (L, 2, "rate"); + i2s_config.sample_rate = luaL_optint(L, -1, 44100); + lua_getfield (L, 2, "bits"); + i2s_config.bits_per_sample = 16; + lua_getfield (L, 2, "channel"); + i2s_config.channel_format = luaL_optint(L, -1, I2S_CHANNEL_FMT_RIGHT_LEFT); + lua_getfield (L, 2, "format"); + i2s_config.communication_format = luaL_optint(L, -1, I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB); + lua_getfield (L, 2, "buffer_count"); + i2s_config.dma_buf_count = luaL_optint(L, -1, 2); + lua_getfield (L, 2, "buffer_len"); + i2s_config.dma_buf_len = luaL_optint(L, -1, i2s_config.sample_rate / 100); + i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL2; + + lua_getfield (L, 2, "bck_pin"); + pin_config.bck_io_num = luaL_optint(L, -1, I2S_PIN_NO_CHANGE); + lua_getfield (L, 2, "ws_pin"); + pin_config.ws_io_num = luaL_optint(L, -1, I2S_PIN_NO_CHANGE); + lua_getfield (L, 2, "data_out_pin"); + pin_config.data_out_num = luaL_optint(L, -1, I2S_PIN_NO_CHANGE); + lua_getfield (L, 2, "data_in_pin"); + pin_config.data_in_num = luaL_optint(L, -1, I2S_PIN_NO_CHANGE); + + lua_settop(L, 3); + i2s_status[i2s_id].event_cb = luaL_ref(L, LUA_REGISTRYINDEX); + + i2s_driver_install(i2s_id, &i2s_config, i2s_config.dma_buf_count, &i2s_status[i2s_id].event_queue); + i2s_set_pin(i2s_id, &pin_config); + + char pcName[5]; + snprintf( pcName, 5, "I2S%d", i2s_id ); + pcName[4] = '\0'; + xTaskCreate(&task_I2S, pcName, 2048, &i2s_status[i2s_id], ESP_TASK_MAIN_PRIO + 1, &i2s_status[i2s_id].taskHandle); + + return 0; +} + +// Lua: stop( i2s_id ) +static int node_i2s_stop( lua_State *L ) +{ + int i2s_id = luaL_checkinteger( L, 1 ); + I2S_CHECK_ID( i2s_id ); + + i2s_driver_uninstall(i2s_id); + i2s_status[i2s_id].event_queue = NULL; + + if(i2s_status[i2s_id].taskHandle != NULL) { + vTaskDelete(i2s_status[i2s_id].taskHandle); + i2s_status[i2s_id].taskHandle = NULL; + } + + if(i2s_status[i2s_id].event_cb != LUA_NOREF) { + luaL_unref(L, LUA_REGISTRYINDEX, i2s_status[i2s_id].event_cb); + i2s_status[i2s_id].event_cb = LUA_NOREF; + } + + return 0; +} + +// Lua: read( i2s_id, bytes[, wait_ms] ) +static int node_i2s_read( lua_State *L ) +{ + int i2s_id = luaL_checkinteger( L, 1 ); + I2S_CHECK_ID( i2s_id ); + + size_t bytes = luaL_checkinteger( L, 2 ); + int wait_ms = luaL_optint(L, 3, 0); + char * data = luaM_malloc( L, bytes ); + size_t read = i2s_read_bytes(i2s_id, data, bytes, wait_ms / portTICK_RATE_MS); + lua_pushlstring(L, data, read); + luaM_free(L, data); + + return 1; +} + +// Lua: write( i2s_id, data[, wait_ms] ) +static int node_i2s_write( lua_State *L ) +{ + int i2s_id = luaL_checkinteger( L, 1 ); + I2S_CHECK_ID( i2s_id ); + + size_t bytes; + char *data; + if( lua_istable( L, 2 ) ) { + bytes = lua_objlen( L, 2 ); + data = (char *)luaM_malloc( L, bytes ); + for( int i = 0; i < bytes; i ++ ) { + lua_rawgeti( L, 2, i + 1 ); + data[i] = (uint8_t)luaL_checkinteger( L, -1 ); + } + } else { + data = (char *) luaL_checklstring(L, 2, &bytes); + } + int wait_ms = luaL_optint(L, 3, 0); + size_t wrote = i2s_write_bytes(i2s_id, data, bytes, wait_ms / portTICK_RATE_MS); + if( lua_istable( L, 2 ) ) + luaM_free(L, data); + lua_pushinteger( L, ( lua_Integer ) wrote ); + + return 1; +} + +// Module function map +static const LUA_REG_TYPE i2s_map[] = +{ + { LSTRKEY( "start" ), LFUNCVAL( node_i2s_start ) }, + { LSTRKEY( "stop" ), LFUNCVAL( node_i2s_stop ) }, + { LSTRKEY( "read" ), LFUNCVAL( node_i2s_read ) }, + { LSTRKEY( "write" ), LFUNCVAL( node_i2s_write ) }, + { LSTRKEY( "FORMAT_I2S" ), LNUMVAL( I2S_COMM_FORMAT_I2S ) }, + { LSTRKEY( "FORMAT_I2S_MSB" ), LNUMVAL( I2S_COMM_FORMAT_I2S_MSB ) }, + { LSTRKEY( "FORMAT_I2S_LSB" ), LNUMVAL( I2S_COMM_FORMAT_I2S_LSB ) }, + { LSTRKEY( "FORMAT_PCM" ), LNUMVAL( I2S_COMM_FORMAT_PCM ) }, + { LSTRKEY( "FORMAT_PCM_SHORT" ), LNUMVAL( I2S_COMM_FORMAT_PCM_SHORT ) }, + { LSTRKEY( "FORMAT_PCM_LONG" ), LNUMVAL( I2S_COMM_FORMAT_PCM_LONG ) }, + + { LSTRKEY( "CHANNEL_RIGHT_LEFT" ), LNUMVAL( I2S_CHANNEL_FMT_RIGHT_LEFT ) }, + { LSTRKEY( "CHANNEL_ALL_LEFT" ), LNUMVAL( I2S_CHANNEL_FMT_ALL_LEFT ) }, + { LSTRKEY( "CHANNEL_ONLY_LEFT" ), LNUMVAL( I2S_CHANNEL_FMT_ONLY_LEFT ) }, + { LSTRKEY( "CHANNEL_ALL_RIGHT" ), LNUMVAL( I2S_CHANNEL_FMT_ALL_RIGHT ) }, + { LSTRKEY( "CHANNEL_ONLY_RIGHT" ), LNUMVAL( I2S_CHANNEL_FMT_ONLY_RIGHT ) }, + + { LSTRKEY( "MODE_MASTER" ), LNUMVAL( I2S_MODE_MASTER ) }, + { LSTRKEY( "MODE_SLAVE" ), LNUMVAL( I2S_MODE_SLAVE ) }, + { LSTRKEY( "MODE_TX" ), LNUMVAL( I2S_MODE_TX ) }, + { LSTRKEY( "MODE_RX" ), LNUMVAL( I2S_MODE_RX ) }, + { LSTRKEY( "MODE_DAC_BUILT_IN" ), LNUMVAL( I2S_MODE_DAC_BUILT_IN ) }, + + { LNILKEY, LNILVAL } +}; + +int luaopen_i2s( lua_State *L ) { + for(int i2s_id = 0; i2s_id < MAX_I2C_NUM; i2s_id++) { + i2s_status[i2s_id].event_queue = NULL; + i2s_status[i2s_id].taskHandle = NULL; + i2s_status[i2s_id].event_cb = LUA_NOREF; + } + i2s_event_task_id = task_get_id( i2s_event_task ); + return 0; +} + +NODEMCU_MODULE(I2S, "i2s", i2s_map, luaopen_i2s); diff --git a/docs/en/modules/i2s.md b/docs/en/modules/i2s.md new file mode 100644 index 00000000..8d2b9bfe --- /dev/null +++ b/docs/en/modules/i2s.md @@ -0,0 +1,6 @@ +# I2S Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2017-04-29 | | | [i2s.c](../../../components/modules/i2s.c)| + +The I2S module provides access to the in-built I2S. diff --git a/mkdocs.yml b/mkdocs.yml index c7aac1a0..7d3c12ba 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,6 +40,7 @@ pages: - 'file': 'en/modules/file.md' - 'gpio': 'en/modules/gpio.md' - 'i2c': 'en/modules/i2c.md' + - 'i2s': 'en/modules/i2s.md' - 'ledc': 'en/modules/ledc.md' - 'mqtt': 'en/modules/mqtt.md' - 'net': 'en/modules/net.md'