STM32与ESP32硬件SPI通信实践及学习记录
一、硬件配置
STM32F103ZET6作为主机,使用SPI2,ESP32S2作为从机,进行SPI双向通信;硬件接线如下:
主机 从机
CS PB12 —————— 14
MOSI PB6 —————— 2
MISO PB14 —————— 13
CLK PB15 —————— 12
HANDSHAKE PB13 —————— 15
GND GND —————— GND
(地线一定相连在一起,不然传输的数据会乱码)
二、主机代码
#define SPI2READY PBin(6) //读取握手线是否准备好
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB |RCC_APB2Periph_AFIO, ENABLE );
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );//SPI2时钟使能
//CS片选线
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //PB12推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
//HANDSHAKE 握手线
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PB6下拉输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //第一个跳变沿数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //软件管理
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //初始化外设SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
}
//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
{
retry++;
if(retry>200) return 0; //发送上一个数据时间过长,报错
}
SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)
{
retry++;
if(retry>200) return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}
u8 TXBuffer[128];
u8 RXBuffer[128];
int main(void)
{
u16 i=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2
uart_init(115200); //串口初始化为115200
LED_Init(); //初始化与LED连接的硬件接口
SPI2_Init();
for (i = 0; i < 10; i++) //给发送数组赋初值
{
TXBuffer[i] = 0x47+i;
}
while(1)
{
if(SPI2READY==1) //如果从机准备好接收
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12); //拉低CS
for(i=0;i<10;i++) //发送数据并接收从机返回数据
{
RXBuffer[i]=SPI2_ReadWriteByte(TXBuffer[i]);
}
GPIO_SetBits(GPIOB,GPIO_Pin_12); //拉高CS
LED0 = ~LED0; //指示灯闪烁
}
printf("%s\r\n",RXBuffer); //串口打印从机接收到的数据
delay_ms(1000);
}
}
三、从机代码
根据官方所给例程修改
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/spi_slave.h"
#include "driver/gpio.h"
#define GPIO_HANDSHAKE 2
#define GPIO_MOSI 12
#define GPIO_MISO 13
#define GPIO_SCLK 15
#define GPIO_CS 14
#ifdef CONFIG_IDF_TARGET_ESP32
#define RCV_HOST HSPI_HOST
#else
#define RCV_HOST SPI2_HOST
#endif
在事务进入队列并准备由master提取时调用。我们用这个来设置握手线
void my_post_setup_cb(spi_slave_transaction_t *trans) {
gpio_set_level(GPIO_HANDSHAKE, 1);
}
//事务发送/接收后调用。我们用这个来降低握手线。
void my_post_trans_cb(spi_slave_transaction_t *trans) {
gpio_set_level(GPIO_HANDSHAKE, 0);
}
//Main application
void app_main(void)
{
int n=0;
esp_err_t ret;
//Configuration for the SPI bus
spi_bus_config_t buscfg={
.mosi_io_num=GPIO_MOSI,
.miso_io_num=GPIO_MISO,
.sclk_io_num=GPIO_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
//Configuration for the SPI slave interface
spi_slave_interface_config_t slvcfg={
.mode=0, //CPOL 0,CPHA 0 (注意模式配置,需要与主机一致)
.spics_io_num=GPIO_CS,
.queue_size=3,
.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(RCV_HOST, &buscfg, &slvcfg, SPI_DMA_CH_AUTO);
assert(ret==ESP_OK);
WORD_ALIGNED_ATTR char sendbuf[129]="";
WORD_ALIGNED_ATTR char recvbuf[129]="";
memset(recvbuf, 0, 33); //将前33个字节填充为0
spi_slave_transaction_t t;
memset(&t, 0, sizeof(t));
while(1) {
//Clear receive buffer, set send buffer to something sane
memset(recvbuf, 0x00, 129); //将129个字节全部填充为0xA5
for(int j=0;j<26;j++) //给从机发送缓存区赋值
{
sendbuf[j]=0x41+j;
}
//Set up a transaction of 128 bytes to send/receive
t.length=20*8; //设置一次接收/发送的最大值,必须比主机一次发送的数据大小要大,否则会乱序
t.tx_buffer=sendbuf;
t.rx_buffer=recvbuf;
ret=spi_slave_transmit(RCV_HOST, &t, portMAX_DELAY);
// spi_slave_queue_trans(RCV_HOST, &t, portMAX_DELAY); //可以用这个函数,但是没有验证,容易出错
printf("Received: %s\n", recvbuf);
n++;
}
}
运行顺序应该是从机接收先复位,再等待主机复位;如果主机先复位,则接收时序可能会不正确。
四、运行结果
主机接收到从机返回的数据
从机接收到主机发送的数据