Keil C51對外設操作的編程——舊文重讀

《單片機與嵌入式系統應用》有兩期文章先後探討了Keil C51對外設操作的編程,今天又讀了一遍,覺得很有啓發,誠如第二篇文章作者所說:“在設計中遇到問題時.一定不要被表面現象矇蔽,不要急於解決,應該認真分析,找出問題的原因.這樣才能從根本上徹底解決問題”。
先發表的文章(摘自《單片機與嵌入式系統應用》2005年10期)
C語言是當前舉世公認的高效簡潔而又非常貼近硬件的編程語言之一。將C語言向單片機MCS-51上的移植始於2O世紀8O年代的中後期,經過近1O年的發展,C語言克服了產生代碼過長、運行速度較慢的缺點,並且由於C語言在開發速度、軟件質量、結構化、可維護性等方面有着彙編語言無法比擬的優勢,從而得到日益廣泛的應用。KeilC51是德國Keil公司開發的單片機C語言編譯系統.該軟件功能完備,是目前國內技術開發人員使用最爲廣泛的語言之一。
在實際工作中發現,用C語言編寫的對同一端口進行連續讀取的程序,經Keil C51編譯後執行結果往往會出錯,現以8051單片機讀取12位A/D MAX197爲例,如圖1所示。

圖1中,P1.1口用於讀取轉換完成時A/D發出的中斷信號,P1.0對讀取高4位或低8位進行選擇。現假定A/D 的地址爲8000H,啓動CH0端口工作字爲40H。爲得到相應的高、低位轉換數據,用C語言編程如下。
unsigned char xdata MAX197 _at_ 0x8000;
sbit MAXINT= P1^1;
sbit MAXHBEN= P1^0;
……
void main()
{unsigned char up4,down8;//設置接收數據的2個變量
……
MAX197= 0X40;//啓動A/D CH0口進行轉換
while(MAXINT) //等待轉換完成
{};
P1.0=0; //讀取低8位
down8=MAX197;
P1.0=1; //讀取高4位
up4=MAX197;
}
上述的程序並沒有如所希望的那樣分別得到高、低位數據,實際上在down8和up4中得到的都是低8位的數據。下面是上段C語言經編譯後的部分代碼。
41: //取低842: MAXHBEN=0;
C:0x000C C290 CLR MAXHBEN(0x90.0)
  43: down8=MAX197;
C:0x000E 908000 MOV DPTR,#MAX197(0x8000)
C:0x0011 E0 MOVX A,@DPTR
C:0x0012 F509 MOV 0x09,A
  44: //取高445: MAXHBEN=1
C:0x0014 D290 SETB MAXHBEN(0x90.0)
  46: up4=MAX197;
  47:
  48: 
C:0x0016 F5O8 MOV 0x08,A //0x08爲up4
  49: }
通過分析上面的程序會發現,C編譯出來的程序並沒有在P1.0置爲高電位後再去讀一次端口,而只是直接將上次讀來的結果直接送給高4位變量。如果先讀高位後讀低位,結果會得到兩個高4位數據。爲證實這一點,將4條連續重複讀取一個外部端口的C語言語句放在一起,編譯後發現只有第一條語句被編譯執行。也就是說,Keil C51對於連續重複讀取同一個端口地址,在編譯時進行了“特殊”處理,這一點是十分值得注意的。那麼對於確實需要對同一端口進行連續讀取的情況應該如何處理呢?下面介紹兩種方法以供參考。

第一種方法:加延時。
延時不宜太長,特別是在對轉換速度要求較高時。首先寫一個延時函數:

void delay()
{unsigned char i;
for (i=0,i<=1;i++);
}

然後將延時程序放在上面兩次讀取的中間位置。

P1.0=0; //讀取低8down8=MAX197:
delay();
P1.0=1; //讀取高4up4=MAX197;

編譯後的結果如下:

49: //取低850: MAXHBEN=0:
C:0x000C C29O CLR MAXHBEN(0x90.0)
 51: down8=MAX197;
C:0x000E 908000 MOV DPTR,#MAX197(0x8000)
C:0x0011 E0 MOVX A,@DPTR
C:0x0012 F509 MOV 0x09,A
 52: delay();
 53: //取高4C:0x0014 120029 LCALL delay(C:0029)
 54: MAXHBEN = 1;
C:0x0017 D290 SETB MAXHBEN(0x90.0)
 55:up4=MAX197;
 56:
 57:
