LoRa網關項目——OLED(SSD1306)開發(一)


LoRa網關項目——OLED(SSD1306)開發(一)

#前言

​ 最近在做一個LoRa物聯網網關的項目,網關的作用主要是管理連接的LoRa傳感器終端,將傳感數據通過協議轉換向上轉發到Internet,當然,也要處理下行的數據。

​ 使用到的LoRa射頻芯片是SX1278,MCU為STM32F103RCT6,連接Internet用的是ESP8266+AT,且移植了FreeRTOS(單純是為了學習),開發環境是STM32CubeMX+Keil 5。由於之前沒負責過整個系統的開發,所以開此貼記錄一下開發過程,由於本人上學以來語文一直不好,所以文筆正在努力進步中,如果此文章有您覺得我說的不明白的地方,可以發送郵件到wanglu082@yeah.net,或者在文章下方評論,我看到會盡快回復您,多謝諒解!


​ 為了方便查看網關的狀態,本系統加入了一個0.96‘OLED模塊來顯示一些調試信息。

一. 對SSD1306驅動改進

1.1 初始化函數改進

​ 拿SSD1306的初始化來說,OLED模塊廠商給的驅動是使用MCU發送多次命令對SSD1306進行配置,這就要啟動多次IIC通訊,但實際上,通過DataSheet中給出的實例通信時序,SSD1306是支持一次發送多次命令/數據的:

image-20210512124249540

​ 所以我們可以將初始化的命令序列組成一個數組(1Byte 命令、1Byte數據、1Byte 命令、1Byte數據……),通過一次IIC通信發送給SSD1306。改進后 OLED_Init() 函數如下:

    
void OLED_Init(void) { 	

	/* 	GPIO的初始化在MX_GPIO_Init()中進行 */
	
	/* 初始化命令數組必須定義為 全局變量 或 局部靜態變量,
	   若定義為局部變量,則可能 OLED_Init 執行結束,DMA沒有傳輸完成 */
	static u8 OledInitCmd[29] = 
				   {0xAE,0x00,0x10,0x40,0xB0,0x81,0xFF,0xA1,0xA6,0xA8,\
					  0x3F,0xC8,0xD3,0x00,0xD5,0x80,0xD8,0x05,0xD9,0xF1,\
					  0xDA,0x12,0xDB,0x30,0x8D,0x14,0xAF,0x20,0x00
	};

	u16 OledInitCmdLength = sizeof(OledInitCmd);
	
	HAL_I2C_Mem_Write_DMA(&hi2c1, OLED_IIC_ADDR, OLED_CMD, I2C_MEMADD_SIZE_8BIT, OledInitCmd, OledInitCmdLength);

}  

​ 這里為什么用 HAL_I2C_Mem_Write_DMA() 而不是 HAL_I2C_Master_Transmit_DMA() 呢?

​ 單看函數的描述很難分別其區別,通過對比這兩個函數的輸入參數的不同,HAL_I2C_Mem_Write_DMA()多了兩個形參:MemAddress 、MemAddSize 。其實看看源碼也就明白了,HAL_I2C_Mem_Write_DMA()會在發送完Salve Address后在發送8/16 bit的 MemAddress ,然后才是n byte的 數據。

​ 那么為什么SSD1306要發送 MemAddress 呢?再看通訊時序圖,在控制位的前面有2bit的Co和D/C#,先說D/C#用於標志該字節的數據是命令(Command)還是數據(Data),對應到SSD1306就是該字節是一條控制命令還是對GDDRAM的修改,Co是非常重要的1個控制位,Co為0表示接下來的每個字節都是數據,這個數據不是與命令相區別的數據,而是代表以下的每個字節都不含Co和D/C# 位,默認與前面相同。

image-20210512201748942

​ 正因為有這種機制,才需要調用HAL_I2C_Mem_Write_DMA(),使在發送SlaveAddr后能發送一個字節的控制命令(0x00或0x40)

  1. 0x00 ;Co 和 D/C# 均為0,表示接下來都是純命令,不需要再攜帶Co 和 D/C#。

  2. 0x40;Co 為0, D/C# 為1,表示接下來都是純數據,對GDDRAM的修改,也不需要再攜帶Co 和 D/C#。


1.2 顯示函數重寫

​ 拿顯示一個point來說,SSD1306的驅動中使用如下的流程:

  1. 定位需要操作的page
  2. 對該page的特定位進行操作

​ 這兩步操作是兩次IIC通訊過程,看似沒什么問題,但是我們的顯示器通常不是一直顯示一個頁面的。如配合按鍵實現的多級菜單例程中,我最初是使用先對局部寫0,再寫入數據的方法來實現。這樣也能做,就是延遲有點大。

​ 在生活中,其實顯示器都會以一定的頻率在不斷的刷新,顯示顯存中的內容。如果還是使用官方實例中的方式,那么在While循環中實現刷新就必須要保存當前顯示的信息,而且整個IIC通訊過程還必須要CPU參與,極大的降低了CPU的利用率,這時候就可以讓DMA出場了。

​ DMA是數據的搬運工,它獨立於CPU工作,只需要在開始傳送和傳送結束時通知CPU,CPU就有工夫去干大事。那怎么才能讓DMA參與進來呢?畢竟它只能干搬數據的活,通過DataSheet中以下的文字描述可以了解到,SSD1306可以用過IIC通信向其內部GDDRAM進行操作(內部RAM結構講解:STM32使用OLED模塊(SSD1306):OLED_DrawBMP()),只需要將IIC的 D/C 位置 1。

image-20210512144708881

​ 那我們是不是就能一次性將128*64 bit的GDDRAM全部寫入,在STM32-Flash中定義一個8*128的數組(表示8個page,每個page有128個column),每次要顯示圖片、數字等的時候直接寫入這個STM32-Flash,再定時將這個數組的元素一次性寫入SSD1306的GDDRAM,DMA就負責這個傳輸過程。

​ 然而,SSD1306內部GDDRAM的讀寫指針變化的方式只能在一個page中增加column,不能自動的切換page。

image-20210512150313570

​ 這時我們就要將尋址的模式改位能自動切換到下一個page的模式,SSD1306給我們提供了這種模式的修改方法。

​ 只需要發送20h命令,在修改成00即可,這就是為什么你發現我上節的初始化命令后面跟了個 0x20 0x00.

image-20210512150537908 image-20210512150721905

​ 最終實現的刷新函數如下,可以使用定時器實現固定頻率刷新,也可以在IIC的發送完成標志BTF的回調函數中自動調用,就能實現自動刷新。

void OLED_Refreash(void)
{	
	/* 里面會進行回調函數的具體賦值 */
	HAL_I2C_Mem_Write_DMA(&hi2c1, OLED_IIC_ADDR, OLED_DATA, I2C_MEMADD_SIZE_8BIT, *OledFrameBuf, OLED_FRAMEBUF_LEN);	
}

二、理順IIC事件回調函數

​ 在HAL_I2C_Mem_Write_DMA中會對各類標志的回調函數進行注冊,這里我感覺庫文件寫的好像有點問題,它對hi2c->hdmatx進行傳輸完成的回調函數注冊,但是賦值的I2C_DMAXferCplt()中卻都是接收完成相關的弱定義函數。

image-20210512151123700

——————————————————————————————————————————————————

image-20210512153438003

​ 那我覺得發送完成的回調函數應該叫HAL_I2C_MemTxCpltCallback(),果不其然,搜了一下,還真有這個函數,它被I2C_MasterTransmit_BTF()I2C_MasterTransmit_TXE()調用,這里又很混亂了,即然上面把Mem_Write與Master_Transmit分開,那這里為啥又混起來了呢?感覺HAL庫整的有一點亂。

​ 問題又來了,BTF和TXE這兩個標志有什么區別?找到我們的STM32官方手冊,BTF和TXE分別是 I2C_SR1 寄存器的 bit2 和 bit7 ,然而手冊上寫的解釋我根本沒看懂,寄存器為空和發送完字節都是什么意思?不懂。那就去看看源碼吧,說不定能找到頭緒。

​ 先看I2C_MasterTransmit_TXE(),下面截取了幾個重要的片段,可以分析出TXE標志是每發送一個字節就產生,它控制着每個字節的發送過程。

image-20210512193037094 image-20210512193141206

​ 而I2C_MasterTransmit_BTF()則是對一次IIC通訊過程的結束進行處理:

image-20210512193457256

​ 到這里需要梳理一下,IIC一次傳輸完成后(也就是寫入全部的GDDRAM后)會置 BTF 標志為1,此時會執行I2C_MasterTransmit_BTF(),此函數完成標志位清除、狀態改寫的步驟后,會執行一個HAL_I2C_MemTxCpltCallback(),這是一個弱定義的函數,用戶可以自行實現。而我們要在一次GDDRAM傳輸完成后馬上開啟下一次傳輸實現自動刷新的目的,所以需要在HAL_I2C_MemTxCpltCallback()中再次調用OLED_Refreash()

​ 但是其實,我們還忘了,STM32並不是默認開啟 IIC 各類事件的中斷捕獲,需要在STM32CubeMX中開啟IIC的事件中斷。

image-20210512195229054


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM