上一章我們講解了IIC的通信流程以及通信代碼,這一章就以市面上常見(jiàn)的IIC接口模塊——OLED屏為例教學(xué)一下IIC接口的驅(qū)動(dòng)怎么寫(xiě)。
第一步當(dāng)然是搞清楚自己使用的OLED屏幕用的是什么驅(qū)動(dòng),說(shuō)是屏幕,實(shí)際上就是密集LED點(diǎn)陣,所以必定有用于控制大量LED燈的驅(qū)動(dòng)器,本教學(xué)使用的OLED驅(qū)動(dòng)是SSD1306,該驅(qū)動(dòng)器有多種通信接口,這里使用IIC接口(具體使用什么接口,數(shù)據(jù)手冊(cè)上會(huì)有詳細(xì)介紹)
根據(jù)SSD1306數(shù)據(jù)手冊(cè)的描述,該設(shè)備的從機(jī)地址取決于SA0的電平,但是我翻遍了商家給我的資料也沒(méi)找到整個(gè)模塊的硬件原理圖(也有可能遺漏了),無(wú)奈只能打開(kāi)例程查看從機(jī)地址是0x78。

數(shù)據(jù)手冊(cè)詳細(xì)描述了1306的IIC接口規(guī)則,7bit的地址位+1bit的讀寫(xiě)位,數(shù)據(jù)線和時(shí)鐘線描述的就是標(biāo)準(zhǔn)的IIC協(xié)議,不必過(guò)多糾結(jié)。
下面的內(nèi)容是重點(diǎn),需要注意的是,編寫(xiě)任何一個(gè)外設(shè)的驅(qū)動(dòng),基本上都逃不開(kāi)指令和數(shù)據(jù),驅(qū)動(dòng)方式可能多種多樣,但最多也就是指令與數(shù)據(jù)的排列組合,稍微復(fù)雜一些的會(huì)加上寄存器操作(也就是用單片機(jī)1號(hào)通過(guò)某種通信接口控制單片機(jī)二號(hào)),抓住本質(zhì)之后,思路就能打開(kāi)。
先來(lái)看看數(shù)據(jù)手冊(cè)是怎么描述的:

對(duì)閱讀比較吃力的小伙伴,我撿重點(diǎn)說(shuō)一說(shuō):
第2條描述的就是從機(jī)地址的設(shè)置,這在前面以及提到了;
第3條說(shuō)的是IIC讀寫(xiě)位的定義;
第4條說(shuō)的是IIC協(xié)議中應(yīng)答信號(hào)的規(guī)則,這里說(shuō)明了1306會(huì)對(duì)一切IIC數(shù)據(jù)(包括地址|讀寫(xiě)字節(jié))作出回應(yīng);
第5條說(shuō)的是一旦主機(jī)和1306建立通訊(發(fā)送地址|讀寫(xiě)字節(jié)之后),后續(xù)發(fā)送到IIC總線上的數(shù)據(jù)就會(huì)被識(shí)別成“控制字節(jié)”或“數(shù)據(jù)字節(jié)”,具體什么是控制字節(jié)和數(shù)據(jù)字節(jié),后文會(huì)詳細(xì)說(shuō)明;
第6條說(shuō)的是每個(gè)控制字節(jié)和數(shù)據(jù)字節(jié)都會(huì)被回應(yīng);
第7條說(shuō)的是IIC協(xié)議停止位的規(guī)則;
現(xiàn)在來(lái)說(shuō)明一下什么是控制字節(jié)和數(shù)據(jù)字節(jié)。對(duì)于屏幕,我們的操作他的目的是在屏幕上面顯示內(nèi)容,“顯示”是一個(gè)動(dòng)作、行為,也可以叫他命令,“內(nèi)容”就是一個(gè)數(shù)據(jù)。所以操控設(shè)備的時(shí)候,至少會(huì)包含一個(gè)指令和一個(gè)數(shù)據(jù),但是指令和數(shù)據(jù)在各種通信協(xié)議中,都只是一個(gè)8bit的數(shù),如何區(qū)分他們就成了一個(gè)很關(guān)鍵的點(diǎn)。
1306使用這么一種方式來(lái)區(qū)分他們:一旦通過(guò)IIC接口建立通訊,隨后接收的第0個(gè)字節(jié)(byte0)一定是用來(lái)說(shuō)明下一個(gè)字節(jié)(byte1)的類(lèi)型的,它用2個(gè)字節(jié)來(lái)表達(dá)一個(gè)完整的數(shù)據(jù)傳輸。數(shù)據(jù)手冊(cè)中貼出了1306使用IIC通信的幀結(jié)構(gòu)圖:

控制字節(jié)區(qū)分?jǐn)?shù)據(jù)字節(jié)類(lèi)型的方式,就是通過(guò)其最高的2個(gè)bit位:Co和D/C#。先說(shuō)DC位,D就是data,C就是command,這個(gè)bit位為0就代表緊跟著的下一個(gè)字節(jié)是命令,如果bit位是1,那下一個(gè)緊跟著的字節(jié)就是數(shù)據(jù)。實(shí)際上只要有這么一個(gè)bit位就已經(jīng)可以完成對(duì)1306的控制了,那么現(xiàn)在思考一個(gè)問(wèn)題,如果我需要連續(xù)寫(xiě)入大量的命令(比如100個(gè)),為了完成這100個(gè)命令的傳輸,我需要傳輸200個(gè)字節(jié),因?yàn)槊總€(gè)命令都需要綁定一個(gè)控制字節(jié)。為了提高傳輸效率,Co位應(yīng)運(yùn)而生,如果Co位是0,且DC位也是0,那么1306就會(huì)把該控制字節(jié)之后傳入的所有數(shù)據(jù)都認(rèn)定為命令,這樣一來(lái)如果要寫(xiě)入100個(gè)命令,實(shí)際上只需要發(fā)送101個(gè)字節(jié)就能實(shí)現(xiàn)目的,效率幾乎是翻倍了。對(duì)于傳輸數(shù)據(jù),也是一樣的,只需要把Co位設(shè)置為0,DC位設(shè)置為1,后續(xù)傳入的字節(jié)就全是數(shù)據(jù)了。如果沒(méi)有這種連續(xù)寫(xiě)入大量同類(lèi)型數(shù)據(jù)(命令/數(shù)據(jù),括號(hào)里的數(shù)據(jù)是對(duì)屏幕顯示而言的,這個(gè)括號(hào)之前的數(shù)據(jù)是對(duì)IIC總線而言的)的需求,也可以把Co位置1以采用 “控制字節(jié)+數(shù)據(jù)字節(jié)(DC byte)”的方式實(shí)現(xiàn)功能。
下面就是代碼的編寫(xiě),我們使用聯(lián)合體直接列出需要發(fā)送的幀結(jié)構(gòu),需要發(fā)送時(shí)只需要賦值對(duì)應(yīng)的位再發(fā)送value這個(gè)數(shù)組就可以了,這么寫(xiě)就不需要在發(fā)送的時(shí)候進(jìn)行位操作,大幅度提高代碼可讀性:

再貼一個(gè)發(fā)指令的函數(shù),這個(gè)函數(shù)使用的是單次寫(xiě)入的方式,效率不高但是方便使用,需要注意的是這個(gè)函數(shù)不具備建立IIC通信的能力,他只負(fù)責(zé)在已建立通信的情況下發(fā)出一個(gè)完整的指令。

現(xiàn)在我們擁有了建立IIC通信和發(fā)送指令的函數(shù),實(shí)際上就已經(jīng)可以用這些函數(shù)看看效果了,1306有一個(gè)指令是A5H,他的作用是強(qiáng)制點(diǎn)亮屏幕上所有的像素點(diǎn),正確初始化OLED之后,再發(fā)送A5H即可。

關(guān)于OLED的初始化,模塊的資料提供了一套完整的初始化指令,簡(jiǎn)單來(lái)說(shuō)就是上電之后需要先把這一堆指令發(fā)給OLED驅(qū)動(dòng),之后屏幕才會(huì)正常工作,具體到每個(gè)指令的詳細(xì)功能,還請(qǐng)讀者查看1306數(shù)據(jù)手冊(cè)的指令表章節(jié),或者直接搜索1306指令的相關(guān)資料。
話題拉回屏幕顯示這一塊,我們的目標(biāo)肯定不只是把整個(gè)屏幕擦亮這么簡(jiǎn)單,屏幕要么拿來(lái)畫(huà)畫(huà),要么拿來(lái)寫(xiě)字,他至少要能夠?qū)懽植判小,F(xiàn)在已經(jīng)知道的是,屏幕就是一個(gè)LED陣,那只要控制一批像素點(diǎn)按照固定的規(guī)則點(diǎn)亮就能顯示我們想要的內(nèi)容,實(shí)現(xiàn)這個(gè)目的的過(guò)程也叫取字模,售賣(mài)屏幕的商家打包的資料都會(huì)有取字模的軟件,如果沒(méi)有也可以直接去網(wǎng)上下一個(gè)。將字模數(shù)據(jù)預(yù)置存儲(chǔ)在單片機(jī)里面,需要的時(shí)候直接發(fā)出去就能顯示,這種辦法簡(jiǎn)單而有效。
很好,現(xiàn)在我們寫(xiě)字的目標(biāo)已經(jīng)轉(zhuǎn)變成“在合適的位置點(diǎn)亮合適的像素點(diǎn)”,那怎么確定位置呢?屏幕有那么多像素點(diǎn),現(xiàn)在空有數(shù)據(jù),卻沒(méi)有位置。這個(gè)時(shí)候就需要簡(jiǎn)單說(shuō)明一下OLED的顯示邏輯了,整個(gè)屏幕被劃分成了多個(gè)頁(yè),每一頁(yè)都有128列像素點(diǎn),屏幕的分辨率是128x64,橫向128像素,縱向64像素,我們每次寫(xiě)入的數(shù)據(jù)都是8bit的,這個(gè)8bit數(shù)據(jù)指示了某個(gè)像素頁(yè)內(nèi)某一列的像素狀態(tài)。形象地說(shuō)就是:8個(gè)點(diǎn)排一列,橫向排128列就組成了一個(gè)頁(yè),整個(gè)屏幕一共有8個(gè)頁(yè),這8個(gè)頁(yè)再縱向排列,最終形成了一個(gè)128*64的屏幕。
想要在正確的位置顯示內(nèi)容,就得選擇正確的頁(yè)(后文直接稱(chēng)page),page0-page7一共8個(gè),每個(gè)page都有自己的物理地址,從B0H到B7H,所以我們可以以此寫(xiě)一個(gè)確定光標(biāo)位置的函數(shù),這個(gè)函數(shù)可以在我們需要寫(xiě)字的時(shí)候錨定一個(gè)正確的顯示位置。

前文提到字模,其實(shí)就是一批8bit數(shù)據(jù),再結(jié)合剛剛說(shuō)明的屏幕顯示原理,就不得不再次思考一個(gè)問(wèn)題,像素得精細(xì)到什么程度才能看起來(lái)像一個(gè)字,在像的情況下,還要符合OLED這種一頁(yè)8行像素的特點(diǎn)(因?yàn)檫@樣會(huì)更好操作)。答案是使用8n個(gè)像素寬度的正方形來(lái)顯示字符,目前來(lái)看16*16大小的字符正好符合要求,這也是大部分小屏顯示會(huì)選擇的大小。如此一來(lái)想要顯示一個(gè)字符就需要寫(xiě)2個(gè)page的若干列數(shù)據(jù),于是就有了寫(xiě)字函數(shù),具體代碼如下圖:

該函數(shù)首先建立IIC通信,與從機(jī)建立通信后設(shè)置顯示字符的坐標(biāo),隨后直接按順序發(fā)出上半部分和下半部分的像素?cái)?shù)據(jù)即可,這個(gè)函數(shù)可以獨(dú)立完成對(duì)字符的顯示,后續(xù)演示代碼中顯示字符串的函數(shù)基于此函數(shù)實(shí)現(xiàn),雖然對(duì)于字符串的顯示,最佳方案是建立一次通信就完成所有數(shù)據(jù)的傳輸,但那樣的代碼會(huì)把各種功能雜糅在一起,層次不夠分明,這里這么規(guī)劃也是為了內(nèi)容更好理解,關(guān)于IIC與OLED的代碼文件會(huì)附在文章最后。

所有用于像素顯示的數(shù)據(jù)都會(huì)被存到Graphic Display Data RAM(GGDRAM)中,既然是RAM,理論上在上電的時(shí)候,其存儲(chǔ)的數(shù)據(jù)應(yīng)該都是0才對(duì),但為了避免不必要的干擾可能造成的影響,我們還需要一個(gè)清屏函數(shù),該函數(shù)其實(shí)就是對(duì)所有page的所有數(shù)據(jù)進(jìn)行置0操作。

