diff --git a/components/modules/i2c.c b/components/modules/i2c.c index 19b4075c..ee9c8059 100644 --- a/components/modules/i2c.c +++ b/components/modules/i2c.c @@ -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 (!platform_i2c_setup( id, sda, scl, (uint32_t)speed )) - luaL_error( L, "setup failed" ); + 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 ); - if (!platform_i2c_send_start( id )) - luaL_error( L, "command failed"); + 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 ); - if (!platform_i2c_send_stop( id )) - luaL_error( L, "command failed" ); + 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 ) - break; + 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 ) - break; + 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 ) - break; + 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,21 +178,28 @@ static int i2c_write( lua_State *L ) static int i2c_read( lua_State *L ) { unsigned id = luaL_checkinteger( L, 1 ); - uint32_t size = ( uint32_t )luaL_checkinteger( L, 2 ), i; - luaL_Buffer b; - int data; + luaL_argcheck( L, id < I2C_ID_MAX, 1, "invalid id" ); - MOD_CHECK_ID( i2c, id ); - if( size == 0 ) + uint32_t size = ( uint32_t )luaL_checkinteger( L, 2 ), i; + + if (id == I2C_ID_SW) { + luaL_Buffer b; + int data; + + if( size == 0 ) + return 0; + luaL_buffinit( L, &b ); + for( i = 0; i < size; i ++ ) + if( ( data = platform_i2c_recv_byte( id, i < size - 1 ) ) == -1 ) + break; + else + luaL_addchar( &b, ( char )data ); + luaL_pushresult( &b ); + return 1; + } else { + li2c_hw_master_read( L, id, size ); return 0; - luaL_buffinit( L, &b ); - for( i = 0; i < size; i ++ ) - if( ( data = platform_i2c_recv_byte( id, i < size - 1 ) ) == -1 ) - break; - else - luaL_addchar( &b, ( char )data ); - luaL_pushresult( &b ); - return 1; + } } 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); diff --git a/components/modules/i2c_common.h b/components/modules/i2c_common.h new file mode 100644 index 00000000..14238856 --- /dev/null +++ b/components/modules/i2c_common.h @@ -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_*/ diff --git a/components/modules/i2c_hw_master.c b/components/modules/i2c_hw_master.c new file mode 100644 index 00000000..3528e9bc --- /dev/null +++ b/components/modules/i2c_hw_master.c @@ -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 + + +// 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 ); +} diff --git a/components/platform/include/platform.h b/components/platform/include/platform.h index d19d039d..37fa823f 100644 --- a/components/platform/include/platform.h +++ b/components/platform/include/platform.h @@ -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 diff --git a/docs/en/modules/i2c.md b/docs/en/modules/i2c.md index c7ef9afc..fcba52d7 100644 --- a/docs/en/modules/i2c.md +++ b/docs/en/modules/i2c.md @@ -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