nodemcu-firmware/components/modules/i2s.c

354 lines
10 KiB
C

// Module for interfacing with i2s hardware
#include <string.h>
#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 {
int ref;
const char *data;
size_t len;
} i2s_tx_data_t;
typedef struct {
struct {
xTaskHandle taskHandle;
QueueHandle_t queue;
} tx;
struct {
xTaskHandle taskHandle;
QueueHandle_t queue;
} rx;
int cb;
int i2s_bits_per_sample, data_bits_per_sample;
} i2s_status_t;
static task_handle_t i2s_tx_task_id, i2s_rx_task_id, i2s_disposal_task_id;
static i2s_status_t i2s_status[MAX_I2C_NUM];
// LUA
static void i2s_tx_task( task_param_t param, task_prio_t prio ) {
int i2s_id = (int)param;
i2s_status_t *is = &i2s_status[i2s_id];
if (is->cb != LUA_NOREF) {
lua_State *L = lua_getstate();
lua_rawgeti(L, LUA_REGISTRYINDEX, is->cb);
lua_pushinteger( L, i2s_id );
lua_pushstring( L, "tx" );
lua_call( L, 2, 0 );
}
}
static void i2s_rx_task( task_param_t param, task_prio_t prio ) {
int i2s_id = (int)param;
i2s_status_t *is = &i2s_status[i2s_id];
if (is->cb != LUA_NOREF) {
lua_State *L = lua_getstate();
lua_rawgeti(L, LUA_REGISTRYINDEX, is->cb);
lua_pushinteger( L, i2s_id );
lua_pushstring( L, "rx" );
lua_call( L, 2, 0 );
}
}
static void i2s_disposal_task( task_param_t param, task_prio_t prio ) {
lua_State *L = lua_getstate();
int ref = (int)param;
luaL_unref( L, LUA_REGISTRYINDEX, ref );
}
// RTOS
static void task_I2S_rx( void *pvParameters ) {
int i2s_id = (int)pvParameters;
i2s_status_t *is = &i2s_status[i2s_id];
i2s_event_t i2s_event;
for (;;) {
// process I2S RX events
xQueueReceive( is->rx.queue, &i2s_event, portMAX_DELAY );
if (i2s_event.type == I2S_EVENT_RX_DONE) {
task_post_high( i2s_rx_task_id, i2s_id );
}
}
}
static void task_I2S_tx( void *pvParameters ) {
int i2s_id = (int)pvParameters;
i2s_status_t *is = &i2s_status[i2s_id];
i2s_tx_data_t tx_data;
for (;;) {
// request new TX data
task_post_high( i2s_tx_task_id, i2s_id );
// get TX data from Lua task
xQueueReceive( is->tx.queue, &tx_data, portMAX_DELAY );
// write TX data to I2S, note that this call might block
size_t dummy;
if (is->data_bits_per_sample == is->i2s_bits_per_sample) {
i2s_write( i2s_id, tx_data.data, tx_data.len, &dummy, portMAX_DELAY );
} else {
i2s_write_expand( i2s_id, tx_data.data, tx_data.len, is->data_bits_per_sample, is->i2s_bits_per_sample, &dummy, portMAX_DELAY );
}
// notify Lua to dispose object
task_post_low( i2s_disposal_task_id, tx_data.ref );
}
}
// 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 );
i2s_status_t *is = &i2s_status[i2s_id];
int top = lua_gettop( L );
luaL_checkanytable (L, 2);
i2s_config_t i2s_config;
memset( &i2s_config, 0, sizeof( i2s_config ) );
i2s_pin_config_t pin_config;
memset( &pin_config, 0, sizeof( 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");
is->data_bits_per_sample = luaL_optint(L, -1, 16);
is->i2s_bits_per_sample = is->data_bits_per_sample < I2S_BITS_PER_SAMPLE_16BIT ? I2S_BITS_PER_SAMPLE_16BIT : is->data_bits_per_sample;
i2s_config.bits_per_sample = is->i2s_bits_per_sample;
//
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_LOWMED | ESP_INTR_FLAG_IRAM;
//
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_getfield(L, 2, "dac_mode");
i2s_dac_mode_t dac_mode = luaL_optint(L, -1, I2S_DAC_CHANNEL_DISABLE);
//
lua_getfield(L, 2, "adc1_channel");
adc1_channel_t adc1_channel = luaL_optint(L, -1, ADC1_CHANNEL_MAX);
// handle optional callback functions TX and RX
lua_settop( L, top );
if (lua_isfunction( L, 3 )) {
lua_pushvalue( L, 3 );
is->cb = luaL_ref( L, LUA_REGISTRYINDEX );
} else {
is->cb = LUA_NOREF;
}
esp_err_t err;
if (i2s_config.mode & I2S_MODE_RX) {
err = i2s_driver_install(i2s_id, &i2s_config, i2s_config.dma_buf_count, &is->rx.queue);
} else {
err = i2s_driver_install(i2s_id, &i2s_config, i2s_config.dma_buf_count, NULL);
}
if (err != ESP_OK)
luaL_error( L, "i2s can not start" );
if (dac_mode != I2S_DAC_CHANNEL_DISABLE) {
if (i2s_set_dac_mode( dac_mode ) != ESP_OK)
luaL_error( L, "error setting dac mode" );
}
if (adc1_channel != ADC1_CHANNEL_MAX) {
if (i2s_set_adc_mode( ADC_UNIT_1, adc1_channel ) != ESP_OK)
luaL_error( L, "error setting adc1 mode" );
}
if (i2s_set_pin(i2s_id, &pin_config) != ESP_OK)
luaL_error( L, "error setting pins" );
if (i2s_config.mode & I2S_MODE_TX) {
// prepare TX task
char pcName[8];
snprintf( pcName, 8, "I2S_tx_%d", i2s_id );
pcName[7] = '\0';
if ((is->tx.queue = xQueueCreate( 2, sizeof( i2s_tx_data_t ) )) == NULL)
return luaL_error( L, "cannot create queue" );
xTaskCreate(task_I2S_tx, pcName, 2048, (void *)i2s_id, ESP_TASK_MAIN_PRIO + 1, &is->tx.taskHandle);
}
if (i2s_config.mode & I2S_MODE_RX) {
// prepare RX task
char pcName[8];
snprintf( pcName, 8, "I2S_rx_%d", i2s_id );
pcName[7] = '\0';
xTaskCreate(task_I2S_rx, pcName, 1024, (void *)i2s_id, ESP_TASK_MAIN_PRIO + 1, &is->rx.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_status_t *is = &i2s_status[i2s_id];
i2s_driver_uninstall(i2s_id);
if (is->tx.taskHandle != NULL) {
vTaskDelete( is->tx.taskHandle );
is->tx.taskHandle = NULL;
}
if (is->tx.queue != NULL) {
vQueueDelete( is->tx.queue );
is->tx.queue = NULL;
}
if (is->rx.taskHandle != NULL) {
vTaskDelete( is->rx.taskHandle );
is->rx.taskHandle = NULL;
}
// the rx queue is created and destroyed by the I2S driver
if (is->cb != LUA_NOREF) {
luaL_unref( L, LUA_REGISTRYINDEX, is->cb );
is->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 = 0;
//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 )
static int node_i2s_write( lua_State *L )
{
int stack = 0;
int i2s_id = luaL_checkinteger( L, ++stack );
I2S_CHECK_ID( i2s_id );
i2s_tx_data_t tx_data;
tx_data.data = (const char *)luaL_checklstring( L, ++stack, &tx_data.len );
lua_pushvalue( L, stack );
tx_data.ref = luaL_ref( L, LUA_REGISTRYINDEX );
// post this to the TX task
xQueueSendToBack( i2s_status[i2s_id].tx.queue, &tx_data, portMAX_DELAY );
return 0;
}
// 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 ) },
{ LSTRKEY( "MODE_ADC_BUILT_IN" ), LNUMVAL( I2S_MODE_ADC_BUILT_IN ) },
{ LSTRKEY( "MODE_PDM" ), LNUMVAL( I2S_MODE_PDM ) },
{ LSTRKEY( "DAC_CHANNEL_DISABLE" ), LNUMVAL( I2S_DAC_CHANNEL_DISABLE ) },
{ LSTRKEY( "DAC_CHANNEL_RIGHT" ), LNUMVAL( I2S_DAC_CHANNEL_RIGHT_EN ) },
{ LSTRKEY( "DAC_CHANNEL_LEFT" ), LNUMVAL( I2S_DAC_CHANNEL_LEFT_EN ) },
{ LSTRKEY( "DAC_CHANNEL_BOTH" ), LNUMVAL( I2S_DAC_CHANNEL_BOTH_EN ) },
{ LNILKEY, LNILVAL }
};
int luaopen_i2s( lua_State *L ) {
for(int i2s_id = 0; i2s_id < MAX_I2C_NUM; i2s_id++) {
i2s_status_t *is = &i2s_status[i2s_id];
is->tx.queue = NULL;
is->tx.taskHandle = NULL;
is->rx.queue = NULL;
is->rx.taskHandle = NULL;
is->cb = LUA_NOREF;
}
i2s_tx_task_id = task_get_id( i2s_tx_task );
i2s_rx_task_id = task_get_id( i2s_rx_task );
i2s_disposal_task_id = task_get_id( i2s_disposal_task );
return 0;
}
NODEMCU_MODULE(I2S, "i2s", i2s_map, luaopen_i2s);