ESP32-S2 GPIO Expander

Hello,

I am trying to interface a GPIO Expander MCP23S18 (using SPI) with my ESP32-S2. The library used is spi_slave/sender/app_main.c and driver spi_master.h from components/drivers.
I don't understand what is going wrong and why it fails to work. My code is as below. Please help me with your inputs.

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <stdbool.h>

#include "gpio_expander.h"

#include "definitions.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_spi_flash.h"
#include "esp_system.h"
#include "driver/spi_master.h"

#include <stdbool.h>

#define GPIO_MOSI 35
#define GPIO_MISO 37
#define GPIO_SCLK 36
#define GPIO_CS 41

static uint8_t state_low = 0;
static uint8_t state_high = 1;

#define WRITEADDR 0x40
#define IO_DIR_REG 0x00
#define GPIO_REG 0x09

#define SENDER_HOST SPI3_HOST

static const char *TAG = "GPIOEXP833";

esp_err_t ret;
spi_device_handle_t handle;
spi_transaction_t t;

int initgpio(task_t *_this)
{
if(_this->state > TASKSTATE_UNINITIALIZED) {
return -1;
}

gpio_config_t io_conf;

io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL<<GPIO_CS);
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);

gpio_reset_pin(GPIO_CS);
gpio_set_direction(GPIO_CS, GPIO_MODE_OUTPUT);

//Configuration for the SPI bus
spi_bus_config_t buscfg={
.mosi_io_num=GPIO_MOSI,
.miso_io_num=GPIO_MISO,
.sclk_io_num=GPIO_SCLK,
.quadwp_io_num=-1,
.quadhd_io_num=-1
};

//Configuration for the SPI device on the other side of the bus
spi_device_interface_config_t devcfg={
.command_bits=8,
.address_bits=8,
.dummy_bits=0,
.clock_speed_hz=5000000,
.duty_cycle_pos=128, //50% duty cycle
.mode=1,
.spics_io_num=GPIO_CS, //(1ULL<<GPIO_CS),
.cs_ena_posttrans=3, //Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK
.queue_size=3
};

//Initialize the SPI bus and add the device we want to send stuff to.
ret=spi_bus_initialize(SENDER_HOST, &buscfg, SPI_DMA_CH_AUTO);
assert(ret==ESP_OK);

if (ret != ESP_OK) {
ESP_LOGI(TAG, "initialization failed");
}
else {
ESP_LOGI(TAG, "SPI initialized");
}

ret=spi_bus_add_device(SENDER_HOST, &devcfg, &handle);
assert(ret==ESP_OK);

t.flags = 0x0000;
t.tx_buffer=NULL;
t.rx_buffer=NULL;

t.user=IO_DIR_REG;

_this->state = TASKSTATE_RUNNING;
return -1;
}

void execgpio(task_t *_this) {
gpio_set_level(GPIO_CS, state_low);
t.cmd=WRITEADDR;
t.addr=GPIO_REG;
t.user=0x02;
// memset(&t, 0, sizeof(t));
ret=spi_device_transmit(handle, &t);
gpio_set_level(GPIO_REG, 0x02);
gpio_set_level(GPIO_CS, state_high);
}

Using the ESP32's SPI API is possible under the Arduino IDE.

ESP32_SPI_API.h

#include <driver/spi_master.h>
#include "sdkconfig.h"
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
////////////////////////////////////
//
//#define MAGTYPE  true
//#define XGTYPE   false
//////////////////////////
///////////////////////////
//
////////////////////////////
 uint8_t GetLowBits();
 int8_t GetHighBits();
 int fReadSPIdata16bits( spi_device_handle_t &h, int address );
 int fWriteSPIdata8bits( spi_device_handle_t &h, int address, int sendData );
 int fInitializeSPI_Devices( spi_device_handle_t &h, int csPin);
// spi_device_handle_t fInitializeSPI_Devices( int csPin);
int fInitializeSPI_Channel( int spiCLK, int spiMOSI, int spiMISO, spi_host_device_t SPI_Host, bool EnableDMA);

ESP32_SPI_API.cpp

