Browse Source

Add spi master.

devsaurus 7 years ago
parent
commit
819284530e

+ 6 - 0
components/modules/Kconfig

@@ -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 - 0
components/modules/spi.c

@@ -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);

+ 14 - 0
components/modules/spi_common.h

@@ -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_*/

+ 337 - 0
components/modules/spi_master.c

@@ -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 - 0
docs/en/modules/spi.md

@@ -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 - 0
lua_compat/spi_compat.lua

@@ -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

+ 1 - 0
mkdocs.yml

@@ -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'