[STM32] 20-I2C

Morgan Ting
閱益如美
Published in
17 min readNov 12, 2022
Photo by Vishnu Mohanan on Unsplash

Inter-Integrated Circuit, IIC 或稱為 I2C ( I Square C ) ,是飛利浦公司於 1980 年代發表的通訊界面,主要用在電路板之間的短距離通訊。本文章將介紹 STM32 的 I2C 功能並以 EEPROM AT24C32 作為資料傳輸對象,開發環境使用 STM32CubeIDE 搭配 Hardware Abstraction Layer, HAL 完成實作。

文章內容

  1. I2C 簡介
  2. STM32 的 I2C
  3. EEPROM AT24C32
  4. 實作
  5. 成果展示

工具與材料

  1. STM32CubeIDE
  2. Blue Pill ( STM32F103C8T6 ) 開發板
  3. ST-LINK v2
  4. EEPROM AT24C32
  5. 麵包板與單芯線

I2C 簡介

1980 年代由飛利浦 ( Philips ) 發表的 I2C 通訊界面,其介面簡單只有兩條線路分別是 SCL 時脈線與 SDA 資料線,在標準模式下的傳輸速度為 100 Kbps,快速模式下為 400 Kbps 也有一些裝置能提供最高 5 Mbps 的傳輸速度。

I2C 在硬體上採用開集極 ( open collect ) 或開洩極 ( open drain ) ,因此線路閒置時呈現高電位狀態,各裝置之間採用主從式架構連接由主機端發起資料傳輸要求且同一時間只能有一部主機發號施令,從機端則是採用位址 ( Address ) 方式建立溝通橋梁,因此理論上從機數量最大由定址能力決定但實際上因線路之間存在之電容等因素造成限制。

I2C 連接方式很簡單,因為採用 open drain 輸出所以線路上會有上拉電阻 ( pull up resistor ),線路上可以有單一或多部主機以及數部從機,同一時間只能有一部主機運作。

I2C Connection

I2C 進行傳輸時有其規範,拉低 SDA 電位發出 START 表示即將進行傳輸,待傳輸完畢後回復 SDA 電位發出 STOP 表示結束傳輸。

Ref : datasheet

I2C 是以位址來呼叫從機,當主機發出特定位址時所有線上的裝置都會收到該位址訊息,但是只有屬於該位址的裝置會做出反應。位址一般是由 7 個位元所組成,位址後面會緊接著一個讀寫位元,表示對該位址裝置進行讀取或寫入操作,從機端收到主機的呼叫後會發出一個 ACK ( Acknowledge ) 訊號給主機表示收到呼叫了準備下一個動作。

讀 / 寫 位元為邏輯 1 時表示對從機進行讀取,邏輯 0 表示對從機進行寫入。例如,有一部從機的位址是 「 1010000 」,要進行讀取程序就是發出 「 10100001 」,進行寫入程序就是 「 10100000 」。

常見的溝通組合如下:

  • 主機傳送資料,對從機進行寫入。
  • 主機接收資料,讀取來自從機的資料。
  • 從機傳送資料,對應主機接收資料。
  • 從機接收資料,對應主機傳送資料。

以上組合構成每一次傳輸,主從兩機一搭一唱相互配合。

因為微處理器經常擔任主機角色,以下介紹主機寫入與讀取步驟。

一、主機送出資料給從機

  1. 主機發起 START Condition 昭告天下我要發訊息啦 ! 若線路空閒則取得控制權。
  2. 主機發出從機位址與 「寫」位元,若對應從機有空閒則發出回應 Ack 給主機,主機收到 Ack 表示可以進行下一步。
  3. 主機發出 1 Byte 資料給從機接收,從機收到資料若要繼續收資料則發給主機 Ack 表示收到了並且可以進行下一筆傳輸,主機收到 Ack 決定是否繼續傳送資料。
  4. 重複步驟 3 直到主機發出 STOP Condition 表示傳輸結束,釋出線路控制權,線路回復到空閒狀態。

以上 4 個步驟即是主機對從機發送資料的流程,過程中可能也會有些變化,例如從機忙碌暫時無法接收資料,此時從機會發出 non-Ack 表示無法繼續,這時主機端就要等待或是放棄本次連結稍待再重啟傳輸。

主機端也可以在結束傳輸後不釋放線路並發出 Repeated START Condition 重啟連結訪問另一個從機,例如線路上有 2 個從機,一個是溫度感測器一個是記憶體,我們想要讀取當前溫度後儲存結果到記憶體裡,那就要先與溫度感測器建立連結讀取資料後再與記憶體建立連結儲存資料。

I2C 的資料傳送可依需求彈性變化,唯須考量各個步驟成功完成或是產生錯誤應該要如何進行下一步。

二、主機讀取從機資料

  1. 主機發起 START Condition 請求控制權,若線路空閒則取得控制權。
  2. 主機發出從機位址與 「讀」位元,若對應從機有空閒則發出回應 Ack 給主機,主機收到 Ack 表示可以進行下一步。
  3. 從機發出 1 Byte資料給主機接收,主機收到資料若要繼續收資料則發給從機 Ack 表示收到了並且可以進行下一筆傳輸。
  4. 重複步驟 3 直到主機發出 STOP Condition 表示傳輸結束,釋出線路控制權,線路回復到空閒狀態。

主從端角色變換其傳輸步驟大同小異,只是對應的狀態不同而已。

STM32 的 I2C

STM32 依晶片型號不同提供多組 I2C 模組可以使用,具有以下特點:

  • 支援多主機模式,STM32 可以擔任主機或從機。
  • 支援 7 位元或 10 位元地址格式。
  • 標準傳輸速度 100 Kbps ,高速傳輸速度可達 400 Kbps。
  • 2 個中斷向量,其一作為定址 / 資料傳輸成功,其二用於錯誤產生。
  • 支援 DMA 傳輸。

系統方塊圖

Ref : datasheet

HAL 函式庫

實作 I2C 協議不是一件容易的事,需要考慮每一步驟狀態再進行下一步,HAL 函式庫幫使用者解決了傷腦筋的工作,提供了 APIs 讓使用者操作 I2C 更為容易。

HAL 提供了 I2C 阻斷式、中斷與 DMA 三種不同操作 APIs。

一、阻斷式操作

1. 主機寫入

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

  • hi2c 是一個實例,例如 &hi2c1。
  • DevAddress 是從機裝置位址。
  • *pData 是待傳送資料的起始位址。
  • Size 是資料長度。
  • Timeout 逾時時間,傳輸逾時就會放棄該次傳輸。

2. 主機讀取

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

參數同主機寫入,只是資料傳輸方向相反。

二、中斷式操作

1. 主機寫入

HAL_I2C_Master_Transmit_IT(hi2c, DevAddress, pData, Size)

參數內容同阻斷式操作,唯少了 Timeout 參數,因為傳輸完成會觸發中斷因此不用等待。

2. 主機讀取

HAL_I2C_Master_Receive_IT(hi2c, DevAddress, pData, Size)

參數同主機寫入,只是資料傳輸方向相反。

三、DMA 存取

1. 主機寫入

HAL_I2C_Master_Transmit_DMA(hi2c, DevAddress, pData, Size)

參數內容同中斷式操作。

2. 主機讀取

HAL_I2C_Master_Receive_DMA(hi2c, DevAddress, pData, Size)

參數同主機寫入,只是資料傳輸方向相反。

另外,為了存取記憶體裝置提供相應 APIs,一樣有三種操作方式

1. 主機寫入

HAL_I2C_Mem_Write(hi2c, DevAddress, MemAddress, MemAddSize, pData, Size, Timeout)

HAL_I2C_Mem_Write_IT(hi2c, DevAddress, MemAddress, MemAddSize, pData, Size)

HAL_I2C_Mem_Write_DMA(&hi2c1, DevAddress, MemAddress, MemAddSize, pData, Size)

其中

  • MemAddress ,指定存取記憶體的位址,每存取一筆資料位址會自動移往下一個位址。
  • MemAddSize,記憶體位址長度,8 位元或 16 位元。

2. 主機讀取

HAL_I2C_Mem_Read(hi2c, DevAddress, MemAddress, MemAddSize, pData, Size, Timeout)

HAL_I2C_Mem_Read_IT(hi2c, DevAddress, MemAddress, MemAddSize, pData, Size)

HAL_I2C_Mem_Read_DMA(&hi2c1, DevAddress, MemAddress, MemAddSize, pData, Size)

EEPROM AT24C32

AT24C32 是一款由 ATMEL 公司 ( 2016 年被 Microchip 併購 ) 設計的 EEPROM,其容量為 32 K bits ,也就是 4K Bytes 。主要特性如下:

  • 操作電壓 1.8 v ~ 5.5 v 。
  • 32 K bits ,4K Bytes 容量。
  • I2C 通訊介面。
  • 最大傳輸速度 400 K bps ( 操作於 5 v ),標準速度 100 K bps。
  • 一個分頁 ( page ) 32 Bytes ,因此有 128 個分頁。
  • 一百萬次寫入次數。

AT24C32 從機位址為 7 位元,前 4 個位元是固定的且依裝置型號而異,緊接著 3 個位元可讓使用者調整,最後一個位元是讀 / 寫位元,剛好湊成 8 個位元。

