first draft implementation of i2c slave

This commit is contained in:
devsaurus 2017-02-05 21:23:19 +01:00
parent dbc8d791c6
commit 859e1a3004
5 changed files with 430 additions and 20 deletions

View File

@ -210,6 +210,7 @@ static const LUA_REG_TYPE i2c_map[] = {
{ LSTRKEY( "write" ), LFUNCVAL( i2c_write ) }, { LSTRKEY( "write" ), LFUNCVAL( i2c_write ) },
{ LSTRKEY( "read" ), LFUNCVAL( i2c_read ) }, { LSTRKEY( "read" ), LFUNCVAL( i2c_read ) },
{ LSTRKEY( "transfer" ), LFUNCVAL( li2c_hw_master_transfer ) }, { LSTRKEY( "transfer" ), LFUNCVAL( li2c_hw_master_transfer ) },
{ LSTRKEY( "slave"), LROVAL( li2c_slave_map ) },
{ LSTRKEY( "FASTPLUS" ), LNUMVAL( PLATFORM_I2C_SPEED_FASTPLUS ) }, { LSTRKEY( "FASTPLUS" ), LNUMVAL( PLATFORM_I2C_SPEED_FASTPLUS ) },
{ LSTRKEY( "FAST" ), LNUMVAL( PLATFORM_I2C_SPEED_FAST ) }, { LSTRKEY( "FAST" ), LNUMVAL( PLATFORM_I2C_SPEED_FAST ) },
{ LSTRKEY( "SLOW" ), LNUMVAL( PLATFORM_I2C_SPEED_SLOW ) }, { LSTRKEY( "SLOW" ), LNUMVAL( PLATFORM_I2C_SPEED_SLOW ) },
@ -224,6 +225,7 @@ static const LUA_REG_TYPE i2c_map[] = {
int luaopen_i2c( lua_State *L ) { int luaopen_i2c( lua_State *L ) {
li2c_hw_master_init( L ); li2c_hw_master_init( L );
li2c_hw_slave_init( L );
return 0; return 0;
} }

View File

@ -13,6 +13,9 @@ typedef enum {
} i2c_id_type; } i2c_id_type;
// ***************************************************************************
// Hardware master prototypes
//
void li2c_hw_master_init( lua_State *L ); void li2c_hw_master_init( lua_State *L );
void li2c_hw_master_setup( lua_State *L, unsigned id, unsigned sda, unsigned scl, uint32_t speed ); void li2c_hw_master_setup( lua_State *L, unsigned id, unsigned sda, unsigned scl, uint32_t speed );
void li2c_hw_master_start( lua_State *L, unsigned id ); void li2c_hw_master_start( lua_State *L, unsigned id );
@ -22,4 +25,13 @@ void li2c_hw_master_write( lua_State *L, unsigned id, uint8_t data, bool ack_che
void li2c_hw_master_read( lua_State *L, unsigned id, uint32_t len ); void li2c_hw_master_read( lua_State *L, unsigned id, uint32_t len );
int li2c_hw_master_transfer( lua_State *L ); int li2c_hw_master_transfer( lua_State *L );
// ***************************************************************************
// Hardware slave prototypes
//
extern const LUA_REG_TYPE li2c_slave_map[];
void li2c_hw_slave_init( lua_State *L );
#endif /*_NODEMCU_I2C_COMMON_H_*/ #endif /*_NODEMCU_I2C_COMMON_H_*/

View File

@ -46,34 +46,39 @@ typedef struct {
static i2c_hw_master_ud_type i2c_hw_master_ud[I2C_NUM_MAX]; static i2c_hw_master_ud_type i2c_hw_master_ud[I2C_NUM_MAX];
// Transfer task handle and job queue // Transfer task handle and job queue
static task_handle_t i2c_result_task_id; static task_handle_t i2c_transfer_task_id;
// Transfer Task // Transfer Task, FreeRTOS layer
// This is a fully-fledged FreeRTOS task which runs concurrently and pulls // This is a fully-fledged FreeRTOS task which runs concurrently and pulls
// jobs off the queue. Jobs are forwarded to i2c_master_cmd_begin() which blocks // jobs off the queue. Jobs are forwarded to i2c_master_cmd_begin() which blocks
// this task throughout the I2C transfer. // this task throughout the I2C transfer.
// Posts a task to the nodemcu task system to resume. // Posts a task to the nodemcu task system to resume.
// //
void vTransferTask( void *pvParameters ) static void vTransferTask( void *pvParameters )
{ {
QueueHandle_t xQueue = (QueueHandle_t)pvParameters; QueueHandle_t xQueue = (QueueHandle_t)pvParameters;
i2c_job_desc_type *job; i2c_job_desc_type *job;
for (;;) { for (;;) {
job = (i2c_job_desc_type *)malloc( sizeof( i2c_job_desc_type ) ); job = (i2c_job_desc_type *)malloc( sizeof( i2c_job_desc_type ) );
if (!job) {
// shut down this task in case of memory shortage
vTaskSuspend( NULL );
}
// get a job descriptor // get a job descriptor
xQueueReceive( xQueue, job, portMAX_DELAY ); xQueueReceive( xQueue, job, portMAX_DELAY );
job->err = i2c_master_cmd_begin( job->port, job->cmd, job->err = i2c_master_cmd_begin( job->port, job->cmd,
job->to_ms > 0 ? job->to_ms / portTICK_RATE_MS : portMAX_DELAY ); job->to_ms > 0 ? job->to_ms / portTICK_RATE_MS : portMAX_DELAY );
task_post_low( i2c_result_task_id, (task_param_t)job ); task_post_medium( i2c_transfer_task_id, (task_param_t)job );
} }
} }
// Free memory of a job descriptor // Free memory of a job descriptor
// //
void free_job_memory( lua_State *L, i2c_job_desc_type *job ) static void free_job_memory( lua_State *L, i2c_job_desc_type *job )
{ {
if (job->result) if (job->result)
luaM_free( L, job->result ); luaM_free( L, job->result );
@ -84,11 +89,11 @@ void free_job_memory( lua_State *L, i2c_job_desc_type *job )
i2c_cmd_link_delete( job->cmd ); i2c_cmd_link_delete( job->cmd );
} }
// Result Task // Transfer Task, NodeMCU layer
// Is posted by the Transfer Task and triggers the Lua callback with optional // Is posted by the FreeRTOS Transfer Task and triggers the Lua callback with optional
// read result data. // read result data.
// //
void i2c_result_task( task_param_t param, task_prio_t prio ) static void i2c_transfer_task( task_param_t param, task_prio_t prio )
{ {
i2c_job_desc_type *job = (i2c_job_desc_type *)param; i2c_job_desc_type *job = (i2c_job_desc_type *)param;
@ -115,15 +120,14 @@ void i2c_result_task( task_param_t param, task_prio_t prio )
// Set up FreeRTOS task and queue // Set up FreeRTOS task and queue
// Prepares the gory tasking stuff. // Prepares the gory tasking stuff.
// //
void setup_task_and_queue( lua_State *L, i2c_hw_master_ud_type *ud ) static int setup_rtos_task_and_queue( i2c_hw_master_ud_type *ud )
{ {
// create queue // create queue
// depth 1 allows to skip "wait for empty queue" in synchronous mode // depth 1 allows to skip "wait for empty queue" in synchronous mode
// consider this when increasing depth // consider this when increasing depth
ud->xTransferJobQueue = xQueueCreate( 1, sizeof( i2c_job_desc_type ) ); ud->xTransferJobQueue = xQueueCreate( 1, sizeof( i2c_job_desc_type ) );
if (!ud->xTransferJobQueue) if (!ud->xTransferJobQueue)
luaL_error( L, "failed to create queue" ); return 0;
char pcName[configMAX_TASK_NAME_LEN+1]; char pcName[configMAX_TASK_NAME_LEN+1];
snprintf( pcName, configMAX_TASK_NAME_LEN+1, "I2C_Task_%d", ud->job.port ); snprintf( pcName, configMAX_TASK_NAME_LEN+1, "I2C_Task_%d", ud->job.port );
@ -139,8 +143,10 @@ void setup_task_and_queue( lua_State *L, i2c_hw_master_ud_type *ud )
if (xReturned != pdPASS) { if (xReturned != pdPASS) {
vQueueDelete( ud->xTransferJobQueue ); vQueueDelete( ud->xTransferJobQueue );
ud->xTransferJobQueue = NULL; ud->xTransferJobQueue = NULL;
luaL_error( L, "failed to create task" ); return 0;
} }
return 1;
} }
@ -215,7 +221,11 @@ void li2c_hw_master_setup( lua_State *L, unsigned id, unsigned sda, unsigned scl
i2c_setup_ud_transfer( L, ud ); i2c_setup_ud_transfer( L, ud );
// kick-start transfer task // kick-start transfer task
setup_task_and_queue( L, ud ); if (!setup_rtos_task_and_queue( ud )) {
free_job_memory( L, &(ud->job) );
i2c_driver_delete( port );
luaL_error( L, "rtos task creation failed" );
}
} }
void li2c_hw_master_start( lua_State *L, unsigned id ) void li2c_hw_master_start( lua_State *L, unsigned id )
@ -272,6 +282,8 @@ void li2c_hw_master_read( lua_State *L, unsigned id, uint32_t len )
// allocate read buffer as user data // allocate read buffer as user data
i2c_result_type *res = (i2c_result_type *)luaM_malloc( L, sizeof( i2c_result_type ) + len-1 ); i2c_result_type *res = (i2c_result_type *)luaM_malloc( L, sizeof( i2c_result_type ) + len-1 );
if (!res)
luaL_error( L, "out of memory" );
res->len = len; res->len = len;
job->result = res; job->result = res;
@ -357,5 +369,5 @@ int li2c_hw_master_transfer( lua_State *L )
void li2c_hw_master_init( lua_State *L ) void li2c_hw_master_init( lua_State *L )
{ {
// prepare task id // prepare task id
i2c_result_task_id = task_get_id( i2c_result_task ); i2c_transfer_task_id = task_get_id( i2c_transfer_task );
} }

View File

@ -0,0 +1,332 @@
#include "module.h"
#include "lauxlib.h"
#include "lextra.h"
#include "lmem.h"
#include "driver/i2c.h"
#include "i2c_common.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 );
}
lua_call(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_checkanytable( 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_islightfunction( 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;
}
const LUA_REG_TYPE li2c_slave_map[] = {
{ LSTRKEY( "on" ), LFUNCVAL( li2c_slave_on ) },
{ LSTRKEY( "setup" ), LFUNCVAL( li2c_slave_setup ) },
{ LSTRKEY( "send" ), LFUNCVAL( li2c_slave_send ) },
{ LNILKEY, LNILVAL }
};
void li2c_hw_slave_init( lua_State *L )
{
// prepare task id
i2c_receive_task_id = task_get_id( i2c_receive_task );
}

View File

@ -3,12 +3,12 @@
| :----- | :-------------------- | :---------- | :------ | | :----- | :-------------------- | :---------- | :------ |
| 2014-12-22 | [Zeroday](https://github.com/funshine) | [Zeroday](https://github.com/funshine) | [i2c.c](../../../app/modules/i2c.c)| | 2014-12-22 | [Zeroday](https://github.com/funshine) | [Zeroday](https://github.com/funshine) | [i2c.c](../../../app/modules/i2c.c)|
This module supports different interfaces for talking to I²C slaves. All interfaces can be assigned to arbitrary GPIOs for SCL and SDA and can be operated concurrently. This module supports different interfaces for communicating via I²C protocol. All interfaces can be assigned to arbitrary GPIOs for SCL and SDA and can be operated concurrently.
- `i2c.SW` software based bitbanging, synchronous operation - `i2c.SW` software based bitbanging, master mode only, synchronous operation
- `i2c.HW0` ESP32 hardware port 0, synchronous or asynchronous operation - `i2c.HW0` ESP32 hardware port 0, master or slave mode, synchronous or asynchronous operation
- `i2c.HW1` ESP32 hardware port 1, synchronous or asynchronous operation - `i2c.HW1` ESP32 hardware port 1, master or slave mode, synchronous or asynchronous operation
The hardware interfaces differ from the SW interface as the commands (start, stop, read, write) are queued up to an internal command list. Actual I²C communication is initiated afterwards using the `i2c.transfer()` function. Commands for the `i2c.SW` interface are immediately effective on the I²C bus and read data is also instantly available. The hardware master interfaces differ from the SW interface as the commands (start, stop, read, write) are queued up to an internal command list. Actual I²C communication is initiated afterwards using the `i2c.transfer()` function. Commands for the `i2c.SW` interface are immediately effective on the I²C bus and read data is also instantly available.
## i2c.address() ## i2c.address()
Send (`i2c.SW`) or queue (`i2c.HWx`) I²C address and read/write mode for the next transfer. Send (`i2c.SW`) or queue (`i2c.HWx`) I²C address and read/write mode for the next transfer.
@ -75,7 +75,7 @@ print(string.byte(reg))
[i2c.write()](#i2cwrite) [i2c.write()](#i2cwrite)
## i2c.setup() ## i2c.setup()
Initialize the I²C module. Initialize the I²C interface for master mode.
#### Syntax #### Syntax
`i2c.setup(id, pinSDA, pinSCL, speed)` `i2c.setup(id, pinSDA, pinSCL, speed)`
@ -165,3 +165,55 @@ i2c.write(0, "hello", "world")
#### See also #### See also
[i2c.read()](#i2cread) [i2c.read()](#i2cread)
# I²C slave mode
The I²C slave mode is only available for the hardware interfaces `i2c.HW0` and `i2c.HW1`.
## i2c.slave.on()
Registers or unregisters an event callback handler.
#### Syntax
`i2c.slave.on(id, event[, cb_fn])
#### Parameters
- `id` interface id, `i2c.HW0` or `i2c.HW1`
- `event` one of
- "receive" data received from master
- `cb_fn(err, data)` function to be called when data was received from the master. Unregisters previous callback for `event` when omitted.
#### Returns
`nil`
## i2c.slave.setup()
Initialize the I²C interface for slave mode.
#### Syntax
`i2c.slave.setup(id, slave_config)
#### Parameters
- `id` interface id, `i2c.HW0` or `i2c.HW1`
- `slave_config` table containing slave configuration information
- `sda` 0~33, IO index
- `scl` 0-33, IO index
- `addr` slave address (7bit or 10bit)
- `10bit` enable 10bit addressing with `true`, use 7bit with `false` (optional, defaults to `false` is omitted)
- `rxbuf_len` length of receive buffer (optional, defaults to 128 if omitted)
- `txbuf_len` length of transmit buffer (optional, defaults to 128 if omitted)
#### Returns
`nil`
## i2c.slave.send()
Writes send data for the master into the transmit buffer. This function returns immediately if there's enough room left in the buffer. It blocks if the buffer is doesn't provide enough space.
Data items can be multiple numbers, strings or lua tables.
#### Syntax
`i2c.slave.send(id, data1[, data2[, ..., datan]])`
#### Parameters
- `id` interface id, `i2c.HW0` or `i2c.HW1`
- `data` data can be numbers, string or lua table.
#### Returns
`number` number of bytes written