当前位置: 首页 > article >正文

ESP32S3(主模式) 与 STM32(从模式) 进行SPI全双工通信

开发环境:
ESP-IDF: ESP32S3
Keil5:STM32F103C8T6
通信方式:SPI 全双工通信
两天从0摸索的,大部分时间被卡是因为STM32 引脚模式配置错误
效果:
在这里插入图片描述
主设备收到上次的数据,是因为全双工原理,可以理解为一个U形管道,通过挤压,配置收发缓冲区进行收发数据
在这里插入图片描述

STM32 SPI 从模式配置表:
在这里插入图片描述

STM32:
使用SPI2,配置流程:

  1. 时钟配置
  2. 引脚配置
  3. SPI 参数配置
#ifndef __SPI_COM_H
#define __SPI_COM_H

#include "stm32f10x.h"

#define SPI_NAME  SPI2
#define SPI_MOSI  GPIO_Pin_15// 发送端 SDA    
#define SPI_MISO  GPIO_Pin_14 // 接收端 SDI     
#define SPI_CLK   GPIO_Pin_13 // 时钟 SCL      
#define SPI_CS    GPIO_Pin_12 // 片选 CS       


void SPI_COM_Init(void);

void send_data(void);
#endif
#include "spi_com.h"

// 定义全局变量
static uint8_t rx_data = 0x00;
        // 处理接收数据,并准备发送数据(全双工)
static uint8_t tx_data = 0x00;

// 初始化 SPI 从机
void SPI_COM_Init(void) {
	    SPI_InitTypeDef SPI_InitStructure;
	    GPIO_InitTypeDef GPIO_InitStructure;
	
	    // 使能 SPI_NAME 和 GPIOA 时钟
	     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
		
		 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);


	      // 配置 SPI_NAME 的引脚
		 GPIO_InitStructure.GPIO_Pin = SPI_MOSI; 
		 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
		 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
		 GPIO_Init(GPIOB, &GPIO_InitStructure);
 
	 
	 
		GPIO_InitStructure.GPIO_Pin =  SPI_CLK;
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
		GPIO_Init(GPIOB, &GPIO_InitStructure);


		GPIO_InitStructure.GPIO_Pin = SPI_MISO;
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
		GPIO_Init(GPIOB, &GPIO_InitStructure);

		 GPIO_InitStructure.GPIO_Pin = SPI_CS; // SS 引脚
		 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; 
		 GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 配置 SPI_NAME 为从机模式
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;   // 72M / 4 = 18MHZ 
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 0; //CRC冗余位数
    SPI_Init(SPI_NAME, &SPI_InitStructure);

    // 使能 SPI_NAME
    SPI_Cmd(SPI_NAME, ENABLE);
//	  // 使能 SPI 接收中断
//    SPI_I2S_ITConfig(SPI_NAME, SPI_I2S_FLAG_RXNE, ENABLE);

	 
//	 // 使能 NVIC 中断
//    NVIC_InitTypeDef NVIC_InitStructure;
//    NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;
//    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
//    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
//    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//    NVIC_Init(&NVIC_InitStructure);

}

// 中断服务函数
void SPI2_IRQHandler(void) {
    if (SPI_I2S_GetITStatus(SPI_NAME, SPI_I2S_IT_RXNE) == SET) {
        rx_data = SPI_I2S_ReceiveData(SPI_NAME);
        // 处理接收数据,并准备发送数据(全双工)
        tx_data = rx_data;  // 填充要发送的数据
        SPI_I2S_SendData(SPI_NAME, rx_data);
		 
    }
}

// 软件接收法,可优化为中断控制
void send_data(void){

	   while (SPI_I2S_GetFlagStatus(SPI_NAME, SPI_I2S_FLAG_TXE) == RESET);
	   SPI_I2S_SendData(SPI_NAME, rx_data); 

		while (SPI_I2S_GetFlagStatus(SPI_NAME, SPI_I2S_FLAG_RXNE) == RESET); 
		rx_data = SPI_I2S_ReceiveData(SPI_NAME);
		tx_data = rx_data;

		printf("ReciveData:%d\r\n",rx_data);
		printf("SendData:%d\r\n",tx_data);

}

ESP32S3:
使用SPI3,配置流程:

  1. 总线配置
  2. 设备配置
#ifndef _SPI_COM_H_
#define _SPI_COM_H_

#include <stdio.h>
#include <string.h>
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_log.h" 

// 定义 SPI 主机编号
#define SPI_HOST_NUM  SPI3_HOST 

// 定义 SPI 最大传输大小
#define SPI_MAX_TRANSFER_SIZE  1024 

