Merge branch 'feature/hspi_test' into 'master'

feat(spi): fix some bugs and restructure the spi driver and the demo

See merge request sdk/ESP8266_RTOS_SDK!1113
This commit is contained in:
Dong Heng
2019-11-13 10:00:35 +08:00
44 changed files with 1879 additions and 660 deletions

View File

@ -46,6 +46,7 @@ else()
"driver/i2s.c"
"driver/pwm.c"
"driver/spi.c"
"driver/hspi_logic_layer.c"
"driver/uart.c"
"driver/ir_tx.c"
"driver/ir_rx.c"

View File

@ -627,3 +627,12 @@ config ESP8266_PHY_MAX_WIFI_TX_POWER
endmenu # PHY
menu HSPI
config ESP8266_HSPI_HIGH_THROUGHPUT
bool "Do some optimization to improve throughput"
default n
help
If enable this configuration, some spi api will be placed into iram.
And it will reduce iram memory.
endmenu # Driver

View File

@ -0,0 +1,278 @@
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
#include "freertos/stream_buffer.h"
#include "ringbuf.h"
#include "esp8266/spi_struct.h"
#include "esp8266/gpio_struct.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/spi.h"
#include "driver/hspi_logic_layer.h"
static const char *TAG = "hspi_logic";
#define SPI_CHECK(a, str, ret_val) \
do { \
if (!(a)) { \
ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
} \
} while(0)
typedef struct {
gpio_num_t trigger_pin;
uint8_t trigger_level;
bool is_sending;
bool is_blocking_recv;
uint32_t sending_len;
uint32_t recving_len;
StreamBufferHandle_t* tx_buffer;
StreamBufferHandle_t* rx_buffer;
SemaphoreHandle_t semphor;
spi_event_callback_t event_cb;
} spi_logic_device_t;
static spi_logic_device_t * spi_logic_device;
static void IRAM_ATTR hspi_slave_event_callback(int event, void *arg)
{
int x;
BaseType_t xHigherPriorityTaskWoken;
uint32_t status;
uint32_t trans_done;
uint32_t data[16];
spi_trans_t trans;
uint16_t cmd = 0;
bool trigger_flag = false;
switch (event) {
case SPI_TRANS_DONE_EVENT: {
gpio_set_level(spi_logic_device->trigger_pin, !spi_logic_device->trigger_level);
trans_done = *(uint32_t *)arg;
if (trans_done & SPI_SLV_RD_BUF_DONE) {
if (spi_logic_device->sending_len == 0) {
spi_logic_device->is_sending = false;
spi_logic_device->sending_len = xStreamBufferBytesAvailable(spi_logic_device->tx_buffer);
if (spi_logic_device->sending_len > 0) {
spi_slave_set_status(HSPI_HOST, (uint32_t*)&spi_logic_device->sending_len);
spi_logic_device->is_sending = true;
trigger_flag = true;
}
} else {
memset(&trans, 0x0, sizeof(trans));
trans.cmd = &cmd;
trans.addr = NULL;
trans.bits.val = 0;
// In Slave mode, spi cmd must be longer than 3 bits and shorter than 16 bits
trans.bits.cmd = 8 * 1;
// In Slave mode, spi addr must be longer than 1 bits and shorter than 32 bits
trans.bits.addr = 8 * 1;
trans.bits.mosi = 0;
trans.miso = data;
trans.bits.miso = xStreamBufferReceiveFromISR(spi_logic_device->tx_buffer, data, 64, &xHigherPriorityTaskWoken);
if (trans.bits.miso != 0) {
spi_logic_device->sending_len -= trans.bits.miso;
trans.bits.miso <<= 3;
spi_trans(HSPI_HOST, &trans);
trigger_flag = true;;
}
}
}
if (trans_done & SPI_SLV_WR_BUF_DONE) {
uint32_t len = spi_logic_device->recving_len;
if (len > 64) {
len = 64;
}
if (len > 0) {
for (x = 0; x < 16; x++) {
data[x] = SPI1.data_buf[x];
}
xStreamBufferSendFromISR(spi_logic_device->rx_buffer, (void *) data, len, &xHigherPriorityTaskWoken);
spi_logic_device->recving_len -= len;
} else {
ets_printf("remained %d\r\n", len);
}
if (xStreamBufferSpacesAvailable(spi_logic_device->rx_buffer) >= 64) {
trigger_flag = true;
} else {
spi_logic_device->is_blocking_recv = true;
}
}
if (trans_done & SPI_SLV_WR_STA_DONE) {
spi_slave_get_status(HSPI_HOST, &status);
spi_logic_device->recving_len = status;
uint32_t tx_size = xStreamBufferBytesAvailable(spi_logic_device->tx_buffer);
if (spi_logic_device->recving_len > 0) {
trigger_flag = true;
} else if (tx_size > 0) {
if (spi_logic_device->is_sending == false) {
spi_slave_set_status(HSPI_HOST, &tx_size);
}
trigger_flag = true;
}
}
if (trans_done & SPI_SLV_RD_STA_DONE) {
memset(&trans, 0x0, sizeof(trans));
trans.cmd = &cmd;
trans.addr = NULL;
trans.bits.val = 0;
// In Slave mode, spi cmd must be longer than 3 bits and shorter than 16 bits
trans.bits.cmd = 8 * 1;
// In Slave mode, spi addr must be longer than 1 bits and shorter than 32 bits
trans.bits.addr = 8 * 1;
trans.bits.mosi = 0;
trans.miso = data;
trans.bits.miso = xStreamBufferReceiveFromISR(spi_logic_device->tx_buffer, data, 64, &xHigherPriorityTaskWoken);
if (trans.bits.miso != 0) {
spi_logic_device->sending_len -= trans.bits.miso;
trans.bits.miso <<= 3;
spi_trans(HSPI_HOST, &trans);
trigger_flag = true;
}
}
if (trigger_flag) {
gpio_set_level(spi_logic_device->trigger_pin, spi_logic_device->trigger_level);
}
if (spi_logic_device->event_cb) {
spi_logic_device->event_cb(event, arg);
}
if (xHigherPriorityTaskWoken == pdTRUE) {
taskYIELD();
}
}
break;
case SPI_DEINIT_EVENT: {
}
break;
}
}
uint32_t hspi_slave_logic_read_data(uint8_t*data, uint32_t len, TickType_t xTicksToWait)
{
uint32_t ret = 0;
ret = xStreamBufferReceive(spi_logic_device->rx_buffer, data, len, xTicksToWait);
if (spi_logic_device->is_blocking_recv) {
if (xStreamBufferBytesAvailable(spi_logic_device->rx_buffer) > 64) {
gpio_set_level(spi_logic_device->trigger_pin, spi_logic_device->trigger_level);
spi_logic_device->is_blocking_recv = false;
}
}
return ret;
}
uint32_t hspi_slave_logic_write_data(uint8_t*data, uint32_t len, TickType_t xTicksToWait)
{
uint32_t ret = 0;
uint32_t avail_spaces = 0;
if (!spi_logic_device->is_sending) {
portENTER_CRITICAL();
avail_spaces = xStreamBufferSpacesAvailable(spi_logic_device->tx_buffer);
if (avail_spaces > len) {
avail_spaces = len;
}
ret = xStreamBufferSend(spi_logic_device->tx_buffer, data, avail_spaces, xTicksToWait); //
spi_logic_device->sending_len = xStreamBufferBytesAvailable(spi_logic_device->tx_buffer);
spi_slave_set_status(HSPI_HOST, (uint32_t*)&spi_logic_device->sending_len);
spi_logic_device->is_sending = true;
gpio_set_level(spi_logic_device->trigger_pin, spi_logic_device->trigger_level);
portEXIT_CRITICAL();
}
if (ret < len) {
ret += xStreamBufferSend(spi_logic_device->tx_buffer, data + ret, len - ret, xTicksToWait);
}
return ret;
}
esp_err_t hspi_slave_logic_device_create(gpio_num_t trigger_pin, uint32_t trigger_level,uint32_t tx_buffer_size, uint32_t rx_buffer_size)
{
SPI_CHECK(GPIO_IS_VALID_GPIO(trigger_pin), "gpio num error", ESP_ERR_INVALID_ARG);
SPI_CHECK(tx_buffer_size != 0, "tx buffer error", ESP_ERR_INVALID_ARG);
SPI_CHECK(rx_buffer_size != 0, "rx buffer error", ESP_ERR_INVALID_ARG);
gpio_config_t io_conf;
if (spi_logic_device) {
hspi_slave_logic_device_delete();
}
spi_logic_device = (spi_logic_device_t*)malloc(sizeof(spi_logic_device_t));
assert(spi_logic_device);
memset(spi_logic_device, 0x0, sizeof(spi_logic_device_t));
spi_logic_device->tx_buffer = xStreamBufferCreate(tx_buffer_size,1);
if (!spi_logic_device->tx_buffer) {
free(spi_logic_device);
spi_logic_device = NULL;
return ESP_ERR_NO_MEM;
}
spi_logic_device->rx_buffer = xStreamBufferCreate(rx_buffer_size,1);
if (!spi_logic_device->rx_buffer) {
vStreamBufferDelete(spi_logic_device->tx_buffer);
spi_logic_device->tx_buffer = NULL;
free(spi_logic_device);
spi_logic_device = NULL;
return ESP_ERR_NO_MEM;
}
spi_logic_device->trigger_pin = trigger_pin;
spi_logic_device->trigger_level = (trigger_level==1)?1:0;
memset(&io_conf, 0x0, sizeof(io_conf));
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << trigger_pin);
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
gpio_set_level(trigger_pin, !spi_logic_device->trigger_level);
spi_get_event_callback(HSPI_HOST, &spi_logic_device->event_cb);
spi_event_callback_t event_cb = hspi_slave_event_callback;
spi_set_event_callback(HSPI_HOST, &event_cb);
return ESP_OK;
}
esp_err_t hspi_slave_logic_device_delete(void)
{
if (spi_logic_device == NULL) {
return ESP_ERR_INVALID_STATE;
}
vStreamBufferDelete(spi_logic_device->tx_buffer);
spi_logic_device->tx_buffer = NULL;
vStreamBufferDelete(spi_logic_device->rx_buffer);
spi_logic_device->rx_buffer = NULL;
free(spi_logic_device);
spi_logic_device = NULL;
return ESP_OK;
}

View File

