ESP32与STM32的SPI半双工通信与握手协议讲解(附代码)

 说在最前:下面把主要代码都已经附上,如果有人想要完整的工程文件(包括ESP-IDF和KEIL5和STM32CUBEMX的工程)请移步下载链接:

完整资源下载

如果你自己写的,有bug无法解决,请参考,下面的文章:

含泪Debug:STM32与ESP32 SPI通信避坑

              我先极其粗浅的介绍一下SPI通信:

              四线SPI通信:SPI_SCLK用于传递时钟信号,SPI_CS用于传递片选信号(低有效),MOSI为主机的输出、从机的输入,MISO为主机的输入、从机的输出。

              四线SPI的数据交换:CS片选信号,用于使能从设备接收数据;主从设备在SPI_SCLK时钟信号的驱动下,如下图的方式交换数据。

              实现的时候,STM32与ESP32是轮询式的:

HAL_SPI_Transmit(&hspi5,ptr,128,0xffff);
ret=spi_slave_transmit(RCV_HOST, &t, portMAX_DELAY);

             所以,为了保证二者能够转备好在同一时刻进行数据的交换,就需要握手协议。

            (以半双工,STM32做master为例)如何握手呢?ESP32-IDF例程中的代码如下:

//Called after a transaction is queued and ready for pickup by master. We use this to set the handshake line high.
void my_post_setup_cb(spi_slave_transaction_t *trans) {
    WRITE_PERI_REG(GPIO_OUT_W1TS_REG, (1<<GPIO_HANDSHAKE));
}

//Called after transaction is sent/received. We use this to set the handshake line low.
void my_post_trans_cb(spi_slave_transaction_t *trans) {
    WRITE_PERI_REG(GPIO_OUT_W1TC_REG, (1<<GPIO_HANDSHAKE));
}

              也就是说,ESP32每次请求数据前,先把HANDSHAKE引脚拉高,请求后把HANDSHAKE拉低。如果STM32想给ESP32发送数据的话,就要检测ESP32是否准备好,也就是检测HANDSHAKE的电平值,如果为高电平才可以发送。

话不多说直接上代码:

   ESP-IDF版本为v4.0.1(如果不能编译可能你们的版本太高了),在例程的基础上略微改动

/* SPI Slave example, receiver (uses SPI Slave driver to communicate with sender)

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"

#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
#include "lwip/igmp.h"

#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "soc/rtc_periph.h"
#include "driver/spi_slave.h"
#include "esp_log.h"
#include "esp_spi_flash.h"
#include "driver/gpio.h"




/*
SPI receiver (slave) example.

This example is supposed to work together with the SPI sender. It uses the standard SPI pins (MISO, MOSI, SCLK, CS) to
transmit data over in a full-duplex fashion, that is, while the master puts data on the MOSI pin, the slave puts its own
data on the MISO pin.

This example uses one extra pin: GPIO_HANDSHAKE is used as a handshake pin. After a transmission has been set up and we're
ready to send/receive data, this code uses a callback to set the handshake pin high. The sender will detect this and start
sending a transaction. As soon as the transaction is done, the line gets set low again.
*/

/*
Pins in use. The SPI Master can use the GPIO mux, so feel free to change these if needed.
*/
#define GPIO_HANDSHAKE 2
#define GPIO_MOSI 5
#define GPIO_MISO 17
#define GPIO_SCLK 16
#define GPIO_CS 4

#ifdef CONFIG_IDF_TARGET_ESP32
#define RCV_HOST    HSPI_HOST
#define DMA_CHAN    2

#elif defined CONFIG_IDF_TARGET_ESP32S2
#define RCV_HOST    SPI2_HOST
#define DMA_CHAN    RCV_HOST

#endif



//Called after a transaction is queued and ready for pickup by master. We use this to set the handshake line high.
void my_post_setup_cb(spi_slave_transaction_t *trans) {
    WRITE_PERI_REG(GPIO_OUT_W1TS_REG, (1<<GPIO_HANDSHAKE));
}

//Called after transaction is sent/received. We use this to set the handshake line low.
void my_post_trans_cb(spi_slave_transaction_t *trans) {
    WRITE_PERI_REG(GPIO_OUT_W1TC_REG, (1<<GPIO_HANDSHAKE));
}

//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,
        .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, DMA_CHAN);
    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));

        //memset(recvbuf, 0xA5, 128);//
        //sprintf(sendbuf, "This is the receiver, sending data for transmission number %04d.", n);

        //Set up a transaction of 128 bytes to send/receive
        //t.length=128*8;
        //t.tx_buffer=sendbuf;
        //t.rx_buffer=recvbuf;


    while(1) {
        //Clear receive buffer, set send buffer to something sane
        memset(recvbuf, 0xA5, 129);//
        //sprintf(sendbuf, "This is the receiver, sending data for transmission number %04d.", n);

        //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("receive:%s\r\n", recvbuf);
        n++;
        
    }

}

STM32的版本为STM32F446,只发配置的主要部分:spi.c

/**
  ******************************************************************************
  * File Name          : SPI.c
  * Description        : This file provides code for the configuration
  *                      of the SPI instances.
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "spi.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

SPI_HandleTypeDef hspi2;

/* SPI2 init function */
void MX_SPI2_Init(void)
{

  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(spiHandle->Instance==SPI2)
  {
  /* USER CODE BEGIN SPI2_MspInit 0 */

  /* USER CODE END SPI2_MspInit 0 */
    /* SPI2 clock enable */
    __HAL_RCC_SPI2_CLK_ENABLE();
  
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**SPI2 GPIO Configuration    
    PB13     ------> SPI2_SCK
    PB15     ------> SPI2_MOSI 
    */
    GPIO_InitStruct.Pin = SPI_SCLK_Pin|SPI_MOSI_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /* USER CODE BEGIN SPI2_MspInit 1 */

  /* USER CODE END SPI2_MspInit 1 */
  }
}

void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{

  if(spiHandle->Instance==SPI2)
  {
  /* USER CODE BEGIN SPI2_MspDeInit 0 */

  /* USER CODE END SPI2_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_SPI2_CLK_DISABLE();
  
    /**SPI2 GPIO Configuration    
    PB13     ------> SPI2_SCK
    PB15     ------> SPI2_MOSI 
    */
    HAL_GPIO_DeInit(GPIOB, SPI_SCLK_Pin|SPI_MOSI_Pin);

  /* USER CODE BEGIN SPI2_MspDeInit 1 */

  /* USER CODE END SPI2_MspDeInit 1 */
  }
} 

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
  /* USER CODE BEGIN WHILE */

	__HAL_UART_ENABLE(&huart3);
	hspi2.Instance->CR1 |= (1<<6);  // 打开SPE
	GPIOB->ODR|=1UL<<0;
	int count=0;
	unsigned char wr_buff[128]={0};
	
	
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		GPIOB->ODR^=1UL<<0;
		snprintf((char *)wr_buff,128,"THIS_IS_STM32_SENDING_NUM:%d\n",count);
		spi_send_128(wr_buff);
count++;
		
  }
  /* USER CODE END 3 */

 

  • 15
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 当需要在ESP32STM32之间进行通信时,可能会倾向于使用SPI总线通信。实现SPI通信需要在两个设备之间建立握手协议以及传输数据。在ESP_IDF_V4.0.1中,可以使用HAL库来轻松完成半双工SPI通信,并且可以添加握手协议来确保正确的数据传输。 在使用HAL库时,需要配置SPI主从模式、数据大小、时钟极性、数据采样点和时钟分频等一系列参数。然后,在ESO32中,可以使用spi_device_polling_transmit函数来发送数据,并且可以通过spi_device_queued_transmit函数来添加一个片选线(CS)使通信更加安全可靠。在STM32中,则可以通过SPI_I2S_SendData函数和SPI_I2S_ReceiveData函数来发送接收数据。 为了确保数据传输的正确性,可以通过在半双工SPI通信中添加握手协议来实现。在ESP32中,可以在数据传输前发送一个请求数据的标准消息,并在数据传输完成后发送一个确认消息表明数据已经成功接收。在STM32中,则可以在接收到请求数据的消息后开始准备数据,并在数据传输完成后向ESP32发送一个确认数据接收消息。 总之,建立ESP32STM32之间的半双工SPI通信需要配置一些参数,并使用HAL库来实现数据传输。为了更好地确保数据的可靠性,可以添加握手协议来保证正确的数据传输。 ### 回答2: ESP32STM32是两种不同的芯片,它们之间可以通过半双工SPI通信进行数据交互。要使用这种通信方式,需要使用一个包含握手协议的软件库,例如esp_idf_v4.0.1。 握手协议是一种通信协议,用于确定通信双方的身份和通信限制。在ESP32STM32之间进行通信时,握手协议可以使双方了解彼此的要求,并确定双方可以使用的通信参数和数据格式。 在使用握手协议进行通信之前,需要先在ESP32STM32中设置SPI接口的参数,例如时钟频率、数据位长度和校验方式等。然后,通过发送特定的握手信号交换参数,并确保双方使用相同的参数。 一旦握手协议成功完成,双方可以开始进行数据交换。在半双工SPI通信中,每个设备都可以发送和接收数据,但在同一时刻只能进行一项操作。因此,发送方必须等待接收方完成其操作,然后才能发送下一个数据包。 在使用ESP32STM32进行半双工SPI通信时,需要注意一些问题,例如数据传输的速率、时序的正确性和防止干扰等。通过合理设置通信参数和正确处理通信过程中的异常情况,可以保证通信质量和稳定性。 ### 回答3: ESP32STM32是常见的嵌入式系统开发板,在设计嵌入式系统时常常需要进行板间通信。本文介绍的“esp32stm32半双工spi通信(含握手协议)(esp_idf_v4.0.1).zip”是一种基于半双工SPI协议通信方式。其中包含了基于ESP IDF(IoT Development Framework)V4.0.1的代码示例和STM32的HAL库代码半双工SPI协议是一种同步串行通信协议,可以用于短距离通信。在SPI通信中,需要有一个主设备和一个或多个从设备。主设备发送同步时钟信号,并在时钟上升沿或下降沿时发送数据,从设备在时钟上升沿或下降沿时采集数据。半双工SPI通信指的是主设备和从设备之间的通信是双向的,但数据只能沿一个方向流动。 在本代码示例中,ESP32作为主设备发送请求数据,STM32作为从设备接收请求数据并回应。为了确保通信的可靠性,代码中还实现了握手协议。当ESP32发送请求并收到一个特定字节时,STM32会回复一个确定字节表示已经准备好接收请求数据。接着ESP32会发送请求数据,STM32会收到并处理数据后回复数据结果给ESP32。 总之,这个“esp32stm32半双工spi通信(含握手协议)(esp_idf_v4.0.1).zip”代码示例提供了一种简单而可靠的板间通信方式,可以适用于许多嵌入式系统应用场景。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值