在 Master Mode 主模式下使用 STM32Cube HAL 執行 I2C 驅動程式

背景

STMicroelectronics 為其每個 MCU 系列都提供了一個嵌入式韌體套裝軟體,其中包括硬體抽象層 (Hardware Abstraction Layer, HAL) 驅動程式。該驅動程式提供了一組高級 API,旨在以高度可移植的方式抽象化 MCU 及其周邊裝置的複雜性。I2C 介面就是這樣一種周邊。有關 HAL I2C 驅動程式入門指南,請參閱 Shawn Hymel 撰寫的英文教學。如需快速了解 I2C Master 可用的通訊功能,請繼續閱讀。

HAL 驅動程式的資料處理功能支援三種程式設計模型:輪詢 (polling)、中斷 (interrupt) 和 DMA。輪詢函數以 阻塞模式 (blocking mode) 運行,這表示這些函數在操作完成之前不會返回。為了防止應用程式掛起,用戶必須提供合適的超時值。中斷和 DMA 函數以 非阻塞模式 (non-blocking mode) 運行,這意味著這些函數將在操作啟動後返回,從而允許應用程式繼續執行,同時操作在背景繼續進行。但是,必須配置並啟用回調函數 (callback function) 來處理操作完成後發出的訊號。

在阻塞模式下充當 I2C Master 時,有四個 API 函數可用於與 Slave 通訊:

  • HAL_I2C_Master_Transmit()
  • HAL_I2C_Master_Receive()
  • HAL_I2C_Mem_Write()
  • HAL_I2C_Mem_Read()

對於非阻塞功能,中斷和 DMA 模式具有等效的功能。然而,由於輪詢函數在這三種模式中更為簡單,因此本參考指南將使用它。

傳輸資料

從 Master 向 Slave 發送資料通常並不複雜。可以使用 HAL_I2C_Master_Transmit()HAL_I2C_Mem_Write() 函數。具體選擇哪個函數取決於訊息結構或個人偏好。

HAL_I2C_Master_Transmit()

此 API 函數的原型如下所示。第一個參數是一個配置結構體,其建立方法詳見 getting started tutorial。第二個參數是 Slave 的位址(必須左移一位)。第三個和第四個參數分別是指向資料緩衝區的指標以及緩衝區中需要傳送給 Slave 的資料量。最後一個參數是超時時長(以毫秒為單位)。請注意,使用者可以提供 HAL_MAX_DELAY 作為參數來停用逾時並無限期阻塞,直到函數返回。

HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, 
										  uint16_t DevAddress, 
										  uint8_t *pData, 
										  uint16_t Size, 
										  uint32_t Timeout);

呼叫此函數產生的 I2C 序列如下所示。陰影區域表示訊號由 Slave 驅動的位置。在本例中,Slave 僅確認其自身的位址(加上寫入位元)及其後續的資料位元組。回想一下,發送的資料位元組數由傳遞給函數呼叫的 Size 參數決定。

image

作為該函數使用的範例,請考慮以下程式碼,其中緩衝區的內容被傳送到位址為 0x40 的 Slave。使用邏輯分析儀捕獲了 I2C 傳輸,結果波形也顯示在下方。請注意,啟動條件存在,但解碼後的協定圖中沒有空間容納標籤。

uint8_t dataBuffer[10] = {0x03, 0x01};
HAL_I2C_Master_Transmit(&hi2c1, (0x40 << 1), dataBuffer, 2, HAL_MAX_DELAY);

master_transmit

HAL_I2C_Mem_Write()

此函數適用於 Master 需要寫入 Slave 上特定記憶體位置的常見場景。例如,大多數 I2C 感測器包含用於更改設定和啟動測量的配置和命令暫存器。此函數的原型如下。

HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, 
									uint16_t DevAddress, 
									uint16_t MemAddress, 
									uint16_t MemAddSize, 
									uint8_t *pData, 
									uint16_t Size, 
									uint32_t Timeout);

顯然,它包含與 HAL_I2C_Master_Transmit() 函數相同的所有參數,以及兩個額外的參數。第一個參數 MemAddress 是 Slave 中緩衝區內容將被寫入的起始記憶體位址。第二個參數 MemAddSize 表示內部記憶體位址的大小(I2C_MEMADD_SIZE_8BITI2C_MEMADD_SIZE_16BIT)。 I2C 序列現在將如下所示。

image

它與 HAL_I2C_Master_Transmit() 產生的序列相同,只是 MemAddress 參數在 Slave 位址之後、資料緩衝區的第一個位元組之前發送。以下範例使用 HAL_I2C_Mem_Write() 函數將值 0x01 寫入位於 Slave 記憶體位址 0x03 的暫存器。請注意,邏輯分析儀擷取的 I2C 操作與上面 HAL_I2C_Master_Transmit() 函數範例中顯示的完全相同。

uint8_t dataBuffer[10] = {0x01};
HAL_I2C_Mem_Write(&hi2c1, (0x40 << 1), 0x03, I2C_MEMADD_SIZE_8BIT, dataBuffer, 1, HAL_MAX_DELAY);

mem_write

接收資料

與 Master 向 Slave 發送資料不同,用於從 Slave 向 Master 接收資料的兩個函數不可互換。一個函數只接收數據,另一個函數則需要先指定接收數據的位址。

HAL_I2C_Master_Receive()

此 API 函數用於向 Slave 請求資料。請注意,在下面的原型中,其參數與 HAL_I2C_Master_Transmit() 的參數相同。不過,在本例中,資料緩衝區用於儲存傳入的數據,而 Size 參數指定在發送 Nack 之前需要接收的資料位元組數。

HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, 
										 uint16_t DevAddress, 
										 uint8_t *pData, 
										 uint16_t Size, 
										 uint32_t Timeout);

描述該函數操作的序列圖如下所示。請注意,它與上面討論的任何一個資料傳輸函數都存在根本區別。首先,位址位元組中的方向位元設定為讀取而不是寫入。其次,Slave 確認自身位址後,開始向 Master 發送資料位元組(回想一下,陰影欄位表示 Slave 正在驅動訊號)。現在,Master 負責確認收到的每個字節,直到它不再希望接收這些位元組。它透過發送 Nack 後跟停止條件來通知 Slave 停止發送資料。

image

以下程式碼範例顯示 Master 向位址為 0x40 的 Slave 要求三個位元組的資料。透過觀察邏輯分析儀擷取的數據,我們可以看到,操作完成後,數據緩衝區將包含值 {0x00, 0x68, 0xF0}。

HAL_I2C_Master_Receive(&hi2c1, (0x40 << 1), dataBuffer, 3, HAL_MAX_DELAY);

image

HAL_I2C_Mem_Read()

此 API 函數用於從特定記憶體位址的 Slave 請求資料。同樣,假設有一個 I2C 感測器,其測量值儲存在 Slave 的特定暫存器中,Master 必須從該暫存器讀取資料。如下所示,此函數原型包含與 HAL_I2C_Mem_Write() 函數相同的參數。但是,與上述情況一樣,數據緩衝區將用於儲存傳入數據,而不是獲取數據。

HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, 
								   uint16_t DevAddress, 
								   uint16_t MemAddress, 
								   uint16_t MemAddSize, 
								   uint8_t *pData, 
								   uint16_t Size, 
								   uint32_t Timeout);

此函數的獨特之處在於,它首先執行 I2C 發送操作,告知 Slave 從哪個記憶體位址取得資料。然後,發出重複啟動條件以開始接收操作。完整的 I2C 序列如下所示。請注意,HAL_I2C_Mem_Read() 是唯一能夠在阻塞模式下產生重複啟動條件的函數。如果需要重複啟動,僅僅呼叫 HAL_I2C_Master_Transmit() 並緊接著呼叫 HAL_I2C_Master_Receive()是不夠的。

image

以下程式碼範例從位址為 0x40 的從裝置接收兩個位元組的數據,起始位址為 0x01。從邏輯分析儀擷取的資料可以看出,操作完成後,緩衝區將包含 {0x68, 0x90}。

HAL_I2C_Mem_Read(&hi2c1, (0x40 << 1), 0x01, I2C_MEMADD_SIZE_8BIT, dataBuffer, 2, HAL_MAX_DELAY);

/* This is NOT guaranteed to work as a substitute for the above! */
// dataBuffer[0] = 0x01;
// HAL_I2C_Master_Transmit(&hi2c1, (0x40 << 1), dataBuffer, 1, HAL_MAX_DELAY);
// HAL_I2C_Master_Receive(&hi2c1, (0x40 << 1), dataBuffer, 2, HAL_MAX_DELAY);

image

總結

STMicroelectronics 提供的 HAL I2C 驅動程式允許 Master 以阻塞模式或非阻塞模式與 Slave 通訊(阻塞模式是兩者中較簡單的一種)。若要以阻塞模式傳送數據,可以使用 HAL_I2C_Master_Transmit() 函數或 HAL_I2C_Mem_Write() 函數。這兩個函數可以互換。唯一的區別在於,Slave 的記憶體位址被明確指定為 HAL_I2C_Mem_Write() 的參數。使用者應根據其應用確定哪種函數最合適。若要從裝置接收數據,可以使用 HAL_I2C_Master_Receive() 函數或 HAL_I2C_Mem_Read() 函數。但是,這兩個函數不可互換。 HAL_I2C_Master_Receive() 只是簡單地從 Slave 讀取數據,而 HAL_I2C_Mem_Read() 則首先向 Slave 發送一個​​內存地址,然後發出一個重複的啟動條件,隨後執行讀取操作以獲取位於該內存地址的數據。選擇使用哪個函數取決於 Slave 所需的 I2C 序列。