完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第72章 STM32H7的SPI總線基礎知識和HAL庫API
本章節為大家講解SPI(Serial peripheral interface)總線的基礎知識和對應的HAL庫API。
72.1 初學者重要提示
72.2 SPI總線基礎知識
72.3 SPI總線的HAL庫用法
72.4 源文件stm32h7xx_hal_spi.c
72.5 總結
72.1 初學者重要提示
- STM32H7的SPI支持4到32bit數據傳輸,而STM32F1和F4系列僅支持8bit或者16bit。
- STM32H7的主頻400MHz時,SPI1, 2, 3最高通信時鍾是100MHz,而SPI4, 5, 6是50MHz。
- STM32H7的MISO和MOSI引腳功能可以互換,使用比較靈活。
- SPI總線的片選引腳SS在單一的主從器件配置下是可選的,一般情況下可以不使用。
- 搜集了幾篇質量比較高的SPI總線介紹帖:http://www.armbbs.cn/forum.php?mod=viewthread&tid=96788。
72.2 SPI總線基礎知識
72.2.1 SPI總線的硬件框圖
認識一個外設,最好的方式就是看它的框圖,方便我們快速的了解SPI的基本功能,然后再看手冊了解細節。
通過這個框圖,我們可以得到如下信息:
- spi_wkup輸出
低功耗喚醒信號。
- spi_it輸出
spi的中斷請求信號。
- spi_tx_dma
- spi_rx_dma
spi的DMA發送和接收請求信號。
- spi_pclk
為寄存器提供時鍾。
- spi_ker_ck
為spi內核時鍾。
- SCK(CK),Serial Clock
此引腳在主機模式下用於時鍾輸出,從機模式下用於時鍾輸入。
- MISO(SDI),Master In / Slave Out data
此引腳在從機模式下用於發送數據,主機模式下接收數據。
- MOSI(SDO), Master Out / Slave In data
此引腳在從機模式下用於數據接收,主機模式下發送數據。
- SS(WS), Slave select pin
根據SPI和SS設置,此引腳可用於:
a. 選擇三個從器件進行通信。
b. 同步數據幀。
c. 檢測多個主器件之間是否存在沖突。
通過這個框圖還要認識到一點,SPI有三個時鍾域,分別是寄存器所在的ABP總線時鍾域,內核時鍾發生器時鍾域以及內核時鍾發生器分頻后的串行時鍾域。
72.2.2 SPI接口的區別和時鍾源(SPI1到SPI6)
這個知識點在初學的時候容易忽視,所以我們這里整理下。
- SPI1到SPI6的區別
- SPI1,SPI2和SPI3支持4到32bit數據傳輸,SPI4,SPI5和SPI6是4到16bit數據傳輸。
- SPI1,SPI2和SPI3的FIFO大小是16*8bit,而SPI4,SPI5和SPI6的FIFO大小是8*8bit。
- SPI1到SPI6的所在的總線(對應SPI框圖的SPI_CLK時鍾域)
SPI1,SPI4和SPI5在APB2總線,SPI2,SPI3在APB1總線,SPI6在APB4總線。注意,SPI的最高時鍾不是由這些總線決定的。
- SPI1到SPI6的支持的最高時鍾(對應SPI框圖的SPI_KER_CK)
STM32H7主頻在400MHz下,SPI1,SPI2和SPI3的最高時鍾是200MHz,而SPI4,5,6是100MHz, 以SPI1為了,可以選擇的時鍾源如下:
這里特別注意一點,SPI工作時最少選擇二分頻,也就是說SPI1,2,3實際通信時鍾是100MHz,而SPI4,5,6是50MHz。
72.2.3 SPI總線全雙工,單工和半雙工通信
片選信號SS在單一的主從器件配置下是可選的,一般情況下可以不使用。但需要同步數據流,或者用於TI模式時需要此信號。
- 全雙工通信
全雙工就是主從器件之間同時互傳數據,SPI總線的全雙工模式接線方式如下:
關於這個接線圖要認識到以下幾點:
-
- 注意接線方式,對於主器件來說MISO引腳就是輸入端,從器件的MISO是輸出端,即Master In / Slave Out data。MOSI也是同樣道理。
- 每個時鍾信號SCK的作用了,主器件的MISO引腳接收1個bit數據,MOSI引腳輸出1個bit數據。
- 這種單一的主從接線模式下,SS引腳可以不使用。
- 半雙工通信
半雙工就是同一個時刻只能為一個方向傳輸數據,SPI總線的半工模式接線方式如下:
關於這個接線圖要認識到以下幾點:
-
- 更改通信方式時,要先禁止SPI。
- 主器件的MISO和從器件的MISO不使用,可以繼續用作標准GPIO。
- 1KΩ的接線電阻很有必要,因為當主器件和從器件的通信方向不是同步變化時,容易出現其中一個輸出低電平,另一個輸出高電平,造成短路。
- 這種單一的主從接線模式下,SS引腳可以不使用。
- 單工模式
單工就是只有一種通信方向,即發送或者接收,SPI總線的全雙工模式接線方式如下:
關於這個接線圖要認識到以下幾點:
-
- 未用到的MOSI或者MISO可以用作標准GPIO。
- 這種單一的主從接線模式下,SS引腳可以不使用。
72.2.4 SPI總線星型拓撲
SPI總線星型拓撲用到的地方比較多,V7開發板就是用的星型拓撲外接多種SPI器件:
關於這個接線圖,有以下幾點需要大家了解:
- 主器件的SS引腳不使用,使用通用GPIO控制。為每個器件配一個SS引腳,方便單獨片選控制。
- 從器件的MISO引腳要配置為復用開漏輸出(很多外部芯片在未片選時,數據引腳是呈現高阻態)。
72.2.5 SPI總線通信格式
SPI總線主要有四種通信格式,由CPOL時鍾極性和CPHA時鍾相位控制:
四種通信格式如下:
- 當CPOL = 1, CPHA = 1時
SCK引腳在空閑狀態處於低電平,SCK引腳的第2個邊沿捕獲傳輸的第1個數據。
- 當CPOL = 0, CPHA = 1時
SCK引腳在空閑狀態處於高電平,SCK引腳的第2個邊沿捕獲傳輸的第1個數據。
- 當CPOL = 1, CPHA = 0時
SCK引腳在空閑狀態處於低電平,SCK引腳的第1個邊沿捕獲傳輸的第1個數據。
- 當CPOL = 1, CPHA = 0時
SCK引腳在空閑狀態處於高電平,SCK引腳的第1個邊沿捕獲傳輸的第1個數據。
72.3 SPI總線的HAL庫用法
72.3.1 SPI總線結構體SPI_TypeDef
SPI總線相關的寄存器是通過HAL庫中的結構體SPI_TypeDef定義的,在stm32h743xx.h中可以找到這個類型定義:
typedef struct { __IO uint32_t CR1; /*!< SPI/I2S Control register 1, Address offset: 0x00 */ __IO uint32_t CR2; /*!< SPI Control register 2, Address offset: 0x04 */ __IO uint32_t CFG1; /*!< SPI Configuration register 1, Address offset: 0x08 */ __IO uint32_t CFG2; /*!< SPI Configuration register 2, Address offset: 0x0C */ __IO uint32_t IER; /*!< SPI/I2S Interrupt Enable register, Address offset: 0x10 */ __IO uint32_t SR; /*!< SPI/I2S Status register, Address offset: 0x14 */ __IO uint32_t IFCR; /*!< SPI/I2S Interrupt/Status flags clear register, Address offset: 0x18 */ uint32_t RESERVED0; /*!< Reserved, 0x1C */ __IO uint32_t TXDR; /*!< SPI/I2S Transmit data register, Address offset: 0x20 */ uint32_t RESERVED1[3]; /*!< Reserved, 0x24-0x2C */ __IO uint32_t RXDR; /*!< SPI/I2S Receive data register, Address offset: 0x30 */ uint32_t RESERVED2[3]; /*!< Reserved, 0x34-0x3C */ __IO uint32_t CRCPOLY; /*!< SPI CRC Polynomial register, Address offset: 0x40 */ __IO uint32_t TXCRC; /*!< SPI Transmitter CRC register, Address offset: 0x44 */ __IO uint32_t RXCRC; /*!< SPI Receiver CRC register, Address offset: 0x48 */ __IO uint32_t UDRDR; /*!< SPI Underrun data register, Address offset: 0x4C */ __IO uint32_t I2SCFGR; /*!< I2S Configuration register, Address offset: 0x50 */ } SPI_TypeDef;
這個結構體的成員名稱和排列次序和CPU的寄存器是一 一對應的。
__IO表示volatile, 這是標准C語言中的一個修飾字,表示這個變量是非易失性的,編譯器不要將其優化掉。core_m7.h 文件定義了這個宏:
#define __O volatile /*!< Defines 'write only' permissions */ #define __IO volatile /*!< Defines 'read / write' permissions */
下面我們看下SPI的定義,在stm32h743xx.h文件。
#define PERIPH_BASE (0x40000000UL) #define D2_APB1PERIPH_BASE PERIPH_BASE #define D2_APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL) #define D3_APB1PERIPH_BASE (PERIPH_BASE + 0x18000000UL) #define SPI2_BASE (D2_APB1PERIPH_BASE + 0x3800UL) #define SPI3_BASE (D2_APB1PERIPH_BASE + 0x3C00UL) #define SPI1_BASE (D2_APB2PERIPH_BASE + 0x3000UL) #define SPI4_BASE (D2_APB2PERIPH_BASE + 0x3400UL) #define SPI5_BASE (D2_APB2PERIPH_BASE + 0x5000UL) #define SPI6_BASE (D3_APB1PERIPH_BASE + 0x1400UL) #define SPI1 ((SPI_TypeDef *) SPI1_BASE) #define SPI2 ((SPI_TypeDef *) SPI2_BASE) #define SPI3 ((SPI_TypeDef *) SPI3_BASE) #define SPI4 ((SPI_TypeDef *) SPI4_BASE) #define SPI5 ((SPI_TypeDef *) SPI5_BASE) #define SPI6 ((SPI_TypeDef *) SPI6_BASE) <----- 展開這個宏,(FLASH_TypeDef *)0x58001400
我們訪問SPI的CR1寄存器可以采用這種形式:SPI->CR1 = 0。
72.3.2 SPI總線初始化結構體SPI_InitTypeDef
下面是SPI總線的初始化結構體,用到的地方比較多:
typedef struct { uint32_t Mode; uint32_t Direction; uint32_t DataSize; uint32_t CLKPolarity; uint32_t CLKPhase; uint32_t NSS; uint32_t BaudRatePrescaler; uint32_t FirstBit; uint32_t TIMode; uint32_t CRCCalculation; uint32_t CRCPolynomial; uint32_t CRCLength; uint32_t NSSPMode; uint32_t NSSPolarity; uint32_t TxCRCInitializationPattern; uint32_t RxCRCInitializationPattern; uint32_t MasterSSIdleness; uint32_t MasterInterDataIdleness; uint32_t MasterReceiverAutoSusp; uint32_t MasterKeepIOState; uint32_t IOSwap; } SPI_InitTypeDef;
下面將結構體成員逐一做個說明:
- Mode
用於設置工作在主機模式還是從機模式。
#define SPI_MODE_SLAVE (0x00000000UL) #define SPI_MODE_MASTER SPI_CFG2_MASTER
- Direction
用於設置SPI工作在全雙工,單工,還是半雙工模式。
#define SPI_DIRECTION_2LINES (0x00000000UL) /* 全雙工 */ #define SPI_DIRECTION_2LINES_TXONLY SPI_CFG2_COMM_0 /* 單工,僅發送 */ #define SPI_DIRECTION_2LINES_RXONLY SPI_CFG2_COMM_1 /* 單工,僅接收 */ #define SPI_DIRECTION_1LINE SPI_CFG2_COMM /* 半雙工 */
- DataSize
用於設置SPI總線數據收發的位寬,支持4-32bit。
#define SPI_DATASIZE_4BIT (0x00000003UL) #define SPI_DATASIZE_5BIT (0x00000004UL) #define SPI_DATASIZE_6BIT (0x00000005UL) #define SPI_DATASIZE_7BIT (0x00000006UL) #define SPI_DATASIZE_8BIT (0x00000007UL) #define SPI_DATASIZE_9BIT (0x00000008UL) #define SPI_DATASIZE_10BIT (0x00000009UL) #define SPI_DATASIZE_11BIT (0x0000000AUL) #define SPI_DATASIZE_12BIT (0x0000000BUL) #define SPI_DATASIZE_13BIT (0x0000000CUL) #define SPI_DATASIZE_14BIT (0x0000000DUL) #define SPI_DATASIZE_15BIT (0x0000000EUL) #define SPI_DATASIZE_16BIT (0x0000000FUL) #define SPI_DATASIZE_17BIT (0x00000010UL) #define SPI_DATASIZE_18BIT (0x00000011UL) #define SPI_DATASIZE_19BIT (0x00000012UL) #define SPI_DATASIZE_20BIT (0x00000013UL) #define SPI_DATASIZE_21BIT (0x00000014UL) #define SPI_DATASIZE_22BIT (0x00000015UL) #define SPI_DATASIZE_23BIT (0x00000016UL) #define SPI_DATASIZE_24BIT (0x00000017UL) #define SPI_DATASIZE_25BIT (0x00000018UL) #define SPI_DATASIZE_26BIT (0x00000019UL) #define SPI_DATASIZE_27BIT (0x0000001AUL) #define SPI_DATASIZE_28BIT (0x0000001BUL) #define SPI_DATASIZE_29BIT (0x0000001CUL) #define SPI_DATASIZE_30BIT (0x0000001DUL) #define SPI_DATASIZE_31BIT (0x0000001EUL) #define SPI_DATASIZE_32BIT (0x0000001FUL)
- CLKPolarity
用於設置空閑狀態時,CLK是高電平還是低電平。
#define SPI_POLARITY_LOW (0x00000000UL) #define SPI_POLARITY_HIGH SPI_CFG2_CPOL
- NSS
用於設置NSS信號由硬件NSS引腳管理或者軟件SSI位管理。
#define SPI_NSS_SOFT SPI_CFG2_SSM #define SPI_NSS_HARD_INPUT (0x00000000UL) #define SPI_NSS_HARD_OUTPUT SPI_CFG2_SSOE
- BaudRatePrescaler
用於設置SPI時鍾分頻,僅SPI工作在主控模式下起作用,對SPI從機模式不起作用。
#define SPI_BAUDRATEPRESCALER_2 (0x00000000UL) #define SPI_BAUDRATEPRESCALER_4 (0x10000000UL) #define SPI_BAUDRATEPRESCALER_8 (0x20000000UL) #define SPI_BAUDRATEPRESCALER_16 (0x30000000UL) #define SPI_BAUDRATEPRESCALER_32 (0x40000000UL) #define SPI_BAUDRATEPRESCALER_64 (0x50000000UL) #define SPI_BAUDRATEPRESCALER_128 (0x60000000UL) #define SPI_BAUDRATEPRESCALER_256 (0x70000000UL)
- FirstBit
用於設置數據傳輸從最高bit開始還是從最低bit開始。
#define SPI_FIRSTBIT_MSB (0x00000000UL) #define SPI_FIRSTBIT_LSB SPI_CFG2_LSBFRST
- TIMode
用於設置是否使能SPI總線的TI模式。
#define SPI_TIMODE_DISABLE (0x00000000UL) #define SPI_TIMODE_ENABLE SPI_CFG2_SP_0
- CRCCalculation
用於設置是否使能CRC計算。
#define SPI_CRCCALCULATION_DISABLE (0x00000000UL) #define SPI_CRCCALCULATION_ENABLE SPI_CFG1_CRCEN
- CRCPolynomial
用於設置CRC計算使用的多項式,必須是奇數,范圍0到65535。
- CRCLength
用於設置CRC計算時的CRC長度。大小要與同屬此結構體的DataSize一致。或是DataSize的整數倍。
#define SPI_CRC_LENGTH_DATASIZE (0x00000000UL) #define SPI_CRC_LENGTH_4BIT (0x00030000UL) #define SPI_CRC_LENGTH_5BIT (0x00040000UL) #define SPI_CRC_LENGTH_6BIT (0x00050000UL) #define SPI_CRC_LENGTH_7BIT (0x00060000UL) #define SPI_CRC_LENGTH_8BIT (0x00070000UL) #define SPI_CRC_LENGTH_9BIT (0x00080000UL) #define SPI_CRC_LENGTH_10BIT (0x00090000UL) #define SPI_CRC_LENGTH_11BIT (0x000A0000UL) #define SPI_CRC_LENGTH_12BIT (0x000B0000UL) #define SPI_CRC_LENGTH_13BIT (0x000C0000UL) #define SPI_CRC_LENGTH_14BIT (0x000D0000UL) #define SPI_CRC_LENGTH_15BIT (0x000E0000UL) #define SPI_CRC_LENGTH_16BIT (0x000F0000UL) #define SPI_CRC_LENGTH_17BIT (0x00100000UL) #define SPI_CRC_LENGTH_18BIT (0x00110000UL) #define SPI_CRC_LENGTH_19BIT (0x00120000UL) #define SPI_CRC_LENGTH_20BIT (0x00130000UL) #define SPI_CRC_LENGTH_21BIT (0x00140000UL) #define SPI_CRC_LENGTH_22BIT (0x00150000UL) #define SPI_CRC_LENGTH_23BIT (0x00160000UL) #define SPI_CRC_LENGTH_24BIT (0x00170000UL) #define SPI_CRC_LENGTH_25BIT (0x00180000UL) #define SPI_CRC_LENGTH_26BIT (0x00190000UL) #define SPI_CRC_LENGTH_27BIT (0x001A0000UL) #define SPI_CRC_LENGTH_28BIT (0x001B0000UL) #define SPI_CRC_LENGTH_29BIT (0x001C0000UL) #define SPI_CRC_LENGTH_30BIT (0x001D0000UL) #define SPI_CRC_LENGTH_31BIT (0x001E0000UL) #define SPI_CRC_LENGTH_32BIT (0x001F0000UL)
- NSSPMode
用於設置是否使能NSSP信號,可以通過SPIx_CR2寄存器的SSOM位使能。注意,只有配置為摩托羅拉SPI主控模式時設置此成員才有用。
#define SPI_NSS_PULSE_DISABLE (0x00000000UL) #define SPI_NSS_PULSE_ENABLE SPI_CFG2_SSOM
- NSSPolarity
用於設置NSS引腳上的高電平或者低電平作為激活電平。
#define SPI_NSS_POLARITY_LOW (0x00000000UL) #define SPI_NSS_POLARITY_HIGH SPI_CFG2_SSIOP
- FifoThreshold
用於設置SPI的FIFO閥值。
#define SPI_FIFO_THRESHOLD_01DATA (0x00000000UL) #define SPI_FIFO_THRESHOLD_02DATA (0x00000020UL) #define SPI_FIFO_THRESHOLD_03DATA (0x00000040UL) #define SPI_FIFO_THRESHOLD_04DATA (0x00000060UL) #define SPI_FIFO_THRESHOLD_05DATA (0x00000080UL) #define SPI_FIFO_THRESHOLD_06DATA (0x000000A0UL) #define SPI_FIFO_THRESHOLD_07DATA (0x000000C0UL) #define SPI_FIFO_THRESHOLD_08DATA (0x000000E0UL) #define SPI_FIFO_THRESHOLD_09DATA (0x00000100UL) #define SPI_FIFO_THRESHOLD_10DATA (0x00000120UL) #define SPI_FIFO_THRESHOLD_11DATA (0x00000140UL) #define SPI_FIFO_THRESHOLD_12DATA (0x00000160UL) #define SPI_FIFO_THRESHOLD_13DATA (0x00000180UL) #define SPI_FIFO_THRESHOLD_14DATA (0x000001A0UL) #define SPI_FIFO_THRESHOLD_15DATA (0x000001C0UL) #define SPI_FIFO_THRESHOLD_16DATA (0x000001E0UL)
- TxCRCInitializationPattern
發送CRC初始化模式。
#define SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN (0x00000000UL) #define SPI_CRC_INITIALIZATION_ALL_ONE_PATTERN (0x00000001UL)
- RxCRCInitializationPattern
接收CRC初始化模式
#define SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN (0x00000000UL) #define SPI_CRC_INITIALIZATION_ALL_ONE_PATTERN (0x00000001UL)
- MasterSSIdleness
在主模式下插入到SS有效邊沿和第一個數據開始之間的額外延遲,單位SPI時鍾周期個數。
#define SPI_MASTER_SS_IDLENESS_00CYCLE (0x00000000UL) #define SPI_MASTER_SS_IDLENESS_01CYCLE (0x00000001UL) #define SPI_MASTER_SS_IDLENESS_02CYCLE (0x00000002UL) #define SPI_MASTER_SS_IDLENESS_03CYCLE (0x00000003UL) #define SPI_MASTER_SS_IDLENESS_04CYCLE (0x00000004UL) #define SPI_MASTER_SS_IDLENESS_05CYCLE (0x00000005UL) #define SPI_MASTER_SS_IDLENESS_06CYCLE (0x00000006UL) #define SPI_MASTER_SS_IDLENESS_07CYCLE (0x00000007UL) #define SPI_MASTER_SS_IDLENESS_08CYCLE (0x00000008UL) #define SPI_MASTER_SS_IDLENESS_09CYCLE (0x00000009UL) #define SPI_MASTER_SS_IDLENESS_10CYCLE (0x0000000AUL) #define SPI_MASTER_SS_IDLENESS_11CYCLE (0x0000000BUL) #define SPI_MASTER_SS_IDLENESS_12CYCLE (0x0000000CUL) #define SPI_MASTER_SS_IDLENESS_13CYCLE (0x0000000DUL) #define SPI_MASTER_SS_IDLENESS_14CYCLE (0x0000000EUL) #define SPI_MASTER_SS_IDLENESS_15CYCLE (0x0000000FUL)
- MasterInterDataIdleness
主模式下在兩個連續數據幀之間插入的最小時間延遲,單位SPI時鍾周期個數。
#define SPI_MASTER_RX_AUTOSUSP_DISABLE (0x00000000UL) #define SPI_MASTER_RX_AUTOSUSP_ENABLE SPI_CR1_MASRX
- MasterReceiverAutoSusp
用於控制主器件接收器模式下的連續 SPI 傳輸以及自動管理,以避免出現上溢情況。
#define SPI_MASTER_RX_AUTOSUSP_DISABLE (0x00000000UL) #define SPI_MASTER_RX_AUTOSUSP_ENABLE SPI_CR1_MASRX
- MasterKeepIOState
禁止SPI后,SPI相關引腳保持當前狀態,以防止出現毛刺。在從模式下,該位不應該使用。
#define SPI_MASTER_KEEP_IO_STATE_DISABLE (0x00000000UL) #define SPI_MASTER_KEEP_IO_STATE_ENABLE SPI_CFG2_AFCNTR
- IOSwap
用於交換MISO和MOSI引腳。
#define SPI_IO_SWAP_DISABLE (0x00000000UL) #define SPI_IO_SWAP_ENABLE SPI_CFG2_IOSWP
72.3.3 SPI總線句柄結構體SPI_HandleTypeDef
下面是SPI總線的初始化結構體,用到的地方比較多:
typedef struct __SPI_HandleTypeDef { SPI_TypeDef *Instance; SPI_InitTypeDef Init; uint8_t *pTxBuffPtr; uint16_t TxXferSize; __IO uint16_t TxXferCount; uint8_t *pRxBuffPtr; uint16_t RxXferSize; __IO uint16_t RxXferCount; uint32_t CRCSize; void (*RxISR)(struct __SPI_HandleTypeDef *hspi); void (*TxISR)(struct __SPI_HandleTypeDef *hspi); DMA_HandleTypeDef *hdmatx; DMA_HandleTypeDef *hdmarx; HAL_LockTypeDef Lock; __IO HAL_SPI_StateTypeDef State; __IO uint32_t ErrorCode; #if defined(USE_SPI_RELOAD_TRANSFER) SPI_ReloadTypeDef Reload; #endif #if (USE_HAL_SPI_REGISTER_CALLBACKS == 1UL) void (* TxCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* RxCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* TxRxCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* TxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* RxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* TxRxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* ErrorCallback)(struct __SPI_HandleTypeDef *hspi); void (* AbortCpltCallback)(struct __SPI_HandleTypeDef *hspi); void (* MspInitCallback)(struct __SPI_HandleTypeDef *hspi); void (* MspDeInitCallback)(struct __SPI_HandleTypeDef *hspi); #endif } SPI_HandleTypeDef;
注意事項:
條件編譯USE_HAL_SPI_REGISTER_CALLBACKS用來設置使用自定義回調還是使用默認回調,此定義一般放在stm32h7xx_hal_conf.h文件里面設置:
#define USE_HAL_SPI_REGISTER_CALLBACKS 1
通過函數HAL_SPI_RegisterCallback注冊回調,取消注冊使用函數HAL_SPI_UnRegisterCallback。
這里重點介紹下面幾個參數,其它參數主要是HAL庫內部使用和自定義回調函數。
- SPI_TypeDef *Instance
這個參數是寄存器的例化,方便操作寄存器,比如使能SPI1。
SET_BIT(SPI1 ->CR1, SPI_CR1_SPE)。
- SPI_InitTypeDef Init
這個參數是用戶接觸最多的,在本章節3.2小節已經進行了詳細說明。
- DMA_HandleTypeDef *hdmatx
- DMA_HandleTypeDef *hdmarx
用於SPI句柄關聯DMA句柄,方便操作調用。
72.4 SPI總線源文件stm32h7xx_hal_spi.c
此文件涉及到的函數較多,這里把幾個常用的函數做個說明:
- HAL_SPI_Init
- HAL_SPI_DeInit
- HAL_SPI_TransmitReceive
- HAL_SPI_TransmitReceive_IT
- HAL_SPI_TransmitReceive_DMA
72.4.1 函數HAL_SPI_Init
函數原型:
HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi) { uint32_t crc_length = 0UL; uint32_t packet_length; /* 省略未寫 */ /* 如果數據位寬大於16bit,必須是SPI1,SPI2或者SPI3,而SPI4,SPI5和SPI6不支持大於16bit */ if ((!IS_SPI_HIGHEND_INSTANCE(hspi->Instance)) && (hspi->Init.DataSize > SPI_DATASIZE_16BIT)) { return HAL_ERROR; } /* SPI1,SPI2和SPI3的FIFO大小是16*8bit,而SPI4,SPI5和SPI6的FIFO大小是8*8bit 這里是查看設置的緩沖大小是否超出了FIFO支持的大小。 */ packet_length = SPI_GetPacketSize(hspi); if (((!IS_SPI_HIGHEND_INSTANCE(hspi->Instance)) && (packet_length > SPI_LOWEND_FIFO_SIZE)) || ((IS_SPI_HIGHEND_INSTANCE(hspi->Instance)) && (packet_length > SPI_HIGHEND_FIFO_SIZE))) { return HAL_ERROR; } #if (USE_SPI_CRC != 0UL) /* 省略未寫 */ #endif if (hspi->State == HAL_SPI_STATE_RESET) { /* 解鎖 */ hspi->Lock = HAL_UNLOCKED; /* 使用自定義回調 */ #if (USE_HAL_SPI_REGISTER_CALLBACKS == 1UL) /* 設置默認回調函數 */ hspi->TxCpltCallback = HAL_SPI_TxCpltCallback; /* Legacy weak TxCpltCallback */ hspi->RxCpltCallback = HAL_SPI_RxCpltCallback; /* Legacy weak RxCpltCallback */ hspi->TxRxCpltCallback = HAL_SPI_TxRxCpltCallback; /* Legacy weak TxRxCpltCallback */ hspi->TxHalfCpltCallback = HAL_SPI_TxHalfCpltCallback; /* Legacy weak TxHalfCpltCallback */ hspi->RxHalfCpltCallback = HAL_SPI_RxHalfCpltCallback; /* Legacy weak RxHalfCpltCallback */ hspi->TxRxHalfCpltCallback = HAL_SPI_TxRxHalfCpltCallback; /* Legacy weak TxRxHalfCpltCallback */ hspi->ErrorCallback = HAL_SPI_ErrorCallback; /* Legacy weak ErrorCallback */ hspi->AbortCpltCallback = HAL_SPI_AbortCpltCallback; /* Legacy weak AbortCpltCallback */ if (hspi->MspInitCallback == NULL) { hspi->MspInitCallback = HAL_SPI_MspInit; } /* 初始化地址硬件: GPIO, CLOCK, NVIC... */ hspi->MspInitCallback(hspi); #else /* 初始化底層硬件: GPIO, CLOCK, NVIC... */ HAL_SPI_MspInit(hspi); #endif } hspi->State = HAL_SPI_STATE_BUSY; /* 禁止SPI外設 */ __HAL_SPI_DISABLE(hspi); /*----------------------- SPIx CR1 & CR2 配置---------------------*/ if ((hspi->Init.NSS == SPI_NSS_SOFT) && (hspi->Init.Mode == SPI_MODE_MASTER) && (hspi->Init.NSSPolarity == SPI_NSS_POLARITY_LOW)) { SET_BIT(hspi->Instance->CR1, SPI_CR1_SSI); } /* SPIx CFG1配置 */ WRITE_REG(hspi->Instance->CFG1, (hspi->Init.BaudRatePrescaler | hspi->Init.CRCCalculation | crc_length | hspi->Init.FifoThreshold | hspi->Init.DataSize)); /* SPIx CFG2配置 */ WRITE_REG(hspi->Instance->CFG2, (hspi->Init.NSSPMode | hspi->Init.TIMode | hspi->Init.NSSPolarity | hspi->Init.NSS | hspi->Init.CLKPolarity | hspi->Init.CLKPhase | hspi->Init.FirstBit | hspi->Init.Mode | hspi->Init.MasterInterDataIdleness | hspi->Init.Direction | hspi->Init.MasterSSIdleness | hspi->Init.IOSwap)); #if (USE_SPI_CRC != 0UL) /*---------------------------- SPIx CRC配置 ------------------*/ /* 配置SPI CRC */ if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE) { /* 初始化TX CRC初始值 */ if (hspi->Init.TxCRCInitializationPattern == SPI_CRC_INITIALIZATION_ALL_ONE_PATTERN) { SET_BIT(hspi->Instance->CR1, SPI_CR1_TCRCINI); } else { CLEAR_BIT(hspi->Instance->CR1, SPI_CR1_TCRCINI); } /* 初始化RXCRC初始值 */ if (hspi->Init.RxCRCInitializationPattern == SPI_CRC_INITIALIZATION_ALL_ONE_PATTERN) { SET_BIT(hspi->Instance->CR1, SPI_CR1_RCRCINI); } else { CLEAR_BIT(hspi->Instance->CR1, SPI_CR1_RCRCINI); } /* 使能 33/17 bit CRC計算 */ if (((!IS_SPI_HIGHEND_INSTANCE(hspi->Instance)) && (crc_length == SPI_CRC_LENGTH_16BIT)) || ((IS_SPI_HIGHEND_INSTANCE(hspi->Instance)) && (crc_length == SPI_CRC_LENGTH_32BIT))) { SET_BIT(hspi->Instance->CR1, SPI_CR1_CRC33_17); } else { CLEAR_BIT(hspi->Instance->CR1, SPI_CR1_CRC33_17); } /* 寫CRC多項式到SPI寄存器 */ WRITE_REG(hspi->Instance->CRCPOLY, hspi->Init.CRCPolynomial); } #endif /* SPI從模式,下溢配置 */ if (hspi->Init.Mode == SPI_MODE_SLAVE) { /* 設置默認下溢配置 */ #if (USE_SPI_CRC != 0UL) if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_DISABLE) #endif { MODIFY_REG(hspi->Instance->CFG1, SPI_CFG1_UDRDET, SPI_CFG1_UDRDET_0); } MODIFY_REG(hspi->Instance->CFG1, SPI_CFG1_UDRCFG, SPI_CFG1_UDRCFG_1); } #if defined(SPI_I2SCFGR_I2SMOD) CLEAR_BIT(hspi->Instance->I2SCFGR, SPI_I2SCFGR_I2SMOD); #endif /* 確保AFCNTR bit由SPI主機模式管理 */ if ((hspi->Init.Mode & SPI_MODE_MASTER) == SPI_MODE_MASTER) { /* Alternate function GPIOs control */ MODIFY_REG(hspi->Instance->CFG2, SPI_CFG2_AFCNTR, (hspi->Init.MasterKeepIOState)); } hspi->ErrorCode = HAL_SPI_ERROR_NONE; hspi->State = HAL_SPI_STATE_READY; return HAL_OK; }
函數描述:
此函數用於初始化SPI。
函數參數:
- 第1個參數是SPI_HandleTypeDef類型結構體指針變量,用於配置要初始化的參數。
- 返回值,返回HAL_TIMEOUT表示超時,HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示忙,正在使用中。
注意事項:
- 函數HAL_SPI_MspInit用於初始化SPI的底層時鍾、引腳等功能。需要用戶自己在此函數里面實現具體的功能。由於這個函數是弱定義的,允許用戶在工程其它源文件里面重新實現此函數。當然,不限制一定要在此函數里面實現,也可以像早期的標准庫那樣,用戶自己初始化即可,更靈活些。
- 如果形參hspi的結構體成員State沒有做初始狀態,這個地方就是個坑。特別是用戶搞了一個局部變量SPI_HandleTypeDef SpiHandle。
對於局部變量來說,這個參數就是一個隨機值,如果是全局變量還好,一般MDK和IAR都會將全部變量初始化為0,而恰好這個 HAL_SPI_STATE_RESET = 0x00U。
解決辦法有三
方法1:用戶自己初始化SPI和涉及到的GPIO等。
方法2:定義SPI_HandleTypeDef SpiHandle為全局變量。
方法3:下面的方法
if(HAL_SPI_DeInit(&SpiHandle) != HAL_OK) { Error_Handler(); } if(HAL_SPI_Init(&SpiHandle) != HAL_OK) { Error_Handler(); }
使用舉例:
SPI_HandleTypeDef hspi = {0}; /* 設置SPI參數 */ hspi.Instance = SPIx; /* 例化SPI */ hspi.Init.BaudRatePrescaler = _BaudRatePrescaler; /* 設置波特率 */ hspi.Init.Direction = SPI_DIRECTION_2LINES; /* 全雙工 */ hspi.Init.CLKPhase = _CLKPhase; /* 配置時鍾相位 */ hspi.Init.CLKPolarity = _CLKPolarity; /* 配置時鍾極性 */ hspi.Init.DataSize = SPI_DATASIZE_8BIT; /* 設置數據寬度 */ hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 數據傳輸先傳高位 */ hspi.Init.TIMode = SPI_TIMODE_DISABLE; /* 禁止TI模式 */ hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */ hspi.Init.CRCPolynomial = 7; /* 禁止CRC后,此位無效 */ hspi.Init.CRCLength = SPI_CRC_LENGTH_8BIT; /* 禁止CRC后,此位無效 */ hspi.Init.NSS = SPI_NSS_SOFT; /* 使用軟件方式管理片選引腳 */ hspi.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA; /* 設置FIFO大小是一個數據項 */ hspi.Init.NSSPMode = SPI_NSS_PULSE_DISABLE; /* 禁止脈沖輸出 */ hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相關引腳保持當前狀態 */ hspi.Init.Mode = SPI_MODE_MASTER; /* SPI工作在主控模式 */ if (HAL_SPI_Init(&hspi) != HAL_OK) { Error_Handler(__FILE__, __LINE__); }
72.4.2 函數HAL_SPI_DeInit
函數原型:
HAL_StatusTypeDef HAL_SPI_DeInit(SPI_HandleTypeDef *hspi) { /* 檢測SPI句柄是否有效 */ if (hspi == NULL) { return HAL_ERROR; } /* 檢查SPI例化參數 */ assert_param(IS_SPI_ALL_INSTANCE(hspi->Instance)); hspi->State = HAL_SPI_STATE_BUSY; /* 禁止SPI外設時鍾 */ __HAL_SPI_DISABLE(hspi); #if (USE_HAL_SPI_REGISTER_CALLBACKS == 1UL) if (hspi->MspDeInitCallback == NULL) { hspi->MspDeInitCallback = HAL_SPI_MspDeInit; } /* 復位底層硬件: GPIO, CLOCK, NVIC... */ hspi->MspDeInitCallback(hspi); #else /* 復位底層硬件: GPIO, CLOCK, NVIC... */ HAL_SPI_MspDeInit(hspi); #endif /* 設置無錯誤,復位狀態標記 */ hspi->ErrorCode = HAL_SPI_ERROR_NONE; hspi->State = HAL_SPI_STATE_RESET; /* 解鎖SPI */ __HAL_UNLOCK(hspi); return HAL_OK; }
函數描述:
用於復位SPI總線初始化。
函數參數:
- 第1個參數是SPI_HandleTypeDef類型結構體指針變量。
- 返回值,返回HAL_TIMEOUT表示超時,HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示忙,正在使用中
72.4.3 函數HAL_SPI_TransmitReceive
函數原型:
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout) { /* 省略未寫 */ /* 大於16bit的數據收發 */ if (hspi->Init.DataSize > SPI_DATASIZE_16BIT) { /* 省略未寫 */ } /* 大於8bit,小於16bi的數據收發 */ else if (hspi->Init.DataSize > SPI_DATASIZE_8BIT) { /* 省略未寫 */ } /* 小於等於8bit的數據收發 */ else { /* 省略未寫 */ } }
函數描述:
此函數主要用於SPI數據收發,全雙工查詢方式。
函數參數:
- 第1個參數是SPI_HandleTypeDef類型結構體指針變量。
- 第2個參數是發送數據緩沖地址。
- 第3個參數是接收數據緩沖地址。
- 第4個參數是傳輸的數據大小,單位字節個數。
- 第5個參數是傳輸過程的溢出時間,單位ms。
- 返回值,返回HAL_TIMEOUT表示超時,HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示忙,正在使用中。
使用舉例:
SPI_HandleTypeDef hspi = {0}; if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK) { Error_Handler(__FILE__, __LINE__); }
72.4.4 函數HAL_SPI_TransmitReceive_IT
函數原型:
HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size) { /* 省略未寫 */ /* 設置傳輸參數 */ hspi->ErrorCode = HAL_SPI_ERROR_NONE; hspi->pTxBuffPtr = (uint8_t *)pTxData; hspi->TxXferSize = Size; hspi->TxXferCount = Size; hspi->pRxBuffPtr = (uint8_t *)pRxData; hspi->RxXferSize = Size; hspi->RxXferCount = Size; /* 設置中斷處理 */ if (hspi->Init.DataSize > SPI_DATASIZE_16BIT) { hspi->TxISR = SPI_TxISR_32BIT; hspi->RxISR = SPI_RxISR_32BIT; } else if (hspi->Init.DataSize > SPI_DATASIZE_8BIT) { hspi->RxISR = SPI_RxISR_16BIT; hspi->TxISR = SPI_TxISR_16BIT; } else { hspi->RxISR = SPI_RxISR_8BIT; hspi->TxISR = SPI_TxISR_8BIT; } /* 設置當前傳輸數據大小 */ MODIFY_REG(hspi->Instance->CR2, SPI_CR2_TSIZE, Size); /* 使能SPI外設 */ __HAL_SPI_ENABLE(hspi); /* 使能各種中斷標志 */ __HAL_SPI_ENABLE_IT(hspi, (SPI_IT_EOT | SPI_IT_RXP | SPI_IT_TXP | SPI_IT_DXP | SPI_IT_UDR | SPI_IT_OVR | SPI_IT_FRE | SPI_IT_MODF | SPI_IT_TSERF)); if (hspi->Init.Mode == SPI_MODE_MASTER) { /* 啟動傳輸 */ SET_BIT(hspi->Instance->CR1, SPI_CR1_CSTART); } /* 解鎖 */ __HAL_UNLOCK(hspi); return errorcode; }
函數描述:
此函數主要用於SPI數據收發,全雙工中斷方式。
函數參數:
- 第1個參數是SPI_HandleTypeDef類型結構體指針變量。
- 第2個參數是發送數據緩沖地址。
- 第3個參數是接收數據緩沖地址。
- 第4個參數是傳輸的數據大小,單位字節個數。
- 返回值,返回HAL_TIMEOUT表示超時,HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示忙,正在使用中。
使用舉例:
SPI_HandleTypeDef hspi = {0}; if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK) { Error_Handler(__FILE__, __LINE__); }
72.4.5 函數HAL_SPI_TransmitReceive_DMA
函數原型:
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size) { /* 省略未寫 */ /* 注意DMA的位寬和對齊設置 */ if (((hspi->Init.DataSize > SPI_DATASIZE_16BIT) && (hspi->hdmarx->Init.MemDataAlignment != DMA_MDATAALIGN_WORD)) || ((hspi->Init.DataSize > SPI_DATASIZE_8BIT) && ((hspi->hdmarx->Init.MemDataAlignment != DMA_MDATAALIGN_HALFWORD) && (hspi->hdmarx->Init.MemDataAlignment != DMA_MDATAALIGN_WORD)))) { } /* 調整DMA對齊和數據大小 */ if (hspi->Init.DataSize <= SPI_DATASIZE_8BIT) { /* 省略未寫 */ } else if (hspi->Init.DataSize <= SPI_DATASIZE_16BIT) { /* 省略未寫 */ } else { /* 省略未寫 */ } /* DMA接收配置 */ if (HAL_OK != HAL_DMA_Start_IT(hspi->hdmarx, (uint32_t)&hspi->Instance->RXDR, (uint32_t)hspi->pRxBuffPtr, hspi->RxXferCount)) { } /* DMA發送配置 */ if (HAL_OK != HAL_DMA_Start_IT(hspi->hdmatx, (uint32_t)hspi->pTxBuffPtr, (uint32_t)&hspi->Instance->TXDR, hspi->TxXferCount)) { } /* 省略未寫 */ }
函數描述:
此函數主要用於SPI數據收發,全雙工DMA方式。
函數參數:
- 第1個參數是SPI_HandleTypeDef類型結構體指針變量。
- 第2個參數是發送數據緩沖地址。
- 第3個參數是接收數據緩沖地址。
- 第4個參數是傳輸的數據大小,單位字節個數。
- 返回值,返回HAL_TIMEOUT表示超時,HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示忙,正在使用中。
使用舉例:
SPI_HandleTypeDef hspi = {0}; if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK) { Error_Handler(__FILE__, __LINE__); }
72.5 總結
本章節就為大家講解這么多,要熟練掌握SPI總線的查詢,中斷和DMA方式的實現,因為基於SPI接口的外設芯片很多,熟練后,可以方便的驅動各種SPI接口芯片,以便選擇合適的驅動方式。