项目场景:
STM32与ESP32通过SPI进行数据传输,ESP32采用ESP-IDF进行编程;STM32当做主机,esp32当从机进行通讯。
其中STM32芯片采用STM32F446RET6,系统时钟168M,ESP32采用ESP32-WROOM_32UE,默认配置,编程软件采用VS Code。
问题描述
通讯时使用esp32 spi slave历程,端口总是显示乱码:
原因分析:
出现乱码的原因:
1、主机STM32的SPI工作模式与esp32的SPI工作模式没有一一对应;
2、主机STM32与从机ESP32之间没有共地;
3、ESP32历程中的发送初始化函数未进行修改:memset(recvbuf, 0xA5, 128);
例程中默认使用0xA5初始化,使用0xA5初始化时,esp32与esp32之间通讯正常,但是与stm32通讯的时候,当stm32发送的数据太少,esp32端乱码显示或直接不显示,需要替换为 memset(recvbuf, 0x00, 128);
。
解决方案:
下面直接上程序:spi.h
#ifndef __SPIX_H
#define __SPIX_H
#include "stm32f4xx.h"
//RCC时钟
#define SPI_B_clock RCC_AHB1Periph_GPIOB //GPIOB时钟
#define NRF_CS_clock RCC_AHB1Periph_GPIOD
#define SPI_X_clock RCC_APB2Periph_SPI1 //ADC1时钟
//SPI_GPIO
#define SPI_GPIOB GPIOB
#define NRF_GPIO_CS GPIOD
#define NRF_CS_SCL GPIO_Pin_2
#define SPI_X_SCL GPIO_Pin_3
#define ADC_X_MOSI GPIO_Pin_5
#define ADC_X_MISO GPIO_Pin_4
#define SPI__SCL GPIO_PinSource3
#define ADC__MOSI GPIO_PinSource4
#define ADC__MISO GPIO_PinSource5
#define SPIx_X SPI1
void SPI1_Init(void);
u8 SPI1_ReadWriteByte(u8 TxData);
void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler);
u8 spi_Write_Reg(u8 reg,u8 value); //SPI写寄存器
u8 spi_Read_Reg(u8 reg); //读取SPI寄存器值
u8 spi_Read_Buf(u8 reg,u8 *pBuf,u8 len); //在指定位置读出指定长度的数据
u8 spi_Write_Buf(u8 reg, u8 *pBuf, u8 len); //在指定位置写指定长度的数据
#endif
STM32的spi.c程序:
#include "SPIx.h"
#include "sys.h"
void SPI1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_AHB1PeriphClockCmd(SPI_B_clock, ENABLE);//使能GPIOB时钟
RCC_AHB1PeriphClockCmd(NRF_CS_clock , ENABLE); //使能cs时钟
RCC_APB2PeriphClockCmd( RCC_APB2Periph_SPI1, ENABLE );//SPI2时钟使能
//CS片选线
GPIO_InitStructure.GPIO_Pin =NRF_CS_SCL;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //PB12推挽输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化GPIOB
//HANDSHAKE 握手线
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //PB6下拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//GPIOFB3,4,5初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3~5复用功能输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3复用为 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4复用为 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5复用为 SPI1
//这里只针对SPI口初始化
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//复位SPI1
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
// SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; //设置SPI工作模式:设置为从SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
// SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
// SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI1, ENABLE); //使能SPI外设
SPI1_ReadWriteByte(0xff);//启动传输
}
//SPI1速度设置函数
//SPI速度=fAPB2/分频系数
//@ref SPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256
//fAPB2时钟一般为84Mhz:
void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性
SPI1->CR1&=0XFFC7;//位3-5清零,用来设置波特率
SPI1->CR1|=SPI_BaudRatePrescaler; //设置SPI1速度
SPI_Cmd(SPI1,ENABLE); //使能SPI1
}
//SPI1 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI1_ReadWriteByte(u8 TxData)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){}//等待发送区空
SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个byte 数据
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET){} //等待接收完一个byte
return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据
}
//SPI写寄存器
//reg:指定寄存器地址
//value:写入的值
u8 spi_Write_Reg(u8 reg,u8 value)
{
u8 status;
PDout(2)=0; //使能SPI传输
status =SPI1_ReadWriteByte(reg);//发送寄存器号
SPI1_ReadWriteByte(value); //写入寄存器的值
PDout(2)=1; //禁止SPI传输
return(status); //返回状态值
}
//读取SPI寄存器值
//reg:要读的寄存器
u8 spi_Read_Reg(u8 reg)
{
u8 reg_val;
PDout(2) = 0; //使能SPI传输
SPI1_ReadWriteByte(reg); //发送寄存器号
reg_val=SPI1_ReadWriteByte(0XFF);//读取寄存器内容
PDout(2) = 1; //禁止SPI传输
return(reg_val); //返回状态值
}
//在指定位置读出指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值
u8 spi_Read_Buf(u8 reg,u8 *pBuf,u8 len)
{
u8 status,u8_ctr;
status=SPI1_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
for(u8_ctr=0;u8_ctr<len;u8_ctr++)pBuf[u8_ctr]=SPI1_ReadWriteByte(0x00);//读出数据
return status; //返回读到的状态值
}
//在指定位置写指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值
u8 spi_Write_Buf(u8 reg, u8 *pBuf, u8 len)
{
u8 status,u8_ctr;
PDout(2) = 0; //使能SPI传输
status = SPI1_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
for(u8_ctr=0; u8_ctr<len; u8_ctr++)SPI1_ReadWriteByte(*pBuf++); //写入数据
PDout(2) = 1; //关闭SPI传输
return status; //返回读到的状态值
}
SPIx 读写一个字节
TxData:要写入的字节
返回值:读取到的字节
//u8 SPI1_ReadWriteByte(u8 TxData)
//{
// u8 retry=0;
// while (SPI_I2S_GetFlagStatus(SPIx_X, SPI_I2S_FLAG_TXE) == RESET)
// {
// retry++;
// if(retry>200) {RGB_Color(yellow);return 0;} //发送上一个数据时间过长,报错
// }
// SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个数据
// retry=0;
// RGB_Color(red);
// while (SPI_I2S_GetFlagStatus(SPIx_X, SPI_I2S_FLAG_TXE) == RESET)
// {
// retry++;
// if(retry>200) return 0;
// }
// return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据
//}
main.c函数
int main()
{
delay_init(168);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//串口使用了中断
uart1_init(9600);//通讯串口
LED_Init();
SPI1_Init();
int i;
u8 ch[128]={"abcdefghijklmnopqrst"},tm[128];
while(1)
{
if(PBin(6)==1)
{
PDout(2)=0;
// spi_Write_Buf(0x61,ch,128);//向esp32传输ch内容
spi_Read_Buf(0x61,tm,128);//接收esp32传输回来的内容
PDout(2)=1;
RGB_Color(yellow);
USARTx_p=USART1;
}
else
{
RGB_Color(red);
}
printf("%s\n",tm);
}
}
ESP32历程修改
安装好VS CODE的ESP IDF编程环境后,按F1打开命令窗口,找到例程,打开例程,选择spi slave的receiver例程,步骤如下:
修改例程后的代码如下:
#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
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);
}
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,
.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);
// gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLDOWN_ONLY);
// gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLDOWN_ONLY);
// gpio_set_pull_mode(GPIO_CS, GPIO_PULLDOWN_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);
spi_slave_transaction_t t;
memset(&t, 0, sizeof(t));
while(1) {
//Clear receive buffer, set send buffer to something sane
memset(recvbuf, 0x00, 128);
sprintf(sendbuf,"bcdefgh");
//Set up a transaction of 128 bytes to send/receive
t.length=128*8;
t.tx_buffer=sendbuf;
t.rx_buffer=recvbuf;
/* This call enables the SPI slave interface to send/receive to the sendbuf and recvbuf. The transaction is
initialized by the SPI master, however, so it will not actually happen until the master starts a hardware transaction
by pulling CS low and pulsing the clock etc. In this specific example, we use the handshake line, pulled up by the
.post_setup_cb callback that is called as soon as a transaction is ready, to let the master know it is free to transfer
data.
*/
ret=spi_slave_transmit(RCV_HOST, &t, portMAX_DELAY);
//spi_slave_transmit does not return until the master has done a transmission, so by here we have sent our data and
//received data from the master. Print it.
printf("Received: %s\n", recvbuf);
n++;
}
}
使用上述代码运行结果展示:
STM32接收结果展示:
ESP32端接收主机结果: