OLED(SSD306)移植全解-基于IIC
- 一,什么是oled?
- 二,什么是IIC協議
- 三,IIC通信流程:
- 四,針對SSD1306的IIC通信流程(結合芯片手冊版)
- 1,主機發送起始信號
- 2,從設備應答 ACK 或 NACK
- 3,主機發送控制字節
- 4,重復 START
- 5,STOP
- 五,在 CubeMX 中配置 I2C 外設
- 1,Mode
- 2,Configuration
- Parameter Settings
- 1,Master Feasures
- 2,Timing Configuration即 數字濾波器和模擬濾波器
- 3,Slave Features即從機特性
- GPIO Settings
- 六,HAL庫IIC API
- 1,I2C_HandleTypeDef (句柄)和HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c)
- 2,HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
- 3,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)
- 4,HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout)
- 七,SSD1306驅動庫
- 1,IIC通信層
- 2,亮滅控制與上電初始化
- 3,設置坐標和清屏
- 4,文本顯示層
- 5,高級圖形顯示層
一,什么是oled?
一種屏幕類型,每個像素都能自己發光,不需要背光
大小多為 128×32 或 128×64 像素(像素就是屏幕上最小的發光點),還有一種衡量大小的方式,就是使用對角線長度表示,例如128×32 和 128×64像素數分別對應對角線長0.91英寸 或 0.93 英寸
oled如何發光呢?
首先其內部會有一個驅動芯片ssd1306,
[media pointer=“file-service://file-QsH4on2Cfai99p4uU4zGB4”]
OLED 屏幕多為 128×32 或 128×64 像素(像素就是屏幕上最小的發光點)。那0.91和0.93是什么意思?你說簡單說,STM32F4 通過 I2C 總線把「命令+數據」發給 SSD1306,SSD1306 再把對應的點陣內容顯示到 OLED 屏幕上,難道ssd1306是通過iic接收stm32f4的數據信號,然后依據此數據想stm32一樣控制io引腳電平去點亮像素點?如果不是像stm32通過io引腳輸出高低電平,那是怎么點亮像素點的呢,依據上面的原理圖講講?
這個驅動芯片并不是像STM32一樣直接通過控制 GPIO 引腳來點亮像素點,它通過iic協議接收STM32發過來的數據和命令(SSD1306 會根據 DC 引腳的狀態判斷接收到的是命令還是數據。在寫入控制命令時,DC 設為低電平;在寫入像素數據時,DC 設為高電平),內部電路對這些數據進行解析,然后將數據通過驅動電路輸出到 OLED 屏幕上。
那么什么是命令(Command)?
命令就是不會改變oled的顯示內容,就算說命令配置的不是屏幕,而是ssd1306的寄存器,如:
打開顯示、關閉顯示。
設置顯示的分辨率(128x32 或 128x64)。
設置顯示起始地址或列地址。
什么又是數據(Data)呢?
數據就是可以直接控制oled顯示內容的數據,控制像素的亮滅,直接存儲在顯存中,1字節數據1頁中1個列的像素的亮滅
這里提到了頁,在OLED中什么叫頁呢?
以分辨率為128*64的oled為例,128代表整個屏幕有128列像素點,64代表整個屏幕有64行像素點,頁的劃分就是每8行表示1個頁,那么整個屏幕就分為8個頁。
如果我們在每個頁中討論,1個頁的列(不是整個屏幕的列)即8個像素就是一組,由一個字節數據控制亮滅
STM32F4 向 SSD1306 發送特定的命令和顯示數據,你提到的命令和數據有什么區別和聯系嗎?這些命令和數據有什么作用啊,能不能舉例說明?你說ssd1306有自己的內存區域(顯存),用于存儲屏幕上所有像素的狀態,你提到的顯存只是存儲像素狀態,并沒有說明ssd1306怎么通過顯存數據控制像素的亮滅啊?好像意思是說ssd1306通過iic接收stm32的命令和數據,然后ssd1306去根據這些命令和數據改變自己的顯存數據?這個內部有一個 點陣矩陣,每個像素(點)由電子方式控制。好像說明了像素點亮的原理,但我還是不懂啊?每個字節存儲 8 位像素信息(對于 128×32 分辨率,顯示內容被存儲為 128 列 * 4 頁)。這又是什么意思啊,一個oled屏幕不是有12864個像素點嗎,如果顯存中的一字節存儲8個像素點的狀態,那就需要12864/8 = 1024個字節,也就是說ssd1306的顯存只有1024B即1KB,而你又說顯示內容被存儲為 128 列 * 4 頁,這個頁是什么意思,我記得32位是一個字啊?這里怎么用頁表示,這里4頁是代表一列的32個位嗎?還有你的這個舉例我也不理解啊,發送一個字節數據 0xFF 表示這一列的所有 8 個像素都被點亮,而 0x00 則表示這一列的所有像素都熄滅,0xFF應該表示2個字節吧?怎么只表示了1列,而且只有8個點即1字節呢?
二,什么是IIC協議
接下來我們該了解什么是IIC通信協議:
I2C(Inter-Integrated Circuit, 發音“eye-squared-see”)是一種串行總線協議。由飛利浦公司(現恩智浦半導體)開發的串行通信總線,廣泛應用于連接微控制器和低速外圍設備。
什么是低速外圍設備呢?
舉幾個低速外圍設備的例子:
傳感器:比如溫度傳感器、濕度傳感器、光傳感器等
顯示屏:例如 OLED 顯示屏
RTC(實時時鐘):用于保持時間的模塊
EEPROM(電可擦可編程只讀存儲器)
按鍵、開關、LED 燈等簡單接口設備
對應按鍵和LED使用IIC驅動見很少見,但不是沒有,例如MCP23017 是一個 I2C 控制的 GPIO 擴展器芯片,可以用來控制多個 LED 燈的亮滅。
它只有兩根線:
SCL(時鐘線):告訴對方“我開始傳/收數據了,每一個時鐘脈沖傳/收一個 bit”。
SDA(數據線):真正傳輸數據的線,每個時鐘周期上或下會放置一個數據位(0 或 1)
主機/從機:
STM32F4 配置成 I2C 主機,由它來產生時鐘脈沖。
SSD1306 芯片作為 I2C 從機,等待主機發命令或數據。
I2C 地址:
SSD1306 常見硬件地址通常是 0x3C(有的模塊也可能用 0x3D)。這個地址是寫死在 OLED 板子上的跳線,或者由焊盤決定。
地址的作用就是讓STM32找到ssd1306
核心特點:
1,支持多主機多從機架構(最多128個設備,使用7位尋址)
什么叫做多主機呢?
就是多個主機可以共享同一總線,但是只有一個主機可以在某一時刻控制總線,如何實現多臺主機的發送沖突呢?如果兩個主機同時嘗試發送數據時,較低的電平會“獲勝”,另一方的發送被停止,從而避免了沖突。
注意:雖然支持多主機,但在實際應用中,多主機模式不常用。大多數情況下,我們看到的都是一個主機控制多個從機的結構。
多從機的7位地址是什么?
如果我們使用一個主機控制多個從機,那么為了方便主機向特定的從機發送數據,我們為每個從機設備設置了唯一的一個地址,這個地址用7位數據表示,那么最多可以設置27=128個從機設備(范圍是從 0x00 到 0x7F,其實有些地址會被保留,使用實際上小于128),
2,通信速率靈活(標準模式100kHz,快速模式400kHz等)
我們知道在串口通信中使用波特率來表示每秒傳輸的數據位數,而在IIC通信當中,我們使用比特每秒(bps)表示每秒傳輸的位數,這里也可以用Hz表示(在這里1bps = 1Hz)
為了適應不同的設備需求不同的數據傳輸速度,我們的IIC支持多種通信速度:
標準模式(100 kHz):低速傳感器、簡單的控制設備、溫度傳感器、RTC(實時時鐘)模塊
快速模式(400 kHz):顯示屏
高速模式(3.4 MHz): 圖像傳感器、 高速數據采集
超高速模式(5 MHz):某些高速視頻采集系統
3,內置硬件應答機制確保可靠傳輸
什么是IIC的應答機制呢?
就是接收方不管有沒有接收到數據,都要發送應答信號/非應答信號給發送方(接收方可以是主機也可以是從機)
應答信號(ACK):接收方成功接收到數據并準備好繼續接收下一個字節時,發送 低電平(0),表示應答。
非應答信號(NACK):接收方沒有成功接收到數據,或者接收完所有數據后不再接收時,發送 高電平(1),表示沒有應答
4,起始和停止條件
I2C通信使用特殊的總線狀態表示通信的開始和結束。起始條件(START)是在SCL高電平時,SDA從高變為低;停止條件(STOP)是在SCL高電平時,SDA從低變為高。這兩種條件定義了一個完整通信幀的邊界
三,IIC通信流程:
前面介紹了IIC協議的基礎知識,那么在一個完整的IIC通信過程中,IIC的兩條總線會經歷什么呢?
1,IIC的兩條線(SCL和SDA)默認處于高電平的狀態
2,當某一個主機想訪問某個從設備時,會發送起始信號(SCL保持高電平,SDA從高電平變為低電平),此時其他主設備不能訪問總線,所有的從設備都進入準備接收接下來的地址數據
補充:此時總線進入“忙碌(Busy)”狀態,其他主機檢測到 SDA 被拉低時就知道總線已被占用。不過如果在多主機場景下,可能出現同時兩個主機幾乎同時發起 START,此時會進行總線仲裁(Arbitration)。
若兩個主機都在同一個時鐘周期嘗試拉低 SDA,不同主機對 SDA 的讀寫一致時繼續,若出現沖突則掉線的主機會自動放棄,從而保證一個主機獲得總線控制權
3,主設備發送7位(或10位)從設備地址,緊跟一個讀/寫控制位(表示接下來是讀還是寫),構成8個位即1字節,所有從設備接收并比對此地址,僅地址匹配的從設備繼續參與后續通信。
補充:對于 10 位地址尋址,需要發送兩次地址字節(第一字節包含前兩位“11110”、接著高 2 位地址和 R/W=0,第二字節才是真正的低 8 位地址),詳細流程略微復雜,但大方向與 7 位尋址相似。
4,如果總線上存在匹配地址的從設備,從機會將SDA線拉低一個時鐘周期(發送ACK);如果不存在匹配的從設備,SDA線保持高電平(NACK)
在發送完 8 位(地址+R/W)之后,主機會釋放 SDA,進入第 9 個時鐘周期。此時若有從設備應答, 從機就在第 9 個時鐘上拉低 SDA,表示 ACK;否則 SDA 處于高電平表示 NACK
如果出現 NACK,主機常見做法就是發出 STOP 條件中斷本次傳輸,然后視情況重試或放棄。
5,地址確認后,主設備和從設備開始根據之前的讀/寫位進行數據交換。數據以8位字節為單位傳輸,每個字節后必須有一個應答位
6,當完成一次數據交換后,主機還想發送其他數據,為避免了其他主設備在操作中途獲取總線控制權,主設備可以發送新的起始條件(SCL高,SDA由高變低),不需要停止再開始
重復 START 用于在一次事務(Transaction)中先進行寫地址/寫數據,再切換為讀數據(或反之),而不釋放總線。例如:先給某個寄存器寫入要讀的起始地址,然后立刻發 Re-START,再執行讀操作。這避免了總線空閑期間其他主機介入的可能。
7,通信結束于停止條件:SCL保持高電平,SDA從低電平變為高電平。這一轉換表明主設備釋放總線,將總線狀態恢復為"空閑"。停止條件后,任何主設備都可以通過發送起始條件獲取總線控制權。
四,針對SSD1306的IIC通信流程(結合芯片手冊版)
1,主機發送起始信號
主設備將 7 位從設備地址 + R/W 位構成一個“字節”傳給總線
7位從設備地址哪里來呢?
看芯片手冊:The device will respond to the slave address following by the slave address bit (“SA0” bit) and the read/write select bit (“R/W#” bit) with the following byte format, b7 b6 b5 b4 b3 b2 b1 b0 = 011110 SA0 R/W#.” (D/C# pin acts as SA0)這里是說ssd1306的從地址是011110 SA0 R/W,當SA0為低電平時,7為地址為0111100即0x3c,這也是默認的地址,如果想改變,可以的,這里有一個地址拓展位SA0,將這個引腳改為高電平則7位地址就變成了0111101即0x3d
實際上主機發送的起始信號是8個位組成1字節,這第8個位哪里?不是只有7位地址嗎?
我們的0x3c和0x3d會左移一位,將第8位留給R/W,R/W 為 0 表示“下面我要寫數據給你”;R/W 為 1 表示“我想從你那里讀數據”。
2,從設備應答 ACK 或 NACK
主機發送完起始信號之后,會在第 9 個時鐘周期會先松開 SDA(恢復到高電平) ,看總線上有沒有某個從機把 SDA 拉低(從機拉低 SDA 就表示向主機發送ACK即應答信號,表示從機接收到了地址數據,準備被讀/被寫)
如果主機發現SDA沒有被拉低,而是SDA在第9個時鐘周期,一直保持高電平,就表示從機發送了NACK即非應答信號,于是主機通常會放棄這次嘗試,發 STOP(停止)
參考手冊:After the transmission of the slave address, an acknowledgement signal will be generated after receiving one byte … The acknowledge bit is defined as the SDA line is pulled down during the HIGH period of the acknowledgement related clock pulse.”
“If there is no ACK, the master usually sends a STOP condition to terminate the current communication attempt
3,主機發送控制字節
對于步驟 5:發送控制字節(Co + D/C#)及后續數據或命令,我連什么是控制字節都不知道啊?Co 位(Continuity bit):決定本次 I2C 傳輸里是否還會接著再發“下一個控制字節”;這又是什么意思啊?如果會再發控制字節會怎么樣?不發又會怎么樣?你說D/C# 位(Data/Command bit):決定下面真正的“8 位”是命令(Command),還是顯示數據(Data,即寫入顯存);這里的下面的8位又是在哪里啊?問題又來了,Co和D/C是什么時候發送的?在起始信號之后,命令/數據信號之前?用 “0x00” 作為“單字節命令”的控制字節;用 “0x40” 作為“寫數據到顯存”的控制字節,這里0x00是控制字節?又提到了控制字節,我還是不理解,單字節命令又是什么東西?0x40是寫數據到顯存的控制字節,這里我感覺控制字節是命令啊?他們是什么關系?主機在收到 ACK 之后,緊接著發:0x00(控制字節,表示“下一個字節當命令”)。然后發命令。例如:0xA8 (設置 Multiplex Ratio),后面再跟一個參數字節,比如 0x1F 表示 32 行面板。這里我好像理解了什么是控制字節和命令,控制字節0x00是在每個命令之前都要發送的,表示下面要發送命令,但是你這里又提出了設置0xa8的參數要發送0x1F,我看你的代碼里面,也是在0x00之后發送0x1f,難道0x1f也是命令?為什么沒有緊跟著0xa8呢?現在我還是不理解Co位和D/C位對應在0x00和0x40的哪些位置?顯示之前通常都要先用一系列命令設置好“內存地址模式”(Page 模式 / Horizontal 模式 / Vertical 模式)、“頁面起始地址”、“列起始地址”+“列結束地址”……以便告訴 SSD1306 后面連續發來的顯存數據要怎么往 GDDRAM 里放,這一步怎么實現,我是小白,什么都不懂啊?
什么是“控制字節”,前面的iic通信流程里面沒有講啊?
我們知道主機會發送起始信號(地址+讀/寫),然后會發送命令(操作ssd1306寄存器)和數據(操作屏幕),但是其實在發送數據之前,主機還會向ssd1306發送控制字節(就僅僅8個位),這個“控制字節”本身不算是命令或數據,他有兩個功能:
1,告訴ssd1306,接下來發的下一個字節,是命令(Command)還是要寫到顯存里的數據(Data)
2,告訴ssd1306,以后還會不會再發另一個控制字節。
這個控制字節是怎么組成的呢?
Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
---|---|---|---|---|---|---|---|
Co | D/C# | 0 | 0 | 0 | 0 | 0 | 0 |
Co位和D/C#的作用:
Bit7(Co,Continuity即連續性)
如果 Co = 0:表示“我只發這一個控制字節,不緊跟另一個控制字節”。
就是說下一個字節是數據/命令,不是控制字節了,但是當發送完數據/命令,主機仍然可以發送0x00控制字節,只是兩個控制字節不能連續發送
如果 Co = 1:表示“我這次發完這個控制字節后,還會接著發下一個控制字節
Bit6(D/C#,Data/Command即數據/命令)
如果 D/C# = 0:表示“接下來的字節,要被 SSD1306 當作命令(Command)來執行”。
如果 D/C# = 1:表示“接下來的字節,要被 SSD1306 當作數據(Data,寫入顯存 GDDRAM)”。
Bit5…Bit0(6 位)都必須填 0。
這里我們先舉一個發送命令的例子:
首先明確:0xA8是命令Set Multiplex Ratio,0x1F 是命令 0xA8 的參數(也屬于命令)
我們主機想要發送這兩個命令給從機,它會這么發送呢?
1,發送0x00表示下一個是命令
2,0xA8表示Set Multiplex Ratio(但是這個命令還需要一個參數)
3,0x00又一個控制字節:因為 0xA8 還有參數要發
4,0x1F是命令 0xA8 的參數,屬于命令范疇
注意:0x1F 不是新的“命令”,它只是“命令 0xA8 的參數”,但它也仍然是通過控制字節 0x00 告訴 SSD1306:下一個字節 (0x1F) 是命令相關的數據
這里0xA8是一個多字節命令,就是除了命令本身,還有配套的參數0x1F,這種命令發送時時,它自己和參數前面都要發送0x00,不能只發送一個0x00,然后兩者緊接著發送
如果是單字節命令,就不需要考慮這些,直接發送0x00,然后在將自己發出去就行了
再舉一個發送數據的例子:
首先我們在MCU即STM32中定義一個空間uint8_t buffer[512],這有512個字節,剛好覆蓋128*64分辨率的oled的所有像素點
接下來通過iic通信將這512個字節發送到屏幕上:
1,0x40表示接下來主機要發送數據到顯存 GDDRAM
2,buffer[0]
3,buffer[1]
…
4,buffer[512]發送完最后一個字節
參考手冊:After the transmission of the slave address, either the control byte or the data byte may be sent across the SDA. A control byte mainly consists of Co and D/C# bits following by six 0’s … If the D/C# bit is set to logic 1, it defines the following data byte as data which will be stored at the GDDRAM. The GDDRAM column address pointer will be increased by one automatically after each data write.
到了這里,相信你已經理解了,控制字節的作用就是控制發送命令和數據,但是在發送數據之前,我們會發送命令給ssd1306,而不是直接發送數據,我因為我們要對ssd1306的寄存器進行配置,具體包括:內存地址模式,頁面地址,列地址
下面結合前面的知識,我們一個一個進行配置:
1,內存地址模式:設置此模式的命令是0x20
此命令的參數是:
0x00 → Horizontal 模式
0x01 → Vertical 模式
0x02 → Page 模式
這三個模式的區別是什么呢?
Page 模式:每次寫完 1 列就保持在當前頁不動,需要你手動再發命令才換下一列,或者換下一頁。
Vertical 模式:寫完一個字節后,會自動把 “頁指針”+1(從 Page0 跳到 Page1),到頁末就翻回到起始頁同時 “列指針”+1。
Horizontal 模式:寫完一個字節后,會自動把 “列指針”+1(從 Column0 跳到 Column1),到列末就翻回起始列同時 “頁指針”+1。
代碼示例
// 發控制字節,告訴它我接下來要發命令
send_I2C(0x00);
// 發命令本身:Set Memory Addressing Mode
send_I2C(0x20);// 因為這條命令還要跟一個參數,所以再發控制字節
send_I2C(0x00);
// 發參數:0x02 → Page 模式
send_I2C(0x02);
2,頁面起始地址
在 Page 模式下,顯存被分成若干“頁”,前面我們知道,8行為1頁,128*32的分辨率就分成了4頁,每一頁對應的命令是:
Page0 → 0xB0
Page1 → 0xB1
Page2 → 0xB2
Page3 → 0xB3
發送對應的命令,就表示以這一頁的開頭作為起始地址,例如:
send_I2C(0x00); // 控制字節:準備發命令
send_I2C(0xB1); // 命令:Page1
發完這條命令,SSD1306 內部就把“當前頁指針”切到 Page1。接下來再發 0x40 + 數據,就會把數據寫入 Page1 這一行的 128 列里。
3,列地址
我們知道1頁有8行,128列,前面我們通過命令設置了寫入哪一個頁(Page1),接下來通過命令就可以設置寫入這個頁Page1的哪一個列,這個命令分為兩部分:列低地址命令和列高地址命令
列低地址命令0x00~0x0F,分別表示寫入第0 ~ 15列
列高地址命令0x10~0x17,即0001 0000 ~ 0001 0111,結合列低地址命令表示第16 ~ 127列,這里每加1代表加16列,因為列低地址命令代表低4位,這個代表高3位
發送命令時列高地址命令和列低地址命令要一起發送組成一個完整的地址
列低地址命令 = 0x00 ~ 0x0F = (N & 0x0F)
列高地址命令 = 0x10 ~ 0x17 = 0x10 | ((N >> 4) & 0x07)
例如我要寫在第66列:就發送0x01和0x14
send I2C 0x01 //發送低4位
send I2C 0x14 //發送高4位
結合起來就是4*16+2=66列
還不明白就自己思考,反正我理解了
其實就相當于ssd1306里面有兩個8位的寄存器:列高寄存器(0001 0xxx)和列低寄存器(0000 xxxx),可以看到列低寄存器在0000 0000 ~ 0000 1111里面變化共16種可能,而列高寄存器在0001 0000 ~ 0001 0111里面變化,列低寄存器變化16位,列高寄存器就變化1位,所以總共就有8*16 = 128種可能,對應屏幕的128列,每次發送命令時,需要兩個一起發送,組成對應的列號
4,重復 START
什么是“重復 START”?
I2C 允許在不發 STOP(釋放總線)的情況下,再發起另一次 START 條件。這樣做的好處是:
總線不中斷:其他主機沒法趁你 STOP 之后插隊。
可以在同一次事務里先寫命令、再讀狀態或數據
參考手冊:If the master device needs to transmit more data, it can issue a Repeated START condition (SCL = HIGH, SDA: HIGH→LOW) without issuing a STOP. This avoids other masters from taking control of the bus in the middle of the operation.
在ssd1306中的用法是:先通過 PAGE 模式寫入一部分顯存,再 Re-START,切換到 Horizontal 模式寫下一部分。所以無需在兩個不同事務間 STOP,再重新 START。
為什么都是些數據,我們先通過pqge模式寫,再通過 Horizontal 寫呢?
回顧前面這2種模式的區別,pqge模式可以選擇寫的位置而且寫完后指針不加1,而Horizontal 寫完后指針加1
這就導致2種模式有自己的用處:Page 模式負責把起始指針“精準定位”到某一頁某一列,定位后切換到 Horizontal 模式,一口氣寫后面要填的 N 列數據
5,STOP
通知所有從機“本次通信搞完了,可以釋放總線”
把 SDA 和 SCL 都拉回到空閑狀態(都 HIGH),讓下一個想用總線的主機知道“現在可以發起新的 START”。
參考手冊:The write mode will be finished when a stop condition is applied. The stop condition is … established by pulling the SDA from LOW to HIGH while the SCL stays HIGH
五,在 CubeMX 中配置 I2C 外設
1,Mode
Disable:禁用 I2C 外設。如果選擇該選項,對應的IIC引腳就是普通的GPIO引腳
I2C:表示 I2C 外設工作在 I2C 通信模式,STM32 可以作為主機 (Master) 或從機 (Slave)。
當我們需要主機和和從機設備通信時,需要選擇此模式
SMBus-Alert-mode、SMBus-two-wire-Interface:系統管理總線模式,是 I2C 的一個變種,具有更嚴格的時序和協議要求,例如超時檢測、PEC (Packet Error Checking) 等。通常用于電源管理或特定的傳感器。
2,Configuration
Clock Speed 為 100kHz 是 SSD1306 的推薦設置,完全滿足其需求,避免了較高頻率帶來的不必要的噪聲問題,為什么說這是推薦設置?從哪里看到呢?我是小白,不懂這些,這個配置在圖中屬于Master Feasures,這個配置一般在芯片手冊的哪一個地方啊?如果我要開發其他的IIC芯片,這一項該怎么選擇?看芯片手冊的哪里?你總結一下常見的IIC芯片 對應這一項該選擇什么?Master Feasures這一項的含義是什么呢?這個好像是針對主機即STM32CubeMX而言的,設置的速度有什么有呢?這是IIC通信的速度嗎?Timing Configuration
Coefficient of Digital Filter 設置為 0:這個數字濾波器用于抑制干擾信號,通常對于 I2C 協議,默認設置即可,不需要修改。
Analog Filter 設置為 Enabled:這個模擬濾波器會啟用 I2C 總線上的模擬過濾功能,默認啟用即可。這里的數字濾波器是干什么的?有什么用?具體講講,我是小白,芯片參考手冊里面有Timing Configuration的相關信息嗎?Slave Features這是針對從機的配置嗎?Clock No Stretch Mode 設置為 Disabled:這是控制 I2C 總線時鐘拉伸功能的選項。由于 SSD1306 使用的是標準的 7 位地址 I2C 協議,因此可以保持禁用。什么是總線拉伸功能?為什么說7位地址就要保持禁用?在芯片手冊里面能找到相關信息嗎?Primary Address Length selection 設置為 7-bit:這表明你正在使用 7 位地址。SSD1306 使用的是 7 位地址,例如 0x3C,因此這一選項保持為 7 位是正確的。這里的Primary 代表什么特殊含義嗎?這個能在芯片手冊里面找到嗎?Dual Address Acknowledged 設置為 Disabled:SSD1306 只有一個地址,不需要支持雙地址。因此,禁用此選項是正確的。這里為什么又出現了雙地址?什么是雙地址?我是嵌入式小白,沒有見過啊?能不能舉一些例子?對于IIC芯片手冊,一般在哪里能夠找到地址信息啊?在哪里能夠找到有無雙地址的信息啊?Primary slave address 設置為 0:這表示主設備地址的設置。你不需要修改這個,主機通信時會使用從設備的地址。你說這個是主地址的設置,這個選項有什么應用場景嗎?在芯片手冊里面的哪里有相關的信息?General Call address detection 設置為 Disabled:不啟用通用調用地址檢測。對于 SSD1306,這個選項可以禁用,確保通信時只針對你設置的從設備地址。這里通用地址檢查又是什么東西啊,我是小白,不懂啊?這里配置選項一般在芯片手冊的哪里啊?
Parameter Settings
1,Master Feasures
這里有兩個選項:
IIC Speed Mode I2C 時鐘速度 :
IIC Clock Speed(Hz),IIC Speed Mode可以選擇 Standard Mode 和 Fast Mode ,如果選擇前者那么下面的IIC Clock Speed最大可以設置為100 000,如果選擇Fast Mode 高速模式,則IIC Clock Speed最大可以設置為400 000
IIC Clock Speed是我們期望的頻率,但是系統時鐘輸入到IIC的頻率(通過APB1總線的頻率)不等于IIC Clock Speed,還需要CCR(Clock Control Register)這個分頻器,將IIC總線上的頻率分頻為我們期望的頻率,為什么一定是分頻呢?因為在我們選擇IIC Clock Speed時,手冊就要求 I2CCLK (APB1總線頻率)高于我們選擇IIC Clock Speed, I2CCLK 至少是 Standard Mode 頻率的 2 倍,Fast Mode 頻率的 3 倍
其實還有第三個選項Fast Mode Plus: 最高速率 1 MHz,但是這個需要外設和 GPIO 都支持 FMP(Fast Mode Plus)特性
對應SSD1306而言選擇100 000 Hz的速度就足夠了
當我們選擇 Fast Mode 時:還可以設置 Clock Duty Cycle - Fast Mode快速模式占空比:
這里有兩個選項:
DUTYCYCLE_2: 低電平時間是高電平時間的 2 倍。這是推薦和常用的設置。
DUTYCYCLE_16_9: 低電平時間是高電平時間的 16/9 倍。在某些特定從設備或總線條件下可能需要。
我們選擇DUTYCYCLE_2即可
2,Timing Configuration即 數字濾波器和模擬濾波器
Coefficient of Digital Filter 數字濾波器:它處理的是 I2C 總線上的 數據,只有當總線噪聲較大時,才需要考慮啟用它。
Analog Filter 模擬濾波器:它幫助濾除噪聲源,確保 I2C 數據和時鐘信號不受干擾。SSD1306 的 I2C 通信中,通常會保持啟用模擬濾波器來提高可靠性。
在 STM32 中,默認 啟用模擬濾波器,這是因為 模擬信號更容易受到干擾,明明IIC總線上的信號是數字信號,哪里來的模擬信號呢?其實在IIC總線的數字信號之前是模擬信號,模擬濾波器用來在 I2C 總線的數字信號傳輸之前 處理這些噪聲
3,Slave Features即從機特性
這些選項主要用于配置 I2C 總線的從設備功能,而不涉及主設備 STM32 的配置
Primary Addressing Length selection主要尋址模式:
可以選擇7/10,這個選項只影響HAL庫函數HAL_I2C_Master_Transmit 的地址參數處理,如果從機既有7位地址,又有10位地址,HAL_I2C_Master_Transmit函數可以自動根據傳入的參數進行處理
Clock No Stretch Mode
I2C 總線的一些從設備可能會在數據傳輸時“拉伸”時鐘線,表示它還在處理數據
Primary Address Length selection:選擇主地址長度
在 I2C 通信中,SSD1306 的從設備地址通常是 0x3C,這就是 7 位地址
Primary Slave Address本機地址 1: 直接輸入地址值
注意: 這里輸入的是 7 位地址本身,不需要左移或添加讀寫位
Dual Address Acknowledged:即確認雙地址
對于某些 I2C 設備,它們支持“雙地址模式”,允許它們同時使用兩個地址進行通信。對于 SSD1306,由于它只需要一個地址,因此禁用此選項。
如果使能該選項,CubeMX就會多出一個從地址,本機地址2
General Call Address Detection 廣播呼叫地址檢測:
之前說過7位地址最多不一定支持128個設備,還有些地址被保留了,0x00就是被保留的地址即廣播呼叫地址
當主機向此地址發送信息時,使能此項的從機會響應發往0x00地址的信息
Clock Stretching時鐘延長 :
只有在確定主機不支持時鐘延長或追求極低延遲且能保證從機處理速度的情況下才考慮禁用。
GPIO Settings
GPIO Output level: 無需配置
此項表示GPIO輸出的高低電平,因為我們使用的是硬件IIC,總線的電平由外設控制,不需要我們手動設置GPIO電平(軟件IIC),所以不需要勾選此項
什么是軟件iic?
用普通GPIO口,通過軟件程序控制GPIO的電平變化,來模擬I2C的時序和協議
什么是硬件iic?
主機和從機的的IIC模塊自動完成IIC通信,不需要我們手動配置GPIO電平
對于GPIO Output level,在硬件IIC模式時,就算我們配置了此項,也沒有用,因為I2C模塊自動驅動引腳,你手動設置電平是無效的,配置會被硬件覆蓋或忽略。
GPIO mode: Alternate Function Open Drain
Alternate Function: 將引腳功能配置為連接到 I2C 外設,而不是通用輸入輸出。
Open Drain (開漏): 允許多個設備連接到同一總線。設備只能將線拉低,不能主動推高。高電平由外部上拉電阻提供。
GPIO Pull-up/Pull-down: Pull-up 或 No pull-up and no pull-down
I2C 協議要求 SCL 和 SDA 必須有上拉電阻。
選項:
Pull-up: 啟用 STM32 內部自帶的弱上拉電阻 (通常 30kΩ - 50kΩ)。僅適用于低速 (<=100kHz) 且總線負載很小 (設備少、線短) 的情況。
No pull-up and no pull-down: 推薦 禁用內部上拉。此時必須在 PCB 上為 SCL 和 SDA 各添加一個外部上拉電阻。 這是最可靠的做法,允許根據總線速度和電容選擇合適的電阻值。
外部上拉電阻選擇: 典型值在 1.5kΩ 到 10kΩ 之間。常用 4.7kΩ (適用于 100kHz/400kHz,中等負載)。更高速率或更大總線電容需要更小的上拉電阻 (如 2.2kΩ, 1.8kΩ)。具體計算需參考 I2C 規范和總線電容。
Maximum output speed: High 或 Very High
為確保信號邊沿足夠陡峭以滿足 I2C 時序要求(尤其是在 Fast Mode 或更高速度下),應選擇較高的 GPIO 輸出速度
User Label: (可選) 為引腳添加自定義標簽
六,HAL庫IIC API
1,I2C_HandleTypeDef (句柄)和HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c)
句柄就是包含了iic的所有配置信息,這個初始化代碼是CubeMX自動生成的,舉個例子:
void MX_I2C1_Init(void)
{/* USER CODE BEGIN I2C1_Init 0 *//* USER CODE END I2C1_Init 0 *//* USER CODE BEGIN I2C1_Init 1 *//* USER CODE END I2C1_Init 1 */hi2c1.Instance = I2C1;hi2c1.Init.ClockSpeed = 400000;hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;hi2c1.Init.OwnAddress1 = 0;hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;hi2c1.Init.OwnAddress2 = 0;hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;if (HAL_I2C_Init(&hi2c1) != HAL_OK){Error_Handler();}/** Configure Analogue filter*/if (HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE) != HAL_OK){Error_Handler();}/** Configure Digital filter*/if (HAL_I2CEx_ConfigDigitalFilter(&hi2c1, 0) != HAL_OK){Error_Handler();}/* USER CODE BEGIN I2C1_Init 2 *//* USER CODE END I2C1_Init 2 */}
首先我們要知道iic初始化的目的是配置硬件iic按照什么規則工作,針對的是MCU本身,不是和哪個設備通信:
設置通信速度:
hi2c1.Init.ClockSpeed = 400000;
設置通信時鐘線的占空比:
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
設置MCU自己作為從機時的地址:
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
禁用廣播呼叫功能:
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
當MCU作為從機時,將不響應地址為0x00的廣播指令
允許時鐘延展(更可靠通信) :
I2C_NOSTRETCH_DISABLE;
配置數字濾波和模擬濾波:
// 模擬濾波器(消除電氣噪聲)
if (HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
{
Error_Handler();
}
// 數字濾波器(設為0表示不額外過濾)
if (HAL_I2CEx_ConfigDigitalFilter(&hi2c1, 0) != HAL_OK)
{
Error_Handler();
}
2,HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
有兩個關鍵參數:
DevAddress:即設備地址
注意是那7位地址左移一位后加上讀寫位后得到的8位地址
pData: 傳入我們要寫的數據
3,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)
Mem_Write實際上是對Master_Transmit的封裝
就多了一個MemAddress即內存地址
為什么叫內存地址我也不知道,實際上就是控制字節:
0x00表示將數據寫到寄存器即寫命令
0x40表示將數據寫到顯存即寫數據
4,HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout)
在初始化 OLED 之前,可以調用此函數來確認 OLED 模塊是否正確連接并且 I2C 通信正常。有助于調試硬件連接問題
七,SSD1306驅動庫
HAL_Delay(100); // 上電穩定
for(i=0;i<sizeof(initcmd1);i++)OLED_Write_cmd(initcmd1[i]);
OLED_Clear(); // 防止上電隨機 RAM
OLED_Set_Position(0,0); // 光標歸零
接下來詳細介紹一下這個ssd1306驅動庫,以至于我們可以將它移植到sh1106上面:
1,IIC通信層
OLED_Write_cmd和OLED_Write_data這兩個函數其實就是對前面講的IIC的API的再次封裝:
void OLED_Write_cmd(uint8_t cmd)
{HAL_I2C_Mem_Write(&hi2c1, OLED_ADDRESS, 0x00, I2C_MEMADD_SIZE_8BIT, &cmd, 1, 0x100);
}void OLED_Write_data(uint8_t data)
{HAL_I2C_Mem_Write(&hi2c1, OLED_ADDRESS, 0x40, I2C_MEMADD_SIZE_8BIT, &data, 1, 0x100);
}
竟然HAL庫都已經有IIC發送數據的專用API了,為什么我們還有再次封裝它呢?
調用OLED_Write_cmd比直接調用HAL函數更清晰,我們直接在封裝后的函數寫入命令就行,不用在發送控制字節
2,亮滅控制與上電初始化
初始化命令initcmd1數組:
uint8_t initcmd1[] = {0xAE, //display off0xD5, 0x80, //Set Display Clock Divide Ratio/Oscillator Frequency0xA8, 0x1F, //set multiplex Ratio //128*32
// 0xA8, 0x3F, //set multiplex Ratio //128*640xD3, 0x00, //display offset0x40, //set display start line0x8d, 0x14, //set charge pump0xa1, //set segment remap0xc8, //set com output scan direction
// 0xda, 0x12, //set com pins hardware configuration //128*640xda, 0x00, //set com pins hardware configuration //128*320x81, 0x80, //set contrast control0xd9, 0x1f, //set pre-charge period0xdb, 0x40, //set vcom deselect level0xa4, //Set Entire Display On/Off0xaf, //set display on
};
這些命令來自于哪里?ssd1306的12832與12864分辨率的初始化命令有什么區別?
如果我要移植到sh1106上面需要修改什么?
上電初始化函數:
void OLED_Init(void)
{HAL_Delay(100);uint8_t i;for (i = 0; i < sizeof(initcmd1); i++){OLED_Write_cmd(initcmd1[i]); //display off}OLED_Clear();OLED_Set_Position(0, 0);
}
這里為什么要HAL_Delay?明明我才上電為什么要清屏?OLED_Set_Position有什么用?
HAL_Delay(100):確保OLED完全上電后再發送命令
清屏:確保顯示從干凈狀態開始,避免上電時顯示殘留內容
OLED_Set_Position(0,0):將光標設置到初始位置,準備顯示內容
3,設置坐標和清屏
OLED_Set_Position:設置光標位置(x,y)
OLED_Clear:清屏
OLED_Allfill:全屏填充白色
/*** OLED fill function, after using the function 0.91 inch oled screen into full white
**/
void OLED_Allfill(void)
{uint8_t i, j;for (i = 0; i < 4; i++) //根據分辨率修改總頁數{OLED_Write_cmd(0xb0 + i); //決定寫白幾頁OLED_Write_cmd(0x00);OLED_Write_cmd(0x10);for (j = 0; j < 128; j++){OLED_Write_data(0xFF);}}
}/*** @brief Set coordinates* @param x: X position, range 0 - 127 Because our OLED screen resolution is 128*32, so the horizontal is 128 pixels* @param y: Y position, range 0 - 3 Because the vertical pixels are positioned in pages, each page has 8 pixels, so there are 4 pages
**/
void OLED_Set_Position(uint8_t x, uint8_t y) //把“寫數據指針”移動到 列 x、頁 y,
{OLED_Write_cmd(0xb0 + y); //決定哪一頁寫白OLED_Write_cmd(((x & 0xf0) >> 4) | 0x10); //列地址“高四位”OLED_Write_cmd((x & 0x0f) | 0x00); //列地址“低四位”
}/*** Clear Screen Function* Fill each row and column with 0
**/
void OLED_Clear(void)
{uint8_t i, n;for (i = 0; i < 8; i++){OLED_Write_cmd(0xb0 + i); //決定消除幾頁OLED_Write_cmd(0x00);OLED_Write_cmd(0x10);for (n = 0; n < 128; n++){OLED_Write_data(0);}}
}
這里的清屏和覆蓋全屏操作中的命令0xb0+i,0x01是什么含義?
0xb0+i:設置頁地址(0-7),每頁8像素高
0x00和0x10:設置列地址的低4位和高4位
不是說在發送命令之前都要先發送0x00嗎?為什么在0xb0+i之前沒有呢?
OLED_Write_cmd函數已經通過參數0x00指明了這是命令而非數據
這里的設置坐標的函數邏輯是什么啊?
4,文本顯示層
字符顯示:OLED_ShowChar (支持8x6和8x16字體)
字符串顯示:OLED_ShowStr
數字顯示:OLED_ShowNum和OLED_ShowFloat
5,高級圖形顯示層
中文顯示:OLED_ShowHanzi(16x16)和OLED_ShowHzbig(32x32)
圖片顯示:OLED_ShowPic
這里的文本顯示層和圖像顯示層怎么使用啊?
下面是我使用顯示字符串的代碼:
int Oled_Printf(uint8_t x, uint8_t y, const char *format, ...)
{char buffer[128]; // 緩沖區大小根據需要調整va_list arg;int len;va_start(arg, format);len = vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);OLED_ShowStr(x, y, buffer, 8); // 將 buffer 轉為 uint8_t*,為什么不是char//8:F6*8,一行寫滿跳一頁 16:F8*16像素,一行寫滿跳兩頁return len;}
下面的函數沒有介紹啊?
/*** @brief OLED pow function* @param m - base* @param n - exponent* @return result
*/
static uint32_t OLED_Pow(uint8_t a, uint8_t n)
{uint32_t result = 1;while (n--){result *= a;}return result;
}
這是一個輔助函數,計算a的n次方。在OLED_ShowNum函數中用于從整數中提取各個位的數字。例如,計算10^3用于提取千位數字