??作者:一只大喵咪1201
??專欄:《智能家居項(xiàng)目》
??格言:你只管努力,剩下的交給時間!
輸入子系統(tǒng)中目前僅實(shí)現(xiàn)了按鍵輸入,剩下的網(wǎng)絡(luò)輸入和標(biāo)準(zhǔn)輸入在以后會逐步實(shí)現(xiàn),今天先來實(shí)現(xiàn)設(shè)備子系統(tǒng),包含LED設(shè)備(GPIO控制),風(fēng)扇設(shè)備,OLED設(shè)備。
??設(shè)計(jì)思路
不同內(nèi)核下是訪問設(shè)備的方式是不同的:
- 裸機(jī)里怎么訪問設(shè)備?
對于ST芯片可以使用HAL庫訪問設(shè)備,對于一些國產(chǎn)芯片可以使用廠家自己封裝的庫,甚至自己編寫代碼直接訪問寄存器來訪問設(shè)備。
- FreeRTOS怎么訪問設(shè)備?
FreeRTOS中并沒有驅(qū)動程序框架,它訪問設(shè)備時方法和裸機(jī)一樣。
- RT-Thread怎么訪問設(shè)備?
RT-Thread可以使用兩種方法訪問設(shè)備:
- 像裸機(jī)一樣訪問
- 使用RT-Thread的驅(qū)動程序框架
如上圖便是RT-Thread
驅(qū)動程序框架的示意圖,所謂驅(qū)動框架就是事先定義好的接口函數(shù),無論是訪問設(shè)備還是添加新設(shè)備,直接調(diào)用這些接口函數(shù)即可。
所以我們要做的就是實(shí)現(xiàn)這些接口函數(shù)好讓應(yīng)用層去直接調(diào)用,這樣做的好處是,無論硬件怎么修改,驅(qū)動程序的接口并不會改變,上層的應(yīng)用程序也就不需要改變。
如上圖所示是RT-Thread
驅(qū)動框架下“I/O設(shè)備管理”的接口,應(yīng)用程序通過標(biāo)準(zhǔn)的接口來訪問設(shè)備:
rt_device_find
:查找設(shè)備rt_device_open
:打開設(shè)備rt_device_read
:從設(shè)備中讀取數(shù)據(jù)rt_device_write
:向設(shè)備中寫數(shù)據(jù)
- Linux下怎么訪問設(shè)備?
Linux系統(tǒng)中,和驅(qū)動程序嚴(yán)格分離開,應(yīng)用程序無法直接讀寫寄存器,應(yīng)用程序必須通過驅(qū)動程序訪問設(shè)備,應(yīng)用程序使用的接口只有open/read/write/ioctl等。
如上圖所示,APP就是應(yīng)用程序,在Linux下一切皆文件,所以設(shè)備在Linux內(nèi)核中都被抽象成了一個文件,所以應(yīng)用層必須通過系統(tǒng)調(diào)用接口通過文件系統(tǒng)來訪問設(shè)備。
再來看我們要實(shí)現(xiàn)的設(shè)備子系統(tǒng),目前暫時將其分為兩層,虛線之上寫出統(tǒng)一的應(yīng)程序接口,虛線之下根據(jù)不同的系統(tǒng),調(diào)用不同的函數(shù),這樣一來就統(tǒng)一了設(shè)備的訪問方式。
對于開發(fā)應(yīng)用程序的人,他不關(guān)心LED使用哪個GPIO引腳,GPIO是輸出高還是低來控制LED,open的是什么設(shè)備、read/write的又是什么設(shè)備,也不用去看原理圖,芯片手冊,以及研究HAL庫,RT-Thread,或者Linux的驅(qū)動函數(shù)怎么調(diào)用。
甚至不用理解我們抽象出來的設(shè)備結(jié)構(gòu)體,只需要關(guān)心結(jié)構(gòu)體中的Init
和Contral
兩個函數(shù)指針怎么使用即可,其他的一概不用關(guān)心,所以非常有必要提供更高層的API接口。
以LCD的使用為例,可以分為三層:
如上圖,其中設(shè)備驅(qū)動層中的驅(qū)動程序負(fù)責(zé)提供像素操作的功能,但是顯示什么字符、顯示多大、在哪顯示,不用關(guān)心。
文字/圖像顯示層中包含庫函數(shù)/函數(shù)功能,提供顯示字符、顯示圖片功能的接口,但是顯示什么字符、顯示多大、在哪顯示,不用關(guān)心。
應(yīng)用程序則通過庫函數(shù)來顯示字符以及圖片,并且決定顯示什么字符,顯示在哪里,顯示多大等問題,但是并不用關(guān)心驅(qū)動程序。
- 驅(qū)動只提供功能,不提供策略,應(yīng)用程序只提供策略,不提供功能的實(shí)現(xiàn)。
- 不同的層要各司其職不要越界,實(shí)現(xiàn)不同專業(yè)知識的隔離。
在實(shí)現(xiàn)設(shè)備子系統(tǒng)的時候,使用面向?qū)ο蟮乃枷?,對于每一種設(shè)備,抽象出一個結(jié)構(gòu)體,結(jié)構(gòu)體里有設(shè)備相關(guān)的函數(shù)指針,不同設(shè)備,不強(qiáng)求統(tǒng)一,不強(qiáng)求用一個結(jié)構(gòu)體類型來支持所有設(shè)備。
編寫函數(shù)時要注意:
- 頭文件:這些函數(shù)是面向應(yīng)用程序開發(fā)者,假設(shè)他們對硬件一無所知。
- C文件:函數(shù)內(nèi)部,再根據(jù)不同系統(tǒng)、不同芯片,調(diào)用其他函數(shù)。
??LED設(shè)備
如上圖所示,我們把LED設(shè)備分為4層,接下來就是一層一層來實(shí)現(xiàn)。
??設(shè)備層
LED設(shè)備的功能:
- LED的開和關(guān)
- 設(shè)置LED的顏色
- 設(shè)置LED的亮度
雖然本喵使用的LED燈并不支持設(shè)置顏色和直接設(shè)置亮度,但是有一些高級的LED燈是支持的,這里我們?yōu)榱艘院蟾玫木S護(hù)和擴(kuò)展,所以在抽象LED設(shè)備結(jié)構(gòu)體的時候都考慮上:
如上圖所示,抽象出來的LEDDevice
結(jié)構(gòu)體就是用來描述LED設(shè)備的,包含LED編號,初始化和控制方法,以及設(shè)置顏色和亮度的方法,還需要在LED的設(shè)備層提供一個獲取LED設(shè)備的方法。
如上圖所示,因?yàn)長ED設(shè)備只有三個,所以通過一個全局的LEDDevice
數(shù)組來管理LED設(shè)備,對每一個LED設(shè)備進(jìn)行了初始化,實(shí)現(xiàn)了LED設(shè)備的初始化函數(shù)以及控制函數(shù),為了達(dá)到分層的目的,在函數(shù)內(nèi)部調(diào)用內(nèi)核抽象層的初始化和控制函數(shù)。
至于設(shè)置顏色和亮度的函數(shù)并沒有實(shí)現(xiàn),因?yàn)楸具魇褂玫腖ED燈并不支持這兩種功能,還實(shí)現(xiàn)了一個獲取LED設(shè)備的函數(shù),在上層使用訪問LED設(shè)備的時候,必須先調(diào)用該函數(shù)獲取LED設(shè)備。
??內(nèi)核抽象層
如上圖,無論是訪問LED設(shè)備還是控制LED設(shè)備,甚至初始化LED設(shè)備,在內(nèi)核抽象都有不同的方式,這里定義一個配置文件,用來控制是使用哪種方式:
如上圖所示,用到哪種內(nèi)核就定義相應(yīng)的標(biāo)識符常量。
如上圖所示頭文件,提供了內(nèi)核抽象層對LED設(shè)備初始化和控制的函數(shù)聲明。
如上圖所示內(nèi)核抽象層源文件,提供了LED設(shè)備初始化和控制函數(shù)的實(shí)現(xiàn),通過宏開關(guān)來控制調(diào)用對應(yīng)內(nèi)核下的LED初始化和控制函數(shù)。
其中對于RT-Thread
和Linux
下的LED設(shè)備初始化和控制函數(shù)本喵在這里并不會實(shí)現(xiàn),這里僅僅是為了給大家演示本喵的代碼架構(gòu)寫的。
- 對于裸機(jī)和FreeRTOS,LED設(shè)備的初始化和控制函數(shù)都是一樣的。
??芯片抽象層
如上圖,在真正訪問芯片的時候,不同類型的芯片支持不同的庫,同樣可以通過宏開關(guān)來控制使用哪種庫來訪問芯片:
#define CONFIG_SUPPORT_HAL 0x10
//#define CONFIG_SUPPORT_OTHER 0x20
將這部分代碼加入到config.h
中去。
如上圖頭文件代碼,提供了芯片抽象層LED設(shè)備初始化和控制的函數(shù)聲明。
如上圖源文件代碼,提供了芯片抽象層LED設(shè)備初始化和控制函數(shù)的實(shí)現(xiàn),對于HAL庫,調(diào)用對應(yīng)的HAL庫對應(yīng)的初始化和控制函數(shù),對應(yīng)其他庫則調(diào)用其他方式。
??硬件操作
本喵使用的是ST的芯片,所以使用HAL庫來操作硬件:
如上圖所示頭文件,提供了LED設(shè)備的GPIO信息以及初始化和控制的函數(shù)聲明。
如上圖所示源文件,提供了初始化和控制函數(shù)的實(shí)現(xiàn),在控制函數(shù)中,switch
通過LEDDevice
結(jié)構(gòu)體中的LED編號操作具體的LED設(shè)備。
??單元測試
LED設(shè)備從設(shè)備層到操作硬件共五層已經(jīng)實(shí)現(xiàn)了,下面就進(jìn)行單元測試,看是否符合預(yù)期:
如上圖所示LED設(shè)備的測試函數(shù),其中LED點(diǎn)亮和熄滅中間的延時,為了應(yīng)用層和HAL庫的解耦,本喵只是通過循環(huán)進(jìn)行了一個大概的循環(huán),有興趣的小伙伴可以按照五層的框架,在最底層調(diào)用HAL_Delay()
自己實(shí)現(xiàn)一下。
如上圖,白藍(lán)綠三個燈在不停閃爍。
??顯示設(shè)備
為了讓這里的代碼更加容易維護(hù)和擴(kuò)展,適用于多種類型的顯示設(shè)備,本喵通過LCD顯示的原理來抽象出一個具有普適性的顯示設(shè)備結(jié)構(gòu)體,雖然本喵最終用的是OLED屏幕,但是仍然可以用這個結(jié)構(gòu)。
LCD顯示原理:
什么是LCD?就是多行多列的像素點(diǎn):
- 對于黑白屏幕(單色屏幕),這些像素點(diǎn)只有2個狀態(tài):點(diǎn)亮、熄滅。
- 對于彩色屏幕,這些像素點(diǎn)有顏色:可以用RGB三原色來表示。
怎么控制LCD上每個像素的狀態(tài)?
- 通過顯存,就是一塊內(nèi)存,也被稱為
FrameBuffer
。 - 每個像素在顯存上都有對應(yīng)的數(shù)據(jù),會在屏幕上對應(yīng)位置顯示。
- 對于黑白屏(單色屏),每個像素點(diǎn)在顯存里有對應(yīng)的1位數(shù)據(jù),只有兩種狀態(tài),表示點(diǎn)亮和熄滅。
- 對于彩色屏,每個像素點(diǎn)在顯存里有幾個字節(jié)(可能是1字節(jié)、2字節(jié)、4字節(jié)),可以表示多種顏色。
- BPP:Bits Per Pixel,用來表示每個像素點(diǎn)有多少位數(shù)據(jù)。
LCD可能自帶顯存,也可能不帶有顯存(要使用LCD的話,就需要在系統(tǒng)內(nèi)存中分配顯存),有三種類型的LCD。
- LCD含有顯存, CPU通過I2C協(xié)議訪問顯示
如上圖,很多I支持I2C、SPI接口的屏幕,本身是含有顯存的,要在LCD上顯示文字、圖片,就需要網(wǎng)顯存里寫入數(shù)據(jù),程序通過I2C接口寫顯存,LCD控制器就會讓LCD屏幕顯示相應(yīng)內(nèi)容。
- LCD沒有顯存, LCD控制器從內(nèi)存得到數(shù)據(jù)
如上圖,很多TFT LCD本身是沒有顯存的,那么數(shù)據(jù)保存在系統(tǒng)內(nèi)存里分配一塊空間,這塊RAM中的內(nèi)存就可以看作是顯存。設(shè)置好LCD控制器后,它就會自動從這個顯存取出數(shù)據(jù)、發(fā)送給LCD,我們只需要寫數(shù)據(jù)到RAM中的顯存即可。
- LCD含有顯存, CPU可以直接訪問
如上圖,有些LCD含有顯存,并且CPU可以直接訪問顯存,不需要I2C燈協(xié)議,就像訪問一般內(nèi)存一樣直接訪問顯存,我們只需要寫數(shù)據(jù)到顯存即。
對于軟件來說,這3種LCD都有顯存,第1種無法直接寫顯存;第2、3種可以直接寫顯存,能否讓第1種LCD,能否也直接寫顯存?可以:
- 在系統(tǒng)內(nèi)存RAM中分配另一個顯存
FrameBuffer
。 - 應(yīng)用程序直接寫這個顯存。
- 設(shè)備層等下層再通過I2C將RAM顯存中的數(shù)據(jù)發(fā)送到LCD自帶的顯存中。
如上圖,同樣按照五層來實(shí)現(xiàn)程序。
??管理及設(shè)備層
在設(shè)備層要抽象出一個顯示設(shè)備的結(jié)構(gòu)體,根據(jù)前面的分析以及LED設(shè)備的經(jīng)驗(yàn),該結(jié)構(gòu)體包含:
- 初始化函數(shù),用來初始化顯示設(shè)備。
- 在RAM中都有顯存,通過顯存的起始地址,顯示設(shè)備的分辨率,以及
bpp
來描述顯存。 - 對于第一種LCD,還需要一個
Flush
函數(shù),把RAM顯存中的數(shù)據(jù)刷到LCD顯存中去。
如上圖頭文件中代碼,抽象出了顯示設(shè)備的結(jié)構(gòu)體,用這個結(jié)構(gòu)體可以描述多種類型的顯示設(shè)備,包括LCD和OLED,這些顯示設(shè)備可以用鏈表管理起來。
- void* FBBase:顯示設(shè)備的顯存。
如上圖所示源文件,創(chuàng)建了一個靜態(tài)的全局顯示設(shè)備鏈表,用這個鏈表來管理所有顯示設(shè)備,提供了向鏈表中注冊新設(shè)備的函數(shù),以及從鏈表中獲取設(shè)備的函數(shù)。
- 由于
pDisplayDevice
鏈表頭指針被static
修飾了,就只能在當(dāng)前源文件訪問和操作這個鏈表。- 使用
__
修飾的函數(shù)表明該函數(shù)會在其他位置被調(diào)用,而且要實(shí)現(xiàn)的功能相同。
此時設(shè)備層的宏觀描述已經(jīng)有了,接下來就是創(chuàng)建具體的顯示設(shè)備了,這里創(chuàng)建的是OLED屏。
如上圖所示,創(chuàng)建一個全局的DisplayDevice
變量用來實(shí)例化OLED設(shè)備,根據(jù)OLED的特性填充該結(jié)構(gòu)體變量,其中RAM中的顯存就是一個全局的數(shù)組,用static
修飾的初始化和刷新顯存的函數(shù)初始化結(jié)構(gòu)體中的方法。
對于點(diǎn)亮(X , Y)處像素的函數(shù)本喵單獨(dú)講解一下:
如上圖所示坐標(biāo)系便是顯存抽象出來的坐標(biāo),其中左上角的坐標(biāo)原點(diǎn)就是RAM顯存數(shù)組的首地址,本喵使用的OLED是128*64的,所以可以分為8頁,128列。
現(xiàn)在要點(diǎn)亮坐標(biāo)(x, y)處的像素點(diǎn),所以要計(jì)算出該像素點(diǎn)位于RAM顯存中的哪個位置,然后在程序中向該位置寫入數(shù)據(jù)即可。
共有64行像素點(diǎn),每8行分為一頁,所以(x, y)所在頁為page = y / 8
,由于RAM中的顯存是一個數(shù)組,它并不存在頁的概念,所以可以計(jì)算出(x,y)對應(yīng)那個字節(jié)在數(shù)組中的下標(biāo)是page*128 + x
,地址就是FrameBuffer + page * 128 + x
由于一個字節(jié)有8個bit,而每一個像素點(diǎn)用一個bit,所以(x, y)所用那個bit在該字節(jié)中是第y % 8
個bit。
如上圖代碼所示,在將(x, y)坐標(biāo)所對應(yīng)的顯存位置計(jì)算出來后,根據(jù)dwColor
顏色值來決定是點(diǎn)亮該像素點(diǎn)函數(shù)熄滅,或者是用哪種顏色點(diǎn)亮。
- OLED不支持顯示不同顏色,所以
dwColor
的值不為0則表示點(diǎn)亮,為0則表示熄滅。
如上圖所示代碼便是OLED設(shè)備剩下的代碼,在添加OLED顯示設(shè)備的時候用到了static
修飾的g_DisplayDevice
全局變量,該變量同樣只能在該源文件內(nèi)使用。
AddDisplayDeviceOLED
函數(shù)中調(diào)用的DisplayDeviceRegister
注冊函數(shù)是屬于display_device.h
文件中的,所以需要包含該頭文件。
在display_device
中需要有添加顯示設(shè)備的函數(shù)AddDisplayDevices
,在該函數(shù)中勢必會調(diào)用oled_device.h
中的AddDisplayDeviceOLED
函數(shù),所以要包含該頭文件。
- 此時就產(chǎn)生了互相包含的場景,
display_device
中包含oled_device.h
,oled_device
中包含display_device.h
。
為了避免互相包含,再創(chuàng)建一個dispaly_system.c
文件來管理這兩個導(dǎo)致互相包含的函數(shù)。
如上圖所示,在這里實(shí)現(xiàn)AddDisplayDevices
更加合適,而獲取設(shè)備的方法也是放在這里更合適。
如上圖所示便是這些文件中的內(nèi)容概述和調(diào)用關(guān)系,這些所有內(nèi)容構(gòu)成了整個設(shè)備層。
??內(nèi)核抽象層
內(nèi)核抽象層需要實(shí)現(xiàn)的只有顯示設(shè)備的初始化已經(jīng)將數(shù)據(jù)刷新到OLED自帶的顯存中的Flush
函數(shù)。
如上圖代碼,同樣為了方便或者和維護(hù),對于不同的內(nèi)核,調(diào)用不同的方式,本喵使用的是裸機(jī),所以需要調(diào)用芯片抽象層對應(yīng)的函數(shù)。
??芯片抽象層及硬件操作
芯片抽象層同樣是分為支持HAL庫和支持其他庫,只是需要實(shí)現(xiàn)初始化函數(shù)和CAL_OLEDDeviceFlush
函數(shù)即可。
如上圖所示,對于HAL庫,在初始化的時候直接初始化I2C協(xié)議用到的引腳,以及調(diào)用OLED屏幕的初始化函數(shù),這里部分內(nèi)容本喵在I2C通信協(xié)議 | OLED屏一文中有詳細(xì)講解,這里本喵就直接使用了。
在CAL_OLEDDeviceFlush
函數(shù)中,對于HAL庫,調(diào)用OLED_Copy
拷貝函數(shù)將數(shù)據(jù)從RAM顯存中搬運(yùn)到OLED自帶的顯存中,該拷貝函數(shù)本喵沒有實(shí)現(xiàn)過,這里實(shí)現(xiàn)一下:
如上圖,將RAM顯存中的數(shù)據(jù)分八次寫入OLED自帶的顯存中,每次寫入一個page的數(shù)據(jù),也就是128字節(jié)。
??單元測試
顯示設(shè)備的五層實(shí)現(xiàn)以后,同樣需要進(jìn)單元測試:
如上圖所示,在測試顯示設(shè)備的時候,先添加所有顯示設(shè)備(本喵這里只有一個OLED顯示設(shè)備),然后從設(shè)備管理列表中獲取名為OLED
的設(shè)備,如果不存在則打印錯誤信息。
獲取成功后,先調(diào)用顯示設(shè)備的初始化函數(shù)進(jìn)行初始化,然后清屏,就是將RAM中的顯存全部清0,再調(diào)用顯示設(shè)備中的SetPixel
設(shè)置像素函數(shù)在OLED屏幕上畫一個十字。
如上圖所示,在OLED屏幕上顯示了一個十字。
??風(fēng)扇設(shè)備
如上圖所示,在本喵的開發(fā)板上有一個電機(jī)模塊,上面有一個風(fēng)扇,可以控制它順時針轉(zhuǎn),逆時針轉(zhuǎn),停止。
如上圖所示是電機(jī)接口模塊示意圖,風(fēng)扇的控制非常簡單,只需要改變PC6
和PE6
也就是INB
和INA
口的電平狀態(tài)就可以,真值表如下:
風(fēng)扇狀態(tài) | INA | INB |
---|---|---|
順時針旋轉(zhuǎn) | 0 | 1 |
逆時針旋轉(zhuǎn) | 1 | 0 |
停止 | 0 | 0 |
停止 | 1 | 1 |
其中0表示該接口輸入低電平,1表示該接口輸出高電平。風(fēng)扇設(shè)備分為四層,本喵就不詳細(xì)講解了,直接上代碼。
??設(shè)備層
如上圖所示頭文件代碼,定義了描述風(fēng)扇設(shè)備的結(jié)構(gòu)體,包含風(fēng)扇的速度屬性,初始化函數(shù),設(shè)置速度的函數(shù),當(dāng)前速度屬性是多余的,本喵這里并不會控制速度,包括設(shè)置速度函數(shù)也只是控制風(fēng)扇的啟停和正反轉(zhuǎn)。
如果使用PWM波來控制電風(fēng)扇就可以控制其速度,所以為了以后方便擴(kuò)展維護(hù),本喵就保留了速度這個屬性。
如上圖所示是源文件,創(chuàng)建了一個全局的風(fēng)扇設(shè)備變量來實(shí)例化,并進(jìn)行了初始化,還提供了獲取風(fēng)扇設(shè)備的函數(shù),不再多說。
??內(nèi)核抽象層
如上圖所示,對于不同的內(nèi)核調(diào)用相應(yīng)層的函數(shù)即可,不再講解。
??芯片抽象層
如上圖代碼,在初始化函數(shù)中,對于HAL庫,由于在使用CubeMX創(chuàng)建工程的已經(jīng)配置好了引腳,所以這里不用再調(diào)用HAL庫的初始化函數(shù)了,直接返回0即可,表示初始化成功,對于設(shè)置速度的函數(shù)則需要調(diào)用HAL庫中的設(shè)置速度函數(shù)。
??硬件操作
如上圖代碼所示,根據(jù)iSpeed
的不同情況,對照風(fēng)扇的真值表設(shè)置INA
和INB
引腳的電平狀態(tài)即可。
??單元測試
如上圖所示測試代碼,讓風(fēng)扇順時針轉(zhuǎn)2秒,停止2秒,逆時針轉(zhuǎn)2秒,停止2秒,如此循環(huán)。
為了實(shí)現(xiàn)應(yīng)用層的獨(dú)立,延時函數(shù)使用的是KAL_Delay
,其最在芯片抽象層仍然是封裝的HAL_Delay
。
如上圖代碼所示,實(shí)現(xiàn)了延時函數(shù),頭文件自己加就行,本喵不在講解。文章來源:http://www.zghlxwxcb.cn/news/detail-713930.html
如上圖所示是風(fēng)扇轉(zhuǎn)起來的狀態(tài),符合我們的要求。文章來源地址http://www.zghlxwxcb.cn/news/detail-713930.html
到了這里,關(guān)于【智能家居項(xiàng)目】裸機(jī)版本——設(shè)備子系統(tǒng)(LED && Display && 風(fēng)扇)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!