李加明
(南京航空航天大學(xué) 2015級(jí)工科研究試驗(yàn)班,南京 211106)
李加明
(南京航空航天大學(xué) 2015級(jí)工科研究試驗(yàn)班,南京 211106)
摘要:單片機(jī)C標(biāo)準(zhǔn)編程不能實(shí)現(xiàn)函數(shù)的多個(gè)輸出值,目前常用的全局變量法雖可變通實(shí)現(xiàn),但破壞了函數(shù)的模塊化原則。本文突破一些常規(guī)思維,探索出兩種類型的多種模塊化多輸出值C函數(shù)的編程方法,并提出了新式的C函數(shù)語法規(guī)則,實(shí)現(xiàn)了較為規(guī)范通用的模塊化多輸出值C函數(shù)的編程。本方法編程思路新穎,具有較高的技術(shù)實(shí)用價(jià)值。
關(guān)鍵詞:C函數(shù);多輸出值;模塊化;MCU
引言
函數(shù)是單片機(jī)與嵌入式系統(tǒng)C語言程序的核心,標(biāo)準(zhǔn)C函數(shù)定義遵循y=f(x1,x2,…,xn)的數(shù)學(xué)形式,其中x1,x2,…,xn為代表函數(shù)各輸入值的形式參數(shù),y為函數(shù)唯一的輸出值。函數(shù)調(diào)用以輸入值實(shí)際參數(shù)代替形式參數(shù),經(jīng)運(yùn)算后由return語句返回一個(gè)輸出值。
但在實(shí)際應(yīng)用中,往往希望函數(shù)能有多個(gè)輸出值,例如用于工業(yè)控制的西門子S7-300/400可編程控制器程序中,其具有函數(shù)功能的FC、FB、SFC、SFB塊均可直接實(shí)現(xiàn)多輸出值。相比之下,標(biāo)準(zhǔn)C函數(shù)不能直接實(shí)現(xiàn)多輸出值,其功能存在明顯的不足。
當(dāng)需要C函數(shù)有多輸出值時(shí),可將一個(gè)函數(shù)分為幾個(gè)函數(shù)實(shí)現(xiàn),但從算法和效率考慮顯然極不合理。目前通常采用全局變量法變通實(shí)現(xiàn):由return語句正常返回一個(gè)輸出值,其余輸出值均由另設(shè)的全局變量獲得,但函數(shù)的模塊化原則遭到了極大破壞。
實(shí)現(xiàn)模塊化原則的任意多輸出值的C函數(shù)有很大的技術(shù)需求價(jià)值,值得探索。筆者在C語言編程學(xué)習(xí)中,摸索并試驗(yàn)成功了兩大類多種模塊化多輸出值C函數(shù)的編程方法。各例程分別在軟件Keil C51V9.00與8位機(jī)STC90C516RD+、軟件CCS5.2.1.00018與16位機(jī)MSP430F6638、軟件Keil MDK5.14.0.0與32位機(jī)STM32F103RBT6平臺(tái)編譯下載并運(yùn)行通過。其中,16位機(jī)MSP430F6638和32位機(jī)STM32F103RBT6芯片均具備直接的硬件在線運(yùn)行調(diào)試功能,文中選取了對(duì)應(yīng)的CCS5.2.1.00018和Keil MDK5.14.0.0軟件觀察硬件運(yùn)行結(jié)果畫面。
51核8位機(jī)STC90C516RD+芯片自身不具備硬件在線運(yùn)行調(diào)試功能,故在Keil C51V9.00軟件仿真運(yùn)行成功基礎(chǔ)上添加代碼,通過其串行口向PC機(jī)發(fā)送硬件運(yùn)行結(jié)果,由PC機(jī)側(cè)串行口軟件觀察硬件運(yùn)行結(jié)果。對(duì)于大多數(shù)不具備硬件在線運(yùn)行調(diào)試功能的8位單片機(jī)芯片,這樣的硬件運(yùn)行調(diào)試方法是非常實(shí)用且必要的。文中給出了添加串行口功能的程序和PC機(jī)側(cè)串行口軟件XCOM觀察的硬件運(yùn)行結(jié)果畫面。
1標(biāo)準(zhǔn)C函數(shù)的定義與調(diào)用
標(biāo)準(zhǔn)C函數(shù)返回一個(gè)輸出值的程序如下:
int func(int a,int b){/*函數(shù)定義*/
int y; y=a+b; return(y); }/*返回一個(gè)輸出值*/
int main(void){
int n1,n2;/*輸入*/
int sum;/*輸出*/
while(1){
n1=-132;n2=-12; sum=func(n1,n2);}
}/*函數(shù)調(diào)用*/
此例是標(biāo)準(zhǔn)C函數(shù)的定義與調(diào)用:函數(shù)形式參數(shù)表僅包括各輸入量,唯一輸出量由函數(shù)體return語句返回。函數(shù)采取“輸出量=函數(shù)名(輸入量表)”的格式定義與調(diào)用。
全局變量法實(shí)現(xiàn)標(biāo)準(zhǔn)C函數(shù)的多個(gè)輸出值如下:
int sum; /*全局變量sum:函數(shù)的另一輸出值*/
int func(int a,int b){/*函數(shù)定義*/
int subs;
subs=a-b;/*一個(gè)輸出值為局部變量subs*/
sum=a+b;/*另一輸出值為全局變量sum*/
return(subs);
}/*正常返回一個(gè)輸出值subs*/
int main(void){
int n1, n2;/*輸入*/
int sub;/*輸出*/
while(1){
n1=-132;n2=-12; sub=func(n1,n2);}
}/*由函數(shù)調(diào)用獲得一個(gè)輸出值sub,另一輸出值由全局變量sum獲得*/
此例為目前實(shí)現(xiàn)標(biāo)準(zhǔn)C函數(shù)多輸出值常用的全局變量法:函數(shù)func由return返回一個(gè)輸出值sub,另一輸出值sum也需由函數(shù)func計(jì)算,但函數(shù)只能返回一個(gè)輸出值,故只得將sum定義為在函數(shù)func與調(diào)用func的main程序中均有效的全局變量,變通實(shí)現(xiàn)函數(shù)func的兩個(gè)輸出值。
全局變量法可變通實(shí)現(xiàn)函數(shù)的多輸出值,因而應(yīng)用廣泛。但此方法的函數(shù)體內(nèi)包括了作為輸出量的全局變量,破壞了函數(shù)“模塊自身內(nèi)聚,模塊間不耦合”的模塊化原則,致使函數(shù)無法獨(dú)立、難以移植共用,且極大降低了編程的可靠性與清晰性。
2維持標(biāo)準(zhǔn)C函數(shù)語法規(guī)則的改進(jìn)編程
標(biāo)準(zhǔn)C函數(shù)只能由return直接返回一個(gè)輸出值,可對(duì)這個(gè)輸出值進(jìn)行思路拓展:C語言有一種構(gòu)造數(shù)據(jù)類型“結(jié)構(gòu)”,能包含多個(gè)不同類型的數(shù)據(jù)成員,但對(duì)外卻可以作為一個(gè)數(shù)據(jù)單位出現(xiàn)。若運(yùn)用return直接返回一個(gè)結(jié)構(gòu)變量,也符合標(biāo)準(zhǔn)C函數(shù)的語法規(guī)則。函數(shù)定義時(shí)可將所需的多個(gè)輸出量匯集到一個(gè)專用結(jié)構(gòu)變量中,調(diào)用后逐個(gè)拆分讀取返回的結(jié)構(gòu)變量的數(shù)據(jù)成員,則可間接獲得函數(shù)的多個(gè)輸出值。
如下面的例程中,函數(shù)定義將各輸出量匯集定義到結(jié)構(gòu)中,仍采用標(biāo)準(zhǔn)C函數(shù)的格式調(diào)用:OUT=calc(n1,n2),多個(gè)輸出值可由結(jié)構(gòu)OUT的成員OUT.sum、OUT.sub、OUT.mul、OUT.div間接獲得。CCS5.21.00018與Keil MDK5.14.0.0觀察的硬件在線調(diào)試運(yùn)行結(jié)果略——編者注。
typedef struct{
int sum; int sub;
unsigned int mul; unsigned char div;
}result;
/*定義專用結(jié)構(gòu)result,匯集4個(gè)輸出量*/
result calc(int a,int b){/*函數(shù)定義*/
result c;
c.sum=a+b; c.sub=a-b; c.mul=a*b; c.div=a/b;
return(c); }/*返回一個(gè)結(jié)構(gòu)變量*/
int main(void){
int n1,n2;/*輸入*/
volatile result OUT;
/*輸出,默認(rèn)條件,CCS和KeilMDK編譯時(shí)此處需添加volatile,而KeilC51編譯時(shí)此處無需volatile,詳細(xì)見后文敘述*/
while(1){
n1=-132;n2=-12;
OUT=calc(n1,n2);}/*函數(shù)調(diào)用*/
}
對(duì)于51核8位機(jī)STC90C516RD+,由于芯片自身不支持硬件在線運(yùn)行調(diào)試功能,故在Keil C51V9.00軟件仿真運(yùn)行成功基礎(chǔ)上添加代碼,通過其串行口及printf()函數(shù)向PC機(jī)發(fā)送硬件運(yùn)行結(jié)果,程序略——編者注。
由PC機(jī)側(cè)串口軟件XCOM觀察的硬件運(yùn)行結(jié)果略——編者注。
本方法是維持標(biāo)準(zhǔn)C函數(shù)語法規(guī)則的改進(jìn)方法,通過結(jié)構(gòu)間接實(shí)現(xiàn)函數(shù)的多輸出值,結(jié)果正確、實(shí)用。標(biāo)準(zhǔn)C函數(shù)return不能直接返回多個(gè)輸出值,所以函數(shù)定義時(shí)需另行定義一個(gè)專用結(jié)構(gòu)變量并將期望的多個(gè)輸出量以成員方式匯集定義到其中,函數(shù)調(diào)用后還需對(duì)返回的唯一結(jié)構(gòu)變量進(jìn)行成員拆分讀取才可獲得期望的多輸出值,編程與數(shù)據(jù)操作均顯復(fù)雜繁瑣。
3重新設(shè)計(jì)新式C函數(shù)語法規(guī)則的編程
標(biāo)準(zhǔn)C函數(shù)的形式參數(shù)表僅包括輸入量,輸出量只能依靠return返回,這正是其不能直接實(shí)現(xiàn)多輸出值的根源。為了從根本上解決問題,突破常規(guī)思維,放棄標(biāo)準(zhǔn)C函數(shù)的語法規(guī)則,重新設(shè)計(jì)可直接實(shí)現(xiàn)多輸出值的新式的函數(shù)定義與調(diào)用語法規(guī)則:①函數(shù)體中完全取消return語句,再不用return返回輸出值;②將函數(shù)期望的多個(gè)輸出量與輸入量共同納入形式參數(shù)表。
新式C函數(shù)的定義與調(diào)用形如:f(x1,x2,…,xn,y1,y2,…,yn),即“函數(shù)名(輸入量表,輸出量表)”格式,函數(shù)體內(nèi)完全取消return語句。采用新式函數(shù)語法規(guī)則,沒有原標(biāo)準(zhǔn)語法規(guī)則的不足限制,可靈活自由地實(shí)現(xiàn)多種通用規(guī)范的結(jié)構(gòu)化多輸出值C函數(shù)的編程方法。
3.1數(shù)組法
下面的例程中,函數(shù)的多個(gè)輸出量用數(shù)組result[]定義,函數(shù)以calc(n1,n2,res)格式調(diào)用,輸出的res數(shù)組的元素res[0]、res[1]、res[2]、res[3]即為函數(shù)的多個(gè)輸出值。CCS5.21.00018與Keil MDK5.14.0.0觀察的硬件在線調(diào)試運(yùn)行結(jié)果略——編者注。
void calc(int a,int b,int result[]){
/*新語法規(guī)則函數(shù)定義:輸入輸出量均納入形式參數(shù)表,取消return語句*/
result[0]=a+b; result[1]=a-b;
result[2]=a*b; result[3]=a/b;}
int main(void){
int n1,n2; /*輸入*/
int res[4]; /*輸出*/
while(1)
{ n1=-132;n2=-12; calc(n1,n2,res);}
} /*新語法規(guī)則函數(shù)調(diào)用*/
對(duì)于51核8位機(jī)STC90C516RD+,由于芯片自身不支持硬件在線運(yùn)行調(diào)試功能,故在Keil C51V9.00軟件仿真運(yùn)行成功基礎(chǔ)上添加代碼,通過其串行口及printf()函數(shù)向PC機(jī)發(fā)送硬件運(yùn)行結(jié)果,程序略——編者注。
由PC機(jī)側(cè)串口軟件XCOM觀察的硬件運(yùn)行結(jié)果略——編者注。
函數(shù)多個(gè)輸出值的數(shù)據(jù)類型相同時(shí),只需定義一個(gè)數(shù)組;如多個(gè)輸出值的數(shù)據(jù)類型不相同,只需依據(jù)其數(shù)據(jù)類型歸類定義多個(gè)數(shù)組即可,數(shù)組的個(gè)數(shù)不受限制。
3.2“宏函數(shù)”法
本方法以帶參數(shù)的宏來定義類似函數(shù)功能的“宏函數(shù)”,宏的參數(shù)表包括全部輸入與輸出量。例程中,宏以CALC(n1,n2,sum,sub,mul,div)格式調(diào)用后即可直接獲得所需的多個(gè)輸出值sum、sub、mul、div。CCS5.21.00018與Keil MDK5.14.0.0觀察的硬件在線調(diào)試運(yùn)行結(jié)果略——編者注。
#define CALC(a,b,summ,subs,mult,divi){
(summ)=(a)+(b); (subs)=(a)-(b);
(mult)=(a)*(b); (divi)=(a)/(b);}
/*新語法規(guī)則“宏函數(shù)”定義:輸入輸出量均納入形式參數(shù)表,取消return語句*/
int main(void){
int n1,n2;/*輸入*/
volatile int sum, sub, mul;
volatile unsigned char div;/*輸出,默認(rèn)條件,CCS和KeilMDK編譯時(shí)此處需添加volatile,而Keil C51編譯時(shí)此處無需volatile,詳細(xì)見后文敘述*/
while(1){
n1=-132; n2=-11;
CALC(n1,n2,sum,sub,mul,div);}
} /*新語法規(guī)則“宏函數(shù)”調(diào)用*/
對(duì)于51核8位機(jī)STC90C516RD+,由于芯片自身不支持硬件在線運(yùn)行調(diào)試功能,故在Keil C51V9.00軟件仿真運(yùn)行成功基礎(chǔ)上添加代碼,通過其串行口及printf()函數(shù)向PC機(jī)發(fā)送硬件運(yùn)行結(jié)果,程序略——編者注。
由PC機(jī)側(cè)串口軟件XCOM觀察的硬件運(yùn)行結(jié)果略——編者注。
用宏代替函數(shù)有如下優(yōu)點(diǎn):①函數(shù)的形式參數(shù)類型在定義時(shí)即已固定,實(shí)際參數(shù)類型必須由編程定義與之保持一致,不能自動(dòng)匹配,編程不便;而宏的參數(shù)類型完全由調(diào)用時(shí)的實(shí)際參數(shù)確定,數(shù)據(jù)類型自然自動(dòng)匹配,編程簡(jiǎn)便。②函數(shù)調(diào)用要付出為形式參數(shù)分配臨時(shí)單元及現(xiàn)場(chǎng)保護(hù)恢復(fù)等額外代碼的時(shí)間開銷;而宏調(diào)用的實(shí)質(zhì)是在編譯階段就已完成的代碼中展開,運(yùn)行時(shí)完全不存在函數(shù)上述的額外時(shí)間開銷,故運(yùn)算速度明顯快于函數(shù)。
宏的不足是:宏多次調(diào)用時(shí),每次均會(huì)展開一套大部分相互重復(fù)的代碼,最終的程序代碼比函數(shù)調(diào)用方式的大。但目前單片機(jī)的程序存儲(chǔ)器足夠大,速度更為重要,所以,“宏函數(shù)”在運(yùn)算速度及參數(shù)類型自動(dòng)匹配方面具備獨(dú)特應(yīng)用價(jià)值。
3.3指針法
研究C語言的指針,總結(jié)并成功驗(yàn)證本方法:函數(shù)定義時(shí),各輸出量形式參數(shù)均冠以符號(hào)*,以指針方式定義;函數(shù)調(diào)用時(shí),各輸出量實(shí)際參數(shù)前均冠以地址符&。例程中,函數(shù)各輸出量形式參數(shù)為summ、subs、mult、divi,故定義格式為:
void calc(int a,int b,int *summ,int *subs,unsigned int *mult,unsigned char *divi)
函數(shù)各輸出量實(shí)際參數(shù)為sum、sub、mul、div,故調(diào)用格式為:
calc(n1,n2,&sum,&sub,&mul,&div)
CCS5.21.00018與Keil MDK5.14.0.0觀察的硬件在線調(diào)試運(yùn)行結(jié)果略——編者注。
void calc(int a,int b,int *summ,int *subs,unsigned int *mult,unsigned char *divi){
*summ=a+b; *subs=a-b; *mult=a*b; *divi=a/b;}
/*新語法規(guī)則:輸入輸出量均納入形式參數(shù)表,取消return語句。函數(shù)各輸出量形式參數(shù)均以其指針定義;函數(shù)體內(nèi)采用對(duì)指針形式參數(shù)指向的值賦值的方式實(shí)現(xiàn)對(duì)各輸出量的賦值*/
int main(void) {
int n1,n2;/*輸入*/
int sum,sub;
unsigned int mul;
unsigned char div;/*輸出*/
while(1){
n1=-132; n2=-12;
calc(n1,n2,&sum,&sub,&mul,&div);}
}/*函數(shù)調(diào)用:各輸出值的實(shí)際參數(shù)sum,sub,mul,div前均冠以地址符& */
對(duì)于51核8位機(jī)STC90C516RD+,由于芯片自身不支持硬件在線運(yùn)行調(diào)試功能,故在Keil C51V9.00軟件仿真運(yùn)行成功基礎(chǔ)上添加代碼,通過其串行口及printf()函數(shù)向PC機(jī)發(fā)送硬件運(yùn)行結(jié)果,程序略——編者注。
由PC機(jī)側(cè)串口軟件XCOM觀察的硬件運(yùn)行結(jié)果略——編者注。
本方法概括為,當(dāng)函數(shù)輸入為x1,x2,…,xn、輸出為y1,y2,…,yn、函數(shù)名為f 時(shí),函數(shù)定義格式為:
f(x1,x2…xn,*y1,*y2…*yn)
調(diào)用格式為:
f(x1,x2…xn,&y1,&y2…&yn)
與數(shù)組法比較,不同數(shù)據(jù)類型的多個(gè)輸出量不需歸類定義;與“宏函數(shù)”法比較,是完全規(guī)范的函數(shù)定義與調(diào)用。相比前述的模塊化多輸出值C函數(shù)的編程方法,在函數(shù)定義與調(diào)用方面,本方法的編程風(fēng)格最為規(guī)范化。
4兩類結(jié)構(gòu)化多輸出值函數(shù)編程的比較
4.1維持標(biāo)準(zhǔn)C函數(shù)語法規(guī)則的改進(jìn)編程
此方法的函數(shù)參數(shù)表仍僅包括輸入量,仍保留函數(shù)體return返回輸出量,故受到標(biāo)準(zhǔn)C函數(shù)語法不足的限制,編程與數(shù)據(jù)操作均顯復(fù)雜繁瑣,不是函數(shù)多輸出值編程的最好方法。
4.2重新設(shè)計(jì)新式C函數(shù)語法規(guī)則的編程
此類方法打破標(biāo)準(zhǔn)C函數(shù)語法的思路限制,取消了函數(shù)體return語句,將輸入與輸出參數(shù)全部納入?yún)?shù)表,依此方法重新設(shè)計(jì)的新式函數(shù)語法規(guī)則回避了原標(biāo)準(zhǔn)C函數(shù)的固有不足。
雖然無法推翻C語言的基本語法,但源自于C語言基本語法的標(biāo)準(zhǔn)C函數(shù)的語法規(guī)則并不是不可取代的,此類方法放棄了標(biāo)準(zhǔn)C函數(shù)的語法規(guī)則,可靈活自由地實(shí)現(xiàn)通用規(guī)范的模塊化多輸出值的C函數(shù)編程,是解決函數(shù)多輸出值編程困擾的最好方法。
5輸出變量定義增加volatile的試驗(yàn)及體會(huì)
結(jié)構(gòu)法和宏函數(shù)法列舉的16位及32位機(jī)程序,試驗(yàn)中發(fā)現(xiàn)輸出變量需要添加volatile進(jìn)行定義。在8位機(jī)Keil C51V9.00軟件編譯時(shí),不添加volatile完全沒有任何問題,但在16位機(jī)CCS5.2.1.00018及32位機(jī)Keil MDK5.14.0.0軟件編譯時(shí),如果不添加volatile,編譯時(shí)會(huì)產(chǎn)生報(bào)警,輸出變量會(huì)被意外地優(yōu)化省略掉。
各類單片機(jī)C軟件平臺(tái)對(duì)程序編譯時(shí)均會(huì)對(duì)代碼進(jìn)行自認(rèn)為最合理的優(yōu)化,但有時(shí)會(huì)優(yōu)化過度,導(dǎo)致結(jié)果意外錯(cuò)誤。前例程序中遇到的報(bào)警就是優(yōu)化過度,經(jīng)試驗(yàn),對(duì)會(huì)被意外優(yōu)化省略掉的輸出變量增加volatile定義,問題即可解決。
volatile的功能簡(jiǎn)單地說,就是防止編譯器對(duì)代碼進(jìn)行優(yōu)化。在作為變量定義的修飾關(guān)鍵字時(shí)其功能為:①確保本變量不會(huì)被編譯器優(yōu)化而省略;②確保變量存儲(chǔ)在內(nèi)存,在生成的代碼中,每次的變量訪問均要重新從內(nèi)存直接讀值,而不是去訪問寄存器里的變量備份。使用volatile能徹底回避代碼過度優(yōu)化導(dǎo)致的意外錯(cuò)誤,確保編譯結(jié)果的正確性。
同樣默認(rèn)設(shè)置對(duì)相同C程序編譯,16位與32位機(jī)編譯系統(tǒng)比8位機(jī)編譯系統(tǒng)的編譯優(yōu)化力度要強(qiáng)得多,更易出現(xiàn)過度優(yōu)化問題。觀察MSP430、STM32單片機(jī)的各類例程,變量添加volatile定義的現(xiàn)象確實(shí)比51核單片機(jī)的多,也佐證了這個(gè)印象。試驗(yàn)還發(fā)現(xiàn),變量添加volatile定義會(huì)微量增加存儲(chǔ)空間占用,雖然目前單片機(jī)存儲(chǔ)空間一般都足夠且對(duì)于存儲(chǔ)空間更多的16位與32位機(jī)影響更小,但一般還是希望volatile定義的變量越少越好。當(dāng)編程難以判斷變量是否需要volatile定義時(shí),可以設(shè)想先對(duì)這些變量均添加volatile定義,再逐步取消各自的volatile進(jìn)行調(diào)試驗(yàn)證,直至在確保結(jié)果正確前提下留下最少的volatile定義的變量為止。
結(jié)語
本文探索的編程方法具備良好通用的技術(shù)實(shí)用價(jià)值,其中,新式函數(shù)語法規(guī)則的編程較為規(guī)范,是模塊化多輸出值C函數(shù)編程的較好方法。
參考文獻(xiàn)
[1] 劉同法.單片機(jī)C語言編程基礎(chǔ)與實(shí)踐[M].北京:北京航空航天大學(xué)出版社,2009.
[2] 何欽銘.C語言程序設(shè)計(jì)[M].3版.北京:科學(xué)出版社,2010.
[3] 廖常初.S7-300/400 PLC應(yīng)用技術(shù)[M].3版.北京:機(jī)械工業(yè)出版社,2011.
[4] 西門子.西門子S7-300/400編程手冊(cè)[EB/OL].[2016-01].http://www.gongkong.com/download/200709/63764.html.
李加明(本科),機(jī)電與控制方向。
(責(zé)任編輯:薛士然收修改稿日期:2016-01-05)
Li Jiaming
(2015 Engineering Research Experimental Class,Nanjing Aeronautics Astronautics University,Nanjing 211106,China)
Abstract:MCU C standard programming can not achieve multiple output value function.Although it can be solved by the global variate at present,but the function modular principle is destroyed.In the paper,two kinds of modular multiple output C function programming method are explored,and a new type of C function syntax rules is proposed.Then the more standardized and versatile programming for modular multiple output C function is achieved.The method has novel programming idea and excellent practical technic value.
Key words:C function;multiple output value;modularity;MCU
中圖分類號(hào):TP368.1
文獻(xiàn)標(biāo)識(shí)碼:A