我是老溫,一名熱愛學(xué)習(xí)的嵌入式工程師,關(guān)注我,一起變得更加優(yōu)秀!
源碼:https://gitee.com/embediot/led_module
發(fā)光二極管(LED),是科學(xué)物理世界最重要的發(fā)明之一,三原色RGB-LED燈有一段經(jīng)典的發(fā)展歷史,具體可以在網(wǎng)絡(luò)上搜索相關(guān)資料進(jìn)行了解,特別是得益于藍(lán)色 LED 燈的發(fā)明創(chuàng)造,才能有今天如此繽紛多彩的世界。
點(diǎn)亮一顆LED燈,可以說是每一位嵌入式初學(xué)者入門必學(xué)的首要內(nèi)容,點(diǎn)亮一顆LED之所以成為入門經(jīng)典,是因?yàn)辄c(diǎn)燈這個過程,可以讓每一位初學(xué)者對嵌入式開發(fā)的軟件環(huán)境搭建,開發(fā)板硬件基礎(chǔ),固件編譯和燒錄,等等環(huán)節(jié),有一個概念性的認(rèn)識。
如果在物理電路的角度進(jìn)行分析,就是讓這顆LED燈珠的陽極和陰極有一定的電壓差,LED燈就會被點(diǎn)亮,而對于MCU芯片來說,就是某個驅(qū)動引腳對外輸出高低電平,讓這顆LED燈點(diǎn)亮或者熄滅。
大道至簡,簡單的高低電平翻轉(zhuǎn)就可以讓一顆LED燈不斷亮滅交替,N 顆不同顏色的LED燈進(jìn)行排列組合,再配合產(chǎn)品經(jīng)理給它們賦予的色彩意義,一個繽紛的幻彩世界就這樣出現(xiàn)在我們的面前。
嵌入式軟件入門學(xué)習(xí)點(diǎn)燈,一般都是告訴我們?nèi)绾伟褵酎c(diǎn)亮,然而,在復(fù)雜的產(chǎn)品邏輯背后,多種顏色的LED燈配合不同的亮滅周期,會為產(chǎn)品帶來不同的功能含義,這已經(jīng)不是簡單的高低電平所能夠處理的了。
上一篇文章,我們基于面向?qū)ο蠛?a class="article-link" target="_blank" href="/baike/1473503.html">狀態(tài)機(jī)的思想,設(shè)計了一個通用的單片機(jī)按鍵檢測模塊,關(guān)于上一篇文章的內(nèi)容,可以點(diǎn)擊以下鏈接。
基于狀態(tài)機(jī)和面向?qū)ο蟮乃枷耄O(shè)計一個通用的按鍵檢測模塊。
本篇文章,我們基于面向?qū)ο蟮脑O(shè)計思想,配合簡單工廠設(shè)計模式,編寫一個單片機(jī)通用的LED顯示模塊,模塊盡可能遵循“設(shè)備”與“驅(qū)動”分離原則,模塊內(nèi)部做到高內(nèi)聚,并且面向接口編程,為應(yīng)用層提供統(tǒng)一的調(diào)用接口。
以下是模塊的具體設(shè)計過程。
1、通用的單片機(jī)LED顯示模塊,主要是把LED引腳相關(guān)的驅(qū)動接口抽象出來,因?yàn)椴煌腖ED效果,會有不同的驅(qū)動方式,比如MCU引腳電平翻轉(zhuǎn)驅(qū)動,PWM驅(qū)動,LED芯片驅(qū)動,等等,整個模塊的源碼文件,如下圖所示。
2、先看一下led_driver.h文件,在這里文件里面,主要是聲明了一個枚舉變量,有多少個物理LED燈就有多少個編號,這個編號是操作驅(qū)動層的索引值,因此LED_MAX_NUM通常表示物理LED燈珠的總數(shù)。同時還聲明了一個顏色值結(jié)構(gòu)體以及一些LED驅(qū)動接口,如下圖所示。
3、不同的LED顯示功能,需要對接不同的LED驅(qū)動程序,比如,如果只需要簡單的LED亮滅翻轉(zhuǎn),可以只采用簡單的引腳驅(qū)動,如果需要進(jìn)行調(diào)光或者色彩控制,則可以選擇使用PWM調(diào)光,或者使用自帶LED驅(qū)動芯片的燈珠,如WS2812B。
4、對于不同的LED驅(qū)動程序(led_drv_pin/ led_drv_pwm/ led_drv_ws2812b),都包含了led_driver的抽象接口,我們以led_drv_ws2812b 為例,來描述一下這個架構(gòu),其他兩個驅(qū)動程序同樣也可以作為參考。
5、在led_drv_ws2812b.h文件里面,聲明了一個led_drv_ws2812b_t結(jié)構(gòu)體里面,這個結(jié)構(gòu)體主要是包含了led_driver_t驅(qū)動接口,同時也包含了ws2812b燈珠的某些特定參數(shù),比如亮度參數(shù),顏色值參數(shù),以及對外提供一些必要的操作接口,等等,具體代碼如下圖所示。
6、在led_drv_ws2812b.c文件里面,主要是對led_driver_t接口的實(shí)現(xiàn),包括MCU驅(qū)動引腳的初始化配置,以及ws2812b的時序控制,然后根據(jù)這些接口,分別填充led_driver_t結(jié)構(gòu)體里面的各個成員接口,代碼如下圖所示。
7、在源文件的led_drv_ws2812b_create里面,主要是用上面的函數(shù),填充led_drv_ws2812b結(jié)構(gòu)體對象,記住這個對象,很重要,會被外部的led_module進(jìn)行調(diào)用,具體實(shí)現(xiàn)代碼如下圖所示。
8、完成了LED驅(qū)動程序設(shè)計后,再來考慮一下LED模塊的設(shè)計,在led_module.h文件里面,聲明一個LED模塊結(jié)構(gòu)體,led_module_t,在這個結(jié)構(gòu)體里面,定義了一個led_drv驅(qū)動程序接口,并且對外提供了模塊初始化接口,模塊啟動接口,模塊狀態(tài)機(jī)處理接口,等等。同時還提供了一個配置信息結(jié)構(gòu)體,led_module_cfg_t,主要用來對led_module進(jìn)行參數(shù)配置的,具體代碼實(shí)現(xiàn)如下圖所示。
9、在led_module.c文件里面,_init()函數(shù)主要是初始化模塊的驅(qū)動程序接口,_start()函數(shù)主要是用來啟動狀態(tài)機(jī),_handler()函數(shù)主要是用來進(jìn)行狀態(tài)機(jī)管理(待完善),然后使用led_module_create()函數(shù),創(chuàng)建一個LED模塊實(shí)例,具體代碼如下圖所示。
10、對于led_module_create() 函數(shù)主要是對外部接口進(jìn)行賦值初始化,并且使用簡單工廠設(shè)計模式,根據(jù)應(yīng)用層不同的配置(通過cfg配置信息判斷),從而選擇不同的驅(qū)動程序,對于應(yīng)用層來說,其LED驅(qū)動接口都是一致的(面向接口編程),這樣就可以完全做到“設(shè)備”與“驅(qū)動”分離,提高程序模塊化的內(nèi)聚程度。
11、在應(yīng)用層main.c文件,調(diào)用led_module的時候,一般先定義一個led_module_cfg配置信息結(jié)構(gòu)體,然后調(diào)用led_module_create函數(shù)創(chuàng)建一個led模塊實(shí)例,然后調(diào)用init()函數(shù)接口對模塊進(jìn)行初始化,隨后調(diào)用start接口啟動模塊,最后調(diào)用handler()接口,周期調(diào)用LED模塊的狀態(tài)機(jī)進(jìn)行處理,代碼如下圖所示。
12、美中不足的是,目前這個LED顯示模塊的狀態(tài)機(jī)還在完善當(dāng)中,現(xiàn)在只是簡單地在狀態(tài)機(jī)處理函數(shù)_handler()里面翻轉(zhuǎn)LED燈,用來驗(yàn)證驅(qū)動接口的可行性,感興趣的同學(xué)可以下載模塊源碼,對狀態(tài)機(jī)進(jìn)行完善,或者持續(xù)關(guān)注該模塊的源碼倉庫。