333 lines
8.8 KiB
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 );
|
|
}
|