上一章我們講解了IIC的通信流程以及通信代碼,這一章就以市面上常見的IIC接口模塊——OLED屏為例教學一下IIC接口的驅動怎么寫。
第一步當然是搞清楚自己使用的OLED屏幕用的是什么驅動,說是屏幕,實際上就是密集LED點陣,所以必定有用于控制大量LED燈的驅動器,本教學使用的OLED驅動是SSD1306,該驅動器有多種通信接口,這里使用IIC接口(具體使用什么接口,數據手冊上會有詳細介紹)
根據SSD1306數據手冊的描述,該設備的從機地址取決于SA0的電平,但是我翻遍了商家給我的資料也沒找到整個模塊的硬件原理圖(也有可能遺漏了),無奈只能打開例程查看從機地址是0x78。
數據手冊詳細描述了1306的IIC接口規則,7bit的地址位+1bit的讀寫位,數據線和時鐘線描述的就是標準的IIC協議,不必過多糾結。
下面的內容是重點,需要注意的是,編寫任何一個外設的驅動,基本上都逃不開指令和數據,驅動方式可能多種多樣,但最多也就是指令與數據的排列組合,稍微復雜一些的會加上寄存器操作(也就是用單片機1號通過某種通信接口控制單片機二號),抓住本質之后,思路就能打開。
先來看看數據手冊是怎么描述的:
對閱讀比較吃力的小伙伴,我撿重點說一說:
第2條描述的就是從機地址的設置,這在前面以及提到了;
第3條說的是IIC讀寫位的定義;
第4條說的是IIC協議中應答信號的規則,這里說明了1306會對一切IIC數據(包括地址|讀寫字節)作出回應;
第5條說的是一旦主機和1306建立通訊(發送地址|讀寫字節之后),后續發送到IIC總線上的數據就會被識別成“控制字節”或“數據字節”,具體什么是控制字節和數據字節,后文會詳細說明;
第6條說的是每個控制字節和數據字節都會被回應;
第7條說的是IIC協議停止位的規則;
現在來說明一下什么是控制字節和數據字節。對于屏幕,我們的操作他的目的是在屏幕上面顯示內容,“顯示”是一個動作、行為,也可以叫他命令,“內容”就是一個數據。所以操控設備的時候,至少會包含一個指令和一個數據,但是指令和數據在各種通信協議中,都只是一個8bit的數,如何區分他們就成了一個很關鍵的點。
1306使用這么一種方式來區分他們:一旦通過IIC接口建立通訊,隨后接收的第0個字節(byte0)一定是用來說明下一個字節(byte1)的類型的,它用2個字節來表達一個完整的數據傳輸。數據手冊中貼出了1306使用IIC通信的幀結構圖:
控制字節區分數據字節類型的方式,就是通過其最高的2個bit位:Co和D/C#。先說DC位,D就是data,C就是command,這個bit位為0就代表緊跟著的下一個字節是命令,如果bit位是1,那下一個緊跟著的字節就是數據。實際上只要有這么一個bit位就已經可以完成對1306的控制了,那么現在思考一個問題,如果我需要連續寫入大量的命令(比如100個),為了完成這100個命令的傳輸,我需要傳輸200個字節,因為每個命令都需要綁定一個控制字節。為了提高傳輸效率,Co位應運而生,如果Co位是0,且DC位也是0,那么1306就會把該控制字節之后傳入的所有數據都認定為命令,這樣一來如果要寫入100個命令,實際上只需要發送101個字節就能實現目的,效率幾乎是翻倍了。對于傳輸數據,也是一樣的,只需要把Co位設置為0,DC位設置為1,后續傳入的字節就全是數據了。如果沒有這種連續寫入大量同類型數據(命令/數據,括號里的數據是對屏幕顯示而言的,這個括號之前的數據是對IIC總線而言的)的需求,也可以把Co位置1以采用 “控制字節+數據字節(DC byte)”的方式實現功能。
下面就是代碼的編寫,我們使用聯合體直接列出需要發送的幀結構,需要發送時只需要賦值對應的位再發送value這個數組就可以了,這么寫就不需要在發送的時候進行位操作,大幅度提高代碼可讀性:
再貼一個發指令的函數,這個函數使用的是單次寫入的方式,效率不高但是方便使用,需要注意的是這個函數不具備建立IIC通信的能力,他只負責在已建立通信的情況下發出一個完整的指令。
現在我們擁有了建立IIC通信和發送指令的函數,實際上就已經可以用這些函數看看效果了,1306有一個指令是A5H,他的作用是強制點亮屏幕上所有的像素點,正確初始化OLED之后,再發送A5H即可。
關于OLED的初始化,模塊的資料提供了一套完整的初始化指令,簡單來說就是上電之后需要先把這一堆指令發給OLED驅動,之后屏幕才會正常工作,具體到每個指令的詳細功能,還請讀者查看1306數據手冊的指令表章節,或者直接搜索1306指令的相關資料。
話題拉回屏幕顯示這一塊,我們的目標肯定不只是把整個屏幕擦亮這么簡單,屏幕要么拿來畫畫,要么拿來寫字,他至少要能夠寫字才行。現在已經知道的是,屏幕就是一個LED陣,那只要控制一批像素點按照固定的規則點亮就能顯示我們想要的內容,實現這個目的的過程也叫取字模,售賣屏幕的商家打包的資料都會有取字模的軟件,如果沒有也可以直接去網上下一個。將字模數據預置存儲在單片機里面,需要的時候直接發出去就能顯示,這種辦法簡單而有效。
很好,現在我們寫字的目標已經轉變成“在合適的位置點亮合適的像素點”,那怎么確定位置呢?屏幕有那么多像素點,現在空有數據,卻沒有位置。這個時候就需要簡單說明一下OLED的顯示邏輯了,整個屏幕被劃分成了多個頁,每一頁都有128列像素點,屏幕的分辨率是128x64,橫向128像素,縱向64像素,我們每次寫入的數據都是8bit的,這個8bit數據指示了某個像素頁內某一列的像素狀態。形象地說就是:8個點排一列,橫向排128列就組成了一個頁,整個屏幕一共有8個頁,這8個頁再縱向排列,最終形成了一個128*64的屏幕。
想要在正確的位置顯示內容,就得選擇正確的頁(后文直接稱page),page0-page7一共8個,每個page都有自己的物理地址,從B0H到B7H,所以我們可以以此寫一個確定光標位置的函數,這個函數可以在我們需要寫字的時候錨定一個正確的顯示位置。
前文提到字模,其實就是一批8bit數據,再結合剛剛說明的屏幕顯示原理,就不得不再次思考一個問題,像素得精細到什么程度才能看起來像一個字,在像的情況下,還要符合OLED這種一頁8行像素的特點(因為這樣會更好操作)。答案是使用8n個像素寬度的正方形來顯示字符,目前來看16*16大小的字符正好符合要求,這也是大部分小屏顯示會選擇的大小。如此一來想要顯示一個字符就需要寫2個page的若干列數據,于是就有了寫字函數,具體代碼如下圖:
該函數首先建立IIC通信,與從機建立通信后設置顯示字符的坐標,隨后直接按順序發出上半部分和下半部分的像素數據即可,這個函數可以獨立完成對字符的顯示,后續演示代碼中顯示字符串的函數基于此函數實現,雖然對于字符串的顯示,最佳方案是建立一次通信就完成所有數據的傳輸,但那樣的代碼會把各種功能雜糅在一起,層次不夠分明,這里這么規劃也是為了內容更好理解,關于IIC與OLED的代碼文件會附在文章最后。
所有用于像素顯示的數據都會被存到Graphic Display Data RAM(GGDRAM)中,既然是RAM,理論上在上電的時候,其存儲的數據應該都是0才對,但為了避免不必要的干擾可能造成的影響,我們還需要一個清屏函數,該函數其實就是對所有page的所有數據進行置0操作。
具備所有的前提條件,我們就可以在main函數中顯示內容了,在設備初始化中加入IIC初始化和OLED初始化,再加上字符串顯示就大功告成了。
最后貼一個圖來看看成品效果
文章末尾說一些題外話,互聯網上有很多軟件模擬IIC和OLED驅動的相關資料,除去寫字部分的應用層代碼,數據鏈路層部分的代碼建議還是自己寫,這些開源代碼的IIC總線效率實際上很低,而且容易造成誤解,編者在研究商家給的例程時,一直不理解為什么例程發0x00作為控制字節的時候能初始化成功,而我卻不行,后來仔細思考了一番是因為他們的IIC,每次建立通訊都只會發送2個字節的內容,也就是說,如果要發送20個命令,就需要建立20次IIC通信,每次都要重新發送從機地址,發送這20個命令實際上要發送60個字節(包括IIC地址字節的話),功能當然可以實現,但是效率很低,而且這種代碼注釋并不詳細(甚至是挪用代碼還不改注釋),如果作為學習使用但不加說明的話很容易造成誤解(至少我被誤解了),讀者如果真的有學習需求而不是單純的挪用需求,最好還是以手冊描述的內容為準。
審核編輯 黃宇
-
單片機
+關注
關注
6035文章
44554瀏覽量
634633 -
OLED
+關注
關注
119文章
6198瀏覽量
224097 -
驅動
+關注
關注
12文章
1838瀏覽量
85262 -
IIC
+關注
關注
11文章
300瀏覽量
38311 -
CW32
+關注
關注
1文章
203瀏覽量
626
發布評論請先 登錄
相關推薦
評論