Add spi master.

This commit is contained in:
devsaurus 2017-05-01 22:02:07 +02:00
parent 9e7eb48fef
commit 819284530e
7 changed files with 646 additions and 0 deletions

View File

@ -135,6 +135,12 @@ config LUA_MODULE_SIGMA_DELTA
Includes the sigma_delta module. This module provides access to the
sigma-delta hardware.
config LUA_MODULE_SPI
bool "SPI module"
default "n"
help
Includes the spi module.
config LUA_MODULE_STRUCT
bool "Struct module"
default "n"

24
components/modules/spi.c Normal file
View File

@ -0,0 +1,24 @@
// Module for interfacing with the SPI interface
#include "module.h"
#include "lauxlib.h"
#include "spi_common.h"
#include "driver/spi_common.h"
static const LUA_REG_TYPE lspi_map[] = {
{ LSTRKEY( "master" ), LFUNCVAL( lspi_master ) },
// { LSTRKEY( "slave" ), LFUNCVAL( lspi_slave ) },
{ LSTRKEY( "SPI" ), LNUMVAL( SPI_HOST ) },
{ LSTRKEY( "HSPI" ), LNUMVAL( HSPI_HOST ) },
{ LSTRKEY( "VSPI" ), LNUMVAL( VSPI_HOST ) },
{LNILKEY, LNILVAL}
};
int luaopen_spi( lua_State *L ) {
luaopen_spi_master( L );
return 0;
}
NODEMCU_MODULE(SPI, "spi", lspi_map, luaopen_spi);

View File

@ -0,0 +1,14 @@
#ifndef _NODEMCU_SPI_COMMON_H_
#define _NODEMCU_SPI_COMMON_H_
#include "lauxlib.h"
// ***************************************************************************
// SPI master
//
int luaopen_spi_master( lua_State *L );
int lspi_master( lua_State *L );
#endif /*_NODEMCU_SPI_COMMON_H_*/

View File

@ -0,0 +1,337 @@
// Module for interfacing with the SPI master hardware
#include <string.h>
#include "module.h"
#include "lauxlib.h"
#include "lextra.h"
#include "driver/spi_master.h"
#include "esp_heap_alloc_caps.h"
#include "esp_log.h"
#define SPI_MASTER_TAG "spi.master"
#define UD_HOST_STR "spi.master"
#define UD_DEVICE_STR "spi.device"
static int no_err( esp_err_t err )
{
switch (err)
{
default:
ESP_LOGI(SPI_MASTER_TAG, "unknown error");
return 0;
case ESP_ERR_INVALID_ARG:
ESP_LOGI(SPI_MASTER_TAG, "invalid argument");
return 0;
case ESP_ERR_INVALID_STATE:
ESP_LOGI(SPI_MASTER_TAG, "internal logic error");
return 0;
case ESP_ERR_NO_MEM:
ESP_LOGI(SPI_MASTER_TAG, "no memory");
return 0;
case ESP_OK:
return 1;
}
}
// ****************************************************************************
// Device related functions
//
typedef struct {
spi_device_handle_t device;
int host_ref, host;
} lspi_device_t;
#define GET_UD_DEVICE \
lspi_device_t *ud = (lspi_device_t *)luaL_checkudata( L, 1, UD_DEVICE_STR );
#define CONFIG_TRANS_FROM_FIELD(field) \
lua_getfield( L, stack, #field ); \
trans.field = luaL_optint( L, -1, 0 );
static int lspi_device_free( lua_State *L )
{
GET_UD_DEVICE;
if (ud->device) {
spi_bus_remove_device( ud->device );
ud->device = NULL;
// unref host to unblock automatic gc from this object
luaL_unref( L, LUA_REGISTRYINDEX, ud->host_ref );
ud->host_ref = LUA_NOREF;
}
return 0;
}
// Lua:
// recv = dev:transfer(trans_desc)
// recv = dev:transfer(data)
static int lspi_device_transfer( lua_State *L )
{
GET_UD_DEVICE;
int stack = 1;
luaL_argcheck( L, ud->device, stack, "no device" );
spi_transaction_t trans;
memset( &trans, 0, sizeof( trans ) );
size_t data_len, rx_len;
const char *data;
int type = lua_type( L, ++stack );
luaL_argcheck( L, type == LUA_TSTRING || type == LUA_TTABLE, stack, "string or table expected" );
if (type == LUA_TSTRING) {
data = luaL_checklstring( L, stack, &data_len );
rx_len = data_len;
} else {
const char * const options[] = {"std", "dio", "qio"};
const uint32_t options_flags[] = {0, SPI_TRANS_MODE_DIO, SPI_TRANS_MODE_QIO};
CONFIG_TRANS_FROM_FIELD(command);
CONFIG_TRANS_FROM_FIELD(address);
//
lua_getfield( L, stack, "rxlen" );
rx_len = luaL_optint( L, -1, 0 );
//
lua_getfield( L, stack, "addr_mode" );
trans.flags |= luaL_optbool( L, -1, false ) ? SPI_TRANS_MODE_DIOQIO_ADDR : 0;
//
lua_getfield( L, stack, "mode" );
trans.flags |= options_flags[ luaL_checkoption( L, -1, options[0], options ) ];
//
lua_getfield( L, stack, "txdata" );
data = luaL_optlstring( L, -1, "", &data_len );
lua_settop( L, stack );
}
const char *msg = NULL;
trans.length = data_len * 8;
if (data_len == 0) {
//no MOSI phase requested
trans.tx_buffer = NULL;
} else if (data_len <=4 ) {
// use local tx data buffer
trans.flags |= SPI_TRANS_USE_TXDATA;
memcpy( trans.tx_data, data, data_len );
} else {
// use DMA'able buffer
if ((trans.tx_buffer = pvPortMallocCaps( data_len, MALLOC_CAP_DMA ))) {
memcpy( (void *)trans.tx_buffer, data, data_len );
} else {
msg = "no memory";
goto free_mem;
}
}
trans.rxlength = rx_len * 8;
if (rx_len == 0) {
// no MISO phase requested
trans.rx_buffer = NULL;
} else if (rx_len <= 4) {
// use local rx data buffer
trans.flags |= SPI_TRANS_USE_RXDATA;
} else {
// use DMA'able buffer
if (!(trans.rx_buffer = pvPortMallocCaps( rx_len, MALLOC_CAP_DMA ))) {
msg = "no mem";
goto free_mem;
}
}
// finally perform the transaction
if (no_err( spi_device_transmit( ud->device, &trans ) )) {
// evaluate receive data
if (trans.flags & SPI_TRANS_USE_RXDATA) {
lua_pushlstring( L, (const char *)&(trans.rx_data[0]), rx_len );
} else {
lua_pushlstring( L, trans.rx_buffer, rx_len );
}
} else
msg = "transfer failed";
free_mem:
if (!(trans.flags & SPI_TRANS_USE_TXDATA) && trans.tx_buffer)
free( (void *)trans.tx_buffer );
if (!(trans.flags & SPI_TRANS_USE_RXDATA) && trans.rx_buffer)
free( (void *)trans.rx_buffer );
if (msg)
return luaL_error( L, msg );
return 1;
}
static const LUA_REG_TYPE lspi_device_map[] = {
{ LSTRKEY( "transfer" ), LFUNCVAL( lspi_device_transfer ) },
{ LSTRKEY( "remove" ), LFUNCVAL( lspi_device_free ) },
{ LSTRKEY( "__gc" ), LFUNCVAL( lspi_device_free ) },
{ LSTRKEY( "__index" ), LROVAL( lspi_device_map ) },
{LNILKEY, LNILVAL}
};
// ****************************************************************************
// Host related functions
//
typedef struct {
int host;
} lspi_host_t;
#define GET_UD_HOST \
lspi_host_t *ud = (lspi_host_t *)luaL_checkudata( L, 1, UD_HOST_STR );
//
#define CONFIG_BUS_PIN_FROM_FIELD(pin) \
lua_getfield( L, stack, #pin ); \
config.pin ## _io_num = luaL_optint( L, -1, -1 );
//
#define CONFIG_DEVICE_FROM_INT_FIELD(field) \
lua_getfield( L, stack, #field ); \
config.field = luaL_optint( L, -1, 0 );
#define CONFIG_DEVICE_FROM_BOOL_FIELD(field, mask) \
lua_getfield( L, stack, #field ); \
config.flags |= luaL_optbool( L, -1, false ) ? mask : 0;
static int lspi_host_free( lua_State *L )
{
GET_UD_HOST;
if (ud->host >= 0) {
spi_bus_free( ud->host );
ud->host = -1;
}
return 0;
}
// Lua: master = spi.master(host, config)
int lspi_master( lua_State *L )
{
int stack = 0;
int host = luaL_checkint( L, ++stack );
luaL_argcheck( L,
host == SPI_HOST || host == HSPI_HOST || host == VSPI_HOST,
stack,
"invalid host" );
luaL_checktype( L, ++stack, LUA_TTABLE );
spi_bus_config_t config;
memset( &config, 0, sizeof( config ) );
//
CONFIG_BUS_PIN_FROM_FIELD(sclk);
CONFIG_BUS_PIN_FROM_FIELD(mosi);
CONFIG_BUS_PIN_FROM_FIELD(miso);
CONFIG_BUS_PIN_FROM_FIELD(quadwp);
CONFIG_BUS_PIN_FROM_FIELD(quadhd);
lua_settop( L, stack );
//
if (no_err( spi_bus_initialize( host, &config, 1 ) )) {
lspi_host_t *ud = (lspi_host_t *)lua_newuserdata( L, sizeof( lspi_host_t ) );
luaL_getmetatable( L, UD_HOST_STR );
lua_setmetatable( L, -2 );
ud->host = host;
return 1;
}
return luaL_error( L, "bus init failed" );
}
// Lua: dev = master:device(config)
static int lspi_host_device( lua_State *L )
{
GET_UD_HOST;
int stack = 1;
luaL_argcheck( L, ud->host >= 0, stack, "no active bus host" );
luaL_checktype( L, ++stack, LUA_TTABLE );
spi_device_interface_config_t config;
memset( &config, 0, sizeof( config ) );
// mandatory fields
lua_getfield( L, stack, "cs" );
config.spics_io_num = luaL_optint( L, -1, -1 );
//
lua_getfield( L, stack, "mode" );
int mode = luaL_optint( L, -1, -1 );
luaL_argcheck( L, mode >= 0, stack, "mode setting missing" );
config.mode = (uint8_t)mode;
//
lua_getfield( L, stack, "freq" );
int freq = luaL_optint( L, -1, -1 );
luaL_argcheck( L, freq >= 0, stack, "freq setting missing" );
config.clock_speed_hz = freq;
//
// optional fields
CONFIG_DEVICE_FROM_INT_FIELD(command_bits);
CONFIG_DEVICE_FROM_INT_FIELD(address_bits);
CONFIG_DEVICE_FROM_INT_FIELD(dummy_bits);
CONFIG_DEVICE_FROM_INT_FIELD(cs_ena_pretrans);
CONFIG_DEVICE_FROM_INT_FIELD(cs_ena_posttrans);
CONFIG_DEVICE_FROM_INT_FIELD(duty_cycle_pos);
CONFIG_DEVICE_FROM_BOOL_FIELD(tx_lsb_first, SPI_DEVICE_TXBIT_LSBFIRST);
CONFIG_DEVICE_FROM_BOOL_FIELD(rx_lsb_first, SPI_DEVICE_RXBIT_LSBFIRST);
CONFIG_DEVICE_FROM_BOOL_FIELD(wire3, SPI_DEVICE_3WIRE);
CONFIG_DEVICE_FROM_BOOL_FIELD(positive_cs, SPI_DEVICE_POSITIVE_CS);
CONFIG_DEVICE_FROM_BOOL_FIELD(halfduplex, SPI_DEVICE_HALFDUPLEX);
CONFIG_DEVICE_FROM_BOOL_FIELD(clk_as_cs, SPI_DEVICE_CLK_AS_CS);
lua_settop( L, stack );
//
// fill remaining config entries
config.queue_size = 1;
lspi_device_t *dev = (lspi_device_t *)lua_newuserdata( L, sizeof( lspi_device_t ) );
dev->device = NULL;
if (no_err( spi_bus_add_device( ud->host, &config, &(dev->device) ) )) {
luaL_getmetatable( L, UD_DEVICE_STR );
lua_setmetatable( L, -2 );
// reference host object to avoid automatic gc
lua_pushvalue( L, 1 );
dev->host_ref = luaL_ref( L, LUA_REGISTRYINDEX );
dev->host = ud->host;
return 1;
}
lua_pop( L, 1 );
return luaL_error( L, "failed to add device" );
}
static const LUA_REG_TYPE lspi_master_map[] = {
{ LSTRKEY( "device" ), LFUNCVAL( lspi_host_device ) },
{ LSTRKEY( "close" ), LFUNCVAL( lspi_host_free ) },
{ LSTRKEY( "__gc" ), LFUNCVAL( lspi_host_free ) },
{ LSTRKEY( "__index" ), LROVAL( lspi_master_map ) },
{LNILKEY, LNILVAL}
};
// ****************************************************************************
// Generic
//
int luaopen_spi_master( lua_State *L ) {
luaL_rometatable(L, UD_HOST_STR, (void *)lspi_master_map);
luaL_rometatable(L, UD_DEVICE_STR, (void *)lspi_device_map);
return 0;
}

157
docs/en/modules/spi.md Normal file
View File

@ -0,0 +1,157 @@
# SPI Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2017-05-04 | [Arnim Läuger](https://github.com/devsaurus) | [Arnim Läuger](https://github.com/devsaurus) | [spi.c](../../../components/modules/spi.c)|
# SPI Bus
The ESP32 contains 4 SPI bus hosts called `SPI`, `SPI1`, `HSPI`, and `VSPI`. `SPI` is locked to flash communication and is not available for the application. `SPI1` is currently also tied to flash support, but might be available in the future. Applications can currently only use the `HSPI` and `VSPI` hosts.
The host signals can be mapped to any suitable GPIO pins.
!!! note
The API on ESP32 differs from the API on ESP8266. For backwards compatibility please refer to [`lua_compat/spi_compat.lua`](../../../lua_compat/spi_compat.lua`).
## spi.master()
Initializes a bus in master mode and returns a bus master object.
#### Syntax
`spi.master(host, config)`
#### Parameters
- `host` id, one of
- `spi.SPI1`. not supported yet
- `spi.HSPI`
- `spi.VSPI`
- `config` table listing the assigned GPIOs. All signal assignment are optional.
- `sclk`
- `mosi`
- `miso`
- `quadwp`
- `quadhd`
#### Returns
SPI bus master object
#### Example
```lua
busmaster_config = {sclk = 19, mosi = 23, miso = 25}
busmaster = spi.master(spi.HSPI, busmaster_config)
```
# Bus Master Object
## spi.master:close()
Close the bus host. This fails if there are still devices registered on this bus.
!!! caution
The bus is also closed when the bus master object is automatically destroyed during garbage collection. Registered devices inherently prevent garbage collection of the bus master object.
#### Syntax
`busmaster:close()`
#### Parameters
none
#### Returns
`nil`
## spi.master:device()
Adds a device on the given master bus. Up to three devices per bus are supported.
#### Syntax
`busmaster:device(config)`
#### Parameters
`config` table describing the device parameters:
- `cs` GPIO connected to device's chip-select pin, optional
- `mode` SPI mode used for this device (0-3), mandatory
- `freq` clock frequency used for this device [Hz], mandatory
- `command_bits` amount of bits in command phase (0-16), defaults to 0 if omitted
- `address_bits` amount of bits in address phase (0-64), defaults to 0 if omitted
- `dummy_bits` amount of dummy bits to insert address and data phase, defaults to 0 if omitted
- `cs_ena_pretrans`, optional
- `cs_ena_posttrans`, optional
- `duty_cycle_pos`, optional
- `tx_lsb_first` transmit command/address/data LSB first if `true`, MSB first otherwise
- `rx_lsb_first` receive data LSB first if `true`, MSB first otherwise
- `wire3` use spiq for both transmit and receive if `true`, use mosi and miso otherwise
- `positive_cs` chip-select is active high during a transaction if `true`, cs is active low otherwise
- `halfduplex` transmit data before receiving data if `true`, transmit and receive simultaneously otherwise
- `clk_as_cs` output clock on cs line when cs is active if `true`
#### Returns
SPI device object
#### Example
```lua
device_config = {mode = 0, freq = 1000000}
device_config.cs = 22
dev1 = busmaster:device(device_config)
device_config.cs = 4
dev2 = busmaster:device(device_config)
```
# Device Object
## spi.master:device:remove()
Removes a device from the related bus master.
!!! caution
The device is also removed when the device object is automatically destroyed during garbage collection.
#### Syntax
`device:remove()`
#### Parameters
none
#### Returns
`nil`
## spi.master:device:transfer()
Initiate an SPI transaction consisting of
1. assertion of cs signal
2. optional sending of command bits
3. optional sending of address bits
4. optional sending of dummy bits
5. sending of tx data
6. concurrent or appended reception of rx data, optional
7. de-assertion of cs signal
The function returns after the transaction is completed.
#### Syntax
```lua
device:transfer(trans)
device:transfer(txdata)
```
#### Parameters
`trans` table containing the elements of the transaction:
- `command` data for command phase, amount of bits was defined during device creation, optional
- `address` data for address phase, amount of bits was defined during device creation, optional
- `txdata` string of data to be sent to the device, optional
- `rxlen` number of bytes to be received, optional
- `mode` optional, one of
- `sio` transmit in SIO mode, default if omitted
- `dio` transmit in DIO mode
- `qio` transmit in QIO mode
- `addr_mode` transmit address also in selected `mode` if `true`, transmit address in SIO otherwise.
`txdata` string of data to be sent to the device
#### Returns
String of `rxlen` length, or `#txdata` length if `rxlen` is omitted.
## spi.slave()
Initializes a bus in slave mode and returns a slave object.
Not yet supported.

107
lua_compat/spi_compat.lua Normal file
View File

@ -0,0 +1,107 @@
-- ****************************************************************************
--
-- Compatability wrapper for mapping ESP8266's spi module API to ESP32
--
-- Usage:
--
-- spi = require("spi_compat")([pin_sclk], [pin_mosi], [pin_miso], [pin_cs])
--
-- pin_sclk: GPIO pin for SCLK, optional
-- pin_mosi: GPIO pin for MOSI, optional
-- pin_miso: GPIO pin for MISO, optional
-- pin_cs: GPIO pin for CS, optional
--
-- ****************************************************************************
local M = {}
local _pin_sclk, _pin_mosi, _pin_miso, _pin_cs
local _spi
local _duplex
local _device
-- ****************************************************************************
-- Implement esp8266 compatability API
--
function M.setup(id, mode, cpol, cpha, databits, clock_div, duplex_mode)
if databits ~= 8 then
error("only 8 bits per item supported")
end
local bus_master = _spi.master(_spi.HSPI, {sclk = _pin_sclk, mosi = _pin_mosi, miso = _pin_miso})
local dev_config = {}
dev_config.cs = _pin_cs
dev_config.mode = cpol * 2 + cpha
dev_config.freq = 80000000 / clock_div
_device = bus_master:device(dev_config)
_duplex = duplex_mode or M.HALFDUPLEX
end
function M.send(id, ...)
local results = {}
local wrote = 0
for idx = 1, select("#", ...) do
local arg = select(idx, ...)
if type(arg) == "number" then
table.insert(results, _device:transfer(string.char(arg)):byte(1))
wrote = wrote + 1
elseif type(arg) == "string" then
table.insert(results, _device:transfer(arg))
wrote = wrote + #arg
elseif type(arg) == "table" then
local rtab = {}
for i, data in ipairs(arg) do
table.insert(rtab, _device:transfer(string.char(data)):byte(1))
wrote = wrote + 1
end
table.insert(results, rtab)
else
error("wrong argument type")
end
end
if _duplex == M.FULLDUPLEX then
return wrote, unpack(results)
else
return wrote
end
end
function M.recv(id, size, default_data)
local def = default_data or 0xff
return _device:transfer(string.char(def):rep(size))
end
return function (pin_sclk, pin_mosi, pin_miso, pin_cs)
-- cache built-in module
_spi = spi
-- invalidate built-in module
spi = nil
-- forward unchanged functions
-- forward constant definitions
M.MASTER = 0
M.CPOL_LOW = 0
M.CPOL_HIGH = 1
M.CPHA_LOW = 0
M.CPHA_HIGH = 1
M.HALFDUPLEX = 0
M.FULLDUPLEX = 1
_pin_sclk = pin_sclk
_pin_mosi = pin_mosi
_pin_miso = pin_miso
_pin_cs = pin_cs
return M
end

View File

@ -44,6 +44,7 @@ pages:
- 'ow (1-Wire)': 'en/modules/ow.md'
- 'sdmmc': 'en/modules/sdmmc.md'
- 'sigma delta': 'en/modules/sigma-delta.md'
- 'spi': 'en/modules/spi.md'
- 'struct': 'en/modules/struct.md'
- 'tmr': 'en/modules/tmr.md'
- 'uart': 'en/modules/uart.md'