c語言中回調函數的使用以及實際作用詳析

前言

今天給大傢講一下芯片/模塊廠傢寫SDK必須會使用的一種技術:回調函數。

回調函數這個知識點其實並不是很難,難是難在網上很多講解回調函數的都說的太學術化瞭化瞭,一點也不親民。

很多人即使知道怎麼寫回調函數也根本就搞不懂它們在實際產品中也有什麼用,什麼時候用。

所以這節課呢我們會以程序架構的需求為出發點,講解回調函數是怎麼滿足它這個需求的。

為瞭方便大傢理解,這篇內容也對應有一篇文章,大傢可以找無際單片機編程獲取。

一、通過這節課程你能掌握以下知識:

掌握程序架構的核心理念或需求。掌握回調函數的作用掌握回調函數的程序編寫掌握回調函數在產品中的應用

二、程序架構的核心理念和需求

很多人可能會說一個好的程序架構啊,就是代碼很緊湊、執行效率也很高。

其實這個說的很片面,不完全對,這隻能說明你程序算法寫的好,但架構不一定做的好。

即然是架構,那自然是以從”大局”為重,思維不能局限於當下的產品功能,還要考慮到以後功能的增加和裁剪,那麼對於單片機開發來說,我認為一個好的程序架構至少要達到以下要求:

硬件層和應用層的程序代碼分開,相互之間的控制和通訊使用接口,而且不會共享的全局變量或數組。

這裡呢,我就這個要求,別小看這一個要求,因為這個要求裡面蘊藏著很多學問的,比如用專業稱為可移植性、可擴展性。

那麼我們來想象一下我們通常寫單片機代碼的方式啊,在51的時候基本一個.c文件解決,包括寄存器配置啊,產品功能啊。

這種就是沒有架構的程序,然後我們進化到STM32這個單片機以後,程序大瞭,慢慢也會在工程文件裡加幾個文件夾目錄把硬件層和應用層代碼分開瞭。

於是我們會把一些不同的外設功能,比如Led、按鍵、串口等外設功能代碼分別寫在不同的.c文件裡,然後統一用函數接口去調用它。

比方說控制一個LED燈亮,直接在led.c文件裡寫一個驅動led燈狀態的函數然後給外部調用就好瞭。

那我們我們看這種Led的控制函數確實也是滿足程序架構的需求的,硬件層和應用層代碼分開,應用層用硬件層提供的接口來控制,而且又不會有硬件層和應用層共享的全部變量或數組。像這種是不是很簡單?

那麼不知道你們有沒有碰到另外一種情況,就是應用程序需要采集硬件層的數據,比如串口接收數據,按鍵采集、ADC值采集。

這種硬件層的數據怎麼通知應用層來拿,或者怎麼主動給它?

我們以往最簡單粗暴的方式是不是就是用一個全局變量,比方說硬件層串口接收到數據來瞭,那麼我們把數據丟到數組裡,然後把接收完成全局變量標志位置1。

比方說全局變量名為RcvFlag,然後應用層程序會輪詢判斷RcvFlag==1?是的話就開始把數組裡的數據取出來解析。

很多人就會說瞭,你看我用這種方法照樣能實現功能啊,為什麼還要學習別的架構。

這樣做當然可以實現功能,但是會存在移植性很差的問題。

比如說你們老板讓你把這個串口的硬件層封裝起來給客戶用,但不能讓客戶看到你實現的源代碼,隻提供接口(函數名)給對方用。

那麼這時候難道你要告訴客戶先判斷哪個變量為1,然後再取哪個數組的數據這麼LOW的做法嗎?

那麼如果是懂行的客戶一定會懷疑你們公司的技術實力是不是小學生水平。

那怎樣做才會既方便又專業呢? 這裡我們就需要用到回調函數啦。

三、回調函數的作用

那麼在講回調函數之前呢,對於函數調用呢我一般分為2種類型:

1.輸出型

不知道大傢有沒有用過C語言自帶的一些庫函數,比如說sizeof()獲取數據長度的函數,memcpy()是內存拷貝函數,我們調用這個函數之後呢就能完成相應的功能。

還有我們基於單片機的一些程序函數,比方說控制LED點亮熄滅、繼電器吸合斷開、LCD驅動等等。

那麼這些呢,我一般稱為輸出型的函數。

輸出型函數我們是主導的角色,我們知道什麼時候該調用它。

2.輸入型

輸入型呢,也稱為的是響應式的函數