#include "ESP32_SPI_API.h"
/////////////////////////////
///////////////////////////
uint8_t txData[2] = { };
uint8_t rxData[25] = { };
uint8_t low;
int8_t high;
//////
//////////////////////////////////
uint8_t GetLowBits()
{
  return low;
}
int8_t GetHighBits()
{
  return high;
}
////////////////////////////////////////
int fInitializeSPI_Channel( int spiCLK, int spiMOSI, int spiMISO, spi_host_device_t SPI_Host, bool EnableDMA)
{
  esp_err_t intError;
  spi_bus_config_t bus_config = { };
  bus_config.sclk_io_num = spiCLK; // CLK
  bus_config.mosi_io_num = spiMOSI; // MOSI
  bus_config.miso_io_num = spiMISO; // MISO
  bus_config.quadwp_io_num = -1; // Not used
  bus_config.quadhd_io_num = -1; // Not used
  intError = spi_bus_initialize( HSPI_HOST, &bus_config, EnableDMA) ;
  return intError;
}
//////
int fInitializeSPI_Devices( spi_device_handle_t &h, int csPin)
{
  esp_err_t intError;
  spi_device_interface_config_t dev_config = { };  // initializes all field to 0
  dev_config.address_bits     = 0;
  dev_config.command_bits     = 0;
  dev_config.dummy_bits       = 0;
  dev_config.mode             = 3 ;
  dev_config.duty_cycle_pos   = 0;
  dev_config.cs_ena_posttrans = 0;
  dev_config.cs_ena_pretrans  = 0;
  dev_config.clock_speed_hz   = 5000000;
  dev_config.spics_io_num     = csPin;
  dev_config.flags            = 0;
  dev_config.queue_size       = 1;
  dev_config.pre_cb           = NULL;
  dev_config.post_cb          = NULL;
  spi_bus_add_device(HSPI_HOST, &dev_config, &h);
  // return intError;
  // return h;
} // void fInitializeSPI_Devices()
///////////////////////////////////////////////////////////////
int fReadSPIdata16bits( spi_device_handle_t &h, int _address )
{
  uint8_t address = _address;
    esp_err_t intError = 0;
    low=0; high=0;
    spi_transaction_t trans_desc;
    trans_desc = { };
    trans_desc.addr =  0;
    trans_desc.cmd = 0;
    trans_desc.flags = 0;
    trans_desc.length = (8 * 3); // total data bits
    trans_desc.tx_buffer = txData;
    trans_desc.rxlength = 8 * 2 ; // Number of bits NOT number of bytes
    trans_desc.rx_buffer = rxData;
    txData[0] = address | 0x80;
    intError = spi_device_transmit( h, &trans_desc);
    low = rxData[0]; high = rxData[1];
  //  if ( intError != 0 )
  //  {
  //    Serial.print( " WHO I am LSM9DS1. Transmitting error = ");
  //    Serial.println ( esp_err_to_name(intError) );
  //  }
  return intError;
} // void fSendSPI( uint8_t count, uint8_t address, uint8_t DataToSend)
////
int fWriteSPIdata8bits( spi_device_handle_t &h, int _address, int _sendData )
{
  uint8_t address =  _address;
  uint8_t sendData = _sendData;
  esp_err_t intError;
  spi_transaction_t trans_desc;
  trans_desc = { };
  trans_desc.addr =  0;
  trans_desc.cmd = 0;
  trans_desc.flags = 0;
  trans_desc.length = (8 * 2); // total data bits
  trans_desc.tx_buffer = txData;
  trans_desc.rxlength = 0 ; // Number of bits NOT number of bytes
  trans_desc.rx_buffer = NULL;
  txData[0] = address  & 0x7F;
  txData[1] = sendData;
  intError = spi_device_transmit( h, &trans_desc);
  return intError;
//  //  if ( intError != 0 )
//  //  {
//  //    Serial.print( " LSM9DS1_REGISTER_CTRL_REG6_XL. Transmitting error = ");
//  //    Serial.println ( esp_err_to_name(intError) );
//  //  }
} // void fWriteSPIdata8bits(  spi_device_handle_t &h, uint8_t address, uint8_t sendData )
//

Notice how you are initializing the configuration structures and how it differs from how I am initialize the configuration structures.

Thank you @Idahowalker. I have changed my configurations likewise. Which SPI host are you using?

typedef enum {
// SPI_HOST (SPI1_HOST) is not supported by the SPI Master and SPI Slave driver on ESP32-S2
SPI1_HOST=0, ///< SPI1
SPI2_HOST=1, ///< SPI2
SPI3_HOST=2, ///< SPI3
SPI_HOST_MAX=3, ///< invalid host value
} spi_host_device_t;

I used SPI3_HOST is that VSPI (General purpose) ?

SPI Master Driver - ESP32 - — ESP-IDF Programming Guide latest documentation (espressif.com)

Host The SPI controller peripheral inside ESP32 that initiates SPI transmissions over the bus, and acts as an SPI Master.

HSPI_HOST or VSPI_HOST

Thank you @Idahowalker. So now I know that the SPI I am using is right. Below is my configuration:
However when I observe my spi signals on an analyzer, the MOSI data appears to be in reversed polarity (as in the image). Could you please tell me what parameters need to included or what existing configurations are to be changed so that MOSI is synced with the clock? I already played with frequency and spi mode. Thank you

  spi_device_interface_config_t devcfg={
    .command_bits=8,
    .address_bits=8,
    .dummy_bits=0,
    .clock_speed_hz=1000000,
    .duty_cycle_pos=128,        //50% duty cycle
    .mode=0,
    .spics_io_num=GPIO_CS, //(1ULL<<GPIO_CS),
    .cs_ena_pretrans=0,
    .cs_ena_posttrans=3,          spi_device_interface_config_t devcfg={
    .command_bits=8,
    .address_bits=8,
    .dummy_bits=0,
    .clock_speed_hz=1000000,
    .duty_cycle_pos=128,        //50% duty cycle
    .mode=0,
    .spics_io_num=GPIO_CS, //(1ULL<<GPIO_CS),
    .cs_ena_pretrans=0,
    .cs_ena_posttrans=3,        //Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK
    .queue_size=1,
    .flags=0,
    .pre_cb=NULL,
    .post_cb=NULL
  };

I've not experience the issue you seem to think you are having.

For ESP32 SPI Master API configurations and methods see the ESP32 API as linked to in post#4.

And that actually compiles without an error? Interesting.

Yes it compiles and flashes without an error. It gives an error only when my command bits and address bits are set to zero and that makes sense cause I'm send 8 bit command and address.