使用ESP-IDF实现STM32与ESP32之间的SPI通信

项目场景:

	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例程,步骤如下:
打开esp32自己的例程仓库
选择esp32spi 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接收结果展示:
STM32接收结果显示
ESP32端接收主机结果:
esp32端接收主机结果

作者:路有阻夷,旅行者知

物联沃分享整理
物联沃-IOTWORD物联网 » 使用ESP-IDF实现STM32与ESP32之间的SPI通信

发表评论