前言
今天給大家講一下芯片/模塊廠家寫sdk必須會使用的一種技術(shù):回調(diào)函數(shù)。
回調(diào)函數(shù)這個知識點其實并不是很難,難是難在網(wǎng)上很多講解回調(diào)函數(shù)的都說的太學(xué)術(shù)化了化了,一點也不親民。
很多人即使知道怎么寫回調(diào)函數(shù)也根本就搞不懂它們在實際產(chǎn)品中也有什么用,什么時候用。
所以這節(jié)課呢我們會以程序架構(gòu)的需求為出發(fā)點,講解回調(diào)函數(shù)是怎么滿足它這個需求的。
為了方便大家理解,這篇內(nèi)容也對應(yīng)有一篇文章,大家可以找無際單片機編程獲取。
一、通過這節(jié)課程你能掌握以下知識:
掌握程序架構(gòu)的核心理念或需求。掌握回調(diào)函數(shù)的作用掌握回調(diào)函數(shù)的程序編寫掌握回調(diào)函數(shù)在產(chǎn)品中的應(yīng)用
二、程序架構(gòu)的核心理念和需求
很多人可能會說一個好的程序架構(gòu)啊,就是代碼很緊湊、執(zhí)行效率也很高。
其實這個說的很片面,不完全對,這只能說明你程序算法寫的好,但架構(gòu)不一定做的好。
即然是架構(gòu),那自然是以從”大局”為重,思維不能局限于當(dāng)下的產(chǎn)品功能,還要考慮到以后功能的增加和裁剪,那么對于單片機開發(fā)來說,我認(rèn)為一個好的程序架構(gòu)至少要達到以下要求:
硬件層和應(yīng)用層的程序代碼分開,相互之間的控制和通訊使用接口,而且不會共享的全局變量或數(shù)組。
這里呢,我就這個要求,別小看這一個要求,因為這個要求里面蘊藏著很多學(xué)問的,比如用專業(yè)稱為可移植性、可擴展性。
那么我們來想象一下我們通常寫單片機代碼的方式啊,在51的時候基本一個.c文件解決,包括寄存器配置啊,產(chǎn)品功能啊。
這種就是沒有架構(gòu)的程序,然后我們進化到stm32這個單片機以后,程序大了,慢慢也會在工程文件里加幾個文件夾目錄把硬件層和應(yīng)用層代碼分開了。
于是我們會把一些不同的外設(shè)功能,比如led、按鍵、串口等外設(shè)功能代碼分別寫在不同的.c文件里,然后統(tǒng)一用函數(shù)接口去調(diào)用它。
比方說控制一個led燈亮,直接在led.c文件里寫一個驅(qū)動led燈狀態(tài)的函數(shù)然后給外部調(diào)用就好了。
那我們我們看這種led的控制函數(shù)確實也是滿足程序架構(gòu)的需求的,硬件層和應(yīng)用層代碼分開,應(yīng)用層用硬件層提供的接口來控制,而且又不會有硬件層和應(yīng)用層共享的全部變量或數(shù)組。像這種是不是很簡單?
那么不知道你們有沒有碰到另外一種情況,就是應(yīng)用程序需要采集硬件層的數(shù)據(jù),比如串口接收數(shù)據(jù),按鍵采集、adc值采集。
這種硬件層的數(shù)據(jù)怎么通知應(yīng)用層來拿,或者怎么主動給它?
我們以往最簡單粗暴的方式是不是就是用一個全局變量,比方說硬件層串口接收到數(shù)據(jù)來了,那么我們把數(shù)據(jù)丟到數(shù)組里,然后把接收完成全局變量標(biāo)志位置1。
比方說全局變量名為rcvflag,然后應(yīng)用層程序會輪詢判斷rcvflag==1?是的話就開始把數(shù)組里的數(shù)據(jù)取出來解析。
很多人就會說了,你看我用這種方法照樣能實現(xiàn)功能啊,為什么還要學(xué)習(xí)別的架構(gòu)。
這樣做當(dāng)然可以實現(xiàn)功能,但是會存在移植性很差的問題。
比如說你們老板讓你把這個串口的硬件層封裝起來給客戶用,但不能讓客戶看到你實現(xiàn)的源代碼,只提供接口(函數(shù)名)給對方用。
那么這時候難道你要告訴客戶先判斷哪個變量為1,然后再取哪個數(shù)組的數(shù)據(jù)這么low的做法嗎?
那么如果是懂行的客戶一定會懷疑你們公司的技術(shù)實力是不是小學(xué)生水平。
那怎樣做才會既方便又專業(yè)呢? 這里我們就需要用到回調(diào)函數(shù)啦。
三、回調(diào)函數(shù)的作用
那么在講回調(diào)函數(shù)之前呢,對于函數(shù)調(diào)用呢我一般分為2種類型:
1.輸出型
不知道大家有沒有用過c語言自帶的一些庫函數(shù),比如說sizeof()獲取數(shù)據(jù)長度的函數(shù),memcpy()是內(nèi)存拷貝函數(shù),我們調(diào)用這個函數(shù)之后呢就能完成相應(yīng)的功能。
還有我們基于單片機的一些程序函數(shù),比方說控制led點亮熄滅、繼電器吸合斷開、lcd驅(qū)動等等。
那么這些呢,我一般稱為輸出型的函數(shù)。
輸出型函數(shù)我們是主導(dǎo)的角色,我們知道什么時候該調(diào)用它。
2.輸入型
輸入型呢,也稱為的是響應(yīng)式的函數(shù)。
什么叫響應(yīng)式的函數(shù)呢?
比方說接收串口的數(shù)據(jù),我們不知道什么數(shù)據(jù)什么時候來。
再比方說,我們按鍵檢測的函數(shù),我們不知道什么時候會按下按鍵,那么這些就要定義成響應(yīng)式函數(shù)來實現(xiàn),而響應(yīng)式函數(shù)就可以用回調(diào)函數(shù)來實現(xiàn)。
所以通過這兩個種類型的分析啊,我們就可以知道,回調(diào)函數(shù)基本是用在輸入型的處理中。
比方說串口數(shù)據(jù)接收,那么數(shù)據(jù)是輸入到單片機里面的,單片機是處于從機角色。
按鍵檢測,按鍵狀態(tài)是輸入到單片機里的。
再比方說adc值采集,adc值也是輸入到單片機里的。
那么它們輸入的時間節(jié)點都是未知的,這些就能夠用回調(diào)函數(shù)來處理。
具體怎么處理后面我們會用代碼來給大家舉例。
回調(diào)函數(shù)還有一個作用就是為了封裝代碼。
比如說做芯片或者模組的廠家,我們拿典型的stm32來舉例,像外部中斷、定時器、串口等中斷函數(shù)都是屬于回調(diào)函數(shù),這種函數(shù)的目的是把采集到的數(shù)據(jù)傳遞給用戶,或者說應(yīng)用層。
所以回調(diào)函數(shù)的核心作用是:
1.把數(shù)據(jù)從一個.c文件傳遞到另一個.c文件,而不用全局變量共享數(shù)據(jù)這么low的方法。
2.對于這種數(shù)據(jù)傳遞方式,回調(diào)函數(shù)更利于代碼的封裝。
四、掌握回調(diào)函數(shù)的程序編寫
前面說了很多概念性的東西,可能大家也比較難理解,回調(diào)函數(shù)最終呢是靠函數(shù)指針來實現(xiàn)的。
那么我這里通過一些模擬按鍵的例子來演示下怎么回通過調(diào)函數(shù)來處理它們。
下面是我們的c-free工程,用這個來模擬方便點:
從模塊化編程的思想來看,整個工程分為2個部分,應(yīng)用層main.c文件,硬件層key.c和key.h文件。
不管再怎么復(fù)雜的程序,我們都要先從main函數(shù)一步步往下挖,main函數(shù)代碼如下。
1
2
3
4
5
6
7
8
|
int main( int argc, char *argv[]) { keyinit(); keyscancbsregister(keyscanhandle); keypoll(); return 0; } |
keyinit();是key.c文件的按鍵初始化函數(shù)
keyscancbsregister(keyscanhandle);是key.c的函數(shù)指針注冊函數(shù)。
這個函數(shù)可能大家會有點蒙,請跟進我們的節(jié)奏,下面開始燒腦環(huán)節(jié),也是寫回調(diào)函數(shù)的必須步驟,
想理解這個回調(diào)函數(shù)注冊函數(shù),我們要先從硬件層(key.h)頭文件的函數(shù)指針定義說起,具體看下圖。
這里自定義了一個函數(shù)指針類型,帶兩個形參。
然后,我們在key.c這個文件里定義了一個函數(shù)指針變量。
重點來了,我們就是通過這個函數(shù)指針,指向應(yīng)用層的函數(shù)地址(函數(shù)名)。
具體怎么實現(xiàn)指向呢?就是通過函數(shù)指針注冊函數(shù)。
這個函數(shù)是在main函數(shù)里調(diào)用,使用這種注冊函數(shù)的方式注冊靈活性也很高,你想要在哪個.c文件使用按鍵功能就在哪里調(diào)用。
這里要注意,main.c這個文件要定義一個函數(shù)來接收硬件層(key.c)過來的數(shù)據(jù)。
這里定義也不是亂定義的,一定要和那個自定義函數(shù)指針類型返回值、形參一致。
然后把這個函數(shù)名字直接復(fù)制給keyscancbsregister函數(shù)的形參就可以了。
這樣調(diào)用后,我們key.c文件的pkeyscancbs這個指針其實就是指向的keyscanhandle函數(shù)。
也就是說執(zhí)行pkeyscancbs的時候,就是執(zhí)行keyscanhandle函數(shù)。
那具體檢測按鍵的功能就是keypoll函數(shù),這個在main函數(shù)里調(diào)用。
當(dāng)檢測到鍵盤有輸入以后,最終會調(diào)用pkeyscancbs。
最終執(zhí)行的是main.c文件的keyscanhandle函數(shù)。
所以,我們來看下輸出結(jié)果。
如果還是有點模糊,下面我再給大家捋一捋編寫和使用回調(diào)函數(shù)的流程:
- 自定義函數(shù)指針,形參作為硬件層要傳到應(yīng)用層的數(shù)據(jù)。
- 硬件層定義一個函數(shù)指針和函數(shù)指針注冊函數(shù)。
- 應(yīng)用層定義一個函數(shù),返回值和形參都要和函數(shù)指針一致。
- 應(yīng)用層調(diào)用函數(shù)指針注冊函數(shù),把定義好的函數(shù)名稱作為形參傳入。
ok,這就是回調(diào)函數(shù)的使用。
如果還看不懂建議多看兩遍。
下面請大家思考一下,這個程序雖然簡單,但是不是架構(gòu)還不錯?應(yīng)用層和硬件層完全獨立?
總結(jié)
到此這篇關(guān)于c語言中回調(diào)函數(shù)的使用以及實際作用的文章就介紹到這了,更多相關(guān)c語言回調(diào)函數(shù)使用內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://blog.csdn.net/weixin_43982452/article/details/118832603