具備所有的前提條件,我們就可以在main函數(shù)中顯示內(nèi)容了,在設(shè)備初始化中加入IIC初始化和OLED初始化,再加上字符串顯示就大功告成了。

最后貼一個(gè)圖來(lái)看看成品效果

文章末尾說(shuō)一些題外話,互聯(lián)網(wǎng)上有很多軟件模擬IIC和OLED驅(qū)動(dòng)的相關(guān)資料,除去寫(xiě)字部分的應(yīng)用層代碼,數(shù)據(jù)鏈路層部分的代碼建議還是自己寫(xiě),這些開(kāi)源代碼的IIC總線效率實(shí)際上很低,而且容易造成誤解,編者在研究商家給的例程時(shí),一直不理解為什么例程發(fā)0x00作為控制字節(jié)的時(shí)候能初始化成功,而我卻不行,后來(lái)仔細(xì)思考了一番是因?yàn)樗麄兊腎IC,每次建立通訊都只會(huì)發(fā)送2個(gè)字節(jié)的內(nèi)容,也就是說(shuō),如果要發(fā)送20個(gè)命令,就需要建立20次IIC通信,每次都要重新發(fā)送從機(jī)地址,發(fā)送這20個(gè)命令實(shí)際上要發(fā)送60個(gè)字節(jié)(包括IIC地址字節(jié)的話),功能當(dāng)然可以實(shí)現(xiàn),但是效率很低,而且這種代碼注釋并不詳細(xì)(甚至是挪用代碼還不改注釋?zhuān)?,如果作為學(xué)習(xí)使用但不加說(shuō)明的話很容易造成誤解(至少我被誤解了),讀者如果真的有學(xué)習(xí)需求而不是單純的挪用需求,最好還是以手冊(cè)描述的內(nèi)容為準(zhǔn)。
審核編輯 黃宇
-
單片機(jī)
+關(guān)注
關(guān)注
6074文章
45341瀏覽量
663643 -
OLED
+關(guān)注
關(guān)注
121文章
6332瀏覽量
232542 -
驅(qū)動(dòng)
+關(guān)注
關(guān)注
12文章
1928瀏覽量
88205 -
IIC
+關(guān)注
關(guān)注
11文章
308瀏覽量
40381 -
CW32
+關(guān)注
關(guān)注
1文章
281瀏覽量
1678
發(fā)布評(píng)論請(qǐng)先 登錄
【有那么點(diǎn)詳細(xì)的CW32學(xué)習(xí)筆記】單片機(jī)啟動(dòng)\庫(kù)函數(shù)構(gòu)成
【有那么點(diǎn)詳細(xì)的CW32學(xué)習(xí)筆記】通用定時(shí)器——輸出比較
【有那么點(diǎn)詳細(xì)的CW32學(xué)習(xí)筆記】看手冊(cè)配置時(shí)鐘樹(shù)
【有那么點(diǎn)詳細(xì)的CW32學(xué)習(xí)筆記】模數(shù)轉(zhuǎn)換器
【應(yīng)用筆記】CW32 自舉程序中使用的 ISP 協(xié)議
【CW32飯盒派開(kāi)發(fā)板試用體驗(yàn)】+初識(shí)CW32飯盒派開(kāi)發(fā)板
應(yīng)用筆記(二)| 武漢芯源CW32自舉程序中使用的ISP協(xié)議
cw32和gd32的區(qū)別
應(yīng)用筆記-CW32 自舉程序中使用的 ISP 協(xié)議
【有那么點(diǎn)詳細(xì)的CW32學(xué)習(xí)筆記】通用異步收發(fā)器—發(fā)送篇
【CW32學(xué)習(xí)筆記】IIC接口-主機(jī)發(fā)送
【有那么點(diǎn)詳細(xì)的CW32學(xué)習(xí)筆記】IIC接口-主機(jī)發(fā)送
【有那么點(diǎn)詳細(xì)的CW32學(xué)習(xí)筆記】ADC“自動(dòng)模式”

【有那么點(diǎn)詳細(xì)的CW32學(xué)習(xí)筆記】IIC接口-OLED驅(qū)動(dòng)
評(píng)論