ESP32的SPI外设(SPI HSPI VSPI)
ESP32的SPI外设(SPI HSPI VSPI)
ESP32 SPI简介
参考文档:ESP32技术参考手册
ESP32的SPI一共有4个,分别为SPI0、SPI1、SPI2、SPI3。如下图所示:
其中SPI0和SPI1通过一个仲裁器共用一组信号总线,这组信号总线前缀带有SPI,主要用于访问外部存储单元和DMA操作。所以SPI信号总线不是提供给用户使用的。
SPI2和SPI3分别使用带HSPI前缀和带VSPI前缀的信号总线。这两个控制器可以供用户使用。这两组SPI控制器器既可作为主机使用又可作为从机使用。当SPI控制器作为主机时,每个控制器都有CS0、CS1、CS2 3个片选信号,可以连接多个SPI从机。
下图分别为SPI、HSPI、VSPI的信号总线名称:
在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模式