STM32与ESP32通过SPI进行通讯——基于ESP-IDF

1 篇文章 0 订阅

项目场景:

	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端接收主机结果

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个使用ESP-IDF框架的ESP32 SPI3通信的示例代码: ```c #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/spi_master.h" #define PIN_CLK 18 #define PIN_MISO 19 #define PIN_MOSI 23 #define PIN_CS 5 void spi_task(void *pvParameters) { spi_device_handle_t spi; spi_bus_config_t bus_config = { .mosi_io_num = PIN_MOSI, .miso_io_num = PIN_MISO, .sclk_io_num = PIN_CLK, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 0, }; spi_device_interface_config_t dev_config = { .command_bits = 0, .address_bits = 0, .dummy_bits = 0, .mode = 0, .duty_cycle_pos = 0, .cs_ena_pretrans = 0, .cs_ena_posttrans = 0, .clock_speed_hz = 1000000, // 设置时钟频率为1MHz .input_delay_ns = 0, .spics_io_num = PIN_CS, .flags = 0, .queue_size = 1, .pre_cb = NULL, .post_cb = NULL, }; spi_device_handle_t spi_handle; spi_bus_initialize(VSPI_HOST, &bus_config, 1); spi_bus_add_device(VSPI_HOST, &dev_config, &spi_handle); while (1) { uint8_t send_data = 0x55; uint8_t recv_data; spi_transaction_t trans_desc = { .flags = 0, .cmd = 0, .addr = 0, .length = 8, // 数据位宽为8位 .rxlength = 8, .tx_buffer = &send_data, .rx_buffer = &recv_data, }; spi_device_polling_transmit(spi_handle, &trans_desc); printf("Received data: 0x%02x\n", recv_data); vTaskDelay(1000 / portTICK_PERIOD_MS); } } void app_main() { xTaskCreate(spi_task, "spi_task", 2048, NULL, 10, NULL); } ``` 在这个示例中,我们使用了ESP32的VSPI总线,并且将引脚18、19和23分别连接到SPI3的CLK、MISO和MOSI信号。引脚5用作CS(片选)信号。 我们首先定义了SPI总线和设备的配置参数。然后,通过调用spi_bus_initialize()函数初始化SPI总线,并通过spi_bus_add_device()函数将SPI设备添加到总线上。 在任务函数spi_task()中,我们创建了一个spi_transaction_t结构体来描述传输的参数。然后,我们使用spi_device_polling_transmit()函数来发送和接收数据。 最后,我们在app_main()函数中创建了一个任务来执行SPI通信操作。 请根据你的具体需求进行适当的修改和配置。希望这个例程能帮助你使用ESP-IDF框架进行ESP32 SPI3通信!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值