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++;
    }
}

 

运行顺序应该是从机接收先复位,再等待主机复位;如果主机先复位,则接收时序可能会不正确。

 四、运行结果

主机接收到从机返回的数据

 

从机接收到主机发送的数据

物联沃分享整理
物联沃-IOTWORD物联网 » STM32与ESP32硬件SPI通信实践及学习记录

发表回复