寫入程序

不同於一般 I2C 裝置,EEPROM 與主機連接後必須指定記憶體位址才知道要從何寫入或是讀取,因此主機送出從機位址後還須送出指定位址。記憶體位址由 2 個 Byte 組成共 16 位元,因為 AT24C32 只有 4K Bytes 容量,因此 16 位元的記憶體位址只會用到後面 12 位元便能完成 4K Bytes 定址。下圖中的 「 * 」與 「 + 」在 AT24C32 表示 don’t care 位元,意即用不到。

Ref : datasheet

實作

本次使用的模組是由一個 RTC 晶片DS3231 搭配一個 AT24C32N EEPROM晶片,AT24C32N 預設位址 0x57。我們將使用搭載 STM32F103C8T6 的 Blue Pill 開發板 I2C_2 對 AT24C32N進行寫入再讀取以驗證是否成功讀寫。

模組外觀

RCT + AT24C32N Module

照片底下的小晶片即是 AT24C32 相容晶片。

硬體接線

STM32 — — — -AT24C32

SCL — — — — - PB10 SCL

SDA — — — — - PB11 SDA

3.3 v — — — — — Vcc

GND — — — — — GND

EEPROM 裝置位址為 0x57 即 0101 0111,操作時要左移一位變成 1010111X。

X 即讀 / 寫位元,當 X 為 0 時為裝置寫入位址 0xAE ,當 X為 1 時為裝置讀取位址 0xAF。

一、開啟 STM32CubeIDE 開發環境,建立一個新的專案。

start a project

二、選擇微處理器型號,輸入F103C8 可以快速找到,選擇該型號後按下 「NEXT」鍵。

target selection

三、輸入專案名稱後按下「Finish」鍵。

project name

四、設定 SYS ,這邊的 Debug 選項選擇 「Serial Wire」。

SYS

五、RCC 時脈源,HSE 選擇 「Crystal / Ceramic Resonator」,其中 HSE 是連接開發板上的 8 MHz 震盪器提供系統使用。

RCC

六、點開 「Connectivity」可以看到 I2C 、UART 等通訊界面,點選 I2C2 後會出現設定畫面。

I2C 模式選擇 「 I2C 」。底下參數採預設即可,傳輸速度採用標準模式 100 Kbps。

本次採用阻斷式程序對 EEPROM 進行讀寫操作,因此不開啟中斷與 DMA。

I2C configuration

七、Clock Configuration 設定系統時脈為 72 MHz 。

Clock Configuration

八、 Project Manager 按預設方式或依需求更改即可。

九、產生程式碼,可以按存檔鍵或是點擊上方 「Project 」=> 「Generate Code 」產生程式碼。

十、來到程式編輯頁面,開始進行程式撰寫。

首先,在 /* USER CODE BEGIN PM */ 區段宣告兩個陣列變數,一個用來寫入 EEPROM ,一個用來儲存讀取 EEPROM 之資料。

/* USER CODE BEGIN PM */
uint8_t Msg[15] = "HELLO WORLD!"; // 寫入訊息
uint8_t Rx[15] = {0}; // 儲存讀取資料
/* USER CODE END PM */

接著,往下找到 /* USER CODE BEGIN 2 */ 區段寫下讀寫程序,先對 EEPROM 寫入訊息,再從 EEPROM 讀取訊息。

使用的指令為

HAL_I2C_Mem_Write(hi2c, DevAddress, MemAddress, MemAddSize, pData, Size, Timeout)

其中

  • hi2c 是指 I2C2,這裡填上&hi2c2。
  • DevAddress 是 EEPROM 的寫入位址 ,AT24C32 的寫入位址是0xAE。
  • MemAddress 是 EEPROM 起始操作位址,我們想從位址 0 開始寫入所以填上 0。
  • MemAddSize 是 EEPROM 的記憶體位址長度,因為 AT24C32 是 4K Byte,需要 12 位元定址所以要填 16 位元,I2C_MEMADD_SIZE_16BIT,指令會自動分兩次送出位址訊息。
  • pData 是資料內容,填上我們宣告的陣列變數 Msg 。
  • Size 是資料長度,在這裡就是陣列變數 Msg 的長度,利用指令 sizeof 自動計算。
  • Timeout 逾時,阻斷式操作才有這項參數,單位是毫秒,如果出錯卡住了不要癡癡地等下去。

寫入訊息到 EEPROM

HAL_I2C_Mem_Write(&hi2c2, 0xAE, 0, I2C_MEMADD_SIZE_16BIT, Msg, sizeof(Msg), 1000);

從 EEPROM 讀取資料的指令改為 HAL_I2C_Mem_Read ,參數大同小異。

其中

  • DevAddress 改成 0xAF 。
  • pData 改成存放接收資料的變數 Rx。
  • Size 改成陣列變數 Rx 的長度。

從 EEPROM 讀取訊息

HAL_I2C_Mem_Read(&hi2c2, 0xAF, 0, I2C_MEMADD_SIZE_16BIT, Rx, sizeof(Rx), 1000);

十一、完成程式碼撰寫後進行編譯與燒錄,按下綠色箭頭。

在 Debugger 頁面設定 ST-Link v2 燒錄器,將 「ST-LINK S/N 」打勾並點擊右方 「SCAN」按鈕。完成後點下方 「 OK 」鈕開始編譯與燒錄。

十一、點擊甲蟲符號進行 Debug 程序,我們要利用 Live Expression 觀察結果。

在畫面右手邊找到 Live Expression 後新增變數,Rx 。

Live Expression

完成後,點擊綠色小箭頭 ( Resume ) 並將陣列變數並將變數 Rx 展開即可觀察到讀取結果。

成果展示

本次使用 STM32 的 I2C 通訊界面對 EEPROM 進行讀寫操作,藉由 HAL 函式庫簡化了操作複雜度,其結果符合預期。

從第 0 位址寫入字串 「 HELLO WORLD! 」共12 字元佔 12 Bytes,但因為陣列變數 Msg 有 15 Byte 長度,因此會寫入 3 個空白資料。

讀取 EEPROM 時從位址 0 開始讀 15 Byte 存放到陣列變數 Rx 裡,從 Live Expression 可觀察到 Rx 依序存放 「 HELLO WORLD! 」與三個空白內容。

Result

總結

搭載 STM32F103C8T6 的 Blue Pill 開發板提供 2 組 I2C 通訊界面,本文章總結如下。

  • Inter-Integrated Circuit, I2C 為主從式控制架構。
  • 硬體結構為 SDA 資料線與 SCL 時脈線採用 Open Drain 輸出,使用時會搭配上拉電阻。
  • 標準傳輸速度為 100 Kbps ,高速模式為 400 Kbps 。有些裝置可達 4.5 M 或 5 M bps。
  • STM32F103C8T6 提供 100 Kbps 與 400 Kbps 兩種傳輸速度。
  • 操作程序分為阻斷式、中斷與 DMA。
  • 支援 7 位元或 10 位元裝置定址。
  • 透過 HAL 函式庫讓使用者一指令完成傳輸。
  • AT24C32 是 32 K Bits 也就是 4 K Bytes 容量的 EEPROM。
  • AT24C32 容量為 4 K Bytes 因此需要 12 位元定址,使用 HAL 函式庫時記憶體位址長度要設定 16 位元。
  • 注意讀寫操作時裝置位址是否需進行左移計算,例如文章使用的 EEPROM裝置位址為 0x57 ,操作時要左移一位成為 0xAE 為裝置寫入定址,0xAE + 1 = 0xAF 為讀取定址。

參考資料

  1. STM32F103 手冊 [ 連結 ]
  2. STM32F1 HAL and Low-layer drivers [ 連結 ]
  3. STM32F103 電器特性 [ 連結 ]
  4. AT24C32 資料手冊 [ 連結 ]

感謝讀者

若文章有幫助到您可以拍手給我鼓勵,免費支持我。

相關文章

  • [STM32] 00-Install STM32CubeIDE [連結]
  • [STM32] 01-ST-LINK [連結]
  • [STM32] 02-STM32F103C8T6 [連結]
  • [STM32] 03-GPIO-Output [連結]
  • [STM32] 04-GPIO-Input [連結]
  • [STM32] 05-Ext-Interrupt [連結]
  • [STM32] 06-Timer-Basic [連結]
  • [STM32] 07-Timer-Interrupt [連結]
  • [STM32] 08-Timer-Output_Compare [連結]
  • [STM32] 09-Timer-PWM [連結]
  • [STM32] 10-Timer-Input_Capture [連結]
  • [STM32] 11-RTC-Second-Interrupt [連結]
  • [STM32] 12-RTC-Alarm_Interrupt [連結]
  • [STM32] 13-Independent_Watch_Dog [連結]
  • [STM32] 14-Windows_Watch_Dog [連結]
  • [STM32] 15-ADC_Conversion [連結]
  • [STM32] 16-ADC_Conversion_Temperature_Sensor [連結]
  • [STM32] 17-ADC_Convversion_DMA [連結]
  • [STM32] 18-SPI [連結]
  • [STM32] 19-UART [連結]
  • [STM32] 20-I2C [連結]

--

--

Morgan Ting
閱益如美

用好奇心探索世界。喜愛學習樂於分享。