ESP32的SPI外设(SPI HSPI VSPI)

发布时间 2023-12-14 12:04:25作者: HAOstudio

ESP32的SPI外设(SPI HSPI VSPI)

ESP32 SPI简介

参考文档:ESP32技术参考手册

ESP32的SPI一共有4个,分别为SPI0、SPI1、SPI2、SPI3。如下图所示:

imag1

其中SPI0SPI1通过一个仲裁器共用一组信号总线,这组信号总线前缀带有SPI,主要用于访问外部存储单元和DMA操作。所以SPI信号总线不是提供给用户使用的。

SPI2SPI3分别使用带HSPI前缀和带VSPI前缀的信号总线。这两个控制器可以供用户使用。这两组SPI控制器器既可作为主机使用又可作为从机使用。当SPI控制器作为主机时,每个控制器都有CS0、CS1、CS2 3个片选信号,可以连接多个SPI从机。

下图分别为SPI、HSPI、VSPI的信号总线名称:
imag2

在Arduino中使用SPI

在Arduino中,可以使用SPI.h库来使用SPI外设。

SPI.cpp源文件的最后面定义了SPIClass对象SPI

#if CONFIG_IDF_TARGET_ESP32
SPIClass SPI(VSPI);
#else
SPIClass SPI(FSPI);
#endif

sdkconfig.h头文件中可以查看到已经定义了#define CONFIG_IDF_TARGET_ESP32 1,所以是使用VSPI定义了SPI对象。

esp32-hal-spi.h文件中可以找到如下代码,这里定义了FSPI、HSPI、VSPI的宏定义,分别为1、2、3。

#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
#define FSPI  0
#define HSPI  1
#else
#define FSPI  1 //SPI bus attached to the flash (can use the same data lines but different SS)
#define HSPI  2 //SPI bus normally mapped to pins 12 - 15, but can be matrixed to any pins
#if CONFIG_IDF_TARGET_ESP32
#define VSPI  3 //SPI bus normally attached to pins 5, 18, 19 and 23, but can be matrixed to any pins
#endif
#endif

SPI.cpp文件中定义了SPIClass类,在创建SPI对象时,传入了VSPI(3),也就是说将该类中的成员变量_spi_num赋值为了3。
然后,初始化SPI需要调用begin函数,在该函数中,使用了spiStartBus函数创建了_spi结构体,意味着_spi使用的是VSPI的控制器。

SPIClass::SPIClass(uint8_t spi_bus)
    :_spi_num(spi_bus)
    ,_spi(NULL)
    ,_use_hw_ss(false)
    ,_sck(-1)
    ,_miso(-1)
    ,_mosi(-1)
    ,_ss(-1)
    ,_div(0)
    ,_freq(1000000)
    ,_inTransaction(false)

......

void SPIClass::begin(int8_t sck, int8_t miso, int8_t mosi, int8_t ss)
{
    if(_spi) {
        return;
    }

    if(!_div) {
        _div = spiFrequencyToClockDiv(_freq);
    }

    _spi = spiStartBus(_spi_num, _div, SPI_MODE0, SPI_MSBFIRST);
    if(!_spi) {
        return;
    }

    if(sck == -1 && miso == -1 && mosi == -1 && ss == -1) {
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
        _sck = (_spi_num == FSPI) ? SCK : -1;
        _miso = (_spi_num == FSPI) ? MISO : -1;
        _mosi = (_spi_num == FSPI) ? MOSI : -1;
        _ss = (_spi_num == FSPI) ? SS : -1;
#elif CONFIG_IDF_TARGET_ESP32C3
        _sck = SCK;
        _miso = MISO;
        _mosi = MOSI;
        _ss = SS;
#else
        _sck = (_spi_num == VSPI) ? SCK : 14;
        _miso = (_spi_num == VSPI) ? MISO : 12;
        _mosi = (_spi_num == VSPI) ? MOSI : 13;
        _ss = (_spi_num == VSPI) ? SS : 15;
#endif
    } else {
        _sck = sck;
        _miso = miso;
        _mosi = mosi;
        _ss = ss;
    }

    spiAttachSCK(_spi, _sck);
    spiAttachMISO(_spi, _miso);
    spiAttachMOSI(_spi, _mosi);

}

begin函数中可以指定SPI所使用的引脚sck、miso、mosi、ss(在ESP32中,大部分外设可以指定在任意IO引脚上)。如果没有指定,默认为-1,代表着使用默认引脚。

可以看到,如果_spi_num为VSPI(3),则默认引脚使用SCK、MISO、MOSI、SS,这些引脚在pin_arduino.h头文件中定义为了如下所示的IO引脚。

static const uint8_t SS    = 5;
static const uint8_t MOSI  = 23;
static const uint8_t MISO  = 19;
static const uint8_t SCK   = 18;

而如果_spi_num为HSPI(2),则默认引脚使用SCK(14)、MISO(12)、MOSI(13)、SS(15)。

如果指定了引脚,则会将SPI的总线映射在所指定的引脚上。

以上介绍的都是如何使用已经定义好的SPI。如果用户自定义了另外一个SPIClass对象,而且没有指定spi_bus,则默认会使用HSPI。相关代码在SPI.h中,如下所示:

SPIClass(uint8_t spi_bus=HSPI);

示例

下面介绍一下如何在Arduino中完整的使用自定义SPI:

#define MY_CS   32
#define MY_SCK  14
#define MY_MOSI 13
#define MY_MISO 12 //自定义spi引脚
SPIClass my_spi(HSPI);  //创建SPIClass对象my_spi

my_spi.begin(MY_SCK, MY_MISO, MY_MOSI, MY_CS);  //初始化,并绑定引脚
spiAttachSS(my_spi.bus(), 0, MY_CS);            //绑定cs引脚
my_spi.setFrequency(18000000);                  //设置频率,18MHz
my_spi.setBitOrder(SPI_MSBFIRST);               //设置高位bit先传输
my_spi.setDataMode(SPI_MODE1);                  //设置spi模式