【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
现在esp32和mcu一起出现的场合很多,前者适合网络传输和屏幕显示,后者则多出现在工业场景。所以两者一起出现就可以发挥各自的优势。但是两个一起合作,也有通信的需求。虽然用ttl可以解决通信的问题,但是速率比较低不太方便,我们一般还是用spi,会感觉比较简单一点。

1、mcu做spi host,esp32做spi slave
之所以会这样,是因为很多时候都是采集数据的要求,这个时候用mcu做master方便一点。
2、gnd务必对接
不管是测试的时候,还是部署的时候,两个模块的gnd需要互联。
3、pin连接
目前esp32的pin是5/18/19/23,而mcu这边的pin是pa4/pa5/pa6/pa7。两者的连接方式是这样的,5连接pa4,18连接pa5,19连接pa6,23连接pa7。
4、esp32需要主动发起通信怎么办
一般是通过gpio,让mcu触发一个中断。
5、代码部分
这部分代码可以让ai写也可以。首先写mcu的代码,
#include "stm32f10x.h"
#define SPI_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4)
#define SPI_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4)
void SPI1_Master_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);
// SCK(PA5), MOSI(PA7) 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// MISO(PA6) 浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// NSS(PA4) 用普通GPIO推挽输出,软件控制片选
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
SPI_CS_HIGH(); // 默认不选中
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件NSS,用GPIO4手动拉
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; // 72MHz/64=1.125MHz,先低速验证
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
uint8_t SPI1_TransferByte(uint8_t data)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, data);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
// 简单延时(根据你的系统时钟自行调整或换成systick延时)
void delay_us(uint32_t us)
{
for (uint32_t i = 0; i < us * 8; i++) __NOP();
}
void SPI1_TransferBuffer(uint8_t *tx, uint8_t *rx, uint16_t len)
{
SPI_CS_LOW();
delay_us(10); // 给ESP32 slave一点反应时间,非常关键!
for (uint16_t i = 0; i < len; i++)
{
rx[i] = SPI1_TransferByte(tx[i]);
}
SPI_CS_HIGH();
}
int main(void)
{
SPI1_Master_Init();
uint8_t tx_buf[4] = {0x11, 0x22, 0x33, 0x44};
uint8_t rx_buf[4] = {0};
while (1)
{
SPI1_TransferBuffer(tx_buf, rx_buf, 4);
// rx_buf 里是 ESP32 回传的数据
for (volatile int i = 0; i < 500000; i++); // 简单延时
}
}
然后是esp32的代码,
#include "driver/spi_slave.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "string.h"
#define GPIO_MOSI 23
#define GPIO_MISO 19
#define GPIO_SCLK 18
#define GPIO_CS 5
static const char *TAG = "SPI_SLAVE";
void app_main(void)
{
esp_err_t ret;
spi_bus_config_t buscfg = {
.mosi_io_num = GPIO_MOSI,
.miso_io_num = GPIO_MISO,
.sclk_io_num = GPIO_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
spi_slave_interface_config_t slvcfg = {
.mode = 0, // 对应CPOL=0, CPHA=0,要和STM32一致
.spics_io_num = GPIO_CS,
.queue_size = 3,
.flags = 0,
};
// 初始化SPI从机,用DMA
ret = spi_slave_initialize(SPI2_HOST, &buscfg, &slvcfg, SPI_DMA_CH_AUTO);
ESP_ERROR_CHECK(ret);
WORD_ALIGNED_ATTR uint8_t recvbuf[4] = {0};
WORD_ALIGNED_ATTR uint8_t sendbuf[4] = {0xA1, 0xA2, 0xA3, 0xA4}; // 要回传给STM32的数据
while (1)
{
spi_slave_transaction_t t = {0};
t.length = 4 * 8; // 单位bit
t.tx_buffer = sendbuf;
t.rx_buffer = recvbuf;
// 阻塞等待STM32发起一次传输
ret = spi_slave_transmit(SPI2_HOST, &t, portMAX_DELAY);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "recv: %02X %02X %02X %02X",
recvbuf[0], recvbuf[1], recvbuf[2], recvbuf[3]);
// 根据收到的数据,更新下一次要回传的内容
sendbuf[0]++; // 举例:简单自增,实际按你的协议处理
}
}
}
6、测试
测试的时候,一般先把esp32准备,插好之后,再接入mcu。这个时候不出意外,就可以在esp32这边看到源源不断的信息打印了。