@ -15,10 +15,9 @@
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp8266/eagle_soc.h"
#include "esp8266/spi_struct.h"
#include "esp8266/pin_mux_register.h"
@ -27,20 +26,44 @@
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "rom/ets_sys.h"
#include "spi.h"
#define ENTER_CRITICAL() portENTER_CRITICAL()
#define EXIT_CRITICAL() portEXIT_CRITICAL()
static const char *TAG = "spi";
#define SPI_CHECK(a, str, ret_val) \
do { \
if (!(a)) { \
ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
} \
} while(0)
#define portYIELD_FROM_ISR() taskYIELD()
#ifndef CONFIG_ESP8266_HSPI_HIGH_THROUGHPUT
#define ENTER_CRITICAL_HIGH_THROUGHPUT() ENTER_CRITICAL()
#define EXIT_CRITICAL_HIGH_THROUGHPUT() EXIT_CRITICAL()
#define SPI_HIGH_THROUGHPUT_ATTR
#define SPI_CHECK_HIGH_THROUGHPUT(a, str, ret_val) SPI_CHECK(a, str, ret_val)
#else
#define SPI_HIGH_THROUGHPUT_ATTR IRAM_ATTR
#define ENTER_CRITICAL_HIGH_THROUGHPUT() do{} while(0)
#define EXIT_CRITICAL_HIGH_THROUGHPUT() do{} while(0)
#define SPI_CHECK_HIGH_THROUGHPUT(a, str, ret_val) \
do { \
if (!(a)) { \
ets_printf("%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
} \
} while(0)
#endif
static const char *TAG = "spi";
#define spi_intr_enable() _xt_isr_unmask(1 << ETS_SPI_INUM)
#define spi_intr_disable() _xt_isr_mask(1 << ETS_SPI_INUM)
@ -56,6 +79,8 @@ typedef struct {
spi_interface_t interface;
SemaphoreHandle_t trans_mux;
spi_event_callback_t event_cb;
spi_intr_enable_t intr_enable;
uint32_t *buf;
} spi_object_t;
static spi_object_t *spi_object[SPI_NUM_MAX] = {NULL, NULL};
@ -164,6 +189,8 @@ esp_err_t spi_set_intr_enable(spi_host_t host, spi_intr_enable_t *intr_enable)
SPI[host]->slave.trans_done = false;
EXIT_CRITICAL();
spi_object[host]->intr_enable.val = intr_enable->val;
return ESP_OK;
}
@ -209,16 +236,25 @@ esp_err_t spi_set_mode(spi_host_t host, spi_mode_t *mode)
// Set to Slave mode
SPI[host]->pin.slave_mode = true;
SPI[host]->slave.slave_mode = true;
SPI[host]->user.usr_miso_highpart = true;
SPI[host]->user.usr_mosi_highpart = false;
SPI[host]->user.usr_miso_highpart = false;
SPI[host]->user.usr_addr = 1;
// MOSI signals are delayed by APB_CLK(80MHz) mosi_delay_num cycles
SPI[host]->ctrl2.mosi_delay_num = 2;
SPI[host]->ctrl2.miso_delay_num = 0;
SPI[host]->slave.wr_rd_buf_en = 1;
SPI[host]->slave.wr_rd_sta_en = 1;
SPI[host]->slave1.status_bitlen = 31;
SPI[host]->slave1.status_readback = 0;
// Put the slave's miso on the highpart, so you can only send 256bits
// Put the slave's miso on the highpart, so you can only send 512bits
// In Slave mode miso, mosi length is the same
SPI[host]->slave1.buf_bitlen = 255;
SPI[host]->slave1.buf_bitlen = 511;
SPI[host]->slave1.wr_addr_bitlen = 7;
SPI[host]->slave1.rd_addr_bitlen = 7;
SPI[host]->user1.usr_addr_bitlen = 7;
SPI[host]->user1.usr_miso_bitlen = 31;
SPI[host]->user1.usr_mosi_bitlen = 31;
SPI[host]->user2.usr_command_bitlen = 7;
SPI[host]->cmd.usr = 1;
}
@ -231,6 +267,7 @@ esp_err_t spi_set_mode(spi_host_t host, spi_mode_t *mode)
SPI[host]->ctrl.fread_dio = false;
SPI[host]->ctrl.fread_qio = false;
SPI[host]->ctrl.fastrd_mode = true;
SPI[host]->slave.sync_reset = 1;
EXIT_CRITICAL();
return ESP_OK;
@ -396,71 +433,81 @@ esp_err_t spi_slave_get_status(spi_host_t host, uint32_t *status)
return ESP_OK;
}
esp_err_t spi_slave_set_status(spi_host_t host, uint32_t *status)
esp_err_t SPI_HIGH_THROUGHPUT_ATTR spi_slave_set_status(spi_host_t host, uint32_t *status)
{
SPI_CHECK(host < SPI_NUM_MAX, "host num error", ESP_ERR_INVALID_ARG);
SPI_CHECK(spi_object[host], "spi has not been initialized yet", ESP_FAIL);
SPI_CHECK(SPI_SLAVE_MODE == spi_object[host]->mode, "this function must used by spi slave mode", ESP_FAIL);
SPI_CHECK(status, "parameter pointer is empty", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(host < SPI_NUM_MAX, "host num error", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(spi_object[host], "spi has not been initialized yet", ESP_FAIL);
SPI_CHECK_HIGH_THROUGHPUT(SPI_SLAVE_MODE == spi_object[host]->mode, "this function must used by spi slave mode", ESP_FAIL);
SPI_CHECK_HIGH_THROUGHPUT(status, "parameter pointer is empty", ESP_ERR_INVALID_ARG);
ENTER_CRITICAL();
ENTER_CRITICAL_HIGH_THROUGHPUT();
SPI[host]->rd_status.val = *status;
EXIT_CRITICAL();
EXIT_CRITICAL_HIGH_THROUGHPUT();
return ESP_OK;
}
static esp_err_t spi_master_trans(spi_host_t host, spi_trans_t trans)
static esp_err_t SPI_HIGH_THROUGHPUT_ATTR spi_master_trans(spi_host_t host, spi_trans_t *trans)
{
SPI_CHECK(trans.bits.cmd <= 16, "spi cmd must be shorter than 16 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK(trans.bits.addr <= 32, "spi addr must be shorter than 32 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK(trans.bits.mosi <= 512, "spi mosi must be shorter than 512 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK(trans.bits.miso <= 512, "spi miso must be shorter than 512 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(trans->bits.cmd <= 16, "spi cmd must be shorter than 16 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(trans->bits.addr <= 32, "spi addr must be shorter than 32 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(trans->bits.mosi <= 512, "spi mosi must be shorter than 512 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(trans->bits.miso <= 512, "spi miso must be shorter than 512 bits", ESP_ERR_INVALID_ARG);
int x, y;
// Waiting for an incomplete transfer
while (SPI[host]->cmd.usr);
ENTER_CRITICAL();
ENTER_CRITICAL_HIGH_THROUGHPUT();
// Set the cmd length and transfer cmd
if (trans.bits.cmd && trans.cmd) {
if (trans->bits.cmd && trans->cmd) {
SPI[host]->user.usr_command = 1;
SPI[host]->user2.usr_command_bitlen = trans.bits.cmd - 1;
SPI[host]->user2.usr_command_value = *trans.cmd;
SPI[host]->user2.usr_command_bitlen = trans->bits.cmd - 1;
SPI[host]->user2.usr_command_value = *trans->cmd;
} else {
SPI[host]->user.usr_command = 0;
}
// Set addr length and transfer addr
if (trans.bits.addr && trans.addr) {
if (trans->bits.addr && trans->addr) {
SPI[host]->user.usr_addr = 1;
SPI[host]->user1.usr_addr_bitlen = trans.bits.addr - 1;
SPI[host]->addr = *trans.addr;
SPI[host]->user1.usr_addr_bitlen = trans->bits.addr - 1;
SPI[host]->addr = *trans->addr;
} else {
SPI[host]->user.usr_addr = 0;
}
// Set mosi length and transmit mosi
if (trans.bits.mosi && trans.mosi) {
if (trans->bits.mosi && trans->mosi) {
SPI[host]->user.usr_mosi = 1;
SPI[host]->user1.usr_mosi_bitlen = trans.bits.mosi - 1;
for (x = 0; x < trans.bits.mosi; x += 32) {
SPI[host]->user1.usr_mosi_bitlen = trans->bits.mosi - 1;
if ((uint32_t)(trans->mosi) % 4 == 0) {
for (x = 0; x < trans->bits.mosi; x += 32) {
y = x / 32;
SPI[host]->data_buf[y] = trans.mosi[y];
SPI[host]->data_buf[y] = trans->mosi[y];
}
} else {
ESP_LOGW(TAG,"Using unaligned data may reduce transmission efficiency");
memset(spi_object[host]->buf, 0, sizeof(uint32_t) * 16);
memcpy(spi_object[host]->buf, trans->mosi, trans->bits.mosi / 8 + (trans->bits.mosi % 8) ? 1 : 0);
for (x = 0; x < trans->bits.mosi; x += 32) {
y = x / 32;
SPI[host]->data_buf[y] = spi_object[host]->buf[y];
}
}
} else {
SPI[host]->user.usr_mosi = 0;
}
// Set the length of the miso
if (trans.bits.miso && trans.miso) {
if (trans->bits.miso && trans->miso) {
SPI[host]->user.usr_miso = 1;
SPI[host]->user1.usr_miso_bitlen = trans.bits.miso - 1;
SPI[host]->user1.usr_miso_bitlen = trans->bits.miso - 1;
} else {
SPI[host]->user.usr_miso = 0;
}
@ -474,74 +521,98 @@ static esp_err_t spi_master_trans(spi_host_t host, spi_trans_t trans)
SPI[host]->cmd.usr = 1;
// Receive miso data
if (trans.bits.miso && trans.miso) {
if (trans->bits.miso && trans->miso) {
while (SPI[host]->cmd.usr);
for (x = 0; x < trans.bits.miso; x += 32) {
if ((uint32_t)(trans->miso) % 4 == 0) {
for (x = 0; x < trans->bits.miso; x += 32) {
y = x / 32;
trans.miso[y] = SPI[host]->data_buf[y];
trans->miso[y] = SPI[host]->data_buf[y];
}
} else {
ESP_LOGW(TAG,"Using unaligned data may reduce transmission efficiency");
memset(spi_object[host]->buf, 0, sizeof(uint32_t) * 16);
for (x = 0; x < trans->bits.miso; x += 32) {
y = x / 32;
spi_object[host]->buf[y] = SPI[host]->data_buf[y];
}
memcpy(trans->miso, spi_object[host]->buf, trans->bits.miso / 8 + (trans->bits.miso % 8) ? 1 : 0);
}
}
EXIT_CRITICAL();
EXIT_CRITICAL_HIGH_THROUGHPUT();
return ESP_OK;
}
static esp_err_t spi_slave_trans(spi_host_t host, spi_trans_t trans)
static esp_err_t SPI_HIGH_THROUGHPUT_ATTR spi_slave_trans(spi_host_t host, spi_trans_t *trans)
{
SPI_CHECK(trans.bits.cmd >= 3 && trans.bits.cmd <= 16, "spi cmd must be longer than 3 bits and shorter than 16 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK(trans.bits.addr >= 1 && trans.bits.addr <= 32, "spi addr must be longer than 1 bits and shorter than 32 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK(trans.bits.miso <= 256, "spi miso must be shorter than 256 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK(trans.bits.mosi <= 256, "spi mosi must be shorter than 256 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(trans->bits.cmd >= 3 && trans->bits.cmd <= 16, "spi cmd must be longer than 3 bits and shorter than 16 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(trans->bits.addr >= 1 && trans->bits.addr <= 32, "spi addr must be longer than 1 bits and shorter than 32 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(trans->bits.miso <= 512, "spi miso must be shorter than 512 bits", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(trans->bits.mosi <= 512, "spi mosi must be shorter than 512 bits", ESP_ERR_INVALID_ARG);
int x, y;
ENTER_CRITICAL();
ENTER_CRITICAL_HIGH_THROUGHPUT();
// Set cmd length and receive cmd
SPI[host]->user2.usr_command_bitlen = trans.bits.cmd - 1;
SPI[host]->user2.usr_command_bitlen = trans->bits.cmd - 1;
if (trans.cmd) {
*trans.cmd = SPI[host]->user2.usr_command_value;
if (trans->cmd) {
*trans->cmd = SPI[host]->user2.usr_command_value;
}
// Set addr length and transfer addr
SPI[host]->slave1.wr_addr_bitlen = trans.bits.addr - 1;
SPI[host]->slave1.rd_addr_bitlen = trans.bits.addr - 1;
SPI[host]->slave1.wr_addr_bitlen = trans->bits.addr - 1;
SPI[host]->slave1.rd_addr_bitlen = trans->bits.addr - 1;
if (trans.addr) {
*trans.addr = SPI[host]->addr;
if (trans->addr) {
*trans->addr = SPI[host]->addr;
}
// Set the length of the miso and transfer the miso
if (trans.bits.miso && trans.miso) {
for (x = 0; x < trans.bits.miso; x += 32) {
if (trans->bits.miso && trans->miso) {
if ((uint32_t)(trans->miso) % 4 == 0) {
for (x = 0; x < trans->bits.miso; x += 32) {
y = x / 32;
SPI[host]->data_buf[y + 8] = trans.miso[y];
SPI[host]->data_buf[y] = trans->miso[y];
}
} else {
ESP_LOGW(TAG,"Using unaligned data may reduce transmission efficiency");
memset(spi_object[host]->buf, 0, sizeof(uint32_t) * 16);
memcpy(spi_object[host]->buf, trans->miso, trans->bits.miso / 8 + (trans->bits.miso % 8) ? 1 : 0);
for (x = 0; x < trans->bits.miso; x += 32) {
y = x / 32;
SPI[host]->data_buf[y] = spi_object[host]->buf[y];
}
}
// Call the event callback function to send a transfer start event
if (spi_object[host]->event_cb) {
spi_object[host]->event_cb(SPI_TRANS_START_EVENT, NULL);
}
// Receive mosi data
if (trans.bits.mosi && trans.mosi) {
for (x = 0; x < trans.bits.mosi; x += 32) {
if (trans->bits.mosi && trans->mosi) {
if ((uint32_t)(trans->mosi) % 4 == 0) {
for (x = 0; x < trans->bits.mosi; x += 32) {
y = x / 32;
trans.mosi[y] = SPI[host]->data_buf[y];
trans->mosi[y] = SPI[host]->data_buf[y];
}
} else {
ESP_LOGW(TAG,"Using unaligned data may reduce transmission efficiency");
memset(spi_object[host]->buf, 0, sizeof(uint32_t) * 16);
for (x = 0; x < trans->bits.mosi; x += 32) {
y = x / 32;
spi_object[host]->buf[y] = SPI[host]->data_buf[y];
}
memcpy(trans->mosi, spi_object[host]->buf, trans->bits.mosi / 8 + (trans->bits.mosi % 8) ? 1 : 0);
}
}
EXIT_CRITICAL();
EXIT_CRITICAL_HIGH_THROUGHPUT();
return ESP_OK;
}
static esp_err_t spi_trans_static(spi_host_t host, spi_trans_t trans)
static esp_err_t SPI_HIGH_THROUGHPUT_ATTR spi_trans_static(spi_host_t host, spi_trans_t *trans)
{
int ret;
if (SPI_MASTER_MODE == spi_object[host]->mode) {
ret = spi_master_trans(host, trans);
} else {
@ -551,17 +622,30 @@ static esp_err_t spi_trans_static(spi_host_t host, spi_trans_t trans)
return ret;
}
esp_err_t spi_trans(spi_host_t host, spi_trans_t trans)
esp_err_t SPI_HIGH_THROUGHPUT_ATTR spi_trans(spi_host_t host, spi_trans_t *trans)
{
SPI_CHECK(host < SPI_NUM_MAX, "host num error", ESP_ERR_INVALID_ARG);
SPI_CHECK(spi_object[host], "spi has not been initialized yet", ESP_FAIL);
SPI_CHECK(trans.bits.val, "trans bits is empty", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(host < SPI_NUM_MAX, "host num error", ESP_ERR_INVALID_ARG);
SPI_CHECK_HIGH_THROUGHPUT(spi_object[host], "spi has not been initialized yet", ESP_FAIL);
SPI_CHECK_HIGH_THROUGHPUT(trans->bits.val, "trans bits is empty", ESP_ERR_INVALID_ARG);
int ret;
if (xPortInIsrContext()) {
/* In ISR Context */
BaseType_t higher_task_woken = false;
if (xSemaphoreTakeFromISR(spi_object[host]->trans_mux, NULL) != pdTRUE) {
return ESP_FAIL;
}
ret = spi_trans_static(host, trans);
xSemaphoreGiveFromISR(spi_object[host]->trans_mux, &higher_task_woken);
if (higher_task_woken) {
portYIELD_FROM_ISR();
}
}
else {
xSemaphoreTake(spi_object[host]->trans_mux, portMAX_DELAY);
ret = spi_trans_static(host, trans);
xSemaphoreGive(spi_object[host]->trans_mux);
}
return ret;
}
@ -569,7 +653,7 @@ static IRAM_ATTR void spi_intr(void *arg)
{
spi_host_t host;
uint32_t trans_done;
uint32_t cnt = 0;
if (READ_PERI_REG(DPORT_SPI_INT_STATUS_REG) & DPORT_SPI_INT_STATUS_SPI0) { // DPORT_SPI_INT_STATUS_SPI0
trans_done = SPI0.slave.val & 0x1F;
SPI0.slave.val &= ~0x3FF;
@ -577,13 +661,25 @@ static IRAM_ATTR void spi_intr(void *arg)
} else if (READ_PERI_REG(DPORT_SPI_INT_STATUS_REG) & DPORT_SPI_INT_STATUS_SPI1) { // DPORT_SPI_INT_STATUS_SPI1
trans_done = SPI1.slave.val & 0x1F;
SPI1.slave.val &= ~0x1F;
// Hardware issues: We need to wait for the hardware to clear the registers successfully.
while ((SPI1.slave.val & 0x1F) != 0) {
if (cnt >= 50) {
ets_printf("WARNING: waiting too much time, maybe error\r\n");
cnt = 0;
}
SPI1.slave.val &= ~0x1F;
cnt++;
}
host = HSPI_HOST;
} else {
return;
}
if (spi_object[host]) {
if (spi_object[host]->event_cb) {
// Hardware has no interrupt flag, which can be generated by software.
trans_done &= spi_object[host]->intr_enable.val;
if (spi_object[host]->event_cb && trans_done != 0) {
spi_object[host]->event_cb(SPI_TRANS_DONE_EVENT, &trans_done);
}
}
@ -621,7 +717,10 @@ esp_err_t spi_deinit(spi_host_t host)
if (spi_object[host]->trans_mux) {
vSemaphoreDelete(spi_object[host]->trans_mux);
}
free(spi_object[host]);
heap_caps_free(spi_object[host]->buf);
spi_object[host]->buf = NULL;
heap_caps_free(spi_object[host]);
spi_object[host] = NULL;
return ESP_OK;
@ -633,20 +732,25 @@ esp_err_t spi_init(spi_host_t host, spi_config_t *config)
SPI_CHECK(host > CSPI_HOST, "CSPI_HOST can't support now", ESP_FAIL);
SPI_CHECK(NULL == spi_object[host], "spi has been initialized", ESP_FAIL);
spi_object[host] = (spi_object_t *)malloc(sizeof(spi_object_t));
spi_object[host] = (spi_object_t *)heap_caps_malloc(sizeof(spi_object_t), MALLOC_CAP_8BIT);
SPI_CHECK(spi_object[host], "malloc fail", ESP_ERR_NO_MEM);
spi_object[host]->trans_mux = xSemaphoreCreateMutex();
if (NULL == spi_object[host]->trans_mux) {
#ifdef CONFIG_ESP8266_HSPI_HIGH_THROUGHPUT
spi_object[host]->buf = (uint32_t *)heap_caps_malloc(sizeof(uint32_t) * 16, MALLOC_CAP_8BIT);
#else
spi_object[host]->buf = (uint32_t *)heap_caps_malloc(sizeof(uint32_t) * 16, MALLOC_CAP_32BIT);
#endif
if (NULL == spi_object[host]->trans_mux || NULL == spi_object[host]->buf) {
spi_deinit(host);
SPI_CHECK(false, "Semaphore create fail", ESP_ERR_NO_MEM);
SPI_CHECK(false, "no memory", ESP_ERR_NO_MEM);
}
uint16_t dummy_bitlen = 0;
spi_set_event_callback(host, &config->event_cb);
spi_set_mode(host, &config->mode);
spi_set_interface(host, &config->interface);
spi_set_clk_div(host, &config->clk_div);
spi_set_dummy(host, &dummy_bitlen);
spi_set_intr_enable(host, &config->intr_enable);
spi_intr_register(spi_intr, NULL);
spi_intr_enable();
@ -654,6 +758,5 @@ esp_err_t spi_init(spi_host_t host, spi_config_t *config)
if (spi_object[host]->event_cb) {
spi_object[host]->event_cb(SPI_INIT_EVENT, NULL);
}
return ESP_OK;
}

View File

@ -0,0 +1,88 @@
// Copyright 2018-2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Receive SPI data
*
* @param data Data buffer to receive
* @param len Data length
* @param xTicksToWait Ticks to wait until receive data; use portMAX_DELAY to
* never time out.
* @return
* - Actual length received
*/
uint32_t hspi_slave_logic_read_data(uint8_t*data, uint32_t len, TickType_t xTicksToWait);
/**
* @brief Send SPI data
*
* @param data Data buffer to send
* @param len Data length
* @param xTicksToWait Ticks to wait until send data; use portMAX_DELAY to
* never time out.
* @return
* - Actual length received
*/
uint32_t hspi_slave_logic_write_data(uint8_t*data, uint32_t len, TickType_t xTicksToWait);
/**
* @brief Create a SPI device to transmit SPI data
*
* @param trigger_pin The pin used for handshake
* @param trigger_level The number of bytes that must be in the stream
* buffer before a task that is blocked on the stream buffer to wait for data is
* moved out of the blocked state. For example, if a task is blocked on a read
* of an empty stream buffer that has a trigger level of 1 then the task will be
* unblocked when a single byte is written to the buffer or the task's block
* time expires. As another example, if a task is blocked on a read of an empty
* stream buffer that has a trigger level of 10 then the task will not be
* unblocked until the stream buffer contains at least 10 bytes or the task's
* block time expires. If a reading task's block time expires before the
* trigger level is reached then the task will still receive however many bytes
* are actually available. Setting a trigger level of 0 will result in a
* trigger level of 1 being used. It is not valid to specify a trigger level
* that is greater than the buffer size.
* @param tx_buffer_size The total number of bytes the send stream buffer will be
* able to hold at any one time.
* @param rx_buffer_size The total number of bytes the receive stream buffer will be
* able to hold at any one time.
*
* @return
* - ESP_OK Success
* - ESP_ERR_NO_MEM No memory
*/
esp_err_t hspi_slave_logic_device_create(gpio_num_t trigger_pin, uint32_t trigger_level,uint32_t tx_buffer_size, uint32_t rx_buffer_size);
/**
* @brief Delete the SPI slave bus
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_STATE SPI slave already deleted
*/
esp_err_t hspi_slave_logic_device_delete(void);
#ifdef __cplusplus
}
#endif

View File

@ -36,7 +36,7 @@ extern "C" {
#define SPI_BYTE_ORDER_LSB_FIRST 0
/* SPI default bus interface parameter definition */
#define SPI_DEFAULT_INTERFACE 0x1F0 /* CS_EN:1, MISO_EN:1, MOSI_EN:1, BYTE_TX_ORDER:1, BYTE_TX_ORDER:1, BIT_RX_ORDER:0, BIT_TX_ORDER:0, CPHA:0, CPOL:0 */
#define SPI_DEFAULT_INTERFACE 0x1C0 /* CS_EN:1, MISO_EN:1, MOSI_EN:1, BYTE_TX_ORDER:0, BYTE_TX_ORDER:0, BIT_RX_ORDER:0, BIT_TX_ORDER:0, CPHA:0, CPOL:0 */
/* SPI master default interrupt enable definition */
#define SPI_MASTER_DEFAULT_INTR_ENABLE 0x10 /* TRANS_DONE: true, WRITE_STATUS: false, READ_STATUS: false, WRITE_BUFFER: false, READ_BUFFER: false */
@ -140,8 +140,8 @@ typedef union {
typedef struct {
uint16_t *cmd; /*!< SPI transmission command */
uint32_t *addr; /*!< SPI transmission address */
uint32_t *mosi; /*!< SPI transmission MOSI buffer */
uint32_t *miso; /*!< SPI transmission MISO buffer */
uint32_t *mosi; /*!< SPI transmission MOSI buffer, in order to improve the transmission efficiency, it is recommended that the external incoming data is (uint32_t *) type data, do not use other type data. */
uint32_t *miso; /*!< SPI transmission MISO buffer, in order to improve the transmission efficiency, it is recommended that the external incoming data is (uint32_t *) type data, do not use other type data. */
union {
struct {
uint32_t cmd: 5; /*!< SPI transmission command bits */
@ -403,14 +403,14 @@ esp_err_t spi_slave_set_status(spi_host_t host, uint32_t *status);
* - CSPI_HOST SPI0
* - HSPI_HOST SPI1
*
* @param trans Transmission parameter structure
* @param trans Pointer to transmission parameter structure
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL spi has not been initialized yet
*/
esp_err_t spi_trans(spi_host_t host, spi_trans_t trans);
esp_err_t spi_trans(spi_host_t host, spi_trans_t *trans);
/**
* @brief Deinit the spi

View File

@ -0,0 +1,272 @@
## SPI Demo User Guide
### Overview
Since the ESP8266 does not have DMA, it can only transmit 64 bytes per time at most. There are two demos of ESP8266 SPI driver, normal_performance and high_performance.
* normal_performance demo is for the normal SPI speed use case. It provides SPI slave APIs, so users can implement the ESP8266 running as SPI slave easily. But in this demo, the software schedules between task and interrupt when SPI sending/receiving data, so it is a normal speed demo.
* high_performance demo is for the high SPI speed use case. In order to rise SPI speed, ESP8266 SPI slave sends/receives data in the interrupt. It is suggested that host MCU (SPI master) also handles SPI transmission in the interrupt, otherwise the SPI transmission speed will be decreased due to software scheduling.
Both normal_performance and high_performance demo are for the SPI slave, user can implement one program of host MCU to communicate with those two demos.
### Hardware Connection
| Signal | Slave | Master |
| --------- | ------ | ------ |
| SCLK | GPIO14 | GPIO14 |
| MISO | GPIO12 | GPIO12 |
| MOSI | GPIO13 | GPIO13 |
| CS | GPIO15 | GPIO15 |
| HANDSHARK | GPIO4 | GPIO4 |
| GND | GND | GND |
**Note:**
* To run the firmware stored in flash, the GPIO15 needs to be low level when the ESP8266 starts up. So the power-on sequence can be `Master OFF -> Slave ON -> Master ON`.
# Software Introduction
### SPI Transmission Command
Master communicates with the ESP8266 SPI AT in half-duplex mode. The command that master uses to communicates with the ESP8266 SPI AT is in the following format.
| | Command1byte| Address1byte | Data64byte |
| :----: | :-----------: | :---------------: | :-----------: |
| Read Data | 0x3 | 0x0 | Actual length |
| Write Data | 0x2 | 0x0 | Actual length |
Since the ESP8266 does not have SPI DMA, it can only transmit up to 64 bytes per time.
Except the above read/write data commands, ESP8266 also has two 32bit status registers that the SPI master can access: one can be wrote by master and read by ESP8266, the other can be read by master and wrote by ESP8266. In this case, the master can transmit the data length information with these two 32bit status registers.
| | Command1byte | Data4byte |
| :------: | :-----------: | --------------------------- |
| Read Status | 0x4 | Length of data that master read from ESP8266|
| Write Status | 0x1 | Length of data that master transmit to ESP8266 |
**Note: When SPI master reads/writes status, it needs not to use the address bit.** So, the SPI master needs to implement read/write status and read/write data seperately.
### Master Sends Data to ESP8266
The work flow of master transmiting data to the ESP8266 (SPI slave) is as the following figure.
![](res/master_send.png)
1. Since there will be a conflict if both master and slave transmit data to each other at the same time. So, before the master transmiting data, it will check if the ESP8266 is transmitting data now. If it is, then the master will wait until the ESP8266's transmission completed.
2. The master writes the data length to the status register, indicates the length of data that it is going to transmit.
3. The ESP8266 will generate a WR\_STA_DONE interrupt, and pull up the GPIO\_HANDSHAKE pin to inform the master that it has got the data length information.
4. The master can send up to 64 bytes per time. If data is longer than 64 bytes, then it needs to be divided to several subpackets to send.
5. ESP8266 will store the received data into the SPI register, and generate a WR\_BUF\_DONE interrupt, and then pull up the GPIO\_HANDSHAKE pin to inform the master that it has received the previous data and the master can start the next transmission now.
6. The master has to wait for the interrupt of GPIO\_HANDSHAKE pin, which means ESP8266 received the data successfully, to start the next transmission.
7. After sent data, the master will check the software buffer to see if there is more data wait for sending.
* If there is, then the master will repeat the procedure starts from step 2.
* If there is not, then the master needs to write 0 to the status register, indicates that data sending completed. When ESP8266 reads the status 0, the ESP8266 stops pulling up the GPIO\_HANDSHAKE pin.
### ESP8266 Sends Data to Master
![](res/slave_send.png)
1. Before ESP8266 sends data, it will check if the master is sending data now. If it is, then the ESP8266 will wait until the master's transmission completed.
2. The ESP8266 copies data into a software buffer, writes the data length to the rd_status, and then pulls up the GPIO\_HANDSHAKE pin to inform the master to read data.
3. Master reads the data length from the status register, and generates a RD\_STA_DONE interrupt.
4. When ESP8266 gets the RD\_STA_DONE interrupt, it will read 64 bytes from the software buffer and write those 64 bytes to the SPI register, and then pull up the GPIO\_HANDSHAKE pin to inform the master to read data.
5. When the master gets the interrupt, it will read data through SPI.
6. ESP8266 generates RD\_BUF_DONE interrupt, and reads data (64 bytes at most) from the software buffer and write those data to the SPI register again, and then pull up the GPIO\_HANDSHAKE pin to inform the master to read data.
7. Repeat above steps until the software buffer is empty. When all data in software buffer sent, the ESP8266 will not pull up the master, but set the rd_status to be 0 instead.
**Note:**
* The streambuffer of ESP8266 and the ringbuffer of ESP32 are the software buffer to keep SPI data. If the SPI master is based on FreeRTOS, then it is suggested to use the FreeRTOS-provided streambuffer(which only available since FreeRTOS v10.01 and later versions).
### HANDSHAKE Pin Introduction
HANDSHAKE pin is to indicate the transmission completes, and it can be used to the transmission that starts by ESP8266(slave).
1. When ESP8266 is ready to receive/send data, it will pull up the HANDSHAKE pin. Then the SPI master will read data from ESP8266 when it gets the interrupt.
2. After SPI master sent data, the master will block procedure to wait the HANDSHAKE pin interrupt. When ESP8266 read the data from SPI register, the ESP8266 will pull up the HANDSHAKE pin to inform the master to transmit next packet.
### Test Result of SPI Communication Speed
One ESP8266 runs as the SPI master, another ESP8266 runs as the SPI slave, both of them are running in 160MHz and send 64 bytes per time.
The throughput test result of high_performance demo is as the following figure.
| SPI Clock | master -> slave | slave -> master |
| --------- | --------------- | --------------- |
| 20M | 1.33MB/s | 1.31MB/s |
| 16M | 1.17MB/s | 1.13MB/s |
| 10M | 861KB/s | 845KB/s |
The throughput test result of normal_performance demo is as the following figure.
| SPI Clock | master -> slave | slave -> master |
| --------- | --------------- | --------------- |
| 20M | 645KB/s | 450KB/s |
| 16M | 606KB/s | 430KB/s |
| 10M | 510KB/s | 378KB/s |
### Capture Packets
This is an example of SPI master sending “AT\r\n” and the ESP8266(slave) responding “AT\r\n”. The channel0 is SCLK, channel1 is MOSI, channel2 is MISO, channel3 is CS, channel4 is handshake pin.
- SPI\_MASTER\_WRITE\_STATUS\_TO\_SLAVE
![](res/master_send_length.png)
Master sends command of data length, 0x01. And then the master sends the data length, 4. Handshake pin is low level in the initialization.
- SPI\_MASTER\_WRITE\_DATA\_TO_SLAVE
![](res/master_send_data.png)
Master sends command of writing data, 0x02, following with 8bit address, 0x0. And then the master sends the 4bytes data. Handshake pin is high level during the communication.
- SPI\_MASTER\_READ\_STATUS\_FROM_SLAVE
![](res/master_recv_length.png)
Master sends command of reading data length, 0x04. The slave will respond the data length, 4. Handshake pin is high level during the communication.
- SPI\_MASTER\_READ\_DATA\_FROM\_SLAVE
![](res/master_recv_data.png)
Master sends command of reading data, 0x02, following with 8bit address, 0x0. And then the slave will respond the data that needs to be transmitted.
## SPI demo 使用说明
### 简介
受限于 ESP8266 没有 DMA并且每次只能传输最大 64bytes我们使用了两个项目normal_performance 和 high_performance来表述 ESP8266 SPI 驱动的用法,
其中 normal_performance 针对 SPI 速率要求不高的项目,我们封装了 SPI slave 的接口,在开发 ESP8266 作为slave 的项目时,只需要简单的接口调用即可使用,因为每次 SPI 收发均需要中断与 task 之间来回调度,因此速率较低。
high_performance 针对 SPI 速率要求高的场景ESP8266 SPI slave 的收发均在中断中操作,为了尽可能提高传输速率, MCU 最好同样在中断中处理 SPI 传输,否则速率会因为频繁切换 task 而骤降。
两者的区别主要在 SPI slave 侧,对于 MCU 可以使用同一套程序。
### 硬件连接
- 接线:
| Signal | Slave | Master |
| --------- | ------ | ------ |
| SCLK | GPIO14 | GPIO14 |
| MISO | GPIO12 | GPIO12 |
| MOSI | GPIO13 | GPIO13 |
| CS | GPIO15 | GPIO15 |
| HANDSHARK | GPIO4 | GPIO4 |
| GND | GND | GND |
- 注意:
在 ESP8266 上电后,需要保证一开机 GPIO15 处于低电平才能进入 flash 模式,所以开机顺序需要如下:
```
Master OFF -> Slave ON -> Master ON
```
# 软件介绍
### SPI 通信命令
MCU 在与 ESP8266 通信时采用半双工模式, MCU 通过使用不同的命令表示读数据或者写数据。数据格式如下所示:
| | 命令1byte | 地址1byte | 数据长度64byte |
| :----: | :-----------: | ------------- | ------------------ |
| 读数据 | 0x3 | 0x0 | 实际长度 |
| 写数据 | 0x2 | 0x0 | 实际长度 |
ESP8266 没有 SPI DMA 所以一次最多只能传输 64 个字节。
除了收发数据命令之外ESP8266 有两个 MCU 可访问的 32bit 寄存器status 其中一个 MCU 可写,而 ESP8266只可读另外一个则与之相反。MCU 通过读写这两个 status 寄存器,以此实现传递数据长度信息。通信格式如下所示:
| | 命令1byte | 数据长度4byte |
| :------: | :-----------: | --------------------------- |
| 读status | 0x4 | MCU 读取 ESP8266 传输的长度 |
| 写status | 0x1 | MCU 可写需要传输的长度 |
需要注意的是,**MCU 读写 status 不需要使用地址位**,因此在 MCU 开发中需要将读写 status 与读写数据区分开。
### MCU 发送数据给 ESP8266
MCU 主动发送的流程如下:
![](res/master_send.png)
1. MCU 如果有数据需要发送,首先需要检测 ESP8266 是否正在发送数据避免同时发送MCU 需要等待 ESP8266 发送完毕才能传输
2. MCU 首先需要向 status 里面写入本次需要发送的数据长度
3. ESP8266 会产生 WR_STA_DONE 中断, ESP8266 会拉高管脚通知 MCU 已经获取到需要接收的数据长度
4. MCU 可以一次发送最多 64bytes 的数据,如果数据超过 64bytes需分多次发送
5. ESP8266 接收导数据后, 会存储到 SPI 寄存器,并产生 WR_BUF_DONE 中断ESP8266 会拉管脚通知 MCU 数据已经取出,可以继续发送
6. MCU 必须等待 ESP8266 接收到数据产生的 GPIO 中断 (一次有效数据传输),之后才能继续传输
7. 发送完数据之后 MCU 需要再查询发送的软件 buffer 是否还存在数据,如果存在数据,从第 2 步继续开始,否则需要额外再向 status 写入 0 以此来标明发送完毕, ESP8266 收到写入 status 0 后不再拉管脚
### ESP8266 发送数据给 MCU
ESP8266 发送的流程与 MCU 发送的流程基本类似:
![](res/slave_send.png)
1. ESP8266 如果有数据需要发送,首先需要检测 MCU 是否正在发送数据ESP8266 需要等待 MCU 发送完毕
2. ESP8266 会将数据全部拷贝到软件 buffer 中,并向 rd_status 中写入本次需要发送的数据长度,然后拉管脚通知 MCU 取数据
3. MCU 读取 status 中的数据长度信息,读取 status 会产生 RD_STA_DONE 中断
4. ESP8266 在 RD_STA_DONE 中断中会读取软件 buffer 中的 64个字节到 SPI 寄存器,拉管脚通知 MCU 读取数据
5. MCU 在接收到 GPIO 中断后会发起一次 SPI 读数据传输
6. ESP8266 产生 RD_BUF_DONE 中断,并从软件 buffer 中再读取最大 64 个字节的数据填充到 SPI 寄存器,并拉管脚通知 MCU 读取
7. 如此循环,直到软件 buffer 中不再有数据,此时 ESP8266 不会再拉管脚通知 MCU并且 ESP8266 会把 rd_status 置为0读取完成
*备注:* ESP8266 所使用的 streambuffer 以及 ESP32 所使用的 ringbuffer 功能一致,用于数据的缓冲,对于 FreeRTOS 的 MCU建议使用 FreeRTOS 自带的 streambuffer (需要 FreeRTOS 系统版本在 10.01及以上)
### Handshake 线在 SPI-AT 中的作用
Handshake 线在 SPI 相互通信中需要保证传输数据完成,并担任 ESP8266 主动发起传输的任务,其主要作用体现在如下两点:
1. ESP8266 在准备好接收/发送数据时将此针脚拉高,此时 MCU 会产生一个 GPIO 中断信号MCU 在接收到中断信号后会读取 ESP8266 中的数据。
2. MCU 在发送完数据之后需要堵塞等待 GPIO 中断信号ESP8266 在将 SPI 寄存器中的数据取出后会拉高管脚,从而 MCU 会产生 GPIO 中断,之后 MCU 可以继续传输
### 测试速率
一个 ESP8266 作为 MCU 充当 SPI master 另一个 ESP8266 作为 SPI slave两者 CPU 同时跑在 160M 每次发送 64bytes。
使用 high performance demo 测试吞吐率结果如下:
| SPI 时钟 | master -> slave | slave -> master |
| --------- | --------------- | --------------- |
| 20M | 1.33MB/s | 1.31MB/s |
| 16M | 1.17MB/s | 1.13MB/s |
| 10M | 861KB/s | 845KB/s |
使用 normal performance demo 测试吞吐率结果如下:
| SPI 时钟 | master -> slave | slave -> master |
| --------- | --------------- | --------------- |
| 20M | 645KB/s | 450KB/s |
| 16M | 606KB/s | 430KB/s |
| 10M | 510KB/s | 378KB/s |
### 抓包
抓包以 MCU 发送 AT\r\n ESP8266 返回 AT\r\n 为例, 其中 channel0 为 SCLK channel1 为 MOSIchannel2 为 MISOchannel3 为 CSchannel4 为 handshake 线
- SPI_MASTER_WRITE_STATUS_TO_SLAVE
![](res/master_send_length.png)
MCU 发送长度命令 0x01后面跟着需要发送的数据长度 4第一次发送时 handshake 为初始低电平。
- SPI_MASTER_WRITE_DATA_TO_SLAVE
![](res/master_send_data.png)
MCU 发送数据命令 0x02, 后跟 8bit 长的地址 0x0后面跟上实际数据长度 4 字节的数据,注意在发送过程中 handshake 是一直拉高的。
- SPI_MASTER_READ_STATUS_FROM_SLAVE
![](res/master_recv_length.png)
发送读取长度命令 0x04 slave 会返回长度信息 4 在读取过程中 handshake 也是一直拉高的。
- SPI_MASTER_READ_DATA_FROM_SLAVE
![](res/master_recv_data.png)
发送读取数据命令 0x03后跟 8bit 长的地址 0x0 之后就是 slave 返回的实际数据

View File

@ -0,0 +1,331 @@
/* spi_master example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/stream_buffer.h"
#include "esp8266/spi_struct.h"
#include "esp8266/gpio_struct.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/spi.h"
#include "time.h"
static const char* TAG = "spi_master_example";
#define SPI_MASTER_HANDSHARK_GPIO 4
#define SPI_MASTER_HANDSHARK_SEL (1ULL<<SPI_MASTER_HANDSHARK_GPIO)
#define SPI_BUFFER_MAX_SIZE 4096
#define ESP_HSPI_MASTER_SEND // Define the macro is master send mode, delete will be master receive mode
static StreamBufferHandle_t spi_master_send_ring_buf = NULL;
static StreamBufferHandle_t spi_master_recv_ring_buf = NULL;
static uint32_t transmit_len = 0;
static bool wait_recv_data = false;
typedef enum {
SPI_NULL = 0,
SPI_WRITE,
SPI_READ
} spi_master_mode_t;
static spi_master_mode_t intr_trans_mode = SPI_NULL;
/* SPI master send length, format: 8bit command(value:1) + 32bit status length */
static void IRAM_ATTR spi_master_send_length(uint32_t len)
{
spi_trans_t trans;
uint16_t cmd = SPI_MASTER_WRITE_STATUS_TO_SLAVE_CMD;
memset(&trans, 0x0, sizeof(trans));
trans.bits.val = 0;
trans.bits.cmd = 8 * 1;
trans.bits.addr = 0; // transmit status do not use address bit
trans.bits.mosi = 8 * 4; // status length is 32bit
trans.cmd = &cmd;
trans.addr = NULL;
trans.mosi = &len;
spi_trans(HSPI_HOST, &trans);
}
/* SPI master revecive length, format: 8bit command(value:4) + 32bit status length */
static uint32_t IRAM_ATTR spi_master_get_length(void)
{
spi_trans_t trans;
uint32_t len = 0;
uint16_t cmd = SPI_MASTER_READ_STATUS_FROM_SLAVE_CMD;
memset(&trans, 0x0, sizeof(trans));
trans.bits.val = 0;
trans.cmd = &cmd;
trans.miso = &len;
trans.addr = NULL;
trans.bits.cmd = 8 * 1;
trans.bits.miso = 8 * 4;
spi_trans(HSPI_HOST, &trans);
return len;
}
/* SPI transmit data, format: 8bit command (read value: 3, write value: 4) + 8bit address(value: 0x0) + 64byte data
* For convenience, every time we send 64bytes, SPI SLAVE will determine how much data to read based on the status value
*/
static void IRAM_ATTR spi_master_transmit(spi_master_mode_t trans_mode, uint32_t* data)
{
spi_trans_t trans;
uint16_t cmd;
uint32_t addr = 0x0;
memset(&trans, 0x0, sizeof(trans));
trans.bits.val = 0; // clear all bit
if (trans_mode == SPI_WRITE) {
cmd = SPI_MASTER_WRITE_DATA_TO_SLAVE_CMD;
trans.bits.mosi = 8 * 64; // One time transmit only support 64bytes
trans.mosi = data;
} else if (trans_mode == SPI_READ) {
cmd = SPI_MASTER_READ_DATA_FROM_SLAVE_CMD;
trans.bits.miso = 8 * 64;
trans.miso = data;
}
trans.bits.cmd = 8 * 1;
trans.bits.addr = 8 * 1; // transmit data will use 8bit address
trans.cmd = &cmd;
trans.addr = &addr;
spi_trans(HSPI_HOST, &trans);
}
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint32_t read_len = 0;
uint32_t recv_actual_len = 0;
uint32_t transmit_data[16];
if (intr_trans_mode == SPI_NULL) { // Some data need to read or write???
// have some data need to send ???
if (xStreamBufferIsEmpty(spi_master_send_ring_buf) == pdFALSE) {
intr_trans_mode = SPI_WRITE;
transmit_len = xStreamBufferBytesAvailable(spi_master_send_ring_buf);
ESP_EARLY_LOGD(TAG, "Send len: %d\n", transmit_len);
spi_master_send_length(transmit_len);
return;
}
// Check if there is any data to receive
transmit_len = spi_master_get_length();
if (transmit_len > 0) {
ESP_EARLY_LOGD(TAG, "Receive data len: %d\n", transmit_len);
intr_trans_mode = SPI_READ;
return;
} else {
ESP_EARLY_LOGE(TAG, "Nothing to do");
return;
}
}
read_len = transmit_len > 64 ? 64 : transmit_len;
// SPI slave have some data want to transmit, read it
if (intr_trans_mode == SPI_READ) {
if (xStreamBufferSpacesAvailable(spi_master_recv_ring_buf) >= 64) { // Stream buffer not full, can be read agian
spi_master_transmit(SPI_READ, transmit_data);
recv_actual_len = xStreamBufferSendFromISR(spi_master_recv_ring_buf, (void*) transmit_data, read_len, &xHigherPriorityTaskWoken);
assert(recv_actual_len == read_len);
transmit_len -= read_len;
if (transmit_len == 0) {
intr_trans_mode = SPI_NULL;
/* When SPI slave sending data , maybe MCU also have some date wait for send */
if (xStreamBufferIsEmpty(spi_master_send_ring_buf) == pdFALSE) {
GPIO.status_w1ts |= BIT(SPI_MASTER_HANDSHARK_GPIO); // Manual generate GPIO interrupts
}
}
} else { // stream buffer full, wait to be tacken out
wait_recv_data = true;
}
// MCU want to send data to ESP8266
} else if (intr_trans_mode == SPI_WRITE) {
if (read_len > 0) {
recv_actual_len = xStreamBufferReceiveFromISR(spi_master_send_ring_buf,
(void*)transmit_data,
read_len,
&xHigherPriorityTaskWoken);
if (recv_actual_len != read_len) {
ESP_EARLY_LOGE(TAG, "Expect to send %d bytes, but only %d bytes", read_len, recv_actual_len);
return;
}
spi_master_transmit(SPI_WRITE, transmit_data);
transmit_len -= read_len;
} else {
intr_trans_mode = SPI_NULL;
if (xStreamBufferIsEmpty(spi_master_send_ring_buf) == pdFALSE) {
GPIO.status_w1ts |= BIT(SPI_MASTER_HANDSHARK_GPIO); // Manual generate GPIO interrupts
} else {
// if ring buffer is empty, send status=0 tell slave send done
spi_master_send_length(0);
}
}
}
if (xHigherPriorityTaskWoken == pdTRUE) {
taskYIELD();
}
}
#ifdef ESP_HSPI_MASTER_SEND
static void IRAM_ATTR spi_master_write_slave_task(void* arg)
{
#define TEST_SEND_BUFFER_LEN 2048
time_t start;
time_t end;
uint32_t total_len = 0;
uint8_t* buf = malloc(TEST_SEND_BUFFER_LEN);
memset(buf, 0x33, TEST_SEND_BUFFER_LEN);
vTaskDelay(5000 / portTICK_RATE_MS);
printf(" Test send\r\n");
start = time(NULL);
while (1) {
size_t xBytesSent = xStreamBufferSend(spi_master_send_ring_buf, (void*) buf, TEST_SEND_BUFFER_LEN, portMAX_DELAY);
if (xBytesSent != TEST_SEND_BUFFER_LEN) {
ESP_LOGE(TAG, "Send error, len:%d", xBytesSent);
break;
}
portENTER_CRITICAL();
if (intr_trans_mode == SPI_NULL) {
ESP_LOGI(TAG, "Manual generate GPIO interrupts");
GPIO.status_w1ts |= BIT(SPI_MASTER_HANDSHARK_GPIO);
}
portEXIT_CRITICAL();
total_len += TEST_SEND_BUFFER_LEN;
if (total_len > 10 * 1024 * 1024) {
end = time(NULL);
printf("send done, total len: %d, time: %lds\n", total_len, (end - start));
break;
}
}
vTaskDelete(NULL);
}
#else
uint32_t read_count = 0;
static void spi_master_count_task(void* arg)
{
uint32_t tmp_count = 0;
while (1) {
printf("recv_count: %d , speed: %dB/s\n", read_count, ((read_count - tmp_count) / 2));
tmp_count = read_count;
vTaskDelay(2000 / portTICK_RATE_MS);
}
}
static void IRAM_ATTR spi_master_read_slave_task(void* arg)
{
size_t xReceivedBytes;
uint8_t read_data[1024 + 1];
while (1) {
xReceivedBytes = xStreamBufferReceive(spi_master_recv_ring_buf, read_data, 1024, 2000 / portTICK_RATE_MS);
if (xReceivedBytes != 0) {
for (int i = 0; i < xReceivedBytes; i++) {
if (read_data[i] != 0x44) {
printf("receive error data: %x\n", read_data[i]);
}
}
#if 0
read_data[xReceivedBytes] = '\0';
printf("%s", read_data);
fflush(stdout); //Force to print even if have not '\n'
#else
read_count += xReceivedBytes;
#endif
}
// steam buffer full
if (wait_recv_data) {
if (xStreamBufferBytesAvailable(spi_master_recv_ring_buf) > 64) {
wait_recv_data = false;
GPIO.status_w1ts |= BIT(SPI_MASTER_HANDSHARK_GPIO); // Manual generate GPIO interrupts
}
}
}
}
#endif
void app_main(void)
{
spi_master_send_ring_buf = xStreamBufferCreate(SPI_BUFFER_MAX_SIZE, 1024);
spi_master_recv_ring_buf = xStreamBufferCreate(SPI_BUFFER_MAX_SIZE, 1);
ESP_LOGI(TAG, "init gpio");
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_POSEDGE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = SPI_MASTER_HANDSHARK_SEL;
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
gpio_install_isr_service(0);
gpio_isr_handler_add(SPI_MASTER_HANDSHARK_GPIO, gpio_isr_handler, (void*) SPI_MASTER_HANDSHARK_GPIO);
ESP_LOGI(TAG, "init spi");
spi_config_t spi_config;
// Load default interface parameters
// CS_EN:1, MISO_EN:1, MOSI_EN:1, BYTE_TX_ORDER:1, BYTE_TX_ORDER:1, BIT_RX_ORDER:0, BIT_TX_ORDER:0, CPHA:0, CPOL:0
spi_config.interface.val = SPI_DEFAULT_INTERFACE;
// Load default interrupt enable
// TRANS_DONE: true, WRITE_STATUS: false, READ_STATUS: false, WRITE_BUFFER: false, READ_BUFFER: false
spi_config.intr_enable.val = SPI_MASTER_DEFAULT_INTR_ENABLE;
// Set SPI to master mode
// ESP8266 Only support half-duplex
spi_config.mode = SPI_MASTER_MODE;
// Set the SPI clock frequency division factor
spi_config.clk_div = SPI_20MHz_DIV;
// Register SPI event callback function
spi_config.event_cb = NULL;
spi_init(HSPI_HOST, &spi_config);
#ifdef ESP_HSPI_MASTER_SEND
// create spi_master_write_slave_task
xTaskCreate(spi_master_write_slave_task, "spi_master_write_slave_task", 2048, NULL, 6, NULL);
#else
// create spi_master_read_slave_task
xTaskCreate(spi_master_read_slave_task, "spi_master_read_slave_task", 2048, NULL, 5, NULL);
xTaskCreate(spi_master_count_task, "spi_master_count_task", 2048, NULL, 4, NULL);
#endif
}

View File

@ -0,0 +1,7 @@
CONFIG_ESP8266_HSPI_HIGH_THROUGHPUT=y
CONFIG_ESP8266_DEFAULT_CPU_FREQ_160=y
CONFIG_ESP8266_DEFAULT_CPU_FREQ_MHZ=160
CONFIG_FREERTOS_CODE_LINK_TO_IRAM=y

View File

@ -0,0 +1,4 @@
#
# Main Makefile. This is basically the same as a component makefile.
#

View File

@ -0,0 +1,283 @@
/* spi_slave example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
#include "freertos/stream_buffer.h"
#include "ringbuf.h"
#include "esp8266/spi_struct.h"
#include "esp8266/gpio_struct.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/spi.h"
static const char* TAG = "spi_slave_example";
#define SPI_SLAVE_HANDSHARK_GPIO 4
#define SPI_SLAVE_HANDSHARK_SEL (1ULL<<SPI_SLAVE_HANDSHARK_GPIO)
#define SPI_WRITE_BUFFER_MAX_SIZE 2048
#define SPI_READ_BUFFER_MAX_SIZE 1024
#define ESP_SPI_SLAVE_RECV // Define the macro is master send mode, delete will be slave send mode
static StreamBufferHandle_t spi_slave_tx_ring_buf;
static StreamBufferHandle_t spi_slave_rx_ring_buf;
static uint32_t total_recv_len = 0;
static bool wait_recv_data = false;
static bool sending_flag = false;
static volatile uint32_t total_tx_count = 0;
static volatile uint32_t total_send_len = 0;
static void IRAM_ATTR spi_event_callback(int event, void* arg)
{
int x;
BaseType_t xHigherPriorityTaskWoken;
uint32_t status;
uint32_t trans_done;
uint32_t data[16];
spi_trans_t trans = {0};
uint16_t cmd = 0;
bool trigger_flag = false;
switch (event) {
case SPI_TRANS_DONE_EVENT: {
gpio_set_level(SPI_SLAVE_HANDSHARK_GPIO, 0);
trans_done = *(uint32_t*)arg;
if (trans_done & SPI_SLV_RD_BUF_DONE) { // slave -> master data
if (total_send_len == 0) {
sending_flag = false;
total_send_len = xStreamBufferBytesAvailable(spi_slave_tx_ring_buf);
if (total_send_len > 0) { // Have some data send to MCU
spi_slave_set_status(HSPI_HOST, (uint32_t*)&total_send_len);
sending_flag = true;
trigger_flag = true;
}
} else { // Have some data send to MCU
memset(&trans, 0x0, sizeof(trans));
trans.cmd = &cmd;
trans.addr = NULL;
trans.bits.val = 0;
trans.bits.cmd = 8 * 1;
trans.bits.addr = 8 * 1;
trans.bits.mosi = 0;
trans.miso = data;
trans.bits.miso = xStreamBufferReceiveFromISR(spi_slave_tx_ring_buf, data, 64, &xHigherPriorityTaskWoken); // send max 32bytes
if (trans.bits.miso != 0) {
total_send_len -= trans.bits.miso;
trans.bits.miso <<= 3;
spi_trans(HSPI_HOST, &trans);
trigger_flag = true;;
}
}
}
if (trans_done & SPI_SLV_WR_BUF_DONE) { // master -> slave data
uint32_t len = total_recv_len;
if (len > 64) { // only send max 32bytes one time
len = 64;
}
if (len > 0) {
for (x = 0; x < 16; x++) {
data[x] = SPI1.data_buf[x];
}
xStreamBufferSendFromISR(spi_slave_rx_ring_buf, (void*) data, len, &xHigherPriorityTaskWoken);
total_recv_len -= len;
}
if (xStreamBufferSpacesAvailable(spi_slave_rx_ring_buf) >= 64) { // Stream buffer not full, can be read agian
trigger_flag = true;
} else {
wait_recv_data = true;
}
}
if (trans_done & SPI_SLV_WR_STA_DONE) { // master -> slave status len
spi_slave_get_status(HSPI_HOST, &status);
total_recv_len = status;
uint32_t tx_size = xStreamBufferBytesAvailable(spi_slave_tx_ring_buf);
if (total_recv_len > 0) {
trigger_flag = true;
} else if (tx_size > 0) { // SPI send done and ESP8266 send buffer have data
if (sending_flag == false) {
spi_slave_set_status(HSPI_HOST, &tx_size);
}
trigger_flag = true;
}
}
if (trans_done & SPI_SLV_RD_STA_DONE) { // Slave -> Master status len
memset(&trans, 0x0, sizeof(trans));
trans.cmd = &cmd;
trans.addr = NULL;
trans.bits.val = 0;
trans.bits.cmd = 8 * 1;
trans.bits.addr = 8 * 1;
trans.bits.mosi = 0;
trans.miso = data;
trans.bits.miso = xStreamBufferReceiveFromISR(spi_slave_tx_ring_buf, data, 64, &xHigherPriorityTaskWoken);
if (trans.bits.miso != 0) {
total_send_len -= trans.bits.miso;
trans.bits.miso <<= 3;
spi_trans(HSPI_HOST, &trans);
trigger_flag = true;
}
}
if (trigger_flag) {
gpio_set_level(SPI_SLAVE_HANDSHARK_GPIO, 1);
}
if (xHigherPriorityTaskWoken == pdTRUE) {
taskYIELD();
}
}
break;
case SPI_DEINIT_EVENT: {
}
break;
}
}
#ifdef ESP_SPI_SLAVE_RECV
uint32_t read_count = 0;
static void spi_slave_count_task(void* arg)
{
uint32_t tmp_count = 0;
while (1) {
printf("recv_count: %d , speed: %dB/s\n", read_count, ((read_count - tmp_count) / 2));
tmp_count = read_count;
vTaskDelay(2000 / portTICK_RATE_MS);
}
}
static void IRAM_ATTR spi_slave_read_master_task(void* arg)
{
uint8_t read_data[SPI_READ_BUFFER_MAX_SIZE + 1];
size_t xReceivedBytes;
while (1) {
xReceivedBytes = xStreamBufferReceive(spi_slave_rx_ring_buf, read_data, SPI_READ_BUFFER_MAX_SIZE, 2000 / portTICK_RATE_MS);
if (xReceivedBytes != 0) {
for (int i = 0; i < xReceivedBytes; i++) {
if (read_data[i] != 0x33) {
printf("receive error data: %x\n", read_data[i]);
}
}
read_count += xReceivedBytes;
memset(read_data, 0x0, xReceivedBytes);
// steam buffer full
if (wait_recv_data) {
if (xStreamBufferBytesAvailable(spi_slave_rx_ring_buf) > 64) {
gpio_set_level(SPI_SLAVE_HANDSHARK_GPIO, 1);
wait_recv_data = false;
}
}
}
}
}
#else
#include "time.h"
static void IRAM_ATTR spi_slave_write_master_task(void* arg)
{
static uint8_t write_data[SPI_WRITE_BUFFER_MAX_SIZE];
memset(write_data, 0x44, SPI_WRITE_BUFFER_MAX_SIZE);
vTaskDelay(5000 / portTICK_RATE_MS);
printf("Test send\r\n");
time_t start = time(NULL);
while (1) {
total_tx_count += xStreamBufferSend(spi_slave_tx_ring_buf, write_data, SPI_WRITE_BUFFER_MAX_SIZE, portMAX_DELAY);
portENTER_CRITICAL();
if (sending_flag == false) {
total_send_len = xStreamBufferBytesAvailable(spi_slave_tx_ring_buf);
spi_slave_set_status(HSPI_HOST, (uint32_t*)&total_send_len);
sending_flag = true;
gpio_set_level(SPI_SLAVE_HANDSHARK_GPIO, 1);
}
portEXIT_CRITICAL();
if (total_tx_count >= 20 * 1024 * 1024) {
printf("tx done; %d bytes, time : %ld\r\n", total_tx_count, time(NULL) - start);
for (;;) {
vTaskDelay(100);
}
}
}
}
#endif
void app_main(void)
{
spi_slave_tx_ring_buf = xStreamBufferCreate(4096, 1);
spi_slave_rx_ring_buf = xStreamBufferCreate(4096, 1024);
ESP_LOGI(TAG, "init gpio");
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = SPI_SLAVE_HANDSHARK_SEL;
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
gpio_set_level(SPI_SLAVE_HANDSHARK_GPIO, 0);
ESP_LOGI(TAG, "init spi");
spi_config_t spi_config;
// Load default interface parameters
// CS_EN:1, MISO_EN:1, MOSI_EN:1, BYTE_TX_ORDER:1, BYTE_TX_ORDER:1, BIT_RX_ORDER:0, BIT_TX_ORDER:0, CPHA:0, CPOL:0
spi_config.interface.val = SPI_DEFAULT_INTERFACE;
// Load default interrupt enable
// TRANS_DONE: false, WRITE_STATUS: true, READ_STATUS: true, WRITE_BUFFER: true, READ_BUFFER: ture
spi_config.intr_enable.val = SPI_SLAVE_DEFAULT_INTR_ENABLE;
// Set SPI to slave mode
spi_config.mode = SPI_SLAVE_MODE;
// Register SPI event callback function
spi_config.event_cb = spi_event_callback;
spi_init(HSPI_HOST, &spi_config);
#ifdef ESP_SPI_SLAVE_RECV
// create spi_slave_read_master_task
xTaskCreate(spi_slave_read_master_task, "spi_slave_read_master_task", 2048, NULL, 6, NULL);
xTaskCreate(spi_slave_count_task, "spi_slave_count_task", 2048, NULL, 3, NULL);
#else
// create spi_slave_write_master_task
xTaskCreate(spi_slave_write_master_task, "spi_slave_write_master_task", 2048, NULL, 2, NULL);
#endif
}

View File

@ -0,0 +1,5 @@
CONFIG_ESP8266_HSPI_HIGH_THROUGHPUT=y
CONFIG_ESP8266_DEFAULT_CPU_FREQ_160=y
CONFIG_ESP8266_DEFAULT_CPU_FREQ_MHZ=160

View File

@ -0,0 +1,6 @@
# The following four lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(spi_master)

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := spi_master
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,3 @@
set(COMPONENT_SRCS "spi_master_example_main.c")
register_component()

View File

@ -0,0 +1,251 @@
/* spi_master example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "ringbuf.h"
#include "esp8266/spi_struct.h"
#include "esp8266/gpio_struct.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/spi.h"
static const char *TAG = "spi_master_example";
#define SPI_MASTER_HANDSHARK_GPIO 4
#define SPI_MASTER_HANDSHARK_SEL (1ULL<<SPI_MASTER_HANDSHARK_GPIO)
#define SPI_WRITE_BUFFER_MAX_SIZE 2048
#define ESP_SPI_MASTER_TEST_SEND // Define the macro is master send mode, delete will be slave send mode
static SemaphoreHandle_t semphor = NULL;
typedef enum {
SPI_SEND = 0,
SPI_RECV
} spi_master_mode_t;
/* Master receive data or send data done both need wait isr */
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if ((int)arg == SPI_MASTER_HANDSHARK_GPIO) {
xSemaphoreGiveFromISR(semphor, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
taskYIELD();
}
}
}
/* SPI transmit data, format: 8bit command (read value: 3, write value: 4) + 8bit address(value: 0x0) + 64byte data */
static void IRAM_ATTR spi_master_transmit(spi_master_mode_t trans_mode, uint32_t* data, uint32_t len)
{
spi_trans_t trans;
uint16_t cmd;
uint32_t addr = 0x0;
if (len > 16) {
ESP_LOGE(TAG, "ESP8266 only support transmit 64bytes(16 * sizeof(uint32_t)) one time");
return;
}
memset(&trans, 0x0, sizeof(trans));
trans.bits.val = 0; // clear all bit
if (trans_mode == SPI_SEND) {
cmd = SPI_MASTER_WRITE_DATA_TO_SLAVE_CMD;
trans.bits.mosi = len * 32; // One time transmit only support 64bytes
trans.mosi = data;
} else {
cmd = SPI_MASTER_READ_DATA_FROM_SLAVE_CMD;
trans.bits.miso = len * 32;
trans.miso = data;
}
trans.bits.cmd = 8 * 1;
trans.bits.addr = 8 * 1; // transmit data will use 8bit address
trans.cmd = &cmd;
trans.addr = &addr;
spi_trans(HSPI_HOST, &trans);
}
#ifdef ESP_SPI_MASTER_TEST_SEND
/* SPI master send length, format: 8bit command(value:1) + 32bit status length */
static void IRAM_ATTR spi_master_send_length(uint32_t len)
{
spi_trans_t trans;
uint16_t cmd = SPI_MASTER_WRITE_STATUS_TO_SLAVE_CMD;
memset(&trans, 0x0, sizeof(trans));
trans.bits.val = 0;
trans.bits.cmd = 8 * 1;
trans.bits.addr = 0; // transmit status do not use address bit
trans.bits.mosi = 8 * 4; // status length is 32bit
trans.cmd = &cmd;
trans.addr = NULL;
trans.mosi = &len;
spi_trans(HSPI_HOST, &trans);
}
static void IRAM_ATTR spi_master_write_slave_task(void *arg)
{
/* In order to improve the transmission efficiency, it is recommended that the external
incoming data is (uint32_t *) type data, do not use other type data. */
static uint32_t write_data[SPI_WRITE_BUFFER_MAX_SIZE/4];
uint32_t total_len = 0;
uint32_t send_len = 0;
for (uint32_t loop = 0; loop < sizeof(write_data)/sizeof(write_data[0]);loop++) {
write_data[loop] = 0x34343434;
}
xSemaphoreTake(semphor, 0);
vTaskDelay(5000 / portTICK_RATE_MS);
ESP_LOGI(TAG, "Start test send data");
while (1) {
send_len = sizeof(write_data);
spi_master_send_length(send_len);
// wait ESP8266 received the length
xSemaphoreTake(semphor, portMAX_DELAY);
for (uint32_t loop = 0; loop < (send_len + 63)/64; loop++) {
// transmit data, ESP8266 only transmit 64bytes one time
spi_master_transmit(SPI_SEND, write_data + (loop * 16), 64 /sizeof(uint32_t));
xSemaphoreTake(semphor, portMAX_DELAY);
}
// send 0 to clear send length, and tell Slave send done
spi_master_send_length(0);
total_len += send_len;
if (total_len >= (10*1024*1024)) {
ESP_LOGI(TAG, "total_len=%d\r\n", total_len);
for (;;) {
vTaskDelay(1000);
}
}
}
}
#else
/* SPI master revecive length, format: 8bit command(value:4) + 32bit status length */
static uint32_t IRAM_ATTR spi_master_get_length(void)
{
spi_trans_t trans;
uint32_t len = 0;
uint16_t cmd = SPI_MASTER_READ_STATUS_FROM_SLAVE_CMD;
memset(&trans, 0x0, sizeof(trans));
trans.bits.val = 0;
trans.cmd = &cmd;
trans.miso = &len;
trans.addr = NULL;
trans.bits.cmd = 8 * 1;
trans.bits.miso = 8 * 4;
spi_trans(HSPI_HOST, &trans);
return len;
}
uint32_t read_count = 0;
static void spi_master_count_task(void* arg)
{
uint32_t tmp_count = 0;
while(1){
printf("recv_count: %d , speed: %dB/s\n", read_count, ((read_count - tmp_count)/2));
tmp_count = read_count;
vTaskDelay(2000 / portTICK_RATE_MS);
}
}
static void IRAM_ATTR spi_master_read_slave_task(void *arg)
{
uint32_t read_data[16];
uint32_t read_len = 0;
uint32_t read_time = 0;
while (1) {
xSemaphoreTake(semphor, portMAX_DELAY);
read_len = spi_master_get_length();
ESP_LOGD(TAG, "read len: %d\n", read_len);
xSemaphoreTake(semphor, portMAX_DELAY);
read_count += read_len;
if (read_len > 0) {
read_time = (read_len + 63) / 64; // read_len is the total bytes, every time send 32bytes, so send time will be divided by 32
while(read_time > 0) {
spi_master_transmit(SPI_RECV, read_data, 64 /sizeof(uint32_t));
for (int x = 0; x < 16; x++) {
if (read_data[x] != 0xa3a3a3a3) {
ESP_LOGE(TAG, "error 0x%02x,%d\r\n", read_data[x], x);
}
}
read_time--;
if(read_time != 0) {
xSemaphoreTake(semphor, portMAX_DELAY);
}
}
}
}
}
#endif
void app_main(void)
{
semphor = xSemaphoreCreateBinary();
ESP_LOGI(TAG, "init gpio");
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_POSEDGE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = SPI_MASTER_HANDSHARK_SEL;
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
gpio_install_isr_service(0);
gpio_isr_handler_add(SPI_MASTER_HANDSHARK_GPIO, gpio_isr_handler, (void *) SPI_MASTER_HANDSHARK_GPIO);
ESP_LOGI(TAG, "init spi");
spi_config_t spi_config;
// Load default interface parameters
// CS_EN:1, MISO_EN:1, MOSI_EN:1, BYTE_TX_ORDER:1, BYTE_TX_ORDER:1, BIT_RX_ORDER:0, BIT_TX_ORDER:0, CPHA:0, CPOL:0
spi_config.interface.val = SPI_DEFAULT_INTERFACE;
// Load default interrupt enable
// TRANS_DONE: true, WRITE_STATUS: false, READ_STATUS: false, WRITE_BUFFER: false, READ_BUFFER: false
spi_config.intr_enable.val = SPI_MASTER_DEFAULT_INTR_ENABLE;
// Set SPI to master mode
// ESP8266 Only support half-duplex
spi_config.mode = SPI_MASTER_MODE;
// Set the SPI clock frequency division factor
spi_config.clk_div = SPI_10MHz_DIV;
spi_config.event_cb = NULL;
spi_init(HSPI_HOST, &spi_config);
#ifdef ESP_SPI_MASTER_TEST_SEND
xTaskCreate(spi_master_write_slave_task, "spi_master_write_slave_task", 2048, NULL, 3, NULL);
#else
// create spi_master_read_slave_task
xTaskCreate(spi_master_read_slave_task, "spi_master_read_slave_task", 2048, NULL, 2, NULL);
xTaskCreate(spi_master_count_task, "spi_master_count_task", 2048, NULL, 4, NULL);
#endif
}

View File

@ -0,0 +1,6 @@
# The following four lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(spi_slave)

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := spi_slave
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,3 @@
set(COMPONENT_SRCS "spi_slave_example_main.c")
register_component()

View File

@ -0,0 +1,3 @@
#
# Main Makefile. This is basically the same as a component makefile.
#

View File

@ -0,0 +1,109 @@
/* spi_slave example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/spi.h"
#include "driver/hspi_logic_layer.h"
static const char *TAG = "spi_slave_example";
#define SPI_SLAVE_HANDSHARK_GPIO 4
#define SPI_WRITE_BUFFER_MAX_SIZE 2048
#define SPI_READ_BUFFER_MAX_SIZE 1024
//#define SPI_SLAVE_TEST_SEND // Define the macro is slave send mode, delete will be master send mode
#ifdef SPI_SLAVE_TEST_SEND
static void IRAM_ATTR spi_slave_write_master_task(void *arg)
{
static uint8_t write_data[SPI_WRITE_BUFFER_MAX_SIZE];
uint32_t total_tx_count = 0;
for (int32_t loop = 0; loop < SPI_WRITE_BUFFER_MAX_SIZE; loop++) {
write_data[loop] = 0xa3;
}
vTaskDelay(5000 / portTICK_RATE_MS);
ESP_LOGI(TAG, "SPI slave start send data");
for (;;) {
total_tx_count += hspi_slave_logic_write_data(write_data, SPI_READ_BUFFER_MAX_SIZE, portMAX_DELAY);
if (total_tx_count >= 10*1024*1024) {
ESP_LOGI(TAG, "send done; %d bytes\r\n", total_tx_count);
break;
}
}
vTaskDelete(NULL);
}
#else
uint32_t read_count = 0;
static void spi_slave_receive_count_task(void* arg)
{
uint32_t tmp_count = 0;
while(1){
ESP_LOGI(TAG,"recv_count: %d , speed: %dB/s\n", read_count, ((read_count - tmp_count)/2));
tmp_count = read_count;
vTaskDelay(2000 / portTICK_RATE_MS);
}
}
static void IRAM_ATTR spi_slave_read_master_task(void *arg)
{
static uint8_t read_data[SPI_READ_BUFFER_MAX_SIZE];
uint32_t read_len = 0;
for (;;) {
read_len = hspi_slave_logic_read_data(read_data, SPI_READ_BUFFER_MAX_SIZE, 1000);
for(int i=0;i< read_len;i++){
if(read_data[i] != 0x34){
ESP_LOGE(TAG,"Receive error data: %x\n", read_data[i]);
}
}
read_count += read_len;
memset(read_data, 0x0, SPI_READ_BUFFER_MAX_SIZE);
}
}
#endif
void app_main(void)
{
spi_config_t spi_config;
// Load default interface parameters
// CS_EN:1, MISO_EN:1, MOSI_EN:1, BYTE_TX_ORDER:1, BYTE_TX_ORDER:1, BIT_RX_ORDER:0, BIT_TX_ORDER:0, CPHA:0, CPOL:0
spi_config.interface.val = SPI_DEFAULT_INTERFACE;
// Load default interrupt enable
// TRANS_DONE: false, WRITE_STATUS: true, READ_STATUS: true, WRITE_BUFFER: true, READ_BUFFER: ture
spi_config.intr_enable.val = SPI_SLAVE_DEFAULT_INTR_ENABLE;
// Set SPI to slave mode
spi_config.mode = SPI_SLAVE_MODE;
// Register SPI event callback function
spi_config.event_cb = NULL;
spi_init(HSPI_HOST, &spi_config);
hspi_slave_logic_device_create(SPI_SLAVE_HANDSHARK_GPIO, 1, SPI_WRITE_BUFFER_MAX_SIZE, SPI_READ_BUFFER_MAX_SIZE);
#ifdef SPI_SLAVE_TEST_SEND
xTaskCreate(spi_slave_write_master_task, "spi_slave_write_master_task", 2048, NULL, 2, NULL);
#else
// create spi_slave_read_master_task
xTaskCreate(spi_slave_read_master_task, "spi_slave_read_master_task", 2048, NULL, 5, NULL);
xTaskCreate(spi_slave_receive_count_task, "spi_slave_receive_count_task", 2048, NULL, 4, NULL);
#endif
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -1,82 +0,0 @@
# _SPI Master Example_
_This example uses the ESP8266 hspi Master to send and receive data to another ESP8266 hspi Slave_
## How to use example
### Hardware Required
* Connection:
| Signal | Slave | Master |
|-----------|--------|--------|
| SCLK | GPIO14 | GPIO14 |
| MISO | GPIO12 | GPIO12 |
| MOSI | GPIO13 | GPIO13 |
| CS | GPIO15 | GPIO15 |
| HANDSHARK | GPIO4 | GPIO4 |
| GND | GND | GND |
* Note:
When the ESP8266 is powered on, it is necessary to keep the GPIO15 low to enter the Flash mode, so the Master and the Slave have different power-on sequences.
```
Master OFF -> Slave ON -> Master ON
```
### Configure the project
```
make menuconfig
```
* Set serial port under Serial Flasher Options.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
make -j4 flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
* LOG:
```
I (516) spi_master_example: init gpio
I (526) gpio: GPIO[4]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:1
I (536) spi_master_example: init spi
I (556) spi_master_example: Master wrote 3200 bytes in 4302 us
I (656) spi_master_example: Master wrote 3200 bytes in 4519 us
I (766) spi_master_example: Master wrote 3200 bytes in 4522 us
I (866) spi_master_example: Master wrote 3200 bytes in 4520 us
I (966) spi_master_example: Master wrote 3200 bytes in 4521 us
I (1066) spi_master_example: Master wrote 3200 bytes in 4520 us
I (1166) spi_master_example: Master wrote 3200 bytes in 4522 us
I (1266) spi_master_example: Master wrote 3200 bytes in 4521 us
I (1366) spi_master_example: Master wrote 3200 bytes in 4520 us
I (1466) spi_master_example: Master wrote 3200 bytes in 4520 us
I (1566) spi_master_example: Master wrote 3200 bytes in 4520 us
I (1666) spi_master_example: Master wrote 3200 bytes in 4519 us
I (1766) spi_master_example: Master wrote 3200 bytes in 4521 us
I (1866) spi_master_example: Master wrote 3200 bytes in 4519 us
I (1966) spi_master_example: Master wrote 3200 bytes in 4520 us
```
* WAVE FORM:
- SPI_MASTER_WRITE_DATA_TO_SLAVE
![wave](wave_write_to_slave.png)
- SPI_MASTER_READ_DATA_FROM_SLAVE
![wave](wave_read_from_slave.png)

View File

@ -1,175 +0,0 @@
/* spi_master example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "ringbuf.h"
#include "esp8266/spi_struct.h"
#include "esp8266/gpio_struct.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/spi.h"
static const char *TAG = "spi_master_example";
#define SPI_MASTER_HANDSHARK_GPIO 4
#define SPI_MASTER_HANDSHARK_SEL (1ULL<<SPI_MASTER_HANDSHARK_GPIO)
RingbufHandle_t spi_master_rx_ring_buf;
struct timeval now;
static void gpio_isr_handler(void *arg)
{
int x;
BaseType_t xHigherPriorityTaskWoken;
uint32_t read_data[8];
if ((int)arg == SPI_MASTER_HANDSHARK_GPIO) {
while (SPI1.cmd.usr);
SPI1.user.usr_command = 1;
SPI1.user.usr_addr = 1;
SPI1.user.usr_mosi = 0;
SPI1.user.usr_miso = 1;
SPI1.user2.usr_command_bitlen = 8 - 1;
SPI1.user1.usr_addr_bitlen = 32 - 1;
SPI1.user1.usr_miso_bitlen = 32 * 8 - 1;
SPI1.user2.usr_command_value = SPI_MASTER_READ_DATA_FROM_SLAVE_CMD;
SPI1.addr = 0;
SPI1.cmd.usr = 1;
while (SPI1.cmd.usr);
for (x = 0; x < 8; x++) {
read_data[x] = SPI1.data_buf[x];
}
xRingbufferSendFromISR(spi_master_rx_ring_buf, (void *) read_data, sizeof(uint32_t) * 8, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
taskYIELD();
}
}
}
static void spi_master_write_slave_task(void *arg)
{
int x;
uint32_t write_data[8];
spi_trans_t trans;
uint16_t cmd;
uint32_t addr;
uint64_t time_start, time_end;
trans.bits.val = 0;
trans.bits.cmd = 8 * 1;
trans.bits.addr = 32 * 1;
trans.bits.mosi = 32 * 8;
// Write data to the ESP8266 Slave use "SPI_MASTER_WRITE_DATA_TO_SLAVE_CMD" cmd
trans.cmd = &cmd;
trans.addr = &addr;
trans.mosi = write_data;
cmd = SPI_MASTER_WRITE_DATA_TO_SLAVE_CMD;
addr = 0;
write_data[0] = 1;
write_data[1] = 0x11111111;
write_data[2] = 0x22222222;
write_data[3] = 0x33333333;
write_data[4] = 0x44444444;
write_data[5] = 0x55555555;
write_data[6] = 0x66666666;
write_data[7] = 0x77777777;
while (1) {
gettimeofday(&now, NULL);
time_start = now.tv_usec;
for (x = 0;x < 100;x++) {
spi_trans(HSPI_HOST, trans);
write_data[0]++;
}
gettimeofday(&now, NULL);
time_end = now.tv_usec;
ESP_LOGI(TAG, "Master wrote 3200 bytes in %d us", (int)(time_end - time_start));
vTaskDelay(100 / portTICK_RATE_MS);
}
}
static void spi_master_read_slave_task(void *arg)
{
uint32_t *read_data = NULL;
uint32_t size;
while (1) {
read_data = (uint32_t *) xRingbufferReceive(spi_master_rx_ring_buf, &size, portMAX_DELAY);
if (read_data) {
vRingbufferReturnItem(spi_master_rx_ring_buf, read_data);
if (read_data[7] % 100 == 0) {
vTaskDelay(100 / portTICK_RATE_MS);
}
}
}
}
void app_main(void)
{
spi_trans_t trans = {0};
uint16_t cmd;
uint32_t status = true;
spi_master_rx_ring_buf = xRingbufferCreate(4096, RINGBUF_TYPE_NOSPLIT);
ESP_LOGI(TAG, "init gpio");
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_POSEDGE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = SPI_MASTER_HANDSHARK_SEL;
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
gpio_install_isr_service(0);
gpio_isr_handler_add(SPI_MASTER_HANDSHARK_GPIO, gpio_isr_handler, (void *) SPI_MASTER_HANDSHARK_GPIO);
ESP_LOGI(TAG, "init spi");
spi_config_t spi_config;
// Load default interface parameters
// CS_EN:1, MISO_EN:1, MOSI_EN:1, BYTE_TX_ORDER:1, BYTE_TX_ORDER:1, BIT_RX_ORDER:0, BIT_TX_ORDER:0, CPHA:0, CPOL:0
spi_config.interface.val = SPI_DEFAULT_INTERFACE;
// Load default interrupt enable
// TRANS_DONE: true, WRITE_STATUS: false, READ_STATUS: false, WRITE_BUFFER: false, READ_BUFFER: false
spi_config.intr_enable.val = SPI_MASTER_DEFAULT_INTR_ENABLE;
// Set SPI to master mode
// ESP8266 Only support half-duplex
spi_config.mode = SPI_MASTER_MODE;
// Set the SPI clock frequency division factor
spi_config.clk_div = SPI_10MHz_DIV;
// Register SPI event callback function
spi_config.event_cb = NULL;
spi_init(HSPI_HOST, &spi_config);
// Write status to the ESP8266 Slave use "SPI_MASTER_WRITE_STATUS_TO_SLAVE_CMD" cmd
cmd = SPI_MASTER_WRITE_STATUS_TO_SLAVE_CMD;
trans.cmd = &cmd;
trans.mosi = &status;
trans.bits.val = 0;
trans.bits.cmd = 8 * 1;
trans.bits.mosi = 32 * 1;
spi_trans(HSPI_HOST, trans);
// create spi_master_write_slave_task
xTaskCreate(spi_master_write_slave_task, "spi_master_write_slave_task", 2048, NULL, 10, NULL);
// create spi_master_read_slave_task
xTaskCreate(spi_master_read_slave_task, "spi_master_read_slave_task", 2048, NULL, 10, NULL);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@ -45,12 +45,12 @@ static esp_err_t oled_set_dc(uint8_t dc)
// Write an 8-bit cmd
static esp_err_t oled_write_cmd(uint8_t data)
{
uint32_t buf = data << 24;
uint32_t buf = data << 24; // In order to improve the transmission efficiency, it is recommended that the external incoming data is (uint32_t *) type data, do not use other type data.
spi_trans_t trans = {0};
trans.mosi = &buf;
trans.bits.mosi = 8;
oled_set_dc(0);
spi_trans(HSPI_HOST, trans);
spi_trans(HSPI_HOST, &trans);
return ESP_OK;
}
@ -121,8 +121,8 @@ static esp_err_t oled_clear(uint8_t data)
for (x = 0; x < 8; x++) {
oled_set_pos(0, x);
oled_set_dc(1);
spi_trans(HSPI_HOST, trans);
spi_trans(HSPI_HOST, trans);
spi_trans(HSPI_HOST, &trans);
spi_trans(HSPI_HOST, &trans);
}
return ESP_OK;

View File

@ -1,82 +0,0 @@
# _SPI Slave Example_
_This example uses ESP8266 hspi Slave to send and receive data to another ESP8266 hspi Master_
## How to use example
### Hardware Required
* Connection:
| Signal | Slave | Master |
|-----------|--------|--------|
| SCLK | GPIO14 | GPIO14 |
| MISO | GPIO12 | GPIO12 |
| MOSI | GPIO13 | GPIO13 |
| CS | GPIO15 | GPIO15 |
| HANDSHARK | GPIO4 | GPIO4 |
| GND | GND | GND |
* Note:
When the ESP8266 is powered on, it is necessary to keep the GPIO15 low to enter the Flash mode, so the Master and the Slave have different power-on sequences.
```
Master OFF -> Slave ON -> Master ON
```
### Configure the project
```
make menuconfig
```
* Set serial port under Serial Flasher Options.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
make -j4 flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
* LOG:
```
I (500) spi_slave_example: init gpio
I (500) gpio: GPIO[4]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (520) spi_slave_example: init spi
I (3390) spi_slave_example: Slave wrote 3200 bytes in 4632 us
I (3490) spi_slave_example: Slave wrote 3200 bytes in 4616 us
I (3590) spi_slave_example: Slave wrote 3200 bytes in 4622 us
I (3690) spi_slave_example: Slave wrote 3200 bytes in 4611 us
I (3790) spi_slave_example: Slave wrote 3200 bytes in 4612 us
I (3890) spi_slave_example: Slave wrote 3200 bytes in 4612 us
I (3990) spi_slave_example: Slave wrote 3200 bytes in 4619 us
I (4090) spi_slave_example: Slave wrote 3200 bytes in 4607 us
I (4190) spi_slave_example: Slave wrote 3200 bytes in 4613 us
I (4290) spi_slave_example: Slave wrote 3200 bytes in 4609 us
I (4390) spi_slave_example: Slave wrote 3200 bytes in 4618 us
I (4490) spi_slave_example: Slave wrote 3200 bytes in 4619 us
I (4590) spi_slave_example: Slave wrote 3200 bytes in 4614 us
I (4690) spi_slave_example: Slave wrote 3200 bytes in 4613 us
```
* WAVE FORM:
- SPI_MASTER_WRITE_DATA_TO_SLAVE
![wave](wave_write_to_slave.png)
- SPI_MASTER_READ_DATA_FROM_SLAVE
![wave](wave_read_from_slave.png)

View File

@ -1,222 +0,0 @@
/* spi_slave example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "ringbuf.h"
#include "esp8266/spi_struct.h"
#include "esp8266/gpio_struct.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/spi.h"
static const char *TAG = "spi_slave_example";
#define SPI_SLAVE_HANDSHARK_GPIO 4
#define SPI_SLAVE_HANDSHARK_SEL (1ULL<<SPI_SLAVE_HANDSHARK_GPIO)
RingbufHandle_t spi_slave_tx_ring_buf;
RingbufHandle_t spi_slave_rx_ring_buf;
static SemaphoreHandle_t spi_slave_tx_done_sem = NULL;
static SemaphoreHandle_t spi_slave_wr_sta_done_sem = NULL;
static struct timeval now;
void IRAM_ATTR spi_event_callback(int event, void *arg)
{
int x;
BaseType_t xHigherPriorityTaskWoken;
uint32_t status;
uint32_t trans_done;
uint32_t *write_data = NULL;
uint32_t read_data[8];
uint32_t size;
switch (event) {
case SPI_INIT_EVENT: {
}
break;
case SPI_TRANS_START_EVENT: {
}
break;
case SPI_TRANS_DONE_EVENT: {
trans_done = *(uint32_t *)arg;
if (trans_done & SPI_SLV_RD_BUF_DONE) {
gpio_set_level(SPI_SLAVE_HANDSHARK_GPIO, 0);
write_data = (uint32_t *) xRingbufferReceiveFromISR(spi_slave_tx_ring_buf, &size);
if (write_data) {
for (x = 0; x < 8; x++) {
SPI1.data_buf[x + 8] = write_data[x];
}
vRingbufferReturnItemFromISR(spi_slave_tx_ring_buf, write_data, &xHigherPriorityTaskWoken);
gpio_set_level(SPI_SLAVE_HANDSHARK_GPIO, 1);
} else {
xSemaphoreGiveFromISR(spi_slave_tx_done_sem, &xHigherPriorityTaskWoken);
}
}
if (trans_done & SPI_SLV_WR_BUF_DONE) {
for (x = 0; x < 8; x++) {
read_data[x] = SPI1.data_buf[x];
}
xRingbufferSendFromISR(spi_slave_rx_ring_buf, (void *) read_data, sizeof(uint32_t) * 8, &xHigherPriorityTaskWoken);
}
if (trans_done & SPI_SLV_RD_STA_DONE) {
}
if (trans_done & SPI_SLV_WR_STA_DONE) {
spi_slave_get_status(HSPI_HOST, &status);
if (status == true) {
xSemaphoreGiveFromISR(spi_slave_wr_sta_done_sem, &xHigherPriorityTaskWoken);
}
}
if (xHigherPriorityTaskWoken == pdTRUE) {
taskYIELD();
}
}
break;
case SPI_DEINIT_EVENT: {
}
break;
}
}
static void spi_slave_write_master_task(void *arg)
{
int x;
uint16_t cmd;
uint32_t addr;
uint32_t write_data[8];
spi_trans_t trans;
uint32_t size;
uint64_t time_start, time_end;
trans.cmd = &cmd;
trans.addr = &addr;
trans.bits.val = 0;
// In Slave mode, spi cmd must be longer than 3 bits and shorter than 16 bits
trans.bits.cmd = 8 * 1;
// In Slave mode, spi addr must be longer than 1 bits and shorter than 32 bits
trans.bits.addr = 32 * 1;
trans.bits.mosi = 0;
trans.bits.miso = 32 * 8;
write_data[0] = 0xAAAAAAAA;
write_data[1] = 0xBBBBBBBB;
write_data[2] = 0xCCCCCCCC;
write_data[3] = 0xDDDDDDDD;
write_data[4] = 0xEEEEEEEE;
write_data[5] = 0xFFFFFFFF;
write_data[6] = 0xAAAABBBB;
write_data[7] = 1;
// Waiting for master idle
xSemaphoreTake(spi_slave_wr_sta_done_sem, portMAX_DELAY);
while (1) {
for (x = 0;x < 100;x++) {
xRingbufferSend(spi_slave_tx_ring_buf, (void *) write_data, sizeof(uint32_t) * 8, portMAX_DELAY);
write_data[7]++;
}
trans.miso = (uint32_t *) xRingbufferReceive(spi_slave_tx_ring_buf, &size, portMAX_DELAY);
gettimeofday(&now, NULL);
time_start = now.tv_usec;
gpio_set_level(SPI_SLAVE_HANDSHARK_GPIO, 0);
spi_trans(HSPI_HOST, trans);
gpio_set_level(SPI_SLAVE_HANDSHARK_GPIO, 1);
vRingbufferReturnItem(spi_slave_tx_ring_buf, trans.miso);
xSemaphoreTake(spi_slave_tx_done_sem, portMAX_DELAY);
gettimeofday(&now, NULL);
time_end = now.tv_usec;
ESP_LOGI(TAG, "Slave wrote 3200 bytes in %d us", (int)(time_end - time_start));
vTaskDelay(100 / portTICK_RATE_MS);
}
}
static void spi_slave_read_master_task(void *arg)
{
uint32_t *read_data = NULL;
uint32_t size;
while (1) {
read_data = (uint32_t *) xRingbufferReceive(spi_slave_rx_ring_buf, &size, portMAX_DELAY);
if (read_data) {
vRingbufferReturnItem(spi_slave_rx_ring_buf, read_data);
if (read_data[7] % 100 == 0) {
vTaskDelay(100 / portTICK_RATE_MS);
}
}
}
}
void app_main(void)
{
spi_trans_t trans = {0};
spi_slave_tx_ring_buf = xRingbufferCreate(4096, RINGBUF_TYPE_NOSPLIT);
spi_slave_rx_ring_buf = xRingbufferCreate(4096, RINGBUF_TYPE_NOSPLIT);
spi_slave_wr_sta_done_sem = xSemaphoreCreateBinary();
spi_slave_tx_done_sem = xSemaphoreCreateBinary();
ESP_LOGI(TAG, "init gpio");
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = SPI_SLAVE_HANDSHARK_SEL;
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
gpio_set_level(SPI_SLAVE_HANDSHARK_GPIO, 0);
ESP_LOGI(TAG, "init spi");
spi_config_t spi_config;
// Load default interface parameters
// CS_EN:1, MISO_EN:1, MOSI_EN:1, BYTE_TX_ORDER:1, BYTE_TX_ORDER:1, BIT_RX_ORDER:0, BIT_TX_ORDER:0, CPHA:0, CPOL:0
spi_config.interface.val = SPI_DEFAULT_INTERFACE;
// Load default interrupt enable
// TRANS_DONE: false, WRITE_STATUS: true, READ_STATUS: true, WRITE_BUFFER: true, READ_BUFFER: ture
spi_config.intr_enable.val = SPI_SLAVE_DEFAULT_INTR_ENABLE;
// Set SPI to slave mode
spi_config.mode = SPI_SLAVE_MODE;
// Register SPI event callback function
spi_config.event_cb = spi_event_callback;
spi_init(HSPI_HOST, &spi_config);
trans.bits.val = 0;
// In Slave mode, spi cmd must be longer than 3 bits and shorter than 16 bits
trans.bits.cmd = 8 * 1;
// In Slave mode, spi addr must be longer than 1 bits and shorter than 32 bits
trans.bits.addr = 32 * 1;
trans.bits.mosi = 32 * 8;
spi_trans(HSPI_HOST, trans); // init spi slave buf
// create spi_slave_write_master_task
xTaskCreate(spi_slave_write_master_task, "spi_slave_write_master_task", 2048, NULL, 10, NULL);
// create spi_slave_read_master_task
xTaskCreate(spi_slave_read_master_task, "spi_slave_read_master_task", 2048, NULL, 10, NULL);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB