nodemcu-firmware/components/modules/i2c_hw_slave.c

333 lines
8.8 KiB
C

#include "module.h"
#include "lauxlib.h"
#include "lextra.h"
#include "lmem.h"
#include "driver/i2c.h"
#include "i2c_common.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_task.h"
#include "task/task.h"
#include <string.h>
#define DEFAULT_BUF_LEN 128
#define I2C_SLAVE_DEFAULT_RXBUF_LEN DEFAULT_BUF_LEN
#define I2C_SLAVE_DEFAULT_TXBUF_LEN DEFAULT_BUF_LEN
// user data descriptor for a port
typedef struct {
unsigned port;
size_t rxbuf_len;
TaskHandle_t xReceiveTaskHandle;
int receivedcb_ref;
} i2c_hw_slave_ud_type;
// read data descriptor
// it's used as variable length data block, filled by the i2c driver during read
typedef struct {
size_t len;
uint8_t data[1];
} i2c_read_type;
// job descriptor
// contains all information for the transfer task and subsequent result task
// to process the transfer
typedef struct {
unsigned port;
esp_err_t err;
int receivedcb_ref;
i2c_read_type *read_data;
} i2c_job_desc_type;
// the global variables for storing userdata for each I2C port
static i2c_hw_slave_ud_type i2c_hw_slave_ud[I2C_NUM_MAX];
// Receive task handle and job queue
static task_handle_t i2c_receive_task_id;
// Receive Task, FreeRTOS layer
// This is a fully-fledged FreeRTOS task which runs concurrently and waits
// for data from the I2C master using i2c_slave_read_buffer which blocks this task.
// Received data is posted as a job to the NodeMCU layer task.
//
static void vReceiveTask( void *pvParameters )
{
i2c_hw_slave_ud_type *ud = (i2c_hw_slave_ud_type *)pvParameters;
i2c_job_desc_type *job;
i2c_read_type *read_data;
for (;;) {
job = (i2c_job_desc_type *)malloc( sizeof( i2c_job_desc_type ) );
read_data = (i2c_read_type *)malloc( sizeof( i2c_read_type ) + ud->rxbuf_len-1 );
if (!job || !read_data) {
// shut down this task in case of memory shortage
vTaskSuspend( NULL );
}
job->read_data = read_data;
int size = i2c_slave_read_buffer( ud->port, job->read_data->data, ud->rxbuf_len, portMAX_DELAY );
if (size >= 0) {
job->read_data->len = size;
job->err = 0;
} else {
job->read_data->len = 0;
job->err = size;
}
job->port = ud->port;
job->receivedcb_ref = ud->receivedcb_ref;
task_post_medium( i2c_receive_task_id, (task_param_t)job );
}
}
// Receive Task, NodeMCU layer
// Is posted by the FreeRTOS layer and triggers the Lua callback with
// read result data.
//
static void i2c_receive_task( task_param_t param, task_prio_t prio )
{
i2c_job_desc_type *job = (i2c_job_desc_type *)param;
lua_State *L = lua_getstate();
if (job->receivedcb_ref != LUA_NOREF) {
lua_rawgeti( L, LUA_REGISTRYINDEX, job->receivedcb_ref );
lua_pushinteger( L, job->err );
if (job->read_data->len) {
// all fine, deliver read data
lua_pushlstring( L, (char *)job->read_data->data, job->read_data->len );
} else {
lua_pushnil( L );
}
luaL_pcallx(L, 2, 0);
}
// free all memory
free( job->read_data );
free( job );
}
static int i2c_lua_checkerr( lua_State *L, esp_err_t err ) {
const char *msg;
switch (err) {
case ESP_OK: return 0;
case ESP_FAIL: msg = "command failed"; break;
case ESP_ERR_INVALID_ARG: msg = "parameter error"; break;
case ESP_ERR_INVALID_STATE: msg = "driver state error"; break;
case ESP_ERR_TIMEOUT: msg = "timeout"; break;
default: msg = "unknown error"; break;
}
return luaL_error( L, msg );
}
#define get_udata(L) \
unsigned port = luaL_checkint( L, 1 ) - I2C_ID_HW0; \
luaL_argcheck( L, port < I2C_NUM_MAX, 1, "invalid hardware id" ); \
i2c_hw_slave_ud_type *ud = &(i2c_hw_slave_ud[port]);
// Set up FreeRTOS task and queue
// Prepares the gory tasking stuff.
//
static int setup_rtos_task( i2c_hw_slave_ud_type *ud, unsigned port )
{
char pcName[configMAX_TASK_NAME_LEN+1];
snprintf( pcName, configMAX_TASK_NAME_LEN+1, "I2C_Slave_%d", port );
pcName[configMAX_TASK_NAME_LEN] = '\0';
// create task with higher priority
BaseType_t xReturned = xTaskCreate( vReceiveTask,
pcName,
1024,
(void *)ud,
ESP_TASK_MAIN_PRIO + 1,
&(ud->xReceiveTaskHandle) );
if (xReturned != pdPASS) {
return 0;
}
return 1;
}
// Set up the HW as master interface
// Cares for I2C driver creation and initialization.
//
static int li2c_slave_setup( lua_State *L )
{
get_udata(L);
ud->port = port;
int stack = 1;
luaL_checktable( L, ++stack );
lua_settop( L, stack );
i2c_config_t cfg;
memset( &cfg, 0, sizeof( cfg ) );
cfg.mode = I2C_MODE_SLAVE;
lua_getfield( L, stack, "sda" );
int sda = luaL_optint( L , -1, -1 );
luaL_argcheck( L, GPIO_IS_VALID_OUTPUT_GPIO(sda), stack, "invalid sda pin" );
cfg.sda_io_num = (gpio_num_t)sda;
cfg.sda_pullup_en = GPIO_PULLUP_ENABLE;
lua_getfield( L, stack, "scl" );
int scl = luaL_optint( L , -1, -1 );
luaL_argcheck( L, GPIO_IS_VALID_OUTPUT_GPIO(scl), stack, "invalid scl pin" );
cfg.scl_io_num = (gpio_num_t)scl;
cfg.scl_pullup_en = GPIO_PULLUP_ENABLE;
lua_getfield( L, stack, "10bit" );
bool en_10bit = luaL_optbool( L , -1, false );
cfg.slave.addr_10bit_en = en_10bit ? 1 : 0;
lua_getfield( L, stack, "addr" );
int slave_addr = luaL_optint( L , -1, -1 );
if (en_10bit)
luaL_argcheck( L, slave_addr >= 0 && slave_addr < 1<<10, stack, "invalid slave address" );
else
luaL_argcheck( L, slave_addr >= 0 && slave_addr < 1<<7, stack, "invalid slave address" );
cfg.slave.slave_addr = slave_addr;
lua_getfield( L, stack, "rxbuf_len" );
int rxbuf_len = luaL_optint( L , -1, I2C_SLAVE_DEFAULT_RXBUF_LEN );
luaL_argcheck( L, rxbuf_len >= 0, stack, "invalid rxbuf_len" );
ud->rxbuf_len = rxbuf_len;
lua_getfield( L, stack, "txbuf_len" );
int txbuf_len = luaL_optint( L , -1, I2C_SLAVE_DEFAULT_TXBUF_LEN );
luaL_argcheck( L, rxbuf_len >= 0, stack, "invalid rxbuf_len" );
i2c_lua_checkerr( L, i2c_param_config( port, &cfg ) );
i2c_lua_checkerr( L, i2c_driver_install( port, cfg.mode, rxbuf_len, txbuf_len, 0 ));
ud->receivedcb_ref = LUA_NOREF;
if (!setup_rtos_task( ud, port )) {
i2c_driver_delete( port );
luaL_error( L, "rtos task creation failed" );
}
return 0;
}
// Write to slave send buffer
//
static int li2c_slave_send( lua_State *L )
{
get_udata(L);
ud = ud;
const char *pdata;
size_t datalen, i;
int numdata;
uint8_t byte;
uint32_t wrote = 0;
unsigned argn;
if( lua_gettop( L ) < 2 )
return luaL_error( L, "wrong arg type" );
for( argn = 2; argn <= lua_gettop( L ); argn ++ )
{
// lua_isnumber() would silently convert a string of digits to an integer
// whereas here strings are handled separately.
if( lua_type( L, argn ) == LUA_TNUMBER )
{
numdata = ( int )luaL_checkinteger( L, argn );
if( numdata < 0 || numdata > 255 )
return luaL_error( L, "wrong arg range" );
byte = (uint8_t)numdata;
if( i2c_slave_write_buffer( port, &byte, 1, portMAX_DELAY ) < 0 )
break;
wrote ++;
}
else if( lua_istable( L, argn ) )
{
datalen = lua_objlen( L, argn );
for( i = 0; i < datalen; i ++ )
{
lua_rawgeti( L, argn, i + 1 );
numdata = ( int )luaL_checkinteger( L, -1 );
lua_pop( L, 1 );
if( numdata < 0 || numdata > 255 )
return luaL_error( L, "wrong arg range" );
byte = (uint8_t)numdata;
if( i2c_slave_write_buffer( port, &byte, 1, portMAX_DELAY ) < 0 )
break;
}
wrote += i;
if( i < datalen )
break;
}
else
{
pdata = luaL_checklstring( L, argn, &datalen );
if( i2c_slave_write_buffer( port, (uint8_t *)pdata, datalen, portMAX_DELAY ) < 0 )
break;
wrote += datalen;
}
}
lua_pushinteger( L, wrote );
return 1;
}
// Register or unregister an event callback handler
//
static int li2c_slave_on( lua_State *L )
{
enum events{
ON_RECEIVE = 0
};
const char *const eventnames[] = {"receive", NULL};
get_udata(L);
int stack = 1;
int event = luaL_checkoption(L, ++stack, NULL, eventnames);
switch (event) {
case ON_RECEIVE:
luaL_unref( L, LUA_REGISTRYINDEX, ud->receivedcb_ref );
++stack;
if (lua_isfunction( L, stack )) {
lua_pushvalue( L, stack ); // copy argument (func) to the top of stack
ud->receivedcb_ref = luaL_ref( L, LUA_REGISTRYINDEX );
}
break;
default:
break;
}
return 0;
}
LROT_BEGIN(li2c_slave, NULL, 0)
LROT_FUNCENTRY( on, li2c_slave_on )
LROT_FUNCENTRY( setup, li2c_slave_setup )
LROT_FUNCENTRY( send, li2c_slave_send )
LROT_END(li2c_slave, NULL, 0)
void li2c_hw_slave_init( lua_State *L )
{
// prepare task id
i2c_receive_task_id = task_get_id( i2c_receive_task );
}