avr單片機(jī)編程的c語言問題總結(jié)
這兩天在用AVR單片機(jī)做項(xiàng)目,這次是邊做邊深入學(xué),盡量將以前忽略的知識(shí)用上,比如指針、文件包含、條件編譯、變量作用域等一一調(diào)研清楚。收獲不少,再次體會(huì)到學(xué)習(xí)的過程中沒有無用的地方。用的越深入,才發(fā)現(xiàn)學(xué)時(shí)以為沒用的東西都是最有用的,因?yàn)槎急缓雎粤?。幸虧學(xué)習(xí)時(shí)候的最重要的《c程序設(shè)計(jì)》還在,全能解決,所以遇到問題就問“譚浩強(qiáng)”。如果不能解決,就找編譯器的問題。這是這段時(shí)間總結(jié)的經(jīng)驗(yàn)。
一、函數(shù)指針
1.int (*func)(void)--基本函數(shù)指針變量定義方式,變量func是一個(gè)指向返回值為int,沒有參數(shù)的函數(shù)指針,這與其他變量定義有點(diǎn)區(qū)別,一般是把變量名放最后,如int a,而函數(shù)指針比較不直觀,
2.typedef int(*ftype)(void); ftype func;--類型重定義方式,先定義一種新的類型ftype,它是一種指針類型,這種類型專門指向返回值為int,沒有參數(shù)的函數(shù),再通過該類型定義一個(gè)具體的變量func
3.func=functionName--函數(shù)指針賦值,functionName為已經(jīng)定義的函數(shù)的函數(shù)名
4.(*func)()--通過函數(shù)指針調(diào)用函數(shù),也可以直接調(diào)用func(),
5.函數(shù)指針的一般作用:剛學(xué)函數(shù)指針的時(shí)候感覺沒啥用,在接觸操作系統(tǒng)以及一些消息、事件驅(qū)動(dòng)的機(jī)制以后意識(shí)到它的意義。簡(jiǎn)單的講如果希望在發(fā)生某種事件、中斷等情況下不希望主程序再去查看、掃描做判斷,而是自動(dòng)執(zhí)行某個(gè)功能函數(shù)時(shí)候,可以使用回調(diào)函數(shù)實(shí)現(xiàn)。如每次按下鍵盤都讓某個(gè)燈亮,可以將亮燈的函數(shù)指針傳遞給按鍵中斷函數(shù),由中斷內(nèi)部自動(dòng)調(diào)用。這是比較簡(jiǎn)單的應(yīng)用,肯定可以直接用一個(gè)函數(shù)代替,但是一旦希望在執(zhí)行過程中修改該消息、中斷響應(yīng)的操作的話,用函數(shù)指針就方便多了。在正常的程序執(zhí)行過程中,調(diào)用方一般都是遇到函數(shù)就立即執(zhí)行。而回調(diào)函數(shù)則是調(diào)用方通過函數(shù)指針的形式把函數(shù)儲(chǔ)存起來。這樣在合適的實(shí)際調(diào)用方就可以通過這個(gè)函數(shù)指針執(zhí)行某個(gè)功能。回調(diào)函數(shù)可以說是一種訂閱、分發(fā)的機(jī)制。被調(diào)用方可以通過訂閱的形式將自己的處理函數(shù)以函數(shù)指針的形式交給調(diào)用方。當(dāng)調(diào)用方需要執(zhí)行這個(gè)回調(diào)函數(shù)的時(shí)候,就會(huì)通過分發(fā)的形式回調(diào)被調(diào)用方?;卣{(diào)函數(shù)的機(jī)制可以說無處不在,比如Channie Liu 所說的MFC消息機(jī)制,再比如HOOK,等等都是通過回調(diào)函數(shù)機(jī)制來執(zhí)行的。但是回調(diào)函數(shù)并不是系統(tǒng)獨(dú)有的機(jī)制。你完全可以在自己的程序中通過函數(shù)指針來實(shí)現(xiàn)一套回調(diào)函數(shù)。還有回調(diào)函數(shù)并不是面向?qū)ο缶幊蹋谀承┣闆r下可以使用觀察者模式來代替它。在.net中已經(jīng)使用是事件的方式代替了回調(diào)函數(shù)來實(shí)現(xiàn)消息相應(yīng)。
6.avr編程中要注意不是所有的編譯器都能很好的支持函數(shù)指針,使用以及查資料發(fā)現(xiàn)ICCAVR7.1某個(gè)版本在編譯后的代碼進(jìn)行仿真發(fā)現(xiàn)程序總是跑飛,查看匯編發(fā)現(xiàn)生成一個(gè)EIJMP指令(擴(kuò)展間接跳轉(zhuǎn)指令),后來嘗試換用最新版7.21A版本,發(fā)現(xiàn)這個(gè)編譯后就沒這個(gè)指令,可以正常仿真了。同時(shí)CodeVision也有這個(gè)版本的問題。
二、頭文件包含
以前一直以為對(duì)這個(gè)很理解,沒太在意,雖然也犯了幾次錯(cuò)誤,但都沒有深究,這次又復(fù)習(xí)了一下“譚浩強(qiáng)”發(fā)現(xiàn)還是有根本的誤解的。在對(duì)頭文件理解之前需要理解編譯過程。編譯是以源文件為單位,也就是*.c或*.c++等,生成的目標(biāo)文件也是與源文件對(duì)應(yīng)的。而頭文件的作用是把可能公用的聲明放在一起,被源文件包含后,在編譯的過程中可以理解為直接加在源文件的內(nèi)部,而且添加的順序與源文件的include語句順序?qū)?yīng)。所以如果A.c包含B.h,而B.h又用到C.h內(nèi)容,但B.h本身沒包含則在A.c中要注意先包含C.h再包含B.h。另外就是變量的定義,即需要申請(qǐng)內(nèi)存占用內(nèi)存這樣的語句不能放在可能被多處包含的頭文件中,這樣會(huì)引發(fā)多次定義的錯(cuò)誤。這個(gè)問題我一直以為在頭文件的開始和結(jié)尾有個(gè)#ifndef語句就萬事大吉了,后來才知道不是那么回事。#ifndef語句只是為了在A包含B,A包含C,B包含C這樣情況下阻止C被兩次包含,而如果還有一個(gè)D也包含C的話那么A與D兩個(gè)源文件編譯后的代碼中都會(huì)對(duì)C中的變量進(jìn)行定義的,從而造成變量多次定義。
三、條件編譯
這個(gè)沒太多用過,直到最近想把單片機(jī)的程序?qū)懙母ㄓ靡恍?,盡量能抽象出來一些公用的函數(shù),可以在各個(gè)硬件平臺(tái)使用,免去不少的重復(fù)工作。如串口通訊在51下,在avr下都寫過,但是每次都是現(xiàn)用現(xiàn)寫,現(xiàn)在想盡量把每種功能硬件相關(guān)部分提煉出來,并壓縮到最小。這里面就需要用到條件編譯對(duì)各種平臺(tái)進(jìn)行判斷,對(duì)每種功能進(jìn)行控制??催^嵌入式linux內(nèi)核的應(yīng)該都發(fā)現(xiàn)這個(gè)特點(diǎn)了,那就是成篇的條件編譯。
四、變量作用域
我最常犯的就是全局變量與靜態(tài)變量的錯(cuò)誤使用。具體可以看“譚浩強(qiáng)”,這里只說明一下常用的全局變量的使用。有時(shí)候希望一個(gè)公共變量能在各個(gè)源文件訪問,或者作為某種信號(hào)、開關(guān)使用。就需要在某個(gè)源文件中定義,然后在其他需要用到的地方使用extern關(guān)鍵字。如果需要用的地方太多,就在頭文件中使用extern聲明該變量,在其他源文件中包含該頭文件即可。