ESP32学习笔记(20)——SPI(从机)接口使用

发布者:Jinyu521最新更新时间:2025-02-28 来源: jianshu关键字:ESP32  SPI  从机 手机看文章 扫描二维码
随时随地手机看文章

一、SPI简介

SPI(Serial Peripheral Interface) 协议是由摩托罗拉公司提出的通讯协议,即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合。

芯片的管脚上只占用四根线。
MISO: 主器件数据输出,从器件数据输入。
MOSI:主器件数据输入,从器件数据输出。
SCK: 时钟信号,由主设备控制发出。
NSS(CS): 从设备选择信号,由主设备控制。当NSS为低电平则选中从器件。

1.1 ESP32中SPI

ESP32集成了两个通用SPI控制器,可用作片外SPI主设备驱动的从节点


SPI2,有时也称为HSPI


SPI3,有时也称为VSPI

SPI2和SPI3具有独立的信号总线,分别具有相同的名称。


SPI从驱动程序允许将SPI外设用作全双工设备。驱动程序可以发送/接收最长64个字节的数据,或者使用DMA发送/接收更长的数据。但是,存在一些与DMA相关的已知问题。


启用DMA时应当将收发缓存设定为字对齐模式(是4字节的倍数)。


从机模式的DMA需要主机时钟的保持时间足够长才能工作,如果主机无法满足只能放弃使用DMA。


ESP-IDF 编程指南——SPI从驱动


1.2 SPI传输

当主机置位CS线并开始在SCLK线上发送时钟脉冲时,将开始全双工SPI传输。每个时钟脉冲,数据位都从主机移到MOSI线上的设备,同时又移回到MISO线上。在传输结束时,主机将断开CS线路。


使用spi_slave_interface_config_t结构体来设置SPI从模式的物理接口


//用于配置SPI从机接口的spi_slave_interface_config_t结构体spi_slave_interface_config_t slvcfg={

    .mode,//SPI模式,配置为0-3

    .spics_io_num,//片选信号线复用IO

    .queue_size,//传输队列大小,设置同时最多有多少挂起的传输

    .flags,//接口属性,使用位或运算符|连接各属性参数

    .post_setup_cb,//SPI寄存器加载新数据时调用的回调函数

    .post_trans_cb//传输完成回调函数};

使用spi_slave_transaction_t结构体设置从模式下的数据格式和数据缓冲区大小等


//描述一次SPI传输的结构体spi_slave_transaction_t{

    .length,//总数据长度

    .trans_len,//传输数据长度

    .tx_buffer,//数据发送缓冲区指针

    .rx_buffer,//数据接收缓冲区指针

    .user//用户定义变量,一般用于存储本次传输的ID}//注意:上述长度的单位是比特

使用spi_transaction_t结构体配置单独收取/单独发送等特殊情况的传输数据格式


/**

 * This structure describes one SPI transaction. The descriptor should not be modified until the transaction finishes.

 */struct spi_transaction_t {

    uint32_t flags;                 ///< Bitwise OR of SPI_TRANS_* flags

    uint16_t cmd;                   /**< Command data, of which the length is set in the ``command_bits`` of spi_device_interface_config_t.

                                      *

                                      *  NOTE: this field, used to be 'command' in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF 3.0.

                                      *

                                      *  Example: write 0x0123 and command_bits=12 to send command 0x12, 0x3_ (in previous version, you may have to write 0x3_12).

                                      */

    uint64_t addr;                  /**< Address data, of which the length is set in the ``address_bits`` of spi_device_interface_config_t.

                                      *

                                      *  NOTE: this field, used to be 'address' in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF3.0.

                                      *

                                      *  Example: write 0x123400 and address_bits=24 to send address of 0x12, 0x34, 0x00 (in previous version, you may have to write 0x12340000).

                                      */

    size_t length;                  ///< Total data length, in bits

    size_t rxlength;                ///< Total data length received, should be not greater than ``length`` in full-duplex mode (0 defaults this to the value of ``length``).

    void *user;                     ///< User-defined variable. Can be used to store eg transaction ID.

    union {

        const void *tx_buffer;      ///< Pointer to transmit buffer, or NULL for no MOSI phase

        uint8_t tx_data[4];         ///< If SPI_TRANS_USE_TXDATA is set, data set here is sent directly from this variable.

    };

    union {

        void *rx_buffer;            ///< Pointer to receive buffer, or NULL for no MISO phase. Written by 4 bytes-unit if DMA is used.

        uint8_t rx_data[4];         ///< If SPI_TRANS_USE_RXDATA is set, data is received directly to this variable

    };} ;        //the rx data should start from a 32-bit aligned address to get around dma issue.

如果spi_slave_interface_config_t::rx_buffer=NULL,则跳过读取数据段;

如果spi_slave_interface_config_t::tx_buffer=NULL,则跳过写入数据段。


传输开始前,应当配置好一个或以上的spi_slave_transaction_t结构体。


注意:如果传输的数据大于32字节,需要使能DMA通道1或通道2,如果不使用DMA,应将dma_chan参数设置为0。


1.3 GPIO矩阵和IO_MUX

ESP32的大多数外设信号都直接连接到其专用的IO_MUX引脚。但是,也可以使用GPIO矩阵将信号转换到任何其他可用的引脚。如果至少一个信号通过GPIO矩阵转换,则所有信号都将通过GPIO矩阵转换。


GPIO矩阵引入了转换灵活性,但也带来了以下缺点:


增加了MISO信号的输入延迟,这更可能违反MISO设置时间。如果SPI需要高速运行,请使用专用的IO_MUX引脚。


如果使用IO_MUX引脚,则允许信号的时钟频率最多为40 MHz,而时钟频率最高为80 MHz。


SPI总线的IO_MUX引脚如下所示

引脚对应的GPIOSPI2SPI3
CS0 *155
SCLK1418
MISO1219
MOSI1323
QUADWP222
QUADHD421
  • 仅连接到总线的第一个设备可以使用CS0引脚。

二、API说明

以下 SPI 主机接口位于 driver/include/driver/spi_slave.h。

2.1 spi_slave_initialize



注意:如果使用了DMA,需要保证使用pvPortMallocCaps(size, MALLOC_CAP_DMA)为缓冲区开辟内存,这样可以保障DMA能够访问到这些缓冲区


2.2 spi_slave_free

2.3 spi_slave_queue_trans

2.4 spi_slave_get_trans_result

2.5 spi_slave_transmit

三、编程流程

3.1 设置通信参数

通过调用函数初始化SPI总线spi_slave_initialize()。确保在struct中设置正确的I / O引脚bus_config。将不需要的信号设置为-1。


如果传输长于32个字节,则通过将参数分别设置dma_chan为1或来允许DMA通道1或2。否则,设置dma_chan为0。


3.2 运行SPI通信

在启动传输之前,请spi_slave_transaction_t使用所需的通信参数填充一个或多个结构。通过调用该函数将所有传输排队spi_slave_queue_trans(),然后在以后使用该函数查询结果spi_slave_get_trans_result(),或者通过将所有请求馈入来单独处理所有请求spi_slave_transmit()。后两个功能将被阻塞,直到主机启动并完成传输,从而导致发送和接收的数据排队。


(可选)要卸载SPI从驱动程序,请调用spi_slave_free()。


四、SPI从机接收代码

使用 esp-idfexamplesperipheralsspi_slavereceiver 中的例程


/* 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 #include #include #include #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 12#define GPIO_MISO 13#define GPIO_SCLK 15#define GPIO_CS 14#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<    WRITE_PERI_REG(GPIO_OUT_W1TC_REG, (1<    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,

[1] [2]
关键字:ESP32  SPI  从机 引用地址:ESP32学习笔记(20)——SPI(从机)接口使用

上一篇:ESP32学习笔记(21)——构建自己的工程和组件库
下一篇:ESP32学习笔记(19)——SPI(主机)接口使用

推荐阅读最新更新时间:2026-03-24 10:57

PIC单片SPI框架
#include pic.h #include string.h #include STDIO.H __CONFIG(0x3F32); //芯片配置字 选择HS模式振荡器,关WDT typedef unsigned char uchar; typedef unsigned int uint; uchar resive=0; uchar resive1=0; uchar send_buf ={'S',0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xc,0xD,0xe,0xf}; uchar send_num=0; uchar resive_flag=0; #defi
[单片机]
玩转 ESP32 + Arduino (六) 硬件定时器, IIC, SPI
一. 硬件定时器 ESP32 芯片包含两个硬件定时器组。每组有两个通用硬件定时器。它们都是基于 16 位预分频器和 64 位自动重载功能的向上/向下计数器的 64 位通用定时器。 1. 初始化定时器 timerBegin hw_timer_t * timerBegin(uint8_t num, uint16_t divider, bool countUp){} 参数: num : 定时器编号 divider:分频数 countUp: 是否是累加模式 返回值: 返回一个计时器结构体指针 hw_timer_t * ,我们预定义一个指针接收他 hw_timer_t* tim1= NULL;tim1 = t
[单片机]
玩转 ESP32 + Arduino(二十八) TFT_eSPI库驱动ST7789(SPI接口)
我们用到的库 TFT_eSPI 一. 硬件接线 这里我们使用了中景园的ST7789 一般屏幕的引脚定义如下: 接线: 我们直接用VSPI接线 ESP32引脚 ST7789引脚 功能 GND GND 接地 3V3 VCC 电源 (VCLK)18 SCL SPI时钟线 (VMOSI)23 SDA SPI主出从入线 26 RES 复位引脚 27 DC 数据/命令选择线 (VCS0)5 CS SPI片选线 没接 BLK 背光控制线 如何在TFT_eSPI中设置引脚?? 首先, 我们打开 User_Setup.h, 具体位置在(platformIO平台): 然后根据文件中的提示设置就可以了, 对
[单片机]
ESP32学习笔记(19)——SPI(主机)接口使用
一、SPI简介 SPI(Serial Peripheral Interface) 协议是由摩托罗拉公司提出的通讯协议,即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合。 芯片的管脚上只占用四根线。 MISO: 主器件数据输入,从器件数据输出。 MOSI:主器件数据输出,从器件数据输入。 SCK: 时钟信号,由主设备控制发出。 NSS(CS): 从设备选择信号,由主设备控制。当NSS为低电平则选中从器件。 1.1 ESP32中SPI ESP32集成了4个SPI外设。 SPI0和SPI1在内部用于访问ESP32所连接的闪存。两个控制器共享相同的SP
[单片机]
21. 通过串口51单片接收一个16进制数
代码功能:通过串口从51单片机接收一个16进制数 Python代码如下: import os # 导入os模块,处理操作系统相关事务 import serial # 导入serial模块,串口通信相关 com = serial.Serial('COM4', 9600) # 打开指定串口,设置串口通信波特率 def rec_data(): # 接收数据函数 while True: # 循环检测 a = com.read() # 从串口接收数据,类型为bytes b = int(a.hex(), 16) # 将接收的数据转化为16进制数字型,注意是数字型,不是字符串型 if
[单片机]
如何单片平台编写GPIO口程序
单片机平台编写 GPIO 口程序,以 STM32F103 为例,有三种模式:库函数、HAL库、寄存器。 使用库函数的方式操控 GPIO 方式如下: void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能 PB 端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //PB5 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
[单片机]
单片学不会怎么办?单片哪里开始学?
说起单片机学不会怎么办?就想起我自己一些学习的辛酸史。 我做单片机开发10余年了,单片机硬软件开发自然是很熟练了。 但做其他的工作可是一窍不通,在上家公司工作的时候,公司有很多上位机软件开发的需求,所以我就想扩展一下自己的知识,想学一下 C++ 和Java。 话说活到老学到老,多掌握一门技术,将来发展的空间就越大。 我有个朋友是这一块开发的高手,他给我提供了很多资料。 先是看基础语法,又是搭环境,开始实战,奋斗了大概3周,找不到感觉,对着理论知识,冷冰冰的提不起继续学习的兴趣,就放下了,没有继续。 后面想了一下,没继续学习的最主要原因还是没有目标驱动。 如果工作上正好有个项目需要,根据项目需求针对性去学习,相信我很快能学会。 还有
[单片机]
单片学习入门到入土?这3个关键点导致!
今天跟大家分享下我们无际单片机编程学员最近问的比较多的3个问题,这3个问题也是当初我在学习单片机过程中碰到的,并且踩过坑。 今天我就来分享下这3个问题,然后也说一下自己的解决办法,希望对各位兄弟有帮助。 1.程序用keil编译出现错误,不知道怎么解决 有的人编写完程序用keil编译的时候出现了一些错误,不知道怎么去解决。 其实这个问题没有一个标准的答案,因为每个人产生的错误是不一样的。 我这么多年解决的办法就是把错误直接拷贝到度娘上面去搜索,可能搜不到能够完美解决你这个错误的答案。 但是呢,他们的思路是很值得借鉴的,我基本上就是通过这些思路去解决这些错误的。 我举个我们学员的例子: 他的一个stm32工程编译完以后出现了这个错
[单片机]
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved