版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
c語(yǔ)言嵌入式編程注意事項(xiàng)c語(yǔ)言嵌入式編程注意事項(xiàng)/c語(yǔ)言嵌入式編程注意事項(xiàng)C語(yǔ)言嵌入式系統(tǒng)編程注意事項(xiàng)之背景篇
本文的討論主要圍繞以通用處理器為中心的協(xié)議處理模塊進(jìn)行,因?yàn)樗嗟貭可娴骄唧w的C語(yǔ)言編程技巧不同于一般形式的軟件編程,嵌入式系統(tǒng)編程建立在特定的硬件平臺(tái)上,勢(shì)必要求其編程語(yǔ)言具備較強(qiáng)的硬件直接操作能力。無(wú)疑,匯編語(yǔ)言具備這樣的特質(zhì)。但是,歸因于匯編語(yǔ)言開(kāi)發(fā)過(guò)程的復(fù)雜性,它并不是嵌入式系統(tǒng)開(kāi)發(fā)的一般選擇。而與之相比,C語(yǔ)言--一種"高級(jí)的低級(jí)"語(yǔ)言,則成為嵌入式系統(tǒng)開(kāi)發(fā)的最佳選擇。筆者在嵌入式系統(tǒng)項(xiàng)目的開(kāi)發(fā)過(guò)程中,一次又一次感受到C語(yǔ)言的精妙,沉醉于C語(yǔ)言給嵌入式開(kāi)發(fā)帶來(lái)的便利。
圖1給出了本文的討論所基于的硬件平臺(tái),實(shí)際上,這也是大多數(shù)嵌入式系統(tǒng)的硬件平臺(tái)。它包括兩部分:
(1)以通用處理器為中心的協(xié)議處理模塊,用于網(wǎng)絡(luò)控制協(xié)議的處理;
(2)以數(shù)字信號(hào)處理器(DSP)為中心的信號(hào)處理模塊,用于調(diào)制、解調(diào)和數(shù)/模信號(hào)轉(zhuǎn)換。
本文的討論主要圍繞以通用處理器為中心的協(xié)議處理模塊進(jìn)行,因?yàn)樗嗟貭可娴骄唧w的C語(yǔ)言編程技巧。而DSP編程則重點(diǎn)關(guān)注具體的數(shù)字信號(hào)處理算法,主要涉與通信領(lǐng)域的知識(shí),不是本文的討論重點(diǎn)。
著眼于討論普遍的嵌入式系統(tǒng)C編程技巧,系統(tǒng)的協(xié)議處理模塊沒(méi)有選擇特別的CPU,而是選擇了眾所周知的CPU芯片--80186,每一位學(xué)習(xí)過(guò)《微機(jī)原理》的讀者都應(yīng)該對(duì)此芯片有一個(gè)基本的認(rèn)識(shí),且對(duì)其指令集比較熟悉。80186的字長(zhǎng)是16位,可以尋址到的內(nèi)存空間為1MB,只有實(shí)地址模式。C語(yǔ)言編譯生成的指針為32位(雙字),高16位為段地址,低16位為段內(nèi)編譯,一段最多64KB。
協(xié)議處理模塊中的FLASH和RAM幾乎是每個(gè)嵌入式系統(tǒng)的必備設(shè)備,前者用于存儲(chǔ)程序,后者則是程序運(yùn)行時(shí)指令與數(shù)據(jù)的存放位置。系統(tǒng)所選擇的FLASH和RAM的位寬都為16位,與CPU一致。
實(shí)時(shí)鐘芯片可以為系統(tǒng)定時(shí),給出當(dāng)前的年、月、日與具體時(shí)間(小時(shí)、分、秒與毫秒),可以設(shè)定其經(jīng)過(guò)一段時(shí)間即向CPU提出中斷或設(shè)定報(bào)警時(shí)間到來(lái)時(shí)向CPU提出中斷(類(lèi)似鬧鐘功能)。
NVRAM(非易失去性RAM)具有掉電不丟失數(shù)據(jù)的特性,可以用于保存系統(tǒng)的設(shè)置信息,譬如網(wǎng)絡(luò)協(xié)議參數(shù)等。在系統(tǒng)掉電或重新啟動(dòng)后,仍然可以讀取先前的設(shè)置信息。其位寬為8位,比CPU字長(zhǎng)小。文章特意選擇一個(gè)與CPU字長(zhǎng)不一致的存儲(chǔ)芯片,為后文中一節(jié)的討論創(chuàng)造條件。
UART則完成CPU并行數(shù)據(jù)傳輸與RS-232串行數(shù)據(jù)傳輸?shù)霓D(zhuǎn)換,它可以在接收到[1~MAX_BUFFER]字節(jié)后向CPU提出中斷,MAX_BUFFER為UART芯片存儲(chǔ)接收到字節(jié)的最大緩沖區(qū)。
鍵盤(pán)控制器和顯示控制器則完成系統(tǒng)人機(jī)界面的控制。
以上提供的是一個(gè)較完備的嵌入式系統(tǒng)硬件架構(gòu),實(shí)際的系統(tǒng)可能包含更少的外設(shè)。之所以選擇一個(gè)完備的系統(tǒng),是為了后文更全面的討論嵌入式系統(tǒng)C語(yǔ)言編程技巧的方方面面,所有設(shè)備都會(huì)成為后文的分析目標(biāo)。
嵌入式系統(tǒng)需要良好的軟件開(kāi)發(fā)環(huán)境的支持,由于嵌入式系統(tǒng)的目標(biāo)機(jī)資源受限,不可能在其上建立龐大、復(fù)雜的開(kāi)發(fā)環(huán)境,因而其開(kāi)發(fā)環(huán)境和目標(biāo)運(yùn)行環(huán)境相互分離。因此,嵌入式應(yīng)用軟件的開(kāi)發(fā)方式一般是,在宿主機(jī)(Host)上建立開(kāi)發(fā)環(huán)境,進(jìn)行應(yīng)用程序編碼和交叉編譯,然后宿主機(jī)同目標(biāo)機(jī)(Target)建立連接,將應(yīng)用程序下載到目標(biāo)機(jī)上進(jìn)行交叉調(diào)試,經(jīng)過(guò)調(diào)試和優(yōu)化,最后將應(yīng)用程序固化到目標(biāo)機(jī)中實(shí)際運(yùn)行。
CAD-UL是適用于x86處理器的嵌入式應(yīng)用軟件開(kāi)發(fā)環(huán)境,它運(yùn)行在Windows操作系統(tǒng)之上,可生成x86處理器的目標(biāo)代碼并通過(guò)PC機(jī)的COM口(RS-232串口)或以太網(wǎng)口下載到目標(biāo)機(jī)上運(yùn)行,如圖2。其駐留于目標(biāo)機(jī)FLASH存儲(chǔ)器中的monitor程序可以監(jiān)控宿主機(jī)Windows調(diào)試平臺(tái)上的用戶(hù)調(diào)試指令,獲取CPU寄存器的值與目標(biāo)機(jī)存儲(chǔ)空間、I/O空間的內(nèi)容。
后續(xù)章節(jié)將從軟件架構(gòu)、內(nèi)存操作、屏幕操作、鍵盤(pán)操作、性能優(yōu)化等多方面闡述C語(yǔ)言嵌入式系統(tǒng)的編程技巧。軟件架構(gòu)是一個(gè)宏觀概念,與具體硬件的聯(lián)系不大;內(nèi)存操作主要涉與系統(tǒng)中的FLASH、RAM和NVRAM芯片;屏幕操作則涉與顯示控制器和實(shí)時(shí)鐘;鍵盤(pán)操作主要涉與鍵盤(pán)控制器;性能優(yōu)化則給出一些具體的減小程序時(shí)間、空間消耗的技巧。
在我們的修煉旅途中將經(jīng)過(guò)25個(gè)關(guān)口,這些關(guān)口主分為兩類(lèi),一類(lèi)是技巧型,有很強(qiáng)的適用性;一類(lèi)則是常識(shí)型,在理論上有些意義。
So,let’sgo.
C語(yǔ)言嵌入式系統(tǒng)編程注意事項(xiàng)之軟件架構(gòu)篇
模塊劃分的"劃"是規(guī)劃的意思,意指怎樣合理的將一個(gè)很大的軟件劃分為一系列功能獨(dú)立的部分合作完成系統(tǒng)的需求模塊劃分
模塊劃分的"劃"是規(guī)劃的意思,意指怎樣合理的將一個(gè)很大的軟件劃分為一系列功能獨(dú)立的部分合作完成系統(tǒng)的需求。C語(yǔ)言作為一種結(jié)構(gòu)化的程序設(shè)計(jì)語(yǔ)言,在模塊的劃分上主要依據(jù)功能(依功能進(jìn)行劃分在面向?qū)ο笤O(shè)計(jì)中成為一個(gè)錯(cuò)誤,牛頓定律遇到了相對(duì)論),C語(yǔ)言模塊化程序設(shè)計(jì)需理解如下概念:
(1)
模塊即是一個(gè).c文件和一個(gè).h文件的結(jié)合,頭文件(.h)中是對(duì)于該模塊接口的聲明;
(2)某模塊提供給其它模塊調(diào)用的外部函數(shù)與數(shù)據(jù)需在.h中文件中冠以extern關(guān)鍵字聲明;
(3)模塊內(nèi)的函數(shù)和全局變量需在.c文件開(kāi)頭冠以static關(guān)鍵字聲明;
(4)永遠(yuǎn)不要在.h文件中定義變量!定義變量和聲明變量的區(qū)別在于定義會(huì)產(chǎn)生內(nèi)存分配的操作,是匯編階段的概念;而聲明則只是告訴包含該聲明的模塊在連接階段從其它模塊尋找外部函數(shù)和變量。如:/*module1.h*/
inta=5;/*在模塊1的.h文件中定義inta*/
/*module1.c*/
#include
"module1.h"/*在模塊1中包含模塊1的.h文件*/
/*module2.c*/
#include
"module1.h"/*在模塊2中包含模塊1的.h文件*/
/*module3.c*/
#include"module1.h"/*在模塊3中包含模塊1的.h文件*/
以上程序的結(jié)果是在模塊1、2、3中都定義了整型變量a,a在不同的模塊中對(duì)應(yīng)不同的地址單元,這個(gè)世界上從來(lái)不需要這樣的程序。正確的做法是:/*module1.h*/
externinta;/*在模塊1的.h文件中聲明inta*/
/*module1.c*/
#include"module1.h"/*在模塊1中包含模塊1的.h文件*/
inta=5;/*在模塊1的.c文件中定義inta*/
/*module2.c*/
#include"module1.h"/*在模塊2中包含模塊1的.h文件*/
/*module3.c*/
#include"module1.h"/*在模塊3中包含模塊1的.h文件*/
這樣如果模塊1、2、3操作a的話,對(duì)應(yīng)的是同一片內(nèi)存單元。
一個(gè)嵌入式系統(tǒng)通常包括兩類(lèi)模塊:
(1)硬件驅(qū)動(dòng)模塊,一種特定硬件對(duì)應(yīng)一個(gè)模塊;
(2)軟件功能模塊,其模塊的劃分應(yīng)滿足低偶合、高內(nèi)聚的要求。
多任務(wù)還是單任務(wù)
所謂"單任務(wù)系統(tǒng)"是指該系統(tǒng)不能支持多任務(wù)并發(fā)操作,宏觀串行地執(zhí)行一個(gè)任務(wù)。而多任務(wù)系統(tǒng)則可以宏觀并行(微觀上可能串行)地"同時(shí)"執(zhí)行多個(gè)任務(wù)。
多任務(wù)的并發(fā)執(zhí)行通常依賴(lài)于一個(gè)多任務(wù)操作系統(tǒng)(OS),多任務(wù)OS的核心是系統(tǒng)調(diào)度器,它使用任務(wù)控制塊(TCB)來(lái)管理任務(wù)調(diào)度功能。TCB包括任務(wù)的當(dāng)前狀態(tài)、優(yōu)先級(jí)、要等待的事件或資源、任務(wù)程序碼的起始地址、初始堆棧指針等信息。調(diào)度器在任務(wù)被激活時(shí),要用到這些信息。此外,TCB還被用來(lái)存放任務(wù)的"上下文"(context)。任務(wù)的上下文就是當(dāng)一個(gè)執(zhí)行中的任務(wù)被停止時(shí),所要保存的所有信息。通常,上下文就是計(jì)算機(jī)當(dāng)前的狀態(tài),也即各個(gè)寄存器的內(nèi)容。當(dāng)發(fā)生任務(wù)切換時(shí),當(dāng)前運(yùn)行的任務(wù)的上下文被存入TCB,并將要被執(zhí)行的任務(wù)的上下文從它的TCB中取出,放入各個(gè)寄存器中。
嵌入式多任務(wù)OS的典型例子有Vxworks、ucLinux等。嵌入式OS并非遙不可與的神壇之物,我們可以用不到1000行代碼實(shí)現(xiàn)一個(gè)針對(duì)80186處理器的功能最簡(jiǎn)單的OS內(nèi)核,作者正準(zhǔn)備進(jìn)行此項(xiàng)工作,希望能將心得貢獻(xiàn)給大家。
究竟選擇多任務(wù)還是單任務(wù)方式,依賴(lài)于軟件的體系是否龐大。例如,絕大多數(shù)手機(jī)程序都是多任務(wù)的,但也有一些小靈通的協(xié)議棧是單任務(wù)的,沒(méi)有操作系統(tǒng),它們的主程序輪流調(diào)用各個(gè)軟件模塊的處理程序,模擬多任務(wù)環(huán)境。
單任務(wù)程序典型架構(gòu)
(1)從CPU復(fù)位時(shí)的指定地址開(kāi)始執(zhí)行;
(2)跳轉(zhuǎn)至匯編代碼startup處執(zhí)行;
(3)跳轉(zhuǎn)至用戶(hù)主程序main執(zhí)行,在main中完成:
a.初試化各硬件設(shè)備;
b.初始化各軟件模塊;
c.進(jìn)入死循環(huán)(無(wú)限循環(huán)),調(diào)用各模塊的處理函數(shù)
用戶(hù)主程序和各模塊的處理函數(shù)都以C語(yǔ)言完成。用戶(hù)主程序最后都進(jìn)入了一個(gè)死循環(huán),其首選方案是:while(1)
{
}
有的程序員這樣寫(xiě):for(;;)
{
}
這個(gè)語(yǔ)法沒(méi)有確切表達(dá)代碼的含義,我們從for(;;)看不出什么,只有弄明白for(;;)在C語(yǔ)言中意味著無(wú)條件循環(huán)才明白其意。
下面是幾個(gè)"著名"的死循環(huán):
(1)操作系統(tǒng)是死循環(huán);
(2)WIN32程序是死循環(huán);
(3)嵌入式系統(tǒng)軟件是死循環(huán);
(4)多線程程序的線程處理函數(shù)是死循環(huán)。
你可能會(huì)辯駁,大聲說(shuō):"凡事都不是絕對(duì)的,2、3、4都可以不是死循環(huán)"。Yes,youareright,但是你得不到鮮花和掌聲。實(shí)際上,這是一個(gè)沒(méi)有太大意義的牛角尖,因?yàn)檫@個(gè)世界從來(lái)不需要一個(gè)處理完幾個(gè)消息就喊著要OS殺死它的WIN32程序,不需要一個(gè)剛開(kāi)始RUN就自行了斷的嵌入式系統(tǒng),不需要莫名其妙啟動(dòng)一個(gè)做一點(diǎn)事就干掉自己的線程。有時(shí)候,過(guò)于嚴(yán)謹(jǐn)制造的不是便利而是麻煩。君不見(jiàn),五層的TCP/IP協(xié)議棧超越嚴(yán)謹(jǐn)?shù)腎SO/OSI七層協(xié)議棧大行其道成為事實(shí)上的標(biāo)準(zhǔn)?
經(jīng)常有網(wǎng)友討論:printf("%d,%d",++i,i++);/*輸出是什么?*/
c=a+++b;/*c=?*/
等類(lèi)似問(wèn)題。面對(duì)這些問(wèn)題,我們只能發(fā)出由衷的感慨:世界上還有很多有意義的事情等著我們?nèi)ハ瘮z入的食物。
實(shí)際上,嵌入式系統(tǒng)要運(yùn)行到世界末日。
中斷服務(wù)程序
中斷是嵌入式系統(tǒng)中重要的組成部分,但是在標(biāo)準(zhǔn)C中不包含中斷。許多編譯開(kāi)發(fā)商在標(biāo)準(zhǔn)C上增加了對(duì)中斷的支持,提供新的關(guān)鍵字用于標(biāo)示中斷服務(wù)程序(ISR),類(lèi)似于__interrupt、#programinterrupt等。當(dāng)一個(gè)函數(shù)被定義為ISR的時(shí)候,編譯器會(huì)自動(dòng)為該函數(shù)增加中斷服務(wù)程序所需要的中斷現(xiàn)場(chǎng)入棧和出棧代碼。
中斷服務(wù)程序需要滿足如下要求:
(1)不能返回值;
(2)不能向ISR傳遞參數(shù);
(3)ISR應(yīng)該盡可能的短小精悍;
(4)printf(char*lpFormatString,…)函數(shù)會(huì)帶來(lái)重入和性能問(wèn)題,不能在ISR中采用。
在某項(xiàng)目的開(kāi)發(fā)中,我們?cè)O(shè)計(jì)了一個(gè)隊(duì)列,在中斷服務(wù)程序中,只是將中斷類(lèi)型添加入該隊(duì)列中,在主程序的死循環(huán)中不斷掃描中斷隊(duì)列是否有中斷,有則取出隊(duì)列中的第一個(gè)中斷類(lèi)型,進(jìn)行相應(yīng)處理。/*存放中斷的隊(duì)列*/
typedefstructtagIntQueue
{
intintType;/*中斷類(lèi)型*/
structtagIntQueue*next;
}IntQueue;
IntQueuelpIntQueueHead;
__interruptISRexample()
{
intintType;
intType=GetSystemType();
QueueAddTail(lpIntQueueHead,intType);/*在隊(duì)列尾加入新的中斷*/
}
在主程序循環(huán)中判斷是否有中斷:
While(1)
{
If(!IsIntQueueEmpty())
{
intType=GetFirstInt();
switch(intType)/*是不是很象WIN32程序的消息解析函數(shù)?*/
{
/*對(duì),我們的中斷類(lèi)型解析很類(lèi)似于消息驅(qū)動(dòng)*/
casexxx:/*我們稱(chēng)其為"中斷驅(qū)動(dòng)"吧?*/
…
break;
casexxx:
…
break;
…
}
}
}
按上述方法設(shè)計(jì)的中斷服務(wù)程序很小,實(shí)際的工作都交由主程序執(zhí)行了。模塊劃分的"劃"是規(guī)劃的意思,意指怎樣合理的將一個(gè)很大的軟件劃分為一系列功能獨(dú)立的部分合作完成系統(tǒng)的需求硬件驅(qū)動(dòng)模塊
一個(gè)硬件驅(qū)動(dòng)模塊通常應(yīng)包括如下函數(shù):
(1)中斷服務(wù)程序ISR
(2)硬件初始化
a.修改寄存器,設(shè)置硬件參數(shù)(如UART應(yīng)設(shè)置其波特率,AD/DA設(shè)備應(yīng)設(shè)置其采樣速率等);
b.將中斷服務(wù)程序入口地址寫(xiě)入中斷向量表:/*設(shè)置中斷向量表*/
m_myPtr=make_far_pointer(0l);/*返回voidfar型指針voidfar**/
m_myPtr+=ITYPE_UART;/*ITYPE_UART:uart中斷服務(wù)程序*/
/*相對(duì)于中斷向量表首地址的偏移*/
*m_myPtr=&UART_Isr;/*UART_Isr:UART的中斷服務(wù)程序*/
(3)設(shè)置CPU針對(duì)該硬件的控制線
a.如果控制線可作PIO(可編程I/O)和控制信號(hào)用,則設(shè)置CPU內(nèi)部對(duì)應(yīng)寄存器使其作為控制信號(hào);
b.設(shè)置CPU內(nèi)部的針對(duì)該設(shè)備的中斷屏蔽位,設(shè)置中斷方式(電平觸發(fā)還是邊緣觸發(fā))。
(4)提供一系列針對(duì)該設(shè)備的操作接口函數(shù)。例如,對(duì)于LCD,其驅(qū)動(dòng)模塊應(yīng)提供繪制像素、畫(huà)線、繪制矩陣、顯示字符點(diǎn)陣等函數(shù);而對(duì)于實(shí)時(shí)鐘,其驅(qū)動(dòng)模塊則需提供獲取時(shí)間、設(shè)置時(shí)間等函數(shù)。
C的面向?qū)ο蠡?/p>
在面向?qū)ο蟮恼Z(yǔ)言里面,出現(xiàn)了類(lèi)的概念。類(lèi)是對(duì)特定數(shù)據(jù)的特定操作的集合體。類(lèi)包含了兩個(gè)范疇:數(shù)據(jù)和操作。而C語(yǔ)言中的struct僅僅是數(shù)據(jù)的集合,我們可以利用函數(shù)指針將struct模擬為一個(gè)包含數(shù)據(jù)和操作的"類(lèi)"。下面的C程序模擬了一個(gè)最簡(jiǎn)單的"類(lèi)":#ifndefC_Class
#defineC_Classstruct
#endif
C_ClassA
{
C_ClassA*A_this;/*this指針*/
void(*Foo)(C_ClassA*A_this);/*行為:函數(shù)指針*/
inta;/*數(shù)據(jù)*/
intb;
};
我們可以利用C語(yǔ)言模擬出面向?qū)ο蟮娜齻€(gè)特性:封裝、繼承和多態(tài),但是更多的時(shí)候,我們只是需要將數(shù)據(jù)與行為封裝以解決軟件結(jié)構(gòu)混亂的問(wèn)題。C模擬面向?qū)ο笏枷氲哪康牟辉谟谀M行為本身,而在于解決某些情況下使用C語(yǔ)言編程時(shí)程序整體框架結(jié)構(gòu)分散、數(shù)據(jù)和函數(shù)脫節(jié)的問(wèn)題。我們?cè)诤罄m(xù)章節(jié)會(huì)看到這樣的例子。
總結(jié)
本篇介紹了嵌入式系統(tǒng)編程軟件架構(gòu)方面的知識(shí),主要包括模塊劃分、多任務(wù)還是單任務(wù)選取、單任務(wù)程序典型架構(gòu)、中斷服務(wù)程序、硬件驅(qū)動(dòng)模塊設(shè)計(jì)等,從宏觀上給出了一個(gè)嵌入式系統(tǒng)軟件所包含的主要元素。
請(qǐng)記?。很浖Y(jié)構(gòu)是軟件的靈魂!結(jié)構(gòu)混亂的程序面目可憎,調(diào)試、測(cè)試、維護(hù)、升級(jí)都極度困難。
C語(yǔ)言嵌入式系統(tǒng)編程注意事項(xiàng)之內(nèi)存操作
在嵌入式系統(tǒng)的編程中,常常要求在特定的內(nèi)存單元讀寫(xiě)內(nèi)容,匯編有對(duì)應(yīng)的MOV指令,而除C/C++以外的其它編程語(yǔ)言基本沒(méi)有直接訪問(wèn)絕對(duì)地址的能力數(shù)據(jù)指針
在嵌入式系統(tǒng)的編程中,常常要求在特定的內(nèi)存單元讀寫(xiě)內(nèi)容,匯編有對(duì)應(yīng)的MOV指令,而除C/C++以外的其它編程語(yǔ)言基本沒(méi)有直接訪問(wèn)絕對(duì)地址的能力。在嵌入式系統(tǒng)的實(shí)際調(diào)試中,多借助C語(yǔ)言指針?biāo)哂械膶?duì)絕對(duì)地址單元內(nèi)容的讀寫(xiě)能力。以指針直接操作內(nèi)存多發(fā)生在如下幾種情況:
(1)某I/O芯片被定位在CPU的存儲(chǔ)空間而非I/O空間,而且寄存器對(duì)應(yīng)于某特定地址;
(2)兩個(gè)CPU之間以雙端口RAM通信,CPU需要在雙端口RAM的特定單元(稱(chēng)為mailbox)書(shū)寫(xiě)內(nèi)容以在對(duì)方CPU產(chǎn)生中斷;
(3)讀取在ROM或FLASH的特定單元所燒錄的漢字和英文字模。
譬如:
unsignedchar*p=(unsignedchar*)0xF000FF00;
*p=11;
以上程序的意義為在絕對(duì)地址0xF0000+0xFF00(80186使用16位段地址和16位偏移地址)寫(xiě)入11。
在使用絕對(duì)地址指針時(shí),要注意指針自增自減操作的結(jié)果取決于指針指向的數(shù)據(jù)類(lèi)別。上例中p++后的結(jié)果是p=0xF000FF01,若p指向int,即:int*p=(int*)0xF000FF00;
p++(或++p)的結(jié)果等同于:p=p+sizeof(int),而p-(或-p)的結(jié)果是p=p-sizeof(int)。
同理,若執(zhí)行:longint*p=(longint*)0xF000FF00;
則p++(或++p)的結(jié)果等同于:p=p+sizeof(longint),而p-(或-p)的結(jié)果是p=p-sizeof(longint)。
記?。篊PU以字節(jié)為單位編址,而C語(yǔ)言指針以指向的數(shù)據(jù)類(lèi)型長(zhǎng)度作自增和自減。理解這一點(diǎn)對(duì)于以指針直接操作內(nèi)存是相當(dāng)重要的。
函數(shù)指針
首先要理解以下三個(gè)問(wèn)題:
(1)C語(yǔ)言中函數(shù)名直接對(duì)應(yīng)于函數(shù)生成的指令代碼在內(nèi)存中的地址,因此函數(shù)名可以直接賦給指向函數(shù)的指針;
(2)調(diào)用函數(shù)實(shí)際上等同于"調(diào)轉(zhuǎn)指令+參數(shù)傳遞處理+回歸位置入棧",本質(zhì)上最核心的操作是將函數(shù)生成的目標(biāo)代碼的首地址賦給CPU的PC寄存器;
(3)因?yàn)楹瘮?shù)調(diào)用的本質(zhì)是跳轉(zhuǎn)到某一個(gè)地址單元的code去執(zhí)行,所以可以"調(diào)用"一個(gè)根本就不存在的函數(shù)實(shí)體,暈?請(qǐng)往下看:
請(qǐng)拿出你可以獲得的任何一本大學(xué)《微型計(jì)算機(jī)原理》教材,書(shū)中講到,186CPU啟動(dòng)后跳轉(zhuǎn)至絕對(duì)地址0xFFFF0(對(duì)應(yīng)C語(yǔ)言指針是0xF000FFF0,0xF000為段地址,0xFFF0為段內(nèi)偏移)執(zhí)行,請(qǐng)看下面的代碼:typedefvoid(*lp)();/*定義一個(gè)無(wú)參數(shù)、無(wú)返回類(lèi)型的*/
/*函數(shù)指針類(lèi)型*/
lplpReset=(lp)0xF000FFF0;/*定義一個(gè)函數(shù)指針,指向*/
/*CPU啟動(dòng)后所執(zhí)行第一條指令的位置*/
lpReset();/*調(diào)用函數(shù)*/
在以上的程序中,我們根本沒(méi)有看到任何一個(gè)函數(shù)實(shí)體,但是我們卻執(zhí)行了這樣的函數(shù)調(diào)用:lpReset(),它實(shí)際上起到了"軟重啟"的作用,跳轉(zhuǎn)到CPU啟動(dòng)后第一條要執(zhí)行的指令的位置。
記?。汉瘮?shù)無(wú)它,唯指令集合耳;你可以調(diào)用一個(gè)沒(méi)有函數(shù)體的函數(shù),本質(zhì)上只是換一個(gè)地址開(kāi)始執(zhí)行指令!
數(shù)組vs.動(dòng)態(tài)申請(qǐng)
在嵌入式系統(tǒng)中動(dòng)態(tài)內(nèi)存申請(qǐng)存在比一般系統(tǒng)編程時(shí)更嚴(yán)格的要求,這是因?yàn)榍度胧较到y(tǒng)的內(nèi)存空間往往是十分有限的,不經(jīng)意的內(nèi)存泄露會(huì)很快導(dǎo)致系統(tǒng)的崩潰。
所以一定要保證你的malloc和free成對(duì)出現(xiàn),如果你寫(xiě)出這樣的一段程序:char*(void)
{
char*p;
p=(char*)malloc(…);
if(p==NULL)
…;
…/*一系列針對(duì)p的操作*/
returnp;
}
在某處調(diào)用(),用完中動(dòng)態(tài)申請(qǐng)的內(nèi)存后將其free,如下:char*q=();
…
free(q);
上述代碼明顯是不合理的,因?yàn)檫`反了malloc和free成對(duì)出現(xiàn)的原則,即"誰(shuí)申請(qǐng),就由誰(shuí)釋放"原則。不滿足這個(gè)原則,會(huì)導(dǎo)致代碼的耦合度增大,因?yàn)橛脩?hù)在調(diào)用函數(shù)時(shí)需要知道其內(nèi)部細(xì)節(jié)!
正確的做法是在調(diào)用處申請(qǐng)內(nèi)存,并傳入函數(shù),如下:char*p=malloc(…);
if(p==NULL)
…;
(p);
…
free(p);
p=NULL;
而函數(shù)則接收參數(shù)p,如下:void(char*p)
{
…/*一系列針對(duì)p的操作*/
}
基本上,動(dòng)態(tài)申請(qǐng)內(nèi)存方式可以用較大的數(shù)組替換。對(duì)于編程新手,筆者推薦你盡量采用數(shù)組!嵌入式系統(tǒng)可以以博大的胸襟接收瑕疵,而無(wú)法"海納"錯(cuò)誤。畢竟,以最笨的方式苦練神功的郭靖勝過(guò)機(jī)智聰明卻范政治錯(cuò)誤走反革命道路的楊康。
給出原則:
(1)盡可能的選用數(shù)組,數(shù)組不能越界訪問(wèn)(真理越過(guò)一步就是謬誤,數(shù)組越過(guò)界限就光榮地成全了一個(gè)混亂的嵌入式系統(tǒng));
(2)如果使用動(dòng)態(tài)申請(qǐng),則申請(qǐng)后一定要判斷是否申請(qǐng)成功了,并且malloc和free應(yīng)成對(duì)出現(xiàn)!
在嵌入式系統(tǒng)的編程中,常常要求在特定的內(nèi)存單元讀寫(xiě)內(nèi)容,匯編有對(duì)應(yīng)的MOV指令,而除C/C++以外的其它編程語(yǔ)言基本沒(méi)有直接訪問(wèn)絕對(duì)地址的能力關(guān)鍵字const
const意味著"只讀"。區(qū)別如下代碼的功能非常重要,也是老生長(zhǎng)嘆,如果你還不知道它們的區(qū)別,而且已經(jīng)在程序界摸爬滾打多年,那只能說(shuō)這是一個(gè)悲哀:constinta;
intconsta;
constint*a;
int*consta;
intconst*aconst;
(1)關(guān)鍵字const的作用是為給讀你代碼的人傳達(dá)非常有用的信息。例如,在函數(shù)的形參前添加const關(guān)鍵字意味著這個(gè)參數(shù)在函數(shù)體內(nèi)不會(huì)被修改,屬于"輸入?yún)?shù)"。在有多個(gè)形參的時(shí)候,函數(shù)的調(diào)用者可以憑借參數(shù)前是否有const關(guān)鍵字,清晰的辨別哪些是輸入?yún)?shù),哪些是可能的輸出參數(shù)。
(2)合理地使用關(guān)鍵字const可以使編譯器很自然地保護(hù)那些不希望被改變的參數(shù),防止其被無(wú)意的代碼修改,這樣可以減少bug的出現(xiàn)。
const在C++語(yǔ)言中則包含了更豐富的含義,而在C語(yǔ)言中僅意味著:"只能讀的普通變量",可以稱(chēng)其為"不能改變的變量"(這個(gè)說(shuō)法似乎很拗口,但卻最準(zhǔn)確的表達(dá)了C語(yǔ)言中const的本質(zhì)),在編譯階段需要的常數(shù)仍然只能以#define宏定義!故在C語(yǔ)言中如下程序是非法的:constintSIZE=10;
chara[SIZE];/*非法:編譯階段不能用到變量*/
關(guān)鍵字volatile
C語(yǔ)言編譯器會(huì)對(duì)用戶(hù)書(shū)寫(xiě)的代碼進(jìn)行優(yōu)化,譬如如下代碼:inta,b,c;
a=inWord(0x100);/*讀取I/O空間0x100端口的內(nèi)容存入a變量*/
b=a;
a=inWord(0x100);/*再次讀取I/O空間0x100端口的內(nèi)容存入a變量*/
c=a;
很可能被編譯器優(yōu)化為:inta,b,c;
a=inWord(0x100);/*讀取I/O空間0x100端口的內(nèi)容存入a變量*/
b=a;
c=a;
但是這樣的優(yōu)化結(jié)果可能導(dǎo)致錯(cuò)誤,如果I/O空間0x100端口的內(nèi)容在執(zhí)行第一次讀操作后被其它程序?qū)懭胄轮?,則其實(shí)第2次讀操作讀出的內(nèi)容與第一次不同,b和c的值應(yīng)該不同。在變量a的定義前加上volatile關(guān)鍵字可以防止編譯器的類(lèi)似優(yōu)化,正確的做法是:volatileinta;
volatile變量可能用于如下幾種情況:
(1)并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器,例中的代碼屬于此類(lèi));
(2)一個(gè)中斷服務(wù)子程序中會(huì)訪問(wèn)到的非自動(dòng)變量(也就是全局變量);
(3)多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量。
CPU字長(zhǎng)與存儲(chǔ)器位寬不一致處理
在背景篇中提到,本文特意選擇了一個(gè)與CPU字長(zhǎng)不一致的存儲(chǔ)芯片,就是為了進(jìn)行本節(jié)的討論,解決CPU字長(zhǎng)與存儲(chǔ)器位寬不一致的情況。80186的字長(zhǎng)為16,而NVRAM的位寬為8,在這種情況下,我們需要為NVRAM提供讀寫(xiě)字節(jié)、字的接口,如下:
typedefunsignedcharBYTE;
typedefunsignedintWORD;
/*函數(shù)功能:讀NVRAM中字節(jié)
*參數(shù):wOffset,讀取位置相對(duì)NVRAM基地址的偏移
*返回:讀取到的字節(jié)值
*/
externBYTEReadByteNVRAM(WORDwOffset)
{
LPBYTElpAddr=(BYTE*)(NVRAM+wOffset*2);/*為什么偏移要×2?*/
return*lpAddr;
}
/*函數(shù)功能:讀NVRAM中字
*參數(shù):wOffset,讀取位置相對(duì)NVRAM基地址的偏移
*返回:讀取到的字
*/
externWORDReadWordNVRAM(WORDwOffset)
{
WORDwTmp=0;
LPBYTElpAddr;
/*讀取高位字節(jié)*/
lpAddr=(BYTE*)(NVRAM+wOffset*2);/*為什么偏移要×2?*/
wTmp+=(*lpAddr)*256;
/*讀取低位字節(jié)*/
lpAddr=(BYTE*)(NVRAM+(wOffset+1)*2);/*為什么偏移要×2?*/
wTmp+=*lpAddr;
returnwTmp;
}
/*函數(shù)功能:向NVRAM中寫(xiě)一個(gè)字節(jié)
*參數(shù):wOffset,寫(xiě)入位置相對(duì)NVRAM基地址的偏移
*byData,欲寫(xiě)入的字節(jié)
*/
externvoidWriteByteNVRAM(WORDwOffset,BYTEbyData)
{
…
}
/*函數(shù)功能:向NVRAM中寫(xiě)一個(gè)字*/
*參數(shù):wOffset,寫(xiě)入位置相對(duì)NVRAM基地址的偏移
*wData,欲寫(xiě)入的字
*/
externvoidWriteWordNVRAM(WORDwOffset,WORDwData)
{
…
}
子貢問(wèn)曰:Why偏移要乘以2?
子曰:請(qǐng)看圖1,16位80186與8位NVRAM之間互連只能以地址線A1對(duì)其A0,CPU本身的A0與NVRAM不連接。因此,NVRAM的地址只能是偶數(shù)地址,故每次以0x10為單位前進(jìn)!子貢再問(wèn):Sowhy80186的地址線A0不與NVRAM的A0連接?
子曰:請(qǐng)看《IT論語(yǔ)》之《微機(jī)原理篇》,那里面講述了關(guān)于計(jì)算機(jī)組成的圣人之道。
總結(jié)
本篇主要講述了嵌入式系統(tǒng)C編程中內(nèi)存操作的相關(guān)技巧。掌握并深入理解關(guān)于數(shù)據(jù)指針、函數(shù)指針、動(dòng)態(tài)申請(qǐng)內(nèi)存、const與volatile關(guān)鍵字等的相關(guān)知識(shí),是一個(gè)優(yōu)秀的C語(yǔ)言程序設(shè)計(jì)師的基本要求。當(dāng)我們已經(jīng)牢固掌握了上述技巧后,我們就已經(jīng)學(xué)會(huì)了C語(yǔ)言的99%,因?yàn)镃語(yǔ)言最精華的內(nèi)涵皆在內(nèi)存操作中體現(xiàn)。
我們之所以在嵌入式系統(tǒng)中使用C語(yǔ)言進(jìn)行程序設(shè)計(jì),99%是因?yàn)槠鋸?qiáng)大的內(nèi)存操作能力!
如果你愛(ài)編程,請(qǐng)你愛(ài)C語(yǔ)言;
如果你愛(ài)C語(yǔ)言,請(qǐng)你愛(ài)指針;
如果你愛(ài)指針,請(qǐng)你愛(ài)指針的指針!
C語(yǔ)言嵌入式系統(tǒng)編程注意事項(xiàng)之屏幕操作
現(xiàn)在要解決的問(wèn)題是,嵌入式系統(tǒng)中經(jīng)常要使用的并非是完整的漢字庫(kù),往往只是需要提供數(shù)量有限的漢字供必要的顯示功能漢字處理
現(xiàn)在要解決的問(wèn)題是,嵌入式系統(tǒng)中經(jīng)常要使用的并非是完整的漢字庫(kù),往往只是需要提供數(shù)量有限的漢字供必要的顯示功能。例如,一個(gè)微波爐的LCD上沒(méi)有必要提供顯示"電子郵件"的功能;一個(gè)提供漢字顯示功能的空調(diào)的LCD上不需要顯示一條"短消息",諸如此類(lèi)。但是一部手機(jī)、小靈通則通常需要包括較完整的漢字庫(kù)。
如果包括的漢字庫(kù)較完整,那么,由內(nèi)碼計(jì)算出漢字字模在庫(kù)中的偏移是十分簡(jiǎn)單的:漢字庫(kù)是按照區(qū)位的順序排列的,前一個(gè)字節(jié)為該漢字的區(qū)號(hào),后一個(gè)字節(jié)為該字的位號(hào)。每一個(gè)區(qū)記錄94個(gè)漢字,位號(hào)則為該字在該區(qū)中的位置。因此,漢字在漢字庫(kù)中的具體位置計(jì)算公式為:94*(區(qū)號(hào)-1)+位號(hào)-1。減1是因?yàn)閿?shù)組是以0為開(kāi)始而區(qū)號(hào)位號(hào)是以1為開(kāi)始的。只需乘上一個(gè)漢字字模占用的字節(jié)數(shù)即可,即:(94*(區(qū)號(hào)-1)+位號(hào)-1)*一個(gè)漢字字模占用字節(jié)數(shù),以16*16點(diǎn)陣字庫(kù)為例,計(jì)算公式則為:(94*(區(qū)號(hào)-1)+(位號(hào)-1))*32。漢字庫(kù)中從該位置起的32字節(jié)信息記錄了該字的字模信息。
對(duì)于包含較完整漢字庫(kù)的系統(tǒng)而言,我們可以以上述規(guī)則計(jì)算字模的位置。但是如果僅僅是提供少量漢字呢?譬如幾十至幾百個(gè)?最好的做法是:
定義宏:#defineEX_FONT_CHAR()
#defineEX_FONT_UNICODE_VAL()(),
#defineEX_FONT_ANSI_VAL()(),
定義結(jié)構(gòu)體:typedefstruct_wide_unicode_font16x16
{
WORD;/*內(nèi)碼*/
BYTEdata[32];/*字模點(diǎn)陣*/
}Unicode;
#defineCHINESE_CHAR_NUM…/*漢字?jǐn)?shù)量*/
字模的存儲(chǔ)用數(shù)組:Unicodechinese[CHINESE_CHAR_NUM]=
{
{
EX_FONT_CHAR("業(yè)")
EX_FONT_UNICODE_VAL(0x4e1a)
{0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x44,0x44,0x46,0x24,0x4c,0x24,0x48,0x14,0x50,0x1c,0x50,0x14,0x60,0x04,0x40,0x04,0x40,0x04,0x44,0xff,0xfe,0x00,0x00,0x00,0x00}
},
{
EX_FONT_CHAR("中")
EX_FONT_UNICODE_VAL(0x4e2d)
{0x01,0x00,0x01,0x00,0x21,0x08,0x3f,0xfc,0x21,0x08,0x21,0x08,0x21,0x08,0x21,0x08,0x21,0x08,
0x3f,0xf8,0x21,0x08,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00}
},
{
EX_FONT_CHAR("云")
EX_FONT_UNICODE_VAL(0x4e91)
{0x00,0x00,0x00,0x30,0x3f,0xf8,0x00,0x00,0x00,0x00,0x00,0x0c,0xff,0xfe,0x03,0x00,0x07,0x00,
0x06,0x40,0x0c,0x20,0x18,0x10,0x31,0xf8,0x7f,0x0c,0x20,0x08,0x00,0x00}
},
{
EX_FONT_CHAR("件")
EX_FONT_UNICODE_VAL(0x4ef6)
{0x10,0x40,0x1a,0x40,0x13,0x40,0x32,0x40,0x23,0xfc,0x64,0x40,0xa4,0x40,0x28,0x40,0x2f,0xfe,
0x20,0x40,0x20,0x40,0x20,0x40,0x20,0x40,0x20,0x40,0x20,0x40,0x20,0x40}
}
}
要顯示特定漢字的時(shí)候,只需要從數(shù)組中查找內(nèi)碼與要求漢字內(nèi)碼相同的即可獲得字模。如果前面的漢字在數(shù)組中以?xún)?nèi)碼大小順序排列,那么可以以二分查找法更高效的查找到漢字的字模。
這是一種很有效的組織小漢字庫(kù)的方法,它可以保證程序有很好的結(jié)構(gòu)。
系統(tǒng)時(shí)間顯示
從NVRAM中可以讀取系統(tǒng)的時(shí)間,系統(tǒng)一般借助NVRAM產(chǎn)生的秒中斷每秒讀取一次當(dāng)前時(shí)間并在LCD上顯示。關(guān)于時(shí)間的顯示,有一個(gè)效率問(wèn)題。因?yàn)闀r(shí)間有其特殊性,那就是60秒才有一次分鐘的變化,60分鐘才有一次小時(shí)變化,如果我們每次都將讀取的時(shí)間在屏幕上完全重新刷新一次,則浪費(fèi)了大量的系統(tǒng)時(shí)間。
一個(gè)較好的辦法是我們?cè)跁r(shí)間顯示函數(shù)中以靜態(tài)變量分別存儲(chǔ)小時(shí)、分鐘、秒,只有在其內(nèi)容發(fā)生變化的時(shí)候才更新其顯示。externvoidDisplayTime(…)
{
staticBYTEbyHour,byMinute,bySecond;
BYTEbyNewHour,byNewMinute,byNewSecond;
byNewHour=GetSysHour();
byNewMinute=GetSysMinute();
byNewSecond=GetSysSecond();
if(byNewHour!=byHour)
{
…/*顯示小時(shí)*/
byHour=byNewHour;
}
if(byNewMinute!=byMinute)
{
…/*顯示分鐘*/
byMinute=byNewMinute;
}
if(byNewSecond!=bySecond)
{
…/*顯示秒鐘*/
bySecond=byNewSecond;
}
}
這個(gè)例子也可以順便作為C語(yǔ)言中static關(guān)鍵字強(qiáng)大威力的證明。當(dāng)然,在C++語(yǔ)言里,static具有了更加強(qiáng)大的威力,它使得某些數(shù)據(jù)和函數(shù)脫離"對(duì)象"而成為"類(lèi)"的一部分,正是它的這一特點(diǎn),成就了軟件的無(wú)數(shù)優(yōu)秀設(shè)計(jì)。
動(dòng)畫(huà)顯示
動(dòng)畫(huà)是無(wú)所謂有,無(wú)所謂無(wú)的,靜止的畫(huà)面走的路多了,也就成了動(dòng)畫(huà)。隨著時(shí)間的變更,在屏幕上顯示不同的靜止畫(huà)面,即是動(dòng)畫(huà)之本質(zhì)。所以,在一個(gè)嵌入式系統(tǒng)的LCD上欲顯示動(dòng)畫(huà),必須借助定時(shí)器。沒(méi)有硬件或軟件定時(shí)器的世界是無(wú)法想像的:
(1)沒(méi)有定時(shí)器,一個(gè)操作系統(tǒng)將無(wú)法進(jìn)行時(shí)間片的輪轉(zhuǎn),于是無(wú)法進(jìn)行多任務(wù)的調(diào)度,于是便不再成其為一個(gè)多任務(wù)操作系統(tǒng);
(2)沒(méi)有定時(shí)器,一個(gè)多媒體播放軟件將無(wú)法運(yùn)作,因?yàn)樗恢篮螘r(shí)應(yīng)該切換到下一幀畫(huà)面;
(3)沒(méi)有定時(shí)器,一個(gè)網(wǎng)絡(luò)協(xié)議將無(wú)法運(yùn)轉(zhuǎn),因?yàn)槠錈o(wú)法獲知何時(shí)包傳輸超時(shí)并重傳之,無(wú)法在特定的時(shí)間完成特定的任務(wù)。
因此,沒(méi)有定時(shí)器將意味著沒(méi)有操作系統(tǒng)、沒(méi)有網(wǎng)絡(luò)、沒(méi)有多媒體,這將是怎樣的黑暗?所以,合理并靈活地使用各種定時(shí)器,是對(duì)一個(gè)軟件人的最基本需求!
在80186為主芯片的嵌入式系統(tǒng)中,我們需要借助硬件定時(shí)器的中斷來(lái)作為軟件定時(shí)器,在中斷發(fā)生后變更畫(huà)面的顯示內(nèi)容。在時(shí)間顯示"xx:xx"中讓冒號(hào)交替有無(wú),每次秒中斷發(fā)生后,需調(diào)用ShowDot:voidShowDot()
{
staticBOOLbShowDot=TRUE;/*再一次領(lǐng)略static關(guān)鍵字的威力*/
if(bShowDot)
{
showChar(’:’,xPos,yPos);
}
else
{
showChar(’’,xPos,yPos);
}
bShowDot=!bShowDot;
}
菜單操作
無(wú)數(shù)人為之絞盡腦汁的問(wèn)題終于出現(xiàn)了,在這一節(jié)里,我們將看到,在C語(yǔ)言中哪怕用到一丁點(diǎn)的面向?qū)ο笏枷?,軟件結(jié)構(gòu)將會(huì)有何等的改觀!
要求以鍵盤(pán)上的"←→"鍵切換菜單焦點(diǎn),當(dāng)用戶(hù)在焦點(diǎn)處于某菜單時(shí),若敲擊鍵盤(pán)上的OK、CANCEL鍵則調(diào)用該焦點(diǎn)菜單對(duì)應(yīng)之處理函數(shù)。我曾經(jīng)傻傻地這樣做著:/*按下OK鍵*/
voidonOkKey()
{
/*判斷在什么焦點(diǎn)菜單上按下Ok鍵,調(diào)用相應(yīng)處理函數(shù)*/
Switch(currentFocus)
{
caseMENU1:
menu1OnOk();
break;
caseMENU2:
menu2OnOk();
break;
…
}
}
/*按下Cancel鍵*/
voidonCancelKey()
{
/*判斷在什么焦點(diǎn)菜單上按下Cancel鍵,調(diào)用相應(yīng)處理函數(shù)*/
Switch(currentFocus)
{
caseMENU1:
menu1OnCancel();
break;
caseMENU2:
menu2OnCancel();
break;
…
}
}
終于有一天,我這樣做了:/*將菜單的屬性和操作"封裝"在一起*/
typedefstructtagSysMenu
{
char*text;/*菜單的文本*/
BYTExPos;/*菜單在LCD上的x坐標(biāo)*/
BYTEyPos;/*菜單在LCD上的y坐標(biāo)*/
void(*onOkFun)();/*在該菜單上按下ok鍵的處理函數(shù)指針*/
void(*onCancelFun)();/*在該菜單上按下cancel鍵的處理函數(shù)指針*/
}SysMenu,*LPSysMenu;
當(dāng)我定義菜單時(shí),只需要這樣:staticSysMenumenu[MENU_NUM]=
{
{
"menu1",0,48,menu1OnOk,menu1OnCancel
}
,
{
"menu2",7,48,menu2OnOk,menu2OnCancel
}
,
{
"menu3",7,48,menu3OnOk,menu3OnCancel
}
,
{
"menu4",7,48,menu4OnOk,menu4OnCancel
}
…
};
OK鍵和CANCEL鍵的處理變成:/*按下OK鍵*/
voidonOkKey()
{
menu[currentFocusMenu].onOkFun();
}
/*按下Cancel鍵*/
voidonCancelKey()
{
menu[currentFocusMenu].onCancelFun();
}
程序被大大簡(jiǎn)化了,也開(kāi)始具有很好的可擴(kuò)展性!我們僅僅利用了面向?qū)ο笾械姆庋b思想,就讓程序結(jié)構(gòu)清晰,其結(jié)果是幾乎可以在無(wú)需修改程序的情況下在系統(tǒng)中添加更多的菜單,而系統(tǒng)的按鍵處理函數(shù)保持不變。
面向?qū)ο螅嫔窳耍?/p>
模擬MessageBox函數(shù)
MessageBox函數(shù),這個(gè)Windows編程中的超級(jí)猛料,不知道是多少入門(mén)者第一次用到的函數(shù)。還記得我們第一次在Windows中利用MessageBox輸出"Hello,World!"對(duì)話框時(shí)新奇的感覺(jué)嗎?無(wú)法統(tǒng)計(jì),這個(gè)世界上究竟有多少程序員學(xué)習(xí)Windows編程是從MessageBox("Hello,World!",…)開(kāi)始的。在我本科的學(xué)校,廣泛流傳著一個(gè)詞匯,叫做"’Hello,World’級(jí)程序員",意指入門(mén)級(jí)程序員,但似乎"’Hello,World’級(jí)"這個(gè)說(shuō)法更搞笑而形象。
嵌入式系統(tǒng)中沒(méi)有給我們提供MessageBox,但是鑒于其功能強(qiáng)大,我們需要模擬之,一個(gè)模擬的MessageBox函數(shù)為:/******************************************
/*函數(shù)名稱(chēng):MessageBox
/*功能說(shuō)明:彈出式對(duì)話框,顯示提醒用戶(hù)的信息
/*參數(shù)說(shuō)明:lpStr提醒用戶(hù)的字符串輸出信息
/*TYPE輸出格式(ID_OK=0,ID_OKCANCEL=1)
/*返回值:返回對(duì)話框接收的鍵值,只有兩種KEY_OK,KEY_CANCEL
/******************************************
typedefenumTYPE{ID_OK,ID_OKCANCEL}MSG_TYPE;
externBYTEMessageBox(LPBYTElpStr,BYTETYPE)
{
BYTEkey=-1;
ClearScreen();/*清除屏幕*/
DisplayString(xPos,yPos,lpStr,TRUE);/*顯示字符串*/
/*根據(jù)對(duì)話框類(lèi)型決定是否顯示確定、取消*/
switch(TYPE)
{
caseID_OK:
DisplayString(13,yPos+High+1,"確定",0);
break;
caseID_OKCANCEL:
DisplayString(8,yPos+High+1,"確定",0);
DisplayString(17,yPos+High+1,"取消",0);
break;
default:
break;
}
DrawRect(0,0,239,yPos+High+16+4);/*繪制外框*/
/*MessageBox是模式對(duì)話框,阻塞運(yùn)行,等待按鍵*/
while((key!=KEY_OK)||(key!=KEY_CANCEL))
{
key=getSysKey();
}
/*返回按鍵類(lèi)型*/
if(key==KEY_OK)
{
returnID_OK;
}
else
{
returnID_CANCEL;
}
}
上述函數(shù)與我們平素在VC++等中使用的MessageBox是何等的神似???實(shí)現(xiàn)這個(gè)函數(shù),你會(huì)看到它在嵌入式系統(tǒng)中的妙用是無(wú)窮的。
總結(jié)
本篇是本系列文章中技巧性最深的一篇,它提供了嵌入式系統(tǒng)屏幕顯示方面一些很巧妙的處理方法,靈活使用它們,我們將不再被LCD上凌亂不堪的顯示內(nèi)容所困擾。
屏幕乃嵌入式系統(tǒng)生存之重要輔助,面目可憎之顯示將另用戶(hù)逃之夭夭。屏幕編程若處理不好,將是軟件中最不系統(tǒng)、最混亂的部分,筆者曾深受其害。
C語(yǔ)言嵌入式系統(tǒng)編程注意事項(xiàng)之鍵盤(pán)操作處理功能鍵
讓我們來(lái)看看WIN32編程中用到的"窗口"概念,當(dāng)消息(message)被發(fā)送給不同窗口的時(shí)候,該窗口的消息處理函數(shù)(是一個(gè)callback函數(shù))最終被調(diào)用,而在該窗口的消息處理函數(shù)中,又根據(jù)消息的類(lèi)型調(diào)用了該窗口中的對(duì)應(yīng)處理函數(shù)。通過(guò)這種方式,WIN32有效的組織了不同的窗口,并處理不同窗口情況下的消息。
我們從中學(xué)習(xí)到的就是:
(1)將不同的畫(huà)面類(lèi)比為WIN32中不同的窗口,將窗口中的各種元素(菜單、按鈕等)包含在窗口之中;
(2)給各個(gè)畫(huà)面提供一個(gè)功能鍵"消息"處理函數(shù),該函數(shù)接收按鍵信息為參數(shù);
(3)在各畫(huà)面的功能鍵"消息"處理函數(shù)中,判斷按鍵類(lèi)型和當(dāng)前焦點(diǎn)元素,并調(diào)用對(duì)應(yīng)元素的按鍵處理函數(shù)。/*將窗口元素、消息處理函數(shù)封裝在窗口中*/
struct
windows
{
BYTEcurrentFocus;
ELEMENT
element[ELEMENT_NUM];
void(*messageFun)(BYTEkey);
…
};
/*消息處理函數(shù)*/
voidmessage(BYTEkey)
{
BYTEi=0;
/*獲得焦點(diǎn)元素*/
while((element
.ID!=currentFocus)&&(i<ELEMENT_NUM))
{
i++;
}
/*"消息映射"*/
if(i<ELEMENT_NUM)
{
switch(key)
{
caseOK:
element.OnOk();
break;
…
}
}
}
在窗口的消息處理函數(shù)中調(diào)用相應(yīng)元素按鍵函數(shù)的過(guò)程類(lèi)似于"消息映射",這是我們從WIN32編程中學(xué)習(xí)到的。編程到了一個(gè)境界,很多東西都是相通的了。其它地方的思想可以拿過(guò)來(lái)為我所用,是為編程中的"拿來(lái)主義"。
在這個(gè)例子中,如果我們還想玩得更大一點(diǎn),我們可以借鑒MFC中處理MESSAGE_MAP的方法,我們也可以學(xué)習(xí)MFC定義幾個(gè)精妙的宏來(lái)實(shí)現(xiàn)"消息映射"。處理數(shù)字鍵
用戶(hù)輸入數(shù)字時(shí)是一位一位輸入的,每一位的輸入都對(duì)應(yīng)著屏幕上的一個(gè)顯示位置(x坐標(biāo),y坐標(biāo))。此外,程序還需要記錄該位置輸入的值,所以有效組織用戶(hù)數(shù)字輸入的最佳方式是定義一個(gè)結(jié)構(gòu)體,將坐標(biāo)和數(shù)值捆綁在一起:/*用戶(hù)數(shù)字輸入結(jié)構(gòu)體*/
typedefstructtagInputNum
{
BYTEbyNum;/*接收用戶(hù)輸入賦值*/
BYTExPos;/*數(shù)字輸入在屏幕上的顯示位置x坐標(biāo)*/
BYTEyPos;/*數(shù)字輸入在屏幕上的顯示位置y坐標(biāo)*/
}InputNum,*LPInputNum;
那么接收用戶(hù)輸入就可以定義一個(gè)結(jié)構(gòu)體數(shù)組,用數(shù)組中的各位組成一個(gè)完整的數(shù)字:InputNuminputElement[NUM_LENGTH];/*接收用戶(hù)數(shù)字輸入的數(shù)組*/
/*數(shù)字按鍵處理函數(shù)*/
externvoidonNumKey(BYTEnum)
{
if(num==0||num==1)/*只接收二進(jìn)制輸入*/
{
/*在屏幕上顯示用戶(hù)輸入*/
DrawText(inputElement[currentElementInputPlace].xPos,inputElement[currentElementInputPlace].yPos,"%1d",num);
/*將輸入賦值給數(shù)組元素*/
inputElement[currentElementInputPlace].byNum=num;
/*焦點(diǎn)與光標(biāo)右移*/
moveToRight();
}
}
將數(shù)字每一位輸入的坐標(biāo)和輸入值捆綁后,在數(shù)字鍵處理函數(shù)中就可以較有結(jié)構(gòu)的組織程序,使程序顯得很緊湊。
整理用戶(hù)輸入
繼續(xù)第2節(jié)的例子,在第2節(jié)的onNumKey函數(shù)中,只是獲取了數(shù)字的每一位,因而我們需要將其轉(zhuǎn)化為有效數(shù)據(jù),譬如要轉(zhuǎn)化為有效的XXX數(shù)據(jù),其方法是:/*從2進(jìn)制數(shù)據(jù)位轉(zhuǎn)化為有效數(shù)據(jù):XXX*/
voidconvertToXXX()
{
BYTEi;
XXX=0;
for(i=0;i<NUM_LENGTH;i++)
{
XXX+=inputElement.byNum*power(2,NUM_LENGTH-i-1);
}
}
反之,我們也可能需要在屏幕上顯示那些有效的數(shù)據(jù)位,因?yàn)槲覀円残枰軌蚍聪蜣D(zhuǎn)化:/*從有效數(shù)據(jù)轉(zhuǎn)化為2進(jìn)制數(shù)據(jù)位:XXX*/
voidconvertFromXXX()
{
BYTEi;
XXX=0;
for(i=0;i<NUM_LENGTH;i++)
{
inputElement.byNum=XXX/power(2,NUM_LENGTH-i-1)%2;
}
}
當(dāng)然在上面的例子中,因?yàn)閿?shù)據(jù)是2進(jìn)制的,用power函數(shù)不是很好的選擇,直接用"<<>>"移位操作效率更高,我們僅是為了說(shuō)明問(wèn)題的方便。試想,如果用戶(hù)輸入是十進(jìn)制的,power函數(shù)或許是唯一的選擇了。
總結(jié)
本篇給出了鍵盤(pán)操作所涉與的各個(gè)方面:功能鍵處理、數(shù)字鍵處理與用戶(hù)輸入整理,基本上提供了一個(gè)全套的按鍵處理方案。對(duì)于功能鍵處理方法,將LCD屏幕與Windows窗口進(jìn)行類(lèi)比,提出了較新穎地解決屏幕、鍵盤(pán)繁雜交互問(wèn)題的方案。
計(jì)算機(jī)學(xué)的許多知識(shí)都具有相通性,因而,不斷追趕時(shí)髦技術(shù)而忽略基本功的做法是徒勞無(wú)意的。我們最多需要"精通"三種語(yǔ)言(精通,一個(gè)在如今的求職簡(jiǎn)歷里泛濫成災(zāi)的詞語(yǔ)),最佳拍檔是匯編、C、C++(或JAVA),很顯然,如果你"精通"了這三種語(yǔ)言,其它語(yǔ)言你應(yīng)該是可以很快"熟悉"的,否則你就沒(méi)有"精通"它們。
C語(yǔ)言嵌入式系統(tǒng)編程注意事項(xiàng)之性能優(yōu)化
在C語(yǔ)言中,宏是產(chǎn)生內(nèi)嵌代碼的唯一方法。對(duì)于嵌入式系統(tǒng)而言,為了能達(dá)到性能要求,宏是一種很好的代替函數(shù)的方法使用宏定義
在C語(yǔ)言中,宏是產(chǎn)生內(nèi)嵌代碼的唯一方法。對(duì)于嵌入式系統(tǒng)而言,為了能達(dá)到性能要求,宏是一種很好的代替函數(shù)的方法。
寫(xiě)一個(gè)"標(biāo)準(zhǔn)"宏MIN,這個(gè)宏輸入兩個(gè)參數(shù)并返回較小的一個(gè):
錯(cuò)誤做法:#defineMIN(A,B)(A<=B?A:B)
正確做法:#defineMIN(A,B)((A)<=(B)?(A):(B))
對(duì)于宏,我們需要知道三點(diǎn):
(1)宏定義"像"函數(shù);
(2)宏定義不是函數(shù),因而需要括上所有"參數(shù)";
(3)宏定義可能產(chǎn)生副作用。
下面的代碼:least=MIN(*p++,b);
將被替換為:((*p++)<=(b)?(*p++):(b))
發(fā)生的事情無(wú)法預(yù)料。
因而不要給宏定義傳入有副作用的"參數(shù)"。
使用寄存器變量
當(dāng)對(duì)一個(gè)變量頻繁被讀寫(xiě)時(shí),需要反復(fù)訪問(wèn)內(nèi)存,從而花費(fèi)大量的存取時(shí)間。為此,C語(yǔ)言提供了一種變量,即寄存器變量。這種變量存放在CPU的寄存器中,使用時(shí),不需要訪問(wèn)內(nèi)存,而直接從寄存器中讀寫(xiě),從而提高效率。寄存器變量的說(shuō)明符是register。對(duì)于循環(huán)次數(shù)較多的循環(huán)控制變量與循環(huán)體內(nèi)反復(fù)使用的變量均可定義為寄存器變量,而循環(huán)計(jì)數(shù)是
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024建筑工程承發(fā)包合同
- 2024年江蘇省《輔警招聘考試必刷500題》考試題庫(kù)含答案1
- 二零二五年度家具安裝與品牌授權(quán)合作合同3篇
- 二零二五年度市政道路照明設(shè)施清潔維護(hù)服務(wù)合同3篇
- 二零二五年度基站場(chǎng)地租賃與通信基站信號(hào)覆蓋協(xié)議3篇
- 2024年電子信息產(chǎn)品可靠性測(cè)試服務(wù)合同3篇
- 2024房產(chǎn)居間服務(wù)協(xié)議標(biāo)準(zhǔn)模板版B版
- 2024版家具定制:橡木床頭柜協(xié)議樣本版B版
- 2024年軟裝樣板房室內(nèi)外綠化設(shè)計(jì)與施工合同3篇
- 二零二五年度房產(chǎn)分銷(xiāo)網(wǎng)絡(luò)建設(shè)及運(yùn)營(yíng)服務(wù)合同2篇
- 中學(xué)學(xué)校裝修改造工程施工組織設(shè)計(jì)方案
- 幼兒園反恐防暴技能培訓(xùn)內(nèi)容
- 2024年WPS計(jì)算機(jī)二級(jí)考試題庫(kù)350題(含答案)
- 2024年5G網(wǎng)絡(luò)覆蓋工程分包合同
- 2025屆北京市海淀區(qū)交大附中高一物理第一學(xué)期期末復(fù)習(xí)檢測(cè)試題含解析
- 護(hù)理員技能培訓(xùn)課件
- 煤礦防治水細(xì)則解讀
- 四川新農(nóng)村建設(shè)農(nóng)房設(shè)計(jì)方案圖集川東北部分
- 2023智能座艙白皮書(shū)-聚焦電動(dòng)化下半場(chǎng)-畢馬威
- 福建省福州市九師教學(xué)聯(lián)盟2023-2024學(xué)年高一上學(xué)期期末學(xué)業(yè)聯(lián)考化學(xué)試題(解析版)
- 植物病蟲(chóng)害防治技能大賽理論題及答案
評(píng)論
0/150
提交評(píng)論