C:0x0019 E0 MOVX A,@DPTR
C:0x001A F508 MOV 0x08,A
 58: }
可以看出,在將P1.0置高後,又對端口進行了一次讀寫,程序正常並得到了高4位。

第二種方法:另設指針。

void main()
{unsigned char up4,down8; //設置接收數據的2個變量
unsinged char xdata *pt1;
pt1=0x8000;
……
MAX197=0X40; //啓動A/D CH0口進行轉換
while(MAXINT) //等待轉換完成
{};
P1.0=0; //讀取低8位
down8= MAX197:
P1.0=1; //讀取高4位
up4=*pt1:

……
編譯的結果如下:

 42: //取低843: MAXHBEN=0;
C:0x0010 C290 CLR MAXHBEN(0x90.0)
 44: down8=MAX197;
C:0x0012 908000 MOV DPTR,#MAX197(0x8000)
C:0x0015 E0 M0VX A,@DPTR
C:0x0016 F509 MOV 0x09,A
 45: MAXHBEN=1:
 46: //取高447:
C:0x0018 D290 SETB MAXHBEN(0x90.0)
48: up4=*pt1:
49:
50:
C:0x001A 8F82 MOV DPL(0x82),R7
C:0x001C 8E83 MOV DPH (0x83),R6
C:0x001E E0 MOVX A,@DPTR
C:0x001F F508 MOV 0x08,A

上述兩種方法都很好地解決了Keil C51中不能處理對一個端口進行連續讀寫的問題,但如果對轉換速度要求特別高,建議最好使用第二種方法。

第二篇文章(摘自《單片機與嵌入式系統應用》2006年2期)
閱讀了《單片機與嵌入式系統應用》2005年第10期雜誌《經驗交流》欄目的一篇文章《Keil C51對同一端口的連續讀取方法》(原文)後,筆者認爲該文並未就此問題進行深入準確的分析 文章中提到的兩種解決方法並不直接和簡單。筆者認爲這並非是Keil C51中不能處理對一個端口進行連續讀寫的問題,而是對Kei1 C51的使用不夠熟悉和設計不夠細緻的問題,因此特撰寫本文。
本文中對原文提到的問題,提出了三種不同於原文的解決方法。每種方法都比原文中提到的方法更直接和簡單,設計也更規範。(無意批評,請原文作者見諒)
1 問題回顧和分析
原文中提到:在實際工作中遇到對同一端口反覆連續讀取,Keil C51編譯並未達到預期的結果。原文作者對C編譯出來的彙編程序進行分析發現,對同一端口的第二次讀取語句並未被編譯。但可惜原文作者並未分析沒有被編譯的原因,而是匆忙地採用一些不太規範的方法試驗出了兩種解決辦法。
對此問題,翻閱Keil C51的手冊很容易發現:KeilC51的編譯器有一個優化設置,不同的優化設置,會產生不同的編譯結果。一般情況缺省編譯優化設置被設定爲8級優化,實際最高可設定爲9級優化:
1. Dead code elimination。
2.Data overlaying。
3.Peephole optimization。
4.Register variables。
5.Common subexpression elimination。
6.Loop rotation。
7.Extended Index Access Optimizing。
8.Reuse Common Entry Code。
9.Common Block Subroutines。
而以上的問題,正是由於Keil C51編譯優化產生的。因爲在原文程序中將外設地址直接按如下定義:

unsigned char xdata MAX197 _at_ 0x8000
採用_at_將變量MAX197定義到外部擴展RAM 指定地址0x8000。因此,Keil C51優化編譯理所當然認爲重複讀第二次是沒有用的,直接用第一次讀取的結果就可以了,因此編譯器跳過了第二條讀取語句。至此,問題就一目瞭然了。

2 解決方法
由以上分析很容易就能提出很好的解決辦法。
2.1 最簡單最直接的辦法
程序一點都不用修改,將Keil C51的編譯優化選擇設置爲0(不優化)就可以了。選擇project窗口的Target,然後打開“Options for Target”設置對話框,選擇“C51”選項卡,將“Code Optimiztaion”中的“Level”選擇爲“0:Costant folding”。再次編譯後,大家會發現編譯結果爲:

CLR MAXHBEN
MOV DPTR,#MAX197
MOVX A,@DPTR
MOV R7,A
MOV down8,R7
SETB MAXHBEN
MOV DPTR,#MAX197
MOVX A,@DPTR
MOV R7,A
MOV up4,R7

兩次讀取操作都被編譯出來了。
2.2 最好的方法
告訴Keil C51,這個地址不是一般的擴展RAM,而是連接的設備,具有“揮發”特性,每次讀取都是有意義的。可以修改變量定義,增加“volatile”關鍵字說明其特徵:

unsigned char volatile xdata MAX197 _at_ 0x8000
也可以在程序中包含系統頭文件;“#include”,然後在程序中修改變量,定義爲直接地址:
#define MAX197 XBYTE[0x8000]
這樣,Keil C51的設置仍然可以保留高級優化,且編譯結果中,同樣兩次讀取並不會被優化跳過。

2 3 硬件解決方法
原文中將MAX197的數據直接連接到數據總線,而對地址總線並未使用,採用一根端口線選擇操作高低字節。很簡單的修改方法就是使用一根地址線選擇操作高低字節即可。比如:將P2.0(A8)連接到原來P1.0連接的HBEN腳(MAX197的5腳).在程序中分別定義高低字節的操作地址:

unsigned char volatile xdata MAX197_L _at_ 0x8000;
unsigned char volatile xdata MAX197_H _at_ 0x8100;
將原來的程序:
MAXHBEN =0;
down8=MAX197;//讀取低8位
MAXHBEN =1;
up4=MAX197;//讀取高4位
改爲以下兩句即可
down8= MAX197_L;//讀取低8位
up4=MAX197_H;//讀取高4位

3 小結
Keil C51經過長期考驗和改進以及大量開發人員的實際使用,已經克服了絕大多數的問題,並且其編譯效率也非常高。對於一般的使用.很難再發現什麼問題。筆者曾經粗略研究過一下Keil C51優化編洋的結果.非常佩服Keil C51設計者的智慧,一些C程序編譯產生的彙編代碼.甚至比一般程序員直接用匯編編寫的代碼還要優秀和簡練 通過研讀Kell C51編譯產生的彙編代碼.對提高彙編語言編寫程序的水平都是很有幫助的。
由本文中的問題可以看出:在設計中遇到問題時.一定不要被表面現象矇蔽,不要急於解決,應該認真分析,找出問題的原因.這樣才能從根本上徹底解決問題。

附表:Keil C51中的優化級別及優化作用
級別 說明
0 常數合併:編譯器預先計算結果,儘可能用常數代替表達式。包括運行地址計算。
優化簡單訪問:編譯器優化訪問8051系統的內部數據和位地址。
跳轉優化:編譯器總是擴展跳轉到最終目標,多級跳轉指令被刪除。
1 死代碼刪除:沒用的代碼段被刪除。
拒絕跳轉:嚴密的檢查條件跳轉,以確定是否可以倒置測試邏輯來改進或刪除。
2 數據覆蓋:適合靜態覆蓋的數據和位段被確定,並內部標識。BL51連接/定位器可以通過全局數據流分析,選擇可被覆蓋的段。
3 窺孔優化:清除多餘的MOV指令。這包括不必要的從存儲區加載和常數加載操作。當存儲空間或執行時間可節省時,用簡單操作代替複雜操作。
4 寄存器變量:如有可能,自動變量和函數參數分配到寄存器上。爲這些變量保留的存儲區就省略了。
優化擴展訪問:IDATA、XDATA、PDATA和CODE的變量直接包含在操作中。在多數時間沒必要使用中間寄存器。
局部公共子表達式刪除:如果用一個表達式重複進行相同的計算,則保存第一次計算結果,後面有可能就用這結果。多餘的計算就被刪除。
Case/Switch優化:包含SWITCH和CASE的代碼優化爲跳轉表或跳轉隊列。

5 全局公共子表達式刪除:一個函數內相同的子表達式有可能就只計算一次。中間結果保存在寄存器中,在一個新的計算中使用。
簡單循環優化:用一個常數填充存儲區的循環程序被修改和優化。
6 循環優化:如果結果程序代碼更快和有效則程序對循環進行優化。
7 擴展索引訪問優化:適當時對寄存器變量用DPTR。對指針和數組訪問進行執行速度和代碼大小優化。
8 公共尾部合併:當一個函數有多個調用,一些設置代碼可以複用,因此減少程序大小。
9 公共塊子程序:檢測循環指令序列,並轉換成子程序。Cx51甚至重排代碼以得到更大的循環序列。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章