在現(xiàn)有的教課書(shū)及相關(guān)文章中,都難得提到在單片機(jī)C語(yǔ)言編程中對(duì)于自定義“位”的狀態(tài)進(jìn)行保存的理念。
當(dāng)單片機(jī)C語(yǔ)言編程中提及“位”的概念時(shí),人們自然會(huì)想到狀態(tài)字PSW中PSW.5的F0與PSW.1的F1兩個(gè)用戶通用標(biāo)志位。這兩個(gè)標(biāo)志位均可參與布爾運(yùn)算、“位”控操作,也可隨狀態(tài)字PSW一起保存。但是,往往會(huì)忽視這一點(diǎn):在一些特定的情況下,如在C語(yǔ)言編程的中斷服務(wù)程序中,對(duì)狀態(tài)字PSW中PSW.5的F0與PSW.1的F1這兩個(gè)用戶標(biāo)志位的操作可能是無(wú)效的。如:
void EX1_ISR() interrupt 2 {//外部中斷1
static unsigned int tempaddr;//定義接收地址緩存
static unsigned int tempkey;//定義接收數(shù)據(jù)緩存
unsigned int timecnt;
timecnt=TH1*256+TL1;
TH1=0;
TL1=0;
TR1=1;//定時(shí)器1啟動(dòng)
F0=~F0;//取反F0
if(F0) {
tempaddr=tempaddr<<1;
}
else {
tempkey=tempkey<<1;
}
}
以上是一段單片機(jī)外部中斷1的中斷服務(wù)程序,乍看似乎沒(méi)什么問(wèn)題,仿真調(diào)試時(shí)也能通過(guò)“編輯”。但實(shí)際上這是一段錯(cuò)誤的程序——其中對(duì)“F0”用戶標(biāo)志位的“取反”操作是達(dá)不到其預(yù)期效果的。因?yàn)閷?duì)“F0”用戶標(biāo)志位的“取反”操作是在中斷服務(wù)程序中進(jìn)行的。在進(jìn)入中斷時(shí),C語(yǔ)言自動(dòng)會(huì)保護(hù)“中斷現(xiàn)場(chǎng)”——將程序指針PC、累加器ACC、狀態(tài)字PSW等壓入堆棧保護(hù)起來(lái)……直到中斷返回時(shí)彈出堆棧并覆蓋了中斷服務(wù)時(shí)的變值,恢復(fù)到壓入堆棧之前的原樣。因此,狀態(tài)字PSW中的F0也不例外,如果壓入堆棧之前F0是處于邏輯“0”狀態(tài),中斷返回后還是復(fù)原成邏輯“0”狀態(tài)——不管中斷服務(wù)程序中怎么取反改變——也就是說(shuō),在中斷服務(wù)程序中試圖改變F0之值的操作是有失偏頗的。對(duì)于上文例舉的那段中斷服務(wù)程序來(lái)說(shuō),若F0的初始狀態(tài)為邏輯“0”,即進(jìn)入中斷服務(wù)之前和中斷返回之后總是邏輯“0”,那么進(jìn)入執(zhí)行“F0=~F0”指令后F0總是邏輯“1”,因而接下運(yùn)行的是“if(F0)”下花括號(hào)中“tempaddr=tempaddr<<1;”指令,而“else”下花括號(hào)中“tempkey=tempkey<<1”指令永遠(yuǎn)也運(yùn)行不到。所以,若要中斷服務(wù)程序達(dá)到預(yù)期的效果——“if(F0)”下花括號(hào)中的指令與“else”下花括號(hào)中的指令輪番運(yùn)行,必須設(shè)立一個(gè)不受中斷現(xiàn)場(chǎng)保護(hù)等影響的自定義標(biāo)志位。
1? 自定義標(biāo)志“位”的保存
在C語(yǔ)言編程中,自定義標(biāo)志位的運(yùn)用概率很大,有的一個(gè)程序中就會(huì)有好多的自定義標(biāo)志位,且其中幾個(gè)可能是必須要保存的。譬如有些控制器件中對(duì)一些控制狀態(tài)進(jìn)行保持,即使是停電之后再來(lái)電了這種控制狀態(tài)依然能保持不變——這就牽涉到保存問(wèn)題。
例舉2:我們?cè)氵^(guò)一個(gè)鐳射投影屏幕升降的無(wú)線遙控裝置。這個(gè)以單片機(jī)為核心的控制裝置與屏幕升降的卷動(dòng)電機(jī)等都安裝固定在一個(gè)直徑不足50 mm的狹長(zhǎng)鐵桶里面,因此裝入或拆卸都非常麻煩。為了一次性成功——避免再次拆卸裝入的麻煩,在用C語(yǔ)言編程時(shí)我特意多用了一個(gè)自定義的標(biāo)志位——翻轉(zhuǎn)標(biāo)志位“switch_sign”。因?yàn)闊o(wú)線遙控手柄上的向上“▲”、暫停“■”、向下“▼”的鍵標(biāo)志都已是做定的,因此,如果由于接線等失誤導(dǎo)致按向下“▼”鍵時(shí)投影屏幕向上卷、按向上“▲”鍵時(shí)投影屏幕卻向下伸……有了“switch_sign”自定義的標(biāo)志位就不用怕這些了。相關(guān)的C語(yǔ)言程序段如下:
#defineuint unsigned int
#defineuchar unsigned char
uint Decode_addr,Decode_key,addr;
sbit JD1_out=P0^4;//定義繼電器1控制輸出端
sbit JD2_out=P0^5;//定義繼電器2控制輸出端
sbit BEEP=P0^3;//定義蜂鳴聲響輸出
bit bdata switch_sign;//自定義的翻轉(zhuǎn)標(biāo)志位(應(yīng)作全局變量定義)
voidTelecontrol_Data() {
……
if(Decode_addr==0x5535) {//地址碼核對(duì)
if(Decode_key==0x00C0) {//“▲”鍵碼核對(duì)
BEEP=1;//蜂鳴聲響輸出
if(switch_sign) {//翻轉(zhuǎn)標(biāo)志位
JD1_out=0;//繼電器1控制輸出端
JD2_out=1; //繼電器2控制輸出端
}
else {
JD1_out=1;//繼電器1控制輸出端
JD2_out=0;//繼電器2控制輸出端
}
}
if(Decode_key==0x0030) {//“■”鍵碼核對(duì)
BEEP=1;//蜂鳴聲響輸出
JD1_out=0;//繼電器1控制輸出端
JD2_out=0;//繼電器2控制輸出端
}
if(Decode_key==0x000C) {//“▼”鍵碼核對(duì)
BEEP=1;//蜂鳴聲響輸出
if(switch_sign) {//翻轉(zhuǎn)標(biāo)志位
JD1_out=1;//繼電器1控制輸出端
JD2_out=0;//繼電器2控制輸出端
}
else {
JD1_out=0;//繼電器1控制輸出端
JD2_out=1;//繼電器2控制輸出端
}
}
}
if(Decode_addr==0x5D35) {//取反操作糾正時(shí)地址碼核對(duì)
if(Decode_key==0x00C0) {//“▲”鍵碼核對(duì)
BEEP=1;//蜂鳴聲響輸出
switch_sign=~switch_sign; //取反一次翻轉(zhuǎn)標(biāo)志位
save_data();//調(diào)用保存數(shù)據(jù)子程序
}
}
}
從上面這段遙控?cái)?shù)據(jù)處理子程序可以看到:在任何時(shí)候兩個(gè)繼電器的控制輸出端JD1_out與JD2_out至多只能開(kāi)一個(gè)。當(dāng)遙控“▲”鍵按下有效時(shí),翻轉(zhuǎn)標(biāo)志位“switch_sign”的邏輯“0”或邏輯“1”狀態(tài)將決定兩個(gè)繼電器的控制輸出端JD1_out與JD2_out的一開(kāi)一關(guān),或一關(guān)一開(kāi);同理,當(dāng)遙控“▼”鍵按下有效時(shí)也會(huì)得到與“▲”鍵按下相反的類同效果……也就是說(shuō),只要改變翻轉(zhuǎn)標(biāo)志位“switch_sign”的邏輯狀態(tài),就能改變兩個(gè)繼電器控制輸出端誰(shuí)“開(kāi)”誰(shuí)“關(guān)”的控制輸出狀態(tài)。亦即,在同一個(gè)遙控按鍵按下時(shí)(如“▲”鍵按下),標(biāo)志位“switch_sign”的邏輯狀態(tài)不同,兩個(gè)繼電器控制輸出端誰(shuí)“開(kāi)”誰(shuí)“關(guān)”的控制輸出狀態(tài)也將不同。其中的蜂鳴聲響提示按鍵操作是否有效。
投影屏幕升降的動(dòng)力電機(jī)是一個(gè)AC 220V的交流電機(jī),圖1是電機(jī)控制電路的簡(jiǎn)圖。由此可見(jiàn),當(dāng)繼電器JD1閉合,JD2斷開(kāi)時(shí),電機(jī)M中的L1為主繞組,L2為啟動(dòng)副繞組,電機(jī)將一個(gè)方向運(yùn)轉(zhuǎn);當(dāng)繼電器JD1斷開(kāi),JD2閉合時(shí),電機(jī)M中的L1為啟動(dòng)副繞組,而L2為主繞組了,電機(jī)將以原來(lái)的反方向運(yùn)轉(zhuǎn)。結(jié)合上文,改變翻轉(zhuǎn)標(biāo)志位“switch_sign”的邏輯狀態(tài)→改變兩個(gè)繼電器控制輸出端誰(shuí)“開(kāi)”誰(shuí)“關(guān)”的控制輸出狀態(tài)→改變電機(jī)的運(yùn)轉(zhuǎn)方向→投影屏幕的升降狀態(tài)。也就是說(shuō),改變翻轉(zhuǎn)標(biāo)志位“switch_sign”的邏輯狀態(tài),就可糾正遙控電機(jī)運(yùn)轉(zhuǎn)方向及其投影屏幕的升降狀態(tài)。將遙控按鍵與投影屏幕升降的對(duì)應(yīng)關(guān)系協(xié)調(diào)后,必須保存自定義的標(biāo)志位“switch_sign”當(dāng)前的邏輯狀態(tài),否則,斷電后下一次上電復(fù)位初始化,又可能要出洋相了。
從例舉2的程序中還可以看到,翻轉(zhuǎn)標(biāo)志位“switch_sign”的取反操作也是用同一個(gè)遙控器上的“▲”鍵來(lái)執(zhí)行的,只是在遙控器的地址編碼上動(dòng)了點(diǎn)手腳——改變了一下地址編碼(0x5D35),待操作對(duì)應(yīng)協(xié)調(diào)后再改回到原來(lái)的地址編碼(0x5535)。
2? 保存1個(gè)字節(jié)來(lái)復(fù)原自定義標(biāo)志“位”
自定義標(biāo)志“位”的保存及其復(fù)原有很多種方法,我曾嘗試過(guò)幾種方法。例3是一種保存1個(gè)字節(jié)來(lái)復(fù)原1個(gè)自定義標(biāo)志位的方法,具體操作如下:
static unsigned char current_dat;//定義一個(gè)通用的輔助字節(jié)變量
bit bdata switch_sign;//自定義的翻轉(zhuǎn)標(biāo)志位(應(yīng)作全局變量定義)
……
switch_sign=~switch_sign;//取反1次翻轉(zhuǎn)標(biāo)志位
if(switch_sign) {//判斷switch_sign是邏輯“1”還是邏輯“0”
current_dat=0xA5;//對(duì)通用的輔助字節(jié)變量賦值
}
else {
current_dat=0x00;//通用的輔助字節(jié)變量
}
save_data();//調(diào)用保存數(shù)據(jù)子程序
BEEP=1;//蜂鳴聲響輸出
以上程序是:取反翻轉(zhuǎn)標(biāo)志位“switch_sign”后,若“switch_sign”為邏輯“1”狀態(tài),就給通用的輔助字節(jié)變量“current_dat”賦值“0xA5”(當(dāng)然,這數(shù)據(jù)由你自己隨意定);若“switch_sign”為邏輯“0”狀態(tài),則給通用的輔助字節(jié)變量“current_dat”賦值“0x00”(這數(shù)據(jù)也是自己隨意定的,只要與前面那個(gè)不一樣就是了),然后調(diào)用保存數(shù)據(jù)程序?qū)⑼ㄓ玫妮o助字節(jié)變量“current_dat”保存起來(lái)。這樣,即使斷電了翻轉(zhuǎn)標(biāo)志位“switch_sign”的當(dāng)前狀態(tài)已間接地被通用的輔助字節(jié)變量“current_dat”保存起來(lái)了……當(dāng)下一次上電復(fù)位初始化時(shí),應(yīng)先將保存的輔助變量“current_dat”的數(shù)據(jù)讀出來(lái),并還原成翻轉(zhuǎn)標(biāo)志位“switch_sign”相應(yīng)的邏輯狀態(tài)。上電初始化時(shí)若從存儲(chǔ)處讀出的數(shù)據(jù)是“0xA5”,則將翻轉(zhuǎn)標(biāo)志位“switch_sign”置“1”;若讀出的數(shù)據(jù)是“0x00”,則將翻轉(zhuǎn)標(biāo)志位“switch_sign”置“0”——這就與原來(lái)保存時(shí)的狀態(tài)對(duì)應(yīng)起來(lái)了。其操作過(guò)程如例4:
static unsigned char current_dat;//定義一個(gè)通用的輔助字節(jié)變量
static unsigned char addr;//自定義地址變量緩沖單元
static unsigned char Rdat;//自定義讀數(shù)據(jù)緩沖單元
bit bdata switch_sign;//自定義的翻轉(zhuǎn)標(biāo)志位(應(yīng)作全局變量定義)
……
addr=0x7F6;//給一個(gè)原來(lái)的存儲(chǔ)地址
REEPROM();//調(diào)用讀取E2PROM的子程序
current_dat=Rdat;//將讀出的數(shù)據(jù)還給通用的輔助字節(jié)變量
if(current_dat==0xA5) {//判斷讀出的數(shù)據(jù)是否等于“0xA5”
switch_sign=1;//將翻轉(zhuǎn)標(biāo)志位“switch_sign”置“1”
}
else {
switch_sign=0;//將翻轉(zhuǎn)標(biāo)志位“switch_sign”置“0”
}
31個(gè)字節(jié)保存8個(gè)自定義“位”
用保存一個(gè)自定義的字節(jié)變量來(lái)復(fù)原一個(gè)自定義標(biāo)志位的過(guò)程上文已敘述了,接下來(lái)闡述1個(gè)字節(jié)變量保存8個(gè)自定義“位”的方案。1個(gè)字節(jié)變量保存8個(gè)自定義“位”的方案很多,例5是其中比較理想的一種:
#defineuint unsigned int
#defineuchar unsigned char
uintaddr;
ucharWdat,Rdat;
uchar bdatacurrent_dat;//在可位尋址區(qū)定義unsigned char類型的字節(jié)變量current_dat
sbitsign_bit1= current_dat^0;//用關(guān)鍵字sbit 定義位變量來(lái)獨(dú)立訪問(wèn)可尋址位對(duì)象中的1位
sbitsign_bit2= current_dat^1;//自定義標(biāo)志位2
sbitsign_bit3= current_dat^2;//自定義標(biāo)志位3
……
sbitsign_bit8= current_dat^7;//自定義標(biāo)志位8
……
void Bit_save() {//自定義標(biāo)志位保存子程序
addr=0x7F6;//給予存儲(chǔ)地址
Wdat= current_dat;//將current_dat賦值給寫(xiě)E2PROM的緩沖單元Wdat
save_data();//調(diào)用保存子程序存儲(chǔ)current_dat數(shù)據(jù)
}
void Bit_comeback() {//自定義標(biāo)志位復(fù)原子程序
addr=0x7F6;//給一個(gè)原來(lái)的存儲(chǔ)地址
REEPROM();//調(diào)用讀取E2PROM的子程序
current_dat=Rdat;
//將讀出的數(shù)據(jù)還給通用的輔助字節(jié)變量
}
以上這段程序所闡述的,也許是有關(guān)自定義位操作及其保存的一種最簡(jiǎn)捷的方案了。首先是在可位尋址區(qū)定義ucsigned char類型的通用字節(jié)變量current_dat,再用關(guān)鍵字“sbit”定義位變量來(lái)獨(dú)立訪問(wèn)可尋址位對(duì)象的其中一位。這樣將自定義標(biāo)志位提高到類同于特殊功能寄存器(SFR)中可位訪問(wèn)的方式來(lái)操作了——字節(jié)變量current_dat中的8個(gè)位各自可以獨(dú)立操作,且其保存或讀出復(fù)原都只要直接將字節(jié)變量current_dat進(jìn)行保存或讀取即可,無(wú)須像其他方案那樣需要進(jìn)行邏輯與、邏輯或等的輔助操作。
結(jié)語(yǔ)
單片機(jī)的C語(yǔ)言編程中不一定都要有自定義的標(biāo)志
位,但是在某些場(chǎng)合運(yùn)用了自定義的標(biāo)志位,會(huì)使整個(gè)程序顯得簡(jiǎn)潔而明快。當(dāng)然,對(duì)于自定義標(biāo)志位的保存也是視其具體情況而定——應(yīng)該說(shuō)是不得已而為之的。
評(píng)論
查看更多