SPI slave by register configuration
Posted: Mon Aug 12, 2019 8:44 am
Hello ESP32 friends,
I need to read data from a master SPI device with the esp32 as SPI slave.
The specs of the master SPI are:
8 MHz clock frequency
8 bit samples (if it works, then upgrade to 32 bit later)
100 kHz sample rate
1. attempt.
I used the IDF slave SPI receiver example and stripped off everything unnecessary. Code below:
Problem: It worked with the limitation that only every 4th to 5th sample was read. I checked this by hooking the handshake pin from esp32 to an oscilloscope. The measure frequency was around 25 kHz, as can be seen here:
When uncommenting the printf line it showed that the read data was correct at least (even though the frequency was further reduced due to additional communication, of course).
2. attempt.
Because there is nothing left to strip from the while(1) loop in order to increased speed, I concluded that the IDF slave SPI driver does not fit my needs. From what I understand of SPI theory, there is actually no need that the esp32 is any longer busy than until chip select turns high again (mode 0).
So I tried to configure the registers to enable SPI slave communication manually:
It is to the point now where the interrupt triggers successfully after every sample:
The esp32 also puts out correct data at the MISO pin (I only implemented this for debugging).
Problem: It reads 0 from SPI_W0_REG all the time. I checked this by uncommenting printf line in while loop again.
Question: What is missing/wrong in the code above so that it reads 0 instead of the expected non-zero data?
Looking forward to some enlightenment, I know you guys are awesome.
Thanks
I need to read data from a master SPI device with the esp32 as SPI slave.
The specs of the master SPI are:
8 MHz clock frequency
8 bit samples (if it works, then upgrade to 32 bit later)
100 kHz sample rate
1. attempt.
I used the IDF slave SPI receiver example and stripped off everything unnecessary. Code below:
Code: Select all
/* SPI Slave example, receiver MODIFIED
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
#include "lwip/igmp.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_event_loop.h"
#include "nvs_flash.h"
#include "soc/rtc_cntl_reg.h"
#include "rom/cache.h"
#include "driver/spi_slave.h"
#include "esp_log.h"
#include "esp_spi_flash.h"
#define GPIO_HANDSHAKE 2
#define GPIO_MOSI 13
#define GPIO_MISO 12
#define GPIO_SCLK 14
#define GPIO_CS 15
//Called after a transaction is queued and ready for pickup by master. We use this to set the handshake line high.
void my_post_setup_cb(spi_slave_transaction_t *trans) {
WRITE_PERI_REG(GPIO_OUT_W1TS_REG, (1<<GPIO_HANDSHAKE));
}
//Called after transaction is sent/received. We use this to set the handshake line low.
void my_post_trans_cb(spi_slave_transaction_t *trans) {
WRITE_PERI_REG(GPIO_OUT_W1TC_REG, (1<<GPIO_HANDSHAKE));
}
//Main application
void app_main()
{
esp_err_t ret;
//Configuration for the SPI bus
spi_bus_config_t buscfg={
.mosi_io_num=GPIO_MOSI,
.miso_io_num=-1,
.sclk_io_num=GPIO_SCLK
};
//Configuration for the SPI slave interface
spi_slave_interface_config_t slvcfg={
.mode=0,
.spics_io_num=GPIO_CS,
.queue_size=1,
.flags=0,
.post_setup_cb=my_post_setup_cb,
.post_trans_cb=my_post_trans_cb
};
//Configuration for the handshake line
gpio_config_t io_conf={
.intr_type=GPIO_INTR_DISABLE,
.mode=GPIO_MODE_OUTPUT,
.pin_bit_mask=(1<<GPIO_HANDSHAKE)
};
//Configure handshake line as output
gpio_config(&io_conf);
//Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected.
gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY);
//Initialize SPI slave interface
ret=spi_slave_initialize(HSPI_HOST, &buscfg, &slvcfg, 1);
assert(ret==ESP_OK);
uint8_t recvbuf;
spi_slave_transaction_t t;
memset(&t, 0, sizeof(t));
//Set up a transaction of 1 byte to send/receive
t.length=1*8;
t.tx_buffer=NULL;
t.rx_buffer=&recvbuf;
while(1) {
spi_slave_transmit(HSPI_HOST, &t, portMAX_DELAY);
//printf("received: %u\n", recvbuf); //debugging
}
}
When uncommenting the printf line it showed that the read data was correct at least (even though the frequency was further reduced due to additional communication, of course).
2. attempt.
Because there is nothing left to strip from the while(1) loop in order to increased speed, I concluded that the IDF slave SPI driver does not fit my needs. From what I understand of SPI theory, there is actually no need that the esp32 is any longer busy than until chip select turns high again (mode 0).
So I tried to configure the registers to enable SPI slave communication manually:
Code: Select all
/*
SPI Test
*/
#include "soc/soc.h"
#include "soc/spi_reg.h"
#include "soc/io_mux_reg.h"
#include "soc/gpio_reg.h"
#include "soc/gpio_periph.h"
#include "esp_event.h" // für vTaskDelay
#include "driver/periph_ctrl.h" // für periph_module_enable(...)
#include <stdio.h>
// SPI_TRANS_INTEN seems undefined, define here
#define SPI_TRANS_INTEN 0b00000000000000000000001000000000
// debugging;
#define GPIO_HANDSHAKE 2
// Pinout for Olimex ESP32 EVB
// CLK GPIO 14 EVB ext 15
// CS GPIO 15 EVB ext 16
// MOSI GPIO 13 EVB ext 14
void app_main()
{
// activate HSPI (clear reset bit of peripheral, set clk_en bit)
periph_module_enable(PERIPH_HSPI_MODULE);
//debugging; Configuration for the handshake line
gpio_config_t io_conf={.intr_type=GPIO_INTR_DISABLE, .mode=GPIO_MODE_OUTPUT, .pin_bit_mask=(1<<GPIO_HANDSHAKE) };
//Configure handshake line as output
gpio_config(&io_conf);
// use HSPI (HSPI = SPI2)
uint8_t unit = 2;
// Clean SPI registers
WRITE_PERI_REG(SPI_USER_REG(unit), 0);
WRITE_PERI_REG(SPI_USER1_REG(unit), 0);
WRITE_PERI_REG(SPI_USER2_REG(unit), 0);
WRITE_PERI_REG(SPI_CTRL_REG(unit), 0);
WRITE_PERI_REG(SPI_CTRL2_REG(unit), 0);
WRITE_PERI_REG(SPI_SLAVE_REG(unit), 0);
WRITE_PERI_REG(SPI_PIN_REG(unit), 0);
WRITE_PERI_REG(SPI_CLOCK_REG(unit), 0);
WRITE_PERI_REG(SPI_W0_REG(unit), 0);
// debugging; MISO data
WRITE_PERI_REG(SPI_W8_REG(unit), 0b0101010101);
// Pin configuration via IO_MUX
// Select function of IO_MUX pad
SET_PERI_REG_BITS(IO_MUX_GPIO12_REG, MCU_SEL, 1, MCU_SEL_S); // GPIO12 -> PadName MTCK -> Function 2: HSPID -> MOSI
SET_PERI_REG_BITS(IO_MUX_GPIO13_REG, MCU_SEL, 1, MCU_SEL_S); // GPIO13 -> PadName MTCK -> Function 2: HSPID -> MOSI
SET_PERI_REG_BITS(IO_MUX_GPIO14_REG, MCU_SEL, 1, MCU_SEL_S); // GPIO14 -> PadName MTMS -> Function 2: HSPICLK -> CLK
SET_PERI_REG_BITS(IO_MUX_GPIO15_REG, MCU_SEL, 1, MCU_SEL_S); // GPIO15 -> PadName MTDO -> Function 2: HSPICS0 -> CS
// Enable input
SET_PERI_REG_MASK(IO_MUX_GPIO12_REG, FUN_IE); // GPIO12 -> PadName MTDI -> Function 2: HSPIQ -> MISO
SET_PERI_REG_MASK(IO_MUX_GPIO13_REG, FUN_IE); // GPIO13 -> PadName MTCK -> Function 2: HSPID -> MOSI
SET_PERI_REG_MASK(IO_MUX_GPIO14_REG, FUN_IE); // GPIO14 -> PadName MTMS -> Function 2: HSPICLK -> CLK
SET_PERI_REG_MASK(IO_MUX_GPIO15_REG, FUN_IE); // GPIO15 -> PadName MTDO -> Function 2: HSPICS0 -> CS
// Bypass GPIO Matrix
CLEAR_PERI_REG_MASK(GPIO_FUNC9_OUT_SEL_CFG_REG, GPIO_FUNC9_OEN_SEL); // HSPIQ_out (page 51)
CLEAR_PERI_REG_MASK(GPIO_FUNC10_IN_SEL_CFG_REG, GPIO_SIG10_IN_SEL); // HSPID_in
CLEAR_PERI_REG_MASK(GPIO_FUNC8_IN_SEL_CFG_REG, GPIO_SIG8_IN_SEL); // HSPICLK_in
CLEAR_PERI_REG_MASK(GPIO_FUNC11_IN_SEL_CFG_REG, GPIO_SIG11_IN_SEL); // HPSICS0_in
// SPI Mode 0 Non-DMA, technical reference p. 124 Table 28
SET_PERI_REG_MASK(SPI_PIN_REG(unit), SPI_CK_IDLE_EDGE); // set to 1
CLEAR_PERI_REG_MASK(SPI_USER_REG(unit), SPI_CK_I_EDGE | SPI_USR_COMMAND); // cleared to 0
SET_PERI_REG_BITS(SPI_CTRL2_REG(unit), SPI_MISO_DELAY_MODE, 0, SPI_MISO_DELAY_MODE_S); // set to 0
SET_PERI_REG_BITS(SPI_CTRL2_REG(unit), SPI_MISO_DELAY_NUM, 0, SPI_MISO_DELAY_NUM_S); // set to 0
SET_PERI_REG_BITS(SPI_CTRL2_REG(unit), SPI_MOSI_DELAY_MODE, 2, SPI_MOSI_DELAY_MODE_S); // set to 2
SET_PERI_REG_BITS(SPI_CTRL2_REG(unit), SPI_MOSI_DELAY_NUM, 2, SPI_MOSI_DELAY_NUM_S); // set to 2
// Set bit order to MSB
CLEAR_PERI_REG_MASK(SPI_CTRL_REG(unit), SPI_WR_BIT_ORDER | SPI_RD_BIT_ORDER);
// Enable full-duplex communication
SET_PERI_REG_MASK(SPI_USER_REG(unit), SPI_DOUTDIN);
// debugging; Take MISO data from SPI_W8_REG
SET_PERI_REG_MASK(SPI_USER_REG(unit), SPI_USR_MISO_HIGHPART);
// activate MOSI MISO
SET_PERI_REG_MASK(SPI_USER_REG(unit), SPI_USR_MOSI | SPI_USR_MISO);
// buffer lenght to 8 bit
SET_PERI_REG_BITS(SPI_SLV_WRBUF_DLEN_REG(unit), SPI_SLV_WRBUF_DBITLEN, 7, SPI_SLV_WRBUF_DBITLEN_S);
SET_PERI_REG_BITS(SPI_SLV_RDBUF_DLEN_REG(unit), SPI_SLV_RDBUF_DBITLEN, 7, SPI_SLV_RDBUF_DBITLEN_S);
// enable SPI slave transmission done interrupt
SET_PERI_REG_MASK(SPI_SLAVE_REG(unit), SPI_TRANS_INTEN);
// Set to SPI slave mode
SET_PERI_REG_MASK(SPI_SLAVE_REG(unit), SPI_SLAVE_MODE);
// 32 bit buffer for MOSI data (actually 8 bit)
uint32_t recvbuf;
recvbuf = 0;
while(1)
{
// reset interrupt
CLEAR_PERI_REG_MASK(SPI_SLAVE_REG(unit), SPI_TRANS_DONE);
// wait for interrupt bit
while (~(READ_PERI_REG(SPI_SLAVE_REG(unit)))&SPI_TRANS_DONE)
{
};
// copy MOSI data to recvbuf
recvbuf = READ_PERI_REG(SPI_W0_REG(unit));
// debugging;
WRITE_PERI_REG(GPIO_OUT_W1TS_REG, (1<<GPIO_HANDSHAKE));
WRITE_PERI_REG(GPIO_OUT_W1TC_REG, (1<<GPIO_HANDSHAKE));
// debugging; print content of buffer
//printf("received: %u\n", recvbuf);
}
}
It is to the point now where the interrupt triggers successfully after every sample:
The esp32 also puts out correct data at the MISO pin (I only implemented this for debugging).
Problem: It reads 0 from SPI_W0_REG all the time. I checked this by uncommenting printf line in while loop again.
Question: What is missing/wrong in the code above so that it reads 0 instead of the expected non-zero data?
Looking forward to some enlightenment, I know you guys are awesome.
Thanks