#define SPI_SEND_RECIVE_LEN  8*1
// 定义 SPI 引脚编号
#define PIN_NUM_MOSI  GPIO_NUM_1// 发送端 SDA 14
#define PIN_NUM_MISO  GPIO_NUM_14 // 接收端 SDI
#define PIN_NUM_CLK   GPIO_NUM_21 // 时钟 SCL
#define PIN_NUM_CS    GPIO_NUM_47 // 片选 CS

void spi_com_init(void);
esp_err_t spi3_send_data(uint8_t *data);
#endif
#include "SPI_COM.h"

// 定义日志标签
static const char *TAG = "SPI_COM_TEST";

// SPI句柄
spi_device_handle_t spi;


// SPI2初始化函数
void spi_com_init(void)
{
    spi_bus_config_t buscfg;
    buscfg.miso_io_num = PIN_NUM_MISO;
    buscfg.mosi_io_num = PIN_NUM_MOSI;
    buscfg.sclk_io_num = PIN_NUM_CLK;
    buscfg.quadwp_io_num = -1;
    buscfg.quadhd_io_num = -1;
    buscfg.max_transfer_sz = SPI_MAX_TRANSFER_SIZE;
    buscfg.flags = SPICOMMON_BUSFLAG_MASTER;
    buscfg.isr_cpu_id = ESP_INTR_CPU_AFFINITY_1;
    buscfg.intr_flags = 0;
    // 初始化SPI总线
    esp_err_t ret = spi_bus_initialize(SPI_HOST_NUM, &buscfg, SPI_DMA_CH_AUTO);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "SPI bus 初始化失败: %s", esp_err_to_name(ret));
        return;
    }


    spi_device_interface_config_t devcfg;
    devcfg.command_bits = 0;
    devcfg.address_bits = 0;
    devcfg.dummy_bits = 0;
    devcfg.mode = 0;
    devcfg.clock_source = SPI_CLK_SRC_APB;
    devcfg.clock_speed_hz = 1 * 1000 * 1000; // 1MHz
    devcfg.spics_io_num = PIN_NUM_CS;
    devcfg.queue_size = 10;
    devcfg.flags = 0; // 全双工

    // 将设备添加到SPI总线
    ret = spi_bus_add_device(SPI_HOST_NUM, &devcfg, &spi);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "SPI device 初始化失败: %s", esp_err_to_name(ret));
        spi_bus_free(SPI_HOST_NUM);
        return;
    }

    ESP_LOGI(TAG, "SPI3 初始化成功!");
}
esp_err_t spi3_send_data(uint8_t *data)
{
    if (!spi)
    {
        return ESP_FAIL;
    }


    uint8_t rx_buffer[1];
    spi_transaction_t t;
    t.length = SPI_SEND_RECIVE_LEN; 
    t.rxlength = SPI_SEND_RECIVE_LEN;
    t.tx_buffer = data;
    t.rx_buffer = rx_buffer;
    t.flags = 0;

    // 启动传输
    esp_err_t er = spi_device_transmit(spi, &t);

    ESP_LOGI(TAG, "发送数据:%d",  data[0]);
    ESP_LOGI(TAG, "接收数据:%d",  rx_buffer[0]);
    return er;
}

测试代码
STM32:

#include "FreeRTOS.h"
#include "task.h"
#include "delay.h"
#include "usart.h"
#include "spi_com.h"


// 任务句柄
TaskHandle_t app_start_handle = NULL;
TaskHandle_t app_task1_handle = NULL;
TaskHandle_t app_task2_handle = NULL;

// 任务函数声明
void App_Start(void* pvParamenters);
void App_Task1(void* pvParamenters);
void App_Task2(void* pvParamenters);

// 主函数
int main(void)
{
	//将全部优先级设为抢占优先级 0-15 级
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

    //创建任务
    xTaskCreate((TaskFunction_t)App_Start,          //任务入口函数
               (const char *)"App_Start",          //任务名字
               (uint16_t)configMINIMAL_STACK_SIZE*2, //任务栈大小
               (void*)NULL,                        //任务入口函数参数
               (UBaseType_t)1,                     //任务优先级  提高优先级
               (TaskHandle_t*)&app_start_handle);  //任务句柄
					
	 //开启任务调度器                        
    vTaskStartScheduler();
}

void App_Start(void *pvParameters)
{ 
	
	taskENTER_CRITICAL(); //进入临界区
	usart1_init(115200); // 初始化串口,波特率为 115200
	SPI_COM_Init();
	taskEXIT_CRITICAL();//退出临界区
	 
	 
    xTaskCreate((TaskFunction_t)App_Task1,          //任务入口函数
               (const char *)"App_Task1",          //任务名字
               (uint16_t)configMINIMAL_STACK_SIZE*2, //任务栈大小
               (void*)NULL,                        //任务入口函数参数
               (UBaseType_t)1,                     //任务优先级  提高优先级
               (TaskHandle_t*)&app_task1_handle);  //任务句柄
    //创建任务
    xTaskCreate((TaskFunction_t)App_Task2,          //任务入口函数
               (const char *)"App_Task2",          //任务名字
               (uint16_t)configMINIMAL_STACK_SIZE*2, //任务栈大小
               (void*)NULL,                        //任务入口函数参数
               (UBaseType_t)1,                     //任务优先级
               (TaskHandle_t*)&app_task2_handle);  //任务句柄
					
    vTaskDelete(NULL);	 
}


void App_Task1(void *pvParameters)
{ 
	while(1)
	{
		delay_ms(2000);
		printf("App_Task1 is running...\r\n");
	}

}

void App_Task2(void *pvParameters)
{	

	while(1)
	{
	    delay_ms(10);
        printf("Hello, this is a test message!\r\n");
		send_data();
	}
}

ESP32S3:

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "SPI_COM.h"
static const char *TAG = "MAIN_TEST";

// 任务句柄
TaskHandle_t task1_handle = NULL;
TaskHandle_t task2_handle = NULL;

// 函数声明
void task_1(void *pvParameters);
void task_2(void *pvParameters);


// 入口函数导出
extern "C" void app_main()
{

    // 创建 task_1 任务,指定运行在核心 0
    BaseType_t result1 = xTaskCreatePinnedToCore(task_1, "task_1", 1024 * 10, NULL, 1, &task1_handle, 0);

    // 创建 task_2 任务,指定运行在核心 0
    BaseType_t result2 = xTaskCreatePinnedToCore(task_2, "task_2", 1024 * 10, NULL, 1, &task2_handle, 0);
}

// task_1 任务函数
void task_1(void *pvParameters)
{
    while (1)
    {
        vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟 1 秒
        ESP_LOGI(TAG, "这是 task_1 在运行");
    }
}

// task_2 任务函数
void task_2(void *pvParameters)
{
    spi_com_init();
    vTaskDelay(pdMS_TO_TICKS(500));
    uint8_t test_data[] = {0x12};

    while (1)
    {
        ESP_LOGI(TAG, "这是 task_2 在运行");
        vTaskDelay(pdMS_TO_TICKS(500));
        test_data[0]++;
        test_data[0] = test_data[0] % 15;
        spi3_send_data(test_data);
    }
}


http://www.kler.cn/a/541395.html

相关文章:

  • 【AI学习】关于 DeepSeek-R1的几个流程图
  • 基于YOLOv8+PyQt5的目标检测系统(环境配置+数据集+Python源码+PyQt5界面)——持续更新中
  • 怎么查看电脑显存大小(查看电脑配置)
  • shell脚本控制——定时运行作业
  • Sentinel——Spring Boot 应用接入 Sentinel 后内存开销增长计算方式
  • opencv图像处理
  • Ansible与shell脚本执行的区别
  • 数据源和 sqlSessionFactory 配置
  • 13.10 统一配置管理中心:TranslationChain 架构的简洁配置管理方案
  • DeepSeek的崛起:开源AI的革命
  • WebSocket学习记录
  • 数据治理双证通关经验分享 | CDGA/CDGP备考全指南
  • Leetcode Hot100 81-85
  • 企业AI招聘会在2025年出现爆炸式增长吗?
  • Node.js笔记入门篇
  • 【从零开始入门unity游戏开发之——C#篇54】C#补充知识点——元组(Tuple)
  • 【SpringBoot实现全局API限频】 最佳实践
  • AWS Savings Plans 监控与分析工具使用指南
  • 开源AI终端工具Wave Terminal从安装到远程连接内网服务器全流程攻略
  • 深度整理总结MySQL——MySQL加锁工作原理
  • 【图片转换PDF】多个文件夹里图片逐个批量转换成多个pdf软件,子文件夹单独合并转换,子文件夹单独批量转换,基于Py的解决方案
  • 安卓基础(第一集)
  • 嵌入式音视频开发(一)ffmpeg框架及内核解析
  • hive数仓的分层与建模
  • 食品饮料生产瓶颈?富唯智能协作机器人来 “破壁”
  • 使用 DeepSeek 进行图像描述:多模态 AI 技术实践