什麼叫響應式的函數呢?

比方說接收串口的數據,我們不知道什麼數據什麼時候來。

再比方說,我們按鍵檢測的函數,我們不知道什麼時候會按下按鍵,那麼這些就要定義成響應式函數來實現,而響應式函數就可以用回調函數來實現

所以通過這兩個種類型的分析啊,我們就可以知道,回調函數基本是用在輸入型的處理中。

比方說串口數據接收,那麼數據是輸入到單片機裡面的,單片機是處於從機角色。

按鍵檢測,按鍵狀態是輸入到單片機裡的。

再比方說ADC值采集,ADC值也是輸入到單片機裡的。

那麼它們輸入的時間節點都是未知的,這些就能夠用回調函數來處理。

具體怎麼處理後面我們會用代碼來給大傢舉例。

回調函數還有一個作用就是為瞭封裝代碼

比如說做芯片或者模組的廠傢,我們拿典型的STM32來舉例,像外部中斷、定時器、串口等中斷函數都是屬於回調函數,這種函數的目的是把采集到的數據傳遞給用戶,或者說應用層。

所以回調函數的核心作用是:

1.把數據從一個.c文件傳遞到另一個.c文件,而不用全局變量共享數據這麼LOW的方法。

2.對於這種數據傳遞方式,回調函數更利於代碼的封裝。

四、掌握回調函數的程序編寫

前面說瞭很多概念性的東西,可能大傢也比較難理解,回調函數最終呢是靠函數指針來實現的。

那麼我這裡通過一些模擬按鍵的例子來演示下怎麼回通過調函數來處理它們。

下面是我們的c-free工程,用這個來模擬方便點:

從模塊化編程的思想來看,整個工程分為2個部分,應用層main.c文件,硬件層key.c和key.h文件。

不管再怎麼復雜的程序,我們都要先從main函數一步步往下挖,main函數代碼如下。

int main(int argc, char *argv[])
{
KeyInit();
KeyScanCBSRegister(KeyScanHandle);
KeyPoll();
 
return 0;
}

KeyInit();是key.c文件的按鍵初始化函數

KeyScanCBSRegister(KeyScanHandle);是key.c的函數指針註冊函數。

這個函數可能大傢會有點蒙,請跟進我們的節奏,下面開始燒腦環節,也是寫回調函數的必須步驟,

想理解這個回調函數註冊函數,我們要先從硬件層(key.h)頭文件的函數指針定義說起,具體看下圖。

這裡自定義瞭一個函數指針類型,帶兩個形參。

然後,我們在key.c這個文件裡定義瞭一個函數指針變量。

重點來瞭,我們就是通過這個函數指針,指向應用層的函數地址(函數名)

具體怎麼實現指向呢?就是通過函數指針註冊函數。

這個函數是在main函數裡調用,使用這種註冊函數的方式註冊靈活性也很高,你想要在哪個.c文件使用按鍵功能就在哪裡調用。

這裡要註意,main.c這個文件要定義一個函數來接收硬件層(key.c)過來的數據。

這裡定義也不是亂定義的,一定要和那個自定義函數指針類型返回值、形參一致。

然後把這個函數名字直接復制給KeyScanCBSRegister函數的形參就可以瞭。

這樣調用後,我們key.c文件的pKeyScanCBS這個指針其實就是指向的KeyScanHandle函數。

也就是說執行pKeyScanCBS的時候,就是執行KeyScanHandle函數。

那具體檢測按鍵的功能就是KeyPoll函數,這個在main函數裡調用。

當檢測到鍵盤有輸入以後,最終會調用pKeyScanCBS。

最終執行的是main.c文件的KeyScanHandle函數。

所以,我們來看下輸出結果。

如果還是有點模糊,下面我再給大傢捋一捋編寫和使用回調函數的流程:

  1. 自定義函數指針,形參作為硬件層要傳到應用層的數據。
  2. 硬件層定義一個函數指針和函數指針註冊函數。
  3. 應用層定義一個函數,返回值和形參都要和函數指針一致。
  4. 應用層調用函數指針註冊函數,把定義好的函數名稱作為形參傳入。

Ok,這就是回調函數的使用。

如果還看不懂建議多看兩遍。

下面請大傢思考一下,這個程序雖然簡單,但是不是架構還不錯?應用層和硬件層完全獨立?

總結

到此這篇關於c語言中回調函數的使用以及實際作用的文章就介紹到這瞭,更多相關c語言回調函數使用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: