extend i2c master with hardware interfaces for synchronous and asynchronous operation
This commit is contained in:
parent
381726f103
commit
f5632f41f9
|
@ -2,23 +2,32 @@
|
|||
|
||||
#include "module.h"
|
||||
#include "lauxlib.h"
|
||||
#include "lextra.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include "i2c_common.h"
|
||||
|
||||
|
||||
// Lua: i2c.setup( id, sda, scl, speed )
|
||||
static int i2c_setup( lua_State *L )
|
||||
{
|
||||
unsigned id = luaL_checkinteger( L, 1 );
|
||||
unsigned sda = luaL_checkinteger( L, 2 );
|
||||
unsigned scl = luaL_checkinteger( L, 3 );
|
||||
uint32_t speed = (uint32_t)luaL_checkinteger( L, 4 );
|
||||
|
||||
MOD_CHECK_ID( i2c, id );
|
||||
luaL_argcheck( L, id < I2C_ID_MAX, 1, "invalid id" );
|
||||
MOD_CHECK_ID( gpio, sda );
|
||||
MOD_CHECK_ID( gpio, scl );
|
||||
|
||||
int32_t speed = ( int32_t )luaL_checkinteger( L, 4 );
|
||||
luaL_argcheck( L, speed > 0, 4, "wrong arg range" );
|
||||
if (id == I2C_ID_SW) {
|
||||
luaL_argcheck( L, speed <= PLATFORM_I2C_SPEED_SLOW, 4, "wrong arg range" );
|
||||
if (!platform_i2c_setup( id, sda, scl, (uint32_t)speed ))
|
||||
luaL_error( L, "setup failed" );
|
||||
} else {
|
||||
luaL_argcheck( L, speed <= PLATFORM_I2C_SPEED_FASTPLUS, 4, "wrong arg range" );
|
||||
li2c_hw_master_setup( L, id, sda, scl, speed );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -28,9 +37,14 @@ static int i2c_start( lua_State *L )
|
|||
{
|
||||
unsigned id = luaL_checkinteger( L, 1 );
|
||||
|
||||
MOD_CHECK_ID( i2c, id );
|
||||
luaL_argcheck( L, id < I2C_ID_MAX, 1, "invalid id" );
|
||||
|
||||
if (id == I2C_ID_SW) {
|
||||
if (!platform_i2c_send_start( id ))
|
||||
luaL_error( L, "command failed");
|
||||
} else {
|
||||
li2c_hw_master_start( L, id );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -40,9 +54,14 @@ static int i2c_stop( lua_State *L )
|
|||
{
|
||||
unsigned id = luaL_checkinteger( L, 1 );
|
||||
|
||||
MOD_CHECK_ID( i2c, id );
|
||||
luaL_argcheck( L, id < I2C_ID_MAX, 1, "invalid id" );
|
||||
|
||||
if (id == I2C_ID_SW) {
|
||||
if (!platform_i2c_send_stop( id ))
|
||||
luaL_error( L, "command failed" );
|
||||
} else {
|
||||
li2c_hw_master_stop( L, id );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -50,14 +69,28 @@ static int i2c_stop( lua_State *L )
|
|||
// Lua: status = i2c.address( id, address, direction )
|
||||
static int i2c_address( lua_State *L )
|
||||
{
|
||||
unsigned id = luaL_checkinteger( L, 1 );
|
||||
int address = luaL_checkinteger( L, 2 );
|
||||
int direction = luaL_checkinteger( L, 3 );
|
||||
int stack = 0;
|
||||
|
||||
MOD_CHECK_ID( i2c, id );
|
||||
luaL_argcheck( L, address >= 0 && address <= 127, 2, "wrong arg range" );
|
||||
unsigned id = luaL_checkinteger( L, ++stack );
|
||||
luaL_argcheck( L, id < I2C_ID_MAX, stack, "invalid id" );
|
||||
|
||||
int ret = platform_i2c_send_address( id, (uint16_t)address, direction, 1 );
|
||||
int address = luaL_checkinteger( L, ++stack );
|
||||
luaL_argcheck( L, address >= 0 && address <= 127, stack, "wrong arg range" );
|
||||
|
||||
int direction = luaL_checkinteger( L, ++stack );
|
||||
luaL_argcheck( L,
|
||||
direction == PLATFORM_I2C_DIRECTION_TRANSMITTER ||
|
||||
direction == PLATFORM_I2C_DIRECTION_RECEIVER,
|
||||
stack, "wrong arg range" );
|
||||
|
||||
bool ack_check_en = luaL_optbool( L, ++stack, true );
|
||||
|
||||
int ret;
|
||||
if (id == I2C_ID_SW) {
|
||||
ret = platform_i2c_send_address( id, (uint16_t)address, direction, ack_check_en ? 1 : 0 );
|
||||
} else {
|
||||
ret = li2c_hw_master_address( L, id, (uint16_t)address, direction, ack_check_en );
|
||||
}
|
||||
lua_pushboolean( L, ret > 0 );
|
||||
|
||||
return 1;
|
||||
|
@ -68,13 +101,20 @@ static int i2c_address( lua_State *L )
|
|||
static int i2c_write( lua_State *L )
|
||||
{
|
||||
unsigned id = luaL_checkinteger( L, 1 );
|
||||
luaL_argcheck( L, id < I2C_ID_MAX, 1, "invalid id" );
|
||||
|
||||
const char *pdata;
|
||||
size_t datalen, i;
|
||||
int numdata;
|
||||
uint32_t wrote = 0;
|
||||
unsigned argn;
|
||||
|
||||
MOD_CHECK_ID( i2c, id );
|
||||
bool ack_check_en = true;
|
||||
if (lua_isboolean( L, -1 )) {
|
||||
ack_check_en = lua_toboolean( L, -1 );
|
||||
lua_pop( L, 1 );
|
||||
}
|
||||
|
||||
if( lua_gettop( L ) < 2 )
|
||||
return luaL_error( L, "wrong arg type" );
|
||||
for( argn = 2; argn <= lua_gettop( L ); argn ++ )
|
||||
|
@ -86,8 +126,12 @@ static int i2c_write( lua_State *L )
|
|||
numdata = ( int )luaL_checkinteger( L, argn );
|
||||
if( numdata < 0 || numdata > 255 )
|
||||
return luaL_error( L, "wrong arg range" );
|
||||
if( platform_i2c_send_byte( id, numdata, 1 ) != 1 )
|
||||
if (id == I2C_ID_SW) {
|
||||
if( platform_i2c_send_byte( id, numdata, 1) != 1 && ack_check_en )
|
||||
break;
|
||||
} else {
|
||||
li2c_hw_master_write( L, id, numdata, ack_check_en );
|
||||
}
|
||||
wrote ++;
|
||||
}
|
||||
else if( lua_istable( L, argn ) )
|
||||
|
@ -100,8 +144,12 @@ static int i2c_write( lua_State *L )
|
|||
lua_pop( L, 1 );
|
||||
if( numdata < 0 || numdata > 255 )
|
||||
return luaL_error( L, "wrong arg range" );
|
||||
if( platform_i2c_send_byte( id, numdata, 1 ) == 0 )
|
||||
if (id == I2C_ID_SW) {
|
||||
if( platform_i2c_send_byte( id, numdata, 1 ) == 0 && ack_check_en)
|
||||
break;
|
||||
} else {
|
||||
li2c_hw_master_write( L, id, numdata, ack_check_en );
|
||||
}
|
||||
}
|
||||
wrote += i;
|
||||
if( i < datalen )
|
||||
|
@ -111,8 +159,12 @@ static int i2c_write( lua_State *L )
|
|||
{
|
||||
pdata = luaL_checklstring( L, argn, &datalen );
|
||||
for( i = 0; i < datalen; i ++ )
|
||||
if( platform_i2c_send_byte( id, pdata[ i ], 1 ) == 0 )
|
||||
if (id == I2C_ID_SW) {
|
||||
if( platform_i2c_send_byte( id, pdata[ i ], 1 ) == 0 && ack_check_en)
|
||||
break;
|
||||
} else {
|
||||
li2c_hw_master_write( L, id, pdata[ i ], ack_check_en );
|
||||
}
|
||||
wrote += i;
|
||||
if( i < datalen )
|
||||
break;
|
||||
|
@ -126,11 +178,14 @@ static int i2c_write( lua_State *L )
|
|||
static int i2c_read( lua_State *L )
|
||||
{
|
||||
unsigned id = luaL_checkinteger( L, 1 );
|
||||
luaL_argcheck( L, id < I2C_ID_MAX, 1, "invalid id" );
|
||||
|
||||
uint32_t size = ( uint32_t )luaL_checkinteger( L, 2 ), i;
|
||||
|
||||
if (id == I2C_ID_SW) {
|
||||
luaL_Buffer b;
|
||||
int data;
|
||||
|
||||
MOD_CHECK_ID( i2c, id );
|
||||
if( size == 0 )
|
||||
return 0;
|
||||
luaL_buffinit( L, &b );
|
||||
|
@ -141,6 +196,10 @@ static int i2c_read( lua_State *L )
|
|||
luaL_addchar( &b, ( char )data );
|
||||
luaL_pushresult( &b );
|
||||
return 1;
|
||||
} else {
|
||||
li2c_hw_master_read( L, id, size );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static const LUA_REG_TYPE i2c_map[] = {
|
||||
|
@ -150,12 +209,23 @@ static const LUA_REG_TYPE i2c_map[] = {
|
|||
{ LSTRKEY( "address" ), LFUNCVAL( i2c_address ) },
|
||||
{ LSTRKEY( "write" ), LFUNCVAL( i2c_write ) },
|
||||
{ LSTRKEY( "read" ), LFUNCVAL( i2c_read ) },
|
||||
{ LSTRKEY( "transfer" ), LFUNCVAL( li2c_hw_master_transfer ) },
|
||||
{ LSTRKEY( "FASTPLUS" ), LNUMVAL( PLATFORM_I2C_SPEED_FASTPLUS ) },
|
||||
{ LSTRKEY( "FAST" ), LNUMVAL( PLATFORM_I2C_SPEED_FAST ) },
|
||||
{ LSTRKEY( "SLOW" ), LNUMVAL( PLATFORM_I2C_SPEED_SLOW ) },
|
||||
{ LSTRKEY( "TRANSMITTER" ), LNUMVAL( PLATFORM_I2C_DIRECTION_TRANSMITTER ) },
|
||||
{ LSTRKEY( "RECEIVER" ), LNUMVAL( PLATFORM_I2C_DIRECTION_RECEIVER ) },
|
||||
{ LSTRKEY( "SW" ), LNUMVAL( I2C_ID_SW ) },
|
||||
{ LSTRKEY( "HW0" ), LNUMVAL( I2C_ID_HW0 ) },
|
||||
{ LSTRKEY( "HW1" ), LNUMVAL( I2C_ID_HW1 ) },
|
||||
{LNILKEY, LNILVAL}
|
||||
};
|
||||
|
||||
|
||||
NODEMCU_MODULE(I2C, "i2c", i2c_map, NULL);
|
||||
int luaopen_i2c( lua_State *L ) {
|
||||
li2c_hw_master_init( L );
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
NODEMCU_MODULE(I2C, "i2c", i2c_map, luaopen_i2c);
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
#ifndef _NODEMCU_I2C_COMMON_H_
|
||||
#define _NODEMCU_I2C_COMMON_H_
|
||||
|
||||
#include "lauxlib.h"
|
||||
|
||||
|
||||
typedef enum {
|
||||
I2C_ID_SW = 0,
|
||||
I2C_ID_HW0,
|
||||
I2C_ID_HW1,
|
||||
I2C_ID_MAX
|
||||
} i2c_id_type;
|
||||
|
||||
|
||||
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_start( lua_State *L, unsigned id );
|
||||
void li2c_hw_master_stop( lua_State *L, unsigned id );
|
||||
int li2c_hw_master_address( lua_State *L, unsigned id, uint16_t address, uint8_t direction, bool ack_check_en );
|
||||
void li2c_hw_master_write( lua_State *L, unsigned id, uint8_t data, bool ack_check_en );
|
||||
void li2c_hw_master_read( lua_State *L, unsigned id, uint32_t len );
|
||||
int li2c_hw_master_transfer( lua_State *L );
|
||||
|
||||
#endif /*_NODEMCU_I2C_COMMON_H_*/
|
|
@ -0,0 +1,361 @@
|
|||
|
||||
#include "module.h"
|
||||
#include "lauxlib.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>
|
||||
|
||||
|
||||
// result 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_result_type;
|
||||
|
||||
// job descriptor
|
||||
// contains all information for the transfer task and subsequent result task
|
||||
// to process the transfer
|
||||
typedef struct {
|
||||
unsigned port;
|
||||
i2c_cmd_handle_t cmd;
|
||||
unsigned to_ms;
|
||||
i2c_result_type *result;
|
||||
esp_err_t err;
|
||||
int cb_ref;
|
||||
} i2c_job_desc_type;
|
||||
|
||||
// user data descriptor for a port
|
||||
// provides buffer for the job setup and holds port-specific task & queue handles
|
||||
typedef struct {
|
||||
i2c_job_desc_type job;
|
||||
TaskHandle_t xTransferTaskHandle;
|
||||
QueueHandle_t xTransferJobQueue;
|
||||
} i2c_hw_master_ud_type;
|
||||
|
||||
// the global variables for storing userdata for each I2C port
|
||||
static i2c_hw_master_ud_type i2c_hw_master_ud[I2C_NUM_MAX];
|
||||
|
||||
// Transfer task handle and job queue
|
||||
static task_handle_t i2c_result_task_id;
|
||||
|
||||
// Transfer Task
|
||||
// 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
|
||||
// this task throughout the I2C transfer.
|
||||
// Posts a task to the nodemcu task system to resume.
|
||||
//
|
||||
void vTransferTask( void *pvParameters )
|
||||
{
|
||||
QueueHandle_t xQueue = (QueueHandle_t)pvParameters;
|
||||
i2c_job_desc_type *job;
|
||||
|
||||
for (;;) {
|
||||
job = (i2c_job_desc_type *)malloc( sizeof( i2c_job_desc_type ) );
|
||||
// get a job descriptor
|
||||
xQueueReceive( xQueue, job, portMAX_DELAY );
|
||||
|
||||
job->err = i2c_master_cmd_begin( job->port, job->cmd,
|
||||
job->to_ms > 0 ? job->to_ms / portTICK_RATE_MS : portMAX_DELAY );
|
||||
|
||||
task_post_low( i2c_result_task_id, (task_param_t)job );
|
||||
}
|
||||
}
|
||||
|
||||
// Free memory of a job descriptor
|
||||
//
|
||||
void free_job_memory( lua_State *L, i2c_job_desc_type *job )
|
||||
{
|
||||
if (job->result)
|
||||
luaM_free( L, job->result );
|
||||
|
||||
luaL_unref( L, LUA_REGISTRYINDEX, job->cb_ref);
|
||||
|
||||
if (job->cmd)
|
||||
i2c_cmd_link_delete( job->cmd );
|
||||
}
|
||||
|
||||
// Result Task
|
||||
// Is posted by the Transfer Task and triggers the Lua callback with optional
|
||||
// read result data.
|
||||
//
|
||||
void i2c_result_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->cb_ref != LUA_NOREF) {
|
||||
lua_rawgeti( L, LUA_REGISTRYINDEX, job->cb_ref );
|
||||
lua_pushinteger( L, job->err );
|
||||
if (job->result) {
|
||||
// all fine, deliver read data
|
||||
lua_pushlstring( L, (char *)job->result->data, job->result->len );
|
||||
} else {
|
||||
lua_pushnil( L );
|
||||
}
|
||||
lua_call(L, 2, 0);
|
||||
}
|
||||
|
||||
// free all memory
|
||||
free_job_memory( L, job );
|
||||
free( job );
|
||||
}
|
||||
|
||||
|
||||
// Set up FreeRTOS task and queue
|
||||
// Prepares the gory tasking stuff.
|
||||
//
|
||||
void setup_task_and_queue( lua_State *L, i2c_hw_master_ud_type *ud )
|
||||
{
|
||||
|
||||
// create queue
|
||||
// depth 1 allows to skip "wait for empty queue" in synchronous mode
|
||||
// consider this when increasing depth
|
||||
ud->xTransferJobQueue = xQueueCreate( 1, sizeof( i2c_job_desc_type ) );
|
||||
if (!ud->xTransferJobQueue)
|
||||
luaL_error( L, "failed to create queue" );
|
||||
|
||||
char pcName[configMAX_TASK_NAME_LEN+1];
|
||||
snprintf( pcName, configMAX_TASK_NAME_LEN+1, "I2C_Task_%d", ud->job.port );
|
||||
pcName[configMAX_TASK_NAME_LEN] = '\0';
|
||||
|
||||
// create task with higher priority
|
||||
BaseType_t xReturned = xTaskCreate( vTransferTask,
|
||||
pcName,
|
||||
1024,
|
||||
(void *)ud->xTransferJobQueue,
|
||||
ESP_TASK_MAIN_PRIO + 1,
|
||||
&(ud->xTransferTaskHandle) );
|
||||
if (xReturned != pdPASS) {
|
||||
vQueueDelete( ud->xTransferJobQueue );
|
||||
ud->xTransferJobQueue = NULL;
|
||||
luaL_error( L, "failed to create task" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define get_udata(L, id) \
|
||||
unsigned port = id - I2C_ID_HW0; \
|
||||
luaL_argcheck( L, port < I2C_NUM_MAX, 1, "invalid hardware id" ); \
|
||||
i2c_hw_master_ud_type *ud = &(i2c_hw_master_ud[port]); \
|
||||
i2c_job_desc_type *job = &(ud->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 or NACK from slave"; 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 );
|
||||
}
|
||||
|
||||
// Set up userdata for a transfer
|
||||
//
|
||||
static void i2c_setup_ud_transfer( lua_State *L, i2c_hw_master_ud_type *ud )
|
||||
{
|
||||
free_job_memory( L, &(ud->job) );
|
||||
ud->job.result = NULL;
|
||||
ud->job.cb_ref = LUA_NOREF;
|
||||
|
||||
// set up an empty command link
|
||||
ud->job.cmd = i2c_cmd_link_create();
|
||||
}
|
||||
|
||||
// Set up the HW as master interface
|
||||
// Cares for I2C driver creation and initialization.
|
||||
// Prepares an empty job descriptor and triggers setup of FreeRTOS stuff.
|
||||
//
|
||||
void li2c_hw_master_setup( lua_State *L, unsigned id, unsigned sda, unsigned scl, uint32_t speed )
|
||||
{
|
||||
get_udata(L, id);
|
||||
|
||||
i2c_config_t cfg;
|
||||
memset( &cfg, 0, sizeof( cfg ) );
|
||||
cfg.mode = I2C_MODE_MASTER;
|
||||
|
||||
luaL_argcheck( L, GPIO_IS_VALID_OUTPUT_GPIO(sda), 1, "invalid sda pin" );
|
||||
cfg.sda_io_num = sda;
|
||||
cfg.sda_pullup_en = GPIO_PULLUP_ENABLE;
|
||||
|
||||
luaL_argcheck( L, GPIO_IS_VALID_OUTPUT_GPIO(scl), 1, "invalid scl pin" );
|
||||
cfg.scl_io_num = scl;
|
||||
cfg.scl_pullup_en = GPIO_PULLUP_ENABLE;
|
||||
|
||||
luaL_argcheck( L, speed > 0 && speed <= 1000000, 1, "invalid speed" );
|
||||
cfg.master.clk_speed = speed;
|
||||
|
||||
// init driver level
|
||||
i2c_lua_checkerr( L, i2c_param_config( port, &cfg ) );
|
||||
i2c_lua_checkerr( L, i2c_driver_install( port, cfg.mode, 0, 0, 0 ));
|
||||
|
||||
job->port = port;
|
||||
|
||||
job->cmd = NULL;
|
||||
job->result = NULL;
|
||||
job->cb_ref = LUA_NOREF;
|
||||
i2c_setup_ud_transfer( L, ud );
|
||||
|
||||
// kick-start transfer task
|
||||
setup_task_and_queue( L, ud );
|
||||
}
|
||||
|
||||
void li2c_hw_master_start( lua_State *L, unsigned id )
|
||||
{
|
||||
get_udata(L, id);
|
||||
|
||||
if (!job->cmd)
|
||||
luaL_error( L, "no commands scheduled" );
|
||||
|
||||
i2c_lua_checkerr( L, i2c_master_start( job->cmd ) );
|
||||
}
|
||||
|
||||
void li2c_hw_master_stop( lua_State *L, unsigned id )
|
||||
{
|
||||
get_udata(L, id);
|
||||
|
||||
if (!job->cmd)
|
||||
luaL_error( L, "no commands scheduled" );
|
||||
|
||||
i2c_lua_checkerr( L, i2c_master_stop( job->cmd ) );
|
||||
}
|
||||
|
||||
int li2c_hw_master_address( lua_State *L, unsigned id, uint16_t address, uint8_t direction, bool ack_check_en )
|
||||
{
|
||||
get_udata(L, id);
|
||||
|
||||
if (!job->cmd)
|
||||
luaL_error( L, "no commands scheduled" );
|
||||
|
||||
i2c_lua_checkerr( L, i2c_master_write_byte( job->cmd, address << 1 | direction, ack_check_en ) );
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void li2c_hw_master_write( lua_State *L, unsigned id, uint8_t data, bool ack_check_en )
|
||||
{
|
||||
get_udata(L, id);
|
||||
|
||||
if (!job->cmd)
|
||||
luaL_error( L, "no commands scheduled" );
|
||||
|
||||
i2c_lua_checkerr( L, i2c_master_write_byte( job->cmd, data, ack_check_en ) );
|
||||
}
|
||||
|
||||
void li2c_hw_master_read( lua_State *L, unsigned id, uint32_t len )
|
||||
{
|
||||
get_udata(L, id);
|
||||
|
||||
if (!job->cmd)
|
||||
luaL_error( L, "no commands scheduled" );
|
||||
|
||||
if (job->result)
|
||||
luaL_error( L, "only one read per transfer supported" );
|
||||
|
||||
// allocate read buffer as user data
|
||||
i2c_result_type *res = (i2c_result_type *)luaM_malloc( L, sizeof( i2c_result_type ) + len-1 );
|
||||
res->len = len;
|
||||
job->result = res;
|
||||
|
||||
if (len > 1)
|
||||
i2c_lua_checkerr( L, i2c_master_read( job->cmd, res->data, len-1, 0 ) );
|
||||
i2c_lua_checkerr( L, i2c_master_read( job->cmd, res->data + len-1, 1, 1 ) );
|
||||
}
|
||||
|
||||
// Initiate the I2C transfer
|
||||
// Depending on the presence of a callback parameter, it will stay in synchronous mode
|
||||
// or posts the job to the FreeRTOS queue for asynchronous processing.
|
||||
//
|
||||
int li2c_hw_master_transfer( lua_State *L )
|
||||
{
|
||||
int stack = 0;
|
||||
|
||||
unsigned id = luaL_checkinteger( L, ++stack );
|
||||
get_udata(L, id);
|
||||
|
||||
if (!job->cmd)
|
||||
luaL_error( L, "no commands scheduled" );
|
||||
|
||||
stack++;
|
||||
if (lua_isfunction( L, stack ) || lua_islightfunction( L, stack )) {
|
||||
lua_pushvalue( L, stack ); // copy argument (func) to the top of stack
|
||||
luaL_unref( L, LUA_REGISTRYINDEX, job->cb_ref );
|
||||
job->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
} else
|
||||
stack--;
|
||||
|
||||
int to_ms = luaL_optint( L, ++stack, 0 );
|
||||
if (to_ms < 0)
|
||||
to_ms = 0;
|
||||
job->to_ms = to_ms;
|
||||
|
||||
if (job->cb_ref != LUA_NOREF) {
|
||||
// asynchronous mode
|
||||
xQueueSend( ud->xTransferJobQueue, job, portMAX_DELAY );
|
||||
|
||||
// the transfer task should be unblocked now
|
||||
// (i.e. in eReady state since it can receive from the queue)
|
||||
// block this task briefly to allow switch over to the transfer task (it has higher prio)
|
||||
vTaskDelay( 1 );
|
||||
|
||||
// invalidate last job, it's queued now
|
||||
job->cmd = NULL; // don't delete link! it's used by the transfer task
|
||||
job->result = NULL; // don't free result memory! it's used by the transfer task
|
||||
job->cb_ref = LUA_NOREF; // don't unref! it's used by the transfer task
|
||||
|
||||
// prepare the next transfer
|
||||
i2c_setup_ud_transfer( L, ud );
|
||||
return 0;
|
||||
|
||||
} else {
|
||||
// synchronous mode
|
||||
|
||||
// no need to wait for queue to become empty when queue depth is 1
|
||||
|
||||
// note that i2c_master_cmd_begin() implements mutual exclusive access
|
||||
// if it is currently in progress from the transfer task, it will block here until
|
||||
esp_err_t err = i2c_master_cmd_begin( job->port, job->cmd,
|
||||
job->to_ms > 0 ? job->to_ms / portTICK_RATE_MS : portMAX_DELAY );
|
||||
|
||||
if (err == ESP_OK) {
|
||||
if (job->result) {
|
||||
// all fine, deliver read data
|
||||
lua_pushlstring( L, (char *)job->result->data, job->result->len );
|
||||
} else {
|
||||
lua_pushnil( L );
|
||||
}
|
||||
|
||||
// prepare the next transfer
|
||||
i2c_setup_ud_transfer( L, ud );
|
||||
return 1;
|
||||
}
|
||||
|
||||
i2c_setup_ud_transfer( L, ud );
|
||||
return i2c_lua_checkerr( L, err );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void li2c_hw_master_init( lua_State *L )
|
||||
{
|
||||
// prepare task id
|
||||
i2c_result_task_id = task_get_id( i2c_result_task );
|
||||
}
|
|
@ -94,7 +94,8 @@ uint8_t platform_sigma_delta_set_duty( uint8_t channel, int8_t duty );
|
|||
enum
|
||||
{
|
||||
PLATFORM_I2C_SPEED_SLOW = 100000,
|
||||
PLATFORM_I2C_SPEED_FAST = 400000
|
||||
PLATFORM_I2C_SPEED_FAST = 400000,
|
||||
PLATFORM_I2C_SPEED_FASTPLUS = 1000000
|
||||
};
|
||||
|
||||
// I2C direction
|
||||
|
|
|
@ -3,39 +3,50 @@
|
|||
| :----- | :-------------------- | :---------- | :------ |
|
||||
| 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.
|
||||
- `i2c.SW` software based bitbanging, synchronous operation
|
||||
- `i2c.HW0` ESP32 hardware port 0, synchronous or asynchronous operation
|
||||
- `i2c.HW1` ESP32 hardware port 1, 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.
|
||||
|
||||
## i2c.address()
|
||||
Setup 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.
|
||||
|
||||
Communication stops when the slave answers with NACK to the address byte. This can be avoided with parameter `ack_check_en` on `false`.
|
||||
|
||||
#### Syntax
|
||||
`i2c.address(id, device_addr, direction)`
|
||||
`i2c.address(id, device_addr, direction[, ack_check_en])`
|
||||
|
||||
#### Parameters
|
||||
- `id` always 0
|
||||
- `id` interface id
|
||||
- `device_addr` device address
|
||||
- `direction` `i2c.TRANSMITTER` for writing mode , `i2c. RECEIVER` for reading mode
|
||||
- `direction` `i2c.TRANSMITTER` for writing mode , `i2c.RECEIVER` for reading mode
|
||||
- `ack_check_en` enable check for slave ACK with `true` (default), disable with `false`
|
||||
|
||||
#### Returns
|
||||
`true` if ack received, `false` if no ack received.
|
||||
`true` if ack received (always for ids `i2c.HW0` and `i2c.HW1`), `false` if no ack received (only possible for `i2c.SW`).
|
||||
|
||||
#### See also
|
||||
[i2c.read()](#i2cread)
|
||||
|
||||
## i2c.read()
|
||||
Read data for variable number of bytes.
|
||||
Read (`i2c.SW`) or queue (`i2c.HWx`) data for variable number of bytes.
|
||||
|
||||
#### Syntax
|
||||
`i2c.read(id, len)`
|
||||
|
||||
#### Parameters
|
||||
- `id` always 0
|
||||
- `id` interface id
|
||||
- `len` number of data bytes
|
||||
|
||||
#### Returns
|
||||
`string` of received data
|
||||
- `string` of received data for interface `i2c.SW`
|
||||
- `nil` for ids `i2c.HW0` and `i2c.HW1`
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
id = 0
|
||||
id = i2c.SW
|
||||
sda = 1
|
||||
scl = 2
|
||||
|
||||
|
@ -70,10 +81,13 @@ Initialize the I²C module.
|
|||
`i2c.setup(id, pinSDA, pinSCL, speed)`
|
||||
|
||||
####Parameters
|
||||
- `id` always 0
|
||||
- `pinSDA` 1~12, IO index
|
||||
- `pinSCL` 1~12, IO index
|
||||
- `speed` only `i2c.SLOW` supported
|
||||
- `id` interface id
|
||||
- `pinSDA` 0~33, IO index
|
||||
- `pinSCL` 0-33, IO index
|
||||
- `speed` bit rate in Hz, positive integer
|
||||
- `i2c.SLOW` for 100000 Hz, max for `i2c.SW`
|
||||
- `i2c.FAST` for 400000 Hz
|
||||
- `i2c.FASTPLUS` for 1000000 Hz
|
||||
|
||||
#### Returns
|
||||
`speed` the selected speed
|
||||
|
@ -82,13 +96,13 @@ Initialize the I²C module.
|
|||
[i2c.read()](#i2cread)
|
||||
|
||||
## i2c.start()
|
||||
Send an I²C start condition.
|
||||
Send (`i2c.SW`) or queue (`i2c.HWx`) an I²C start condition.
|
||||
|
||||
#### Syntax
|
||||
`i2c.start(id)`
|
||||
|
||||
#### Parameters
|
||||
`id` always 0
|
||||
`id` interface id
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
@ -97,13 +111,13 @@ Send an I²C start condition.
|
|||
[i2c.read()](#i2cread)
|
||||
|
||||
## i2c.stop()
|
||||
Send an I²C stop condition.
|
||||
Send (`i2c.SW`) or queue (`i2c.HWx`) an I²C stop condition.
|
||||
|
||||
#### Syntax
|
||||
`i2c.stop(id)`
|
||||
|
||||
####Parameters
|
||||
`id` always 0
|
||||
`id` interface id
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
@ -111,15 +125,35 @@ Send an I²C stop condition.
|
|||
####See also
|
||||
[i2c.read()](#i2cread)
|
||||
|
||||
## i2c.transfer()
|
||||
Starts a transfer for the specified hardware module. Providing a callback function allows the transfer to be started asynchronously in the background and `i2c.transfer()` finishes immediately. Without a callback function, the transfer is executed synchronously and `i2c.transfer()` comes back when the transfer completed. Data from a read operation is returned from `i2c.transfer()` in this case.
|
||||
|
||||
First argument to the callback is the error code (0 = no error), followed by a string with data obtained from a read operation during the transfer or `nil`.
|
||||
|
||||
#### Syntax
|
||||
`i2c.transfer(id[, cb_fn][, to_ms])`
|
||||
|
||||
#### Parameters
|
||||
- `id` interface id, `i2c.SW` not allowed
|
||||
- `cb_fn(err, data)` function to be called when transfer finished
|
||||
- `to_ms` timeout for the transfer in ms, defaults to 0=infinite
|
||||
|
||||
#### Returns
|
||||
- `string` of received data (or `nil` if no read) for synchronous operation
|
||||
- `nil` for asynchronous operation
|
||||
|
||||
## i2c.write()
|
||||
Write data to I²C bus. Data items can be multiple numbers, strings or lua tables.
|
||||
Write (`i2c.SW`) or queue (`i2c.HWx`) data to I²C bus. Data items can be multiple numbers, strings or lua tables.
|
||||
|
||||
Communication stops when the slave answers with NACK to a written byte. This can be avoided with parameter `ack_check_en` on `false`.
|
||||
|
||||
####Syntax
|
||||
`i2c.write(id, data1[, data2[, ..., datan]])`
|
||||
`i2c.write(id, data1[, data2[, ..., datan]][, ack_check_en])`
|
||||
|
||||
####Parameters
|
||||
- `id` always 0
|
||||
- `id` interface id
|
||||
- `data` data can be numbers, string or lua table.
|
||||
- `ack_check_en` enable check for slave ACK with `true` (default), disable with `false`
|
||||
|
||||
#### Returns
|
||||
`number` number of bytes written
|
||||
|
|
Loading…
Reference in New Issue