關(guān)鍵字:LCD12864 數(shù)字示波器
本文針對LCD12864 特性,完成了數(shù)字示波器顯示必須的繪圖驅(qū)動程序設(shè)計(jì),這個教程定位給初學(xué)者使用,我立足從簡單到復(fù)雜一步一步介紹設(shè)計(jì)過程,甚至是調(diào)試的過程,還包括一些經(jīng)驗(yàn)總結(jié),特別是提供了完整的keil 工程附件。希望讀者立足示波器項(xiàng)目,學(xué)到更多軟硬件設(shè)計(jì)經(jīng)驗(yàn)技巧。
一、簡易數(shù)字示波器原理
數(shù)字示波器基本原理可以簡單理解為:數(shù)據(jù)采集+ 圖形顯示,該過程循環(huán)進(jìn)行,如圖1 所示。
圖1 簡易數(shù)字示波器流程圖
LCD 圖形顯示需要根據(jù)LCD 特性設(shè)計(jì),不同LCD驅(qū)動程序不同,本篇將結(jié)合不帶字庫的LCD12864 設(shè)計(jì)顯示程序。
二、圖形液晶LCD12864繪圖驅(qū)動設(shè)計(jì)基礎(chǔ)
關(guān)于LCD 的硬件接口電路,在其他教程中有詳細(xì)介紹,涉及單片機(jī)總線知識和CPLD 內(nèi)部電路,需要認(rèn)真學(xué)習(xí),這里借助現(xiàn)成的驅(qū)動函數(shù),重點(diǎn)講解LCD繪圖程序設(shè)計(jì)。
LCD12864 的電路接口在頭文件中定義:
#define LCD_LCW XBYTE[0xf4ea]
// 左屏命令寫入
#define LCD_LDW XBYTE[0xf5ea]
// 左屏數(shù)據(jù)寫入
#define LCD_LCR XBYTE[0xf6ea]
// 左屏命令讀出
#define LCD_LDR XBYTE[0xf7ea]
// 左屏數(shù)據(jù)讀出
#define LCD_RCW XBYTE[0xf8ea]
// 右屏命令寫入
#define LCD_RDW XBYTE[0xf9ea]
// 右屏數(shù)據(jù)寫入
#define LCD_RCR XBYTE[0xfaea]
// 右屏命令讀出
#define LCD_RDR XBYTE[0xfbea]
// 右屏數(shù)據(jù)讀出
后面所有對LCD 的編程操作都是基于以上接口定義進(jìn)行的各種讀寫操作。
首先來看LCD12864 的點(diǎn)陣結(jié)構(gòu)圖,如圖2 所示。
圖2 LCD點(diǎn)陣分布結(jié)構(gòu)圖
此LCD 屏由水平128 列,垂直64 行組成。水平128 列分左右各64 列兩個半屏構(gòu)成。垂直64 行又分8 頁,每頁8 行(1 列8 點(diǎn)剛好1 字節(jié))。程序每次對LCD 的繪圖操作就是以最小單位1 字節(jié)進(jìn)行操作的。
理解這點(diǎn)至關(guān)重要。也就是每次只能針對8 點(diǎn)進(jìn)行操作,而不是1 點(diǎn)進(jìn)行操作。左右屏由單獨(dú)地址線控制(前面的接口定義就是分左右屏定義的)。實(shí)際打點(diǎn)只需往指定“位置”寫入數(shù)據(jù),“1”亮,“0”暗。
LCD 驅(qū)動忙檢測函數(shù)void loop_lcd12864_is_busy(unsigned char right)。
void loop_lcd12864_is_busy(unsigned char right)
{
unsigned char tmp,counter=0;
do {
if(right) tmp = LCD_RCR;
else tmp = LCD_LCR;
if(counter++>50) break; // 超時跳出
}
while ((tmp|0x7f)==0xff); //bit7 為1 則表示LCD 內(nèi)部執(zhí)行命令,處于“忙”狀態(tài)
}
對LCD 進(jìn)行讀寫操作時,需要進(jìn)行“忙”檢測,LCD 內(nèi)部也是由控制器來完成一系列刷屏操作的,執(zhí)行各種操作都是需要一定的時間,也就是說不是任何時候外部控制器都可以對LCD 發(fā)操作指令的,只有LCD為空閑狀態(tài)時才可以操作,忙檢測就是循環(huán)讀取LCD狀態(tài)標(biāo)志位,判斷是否空閑,關(guān)于命令的細(xì)節(jié)請參考數(shù)據(jù)手冊。
命令寫入函數(shù)void lcd_cmd_wr(unsigned char cmd,right)。
void lcd_cmd_wr(unsigned char cmd, right)
{
loop_lcd12864_is_busy(right); // 忙檢測
if(right) LCD_RCW = cmd; // 右屏命令寫入
else LCD_LCW = cmd; // 左屏命令寫入
}
數(shù)據(jù)寫入函數(shù)void lcd_dat_wr(unsigned char data,right)。
void lcd_dat_wr(unsigned char data,right)
{
loop_lcd12864_is_busy(right);
if(right) LCD_RDW = data;
else LCD_LDW = data;
}
lcd_cmd_wr() 和lcd_dat_wr() 兩個函數(shù)分別是給LCD 寫命令和寫數(shù)據(jù)函數(shù),通過寫命令函數(shù)設(shè)定地址。每個函數(shù)都分左右屏,“right”參數(shù)選擇,“0”選左屏,“非0”選右屏。
讀數(shù)據(jù)函數(shù)unsigned char lcd_dat_rd(unsigned char right)。
unsigned char lcd_dat_rd(unsigned char right)
{
loop_lcd12864_is_busy(right);
if(right) return(LCD_RDR);
else retuen(LCD_LDR);
}
該函數(shù)可以讀出LCD 當(dāng)前顯示的數(shù)據(jù),首次操作需要讀2次才有效。
LCD 清屏函數(shù)void lcd12864_clr(void)。
void lcd12864_clr(void)
{
unsigned char i,j;
for(i=0;i<8;i++) { // 從0 到7 共8 頁
lcd_cmd_wr(ORGX,0); // 分頁設(shè)定左屏0 點(diǎn)地址
lcd_cmd_wr(ORGY+i,0);
lcd_cmd_wr(ORGX,1); // 分頁設(shè)定右屏0 點(diǎn)地址
lcd_cmd_wr(ORGY+i,1);
for(j=0;j<64;j++) {
lcd_data_wr(0,0);
lcd_data_wr(0,1);
}
}
}
該函數(shù)對LCD 所有點(diǎn)陣寫0,完成一次清屏操作。這里的ORGY,PRGX 是設(shè)定光標(biāo)的命令,光標(biāo)指向(0,0)字節(jié),是一個固定值。實(shí)際在執(zhí)行數(shù)據(jù)寫入的時,x 坐標(biāo)范圍從0 到63,在連續(xù)寫入過程中能夠?qū)崿F(xiàn)自動加1,y 軸頁地址范圍從0 到7,需要逐頁設(shè)定。
LCD 初始化函數(shù)void lcd12864_init(void)。
void lcd12864_init(void)
{
lcd_cmd_wr(DISPON,0); // 顯示開啟
lcd_cmd_wr(DISPFIRST,0); // 設(shè)定顯示首行地址,修改首行地址可以實(shí)現(xiàn)屏幕滾動顯示效果
lcd_cmd_wr(ORGY,0); // 設(shè)定初始光標(biāo)
lcd_cmd_wr(ORGX,0);
lcd_cmd_wr(DISPON,1); // 初始另外一半
lcd_cmd_wr(DISPFIRST,1);
lcd_cmd_wr(ORGY,1);
lcd_cmd_wr(ORG,1);
lcd12864_clr(); // 執(zhí)行清屏,非必須操作
}
該函數(shù)用來初始化LCD,設(shè)置顯示模式,光標(biāo)位置等,在對LCD 繪圖時,最多的命令就是設(shè)定當(dāng)前光標(biāo)位置,通過光標(biāo)位置來指定將要操作的LCD 顯示點(diǎn)。
在對LCD 編程操作以前,一定要執(zhí)行此函數(shù)對LCD 進(jìn)行初始化操作。
從驅(qū)動函數(shù)可見,一次對LCD 寫入數(shù)據(jù)是以字節(jié)為單位,通過寫命令設(shè)定坐標(biāo),y 坐標(biāo)從0 頁到7 頁,x 坐標(biāo)從0 列到63 列,分左右屏,左上角為坐標(biāo)(0,0)點(diǎn),這和我們習(xí)慣的左下角為(0,0)坐標(biāo)軸是不一樣的。
因?yàn)槊看尾僮鱈CD 是一個字節(jié)為單位,對應(yīng)8 點(diǎn),如果我們希望以任意點(diǎn)為坐標(biāo)顯示,還得另外尋找別的辦法編程實(shí)現(xiàn)真正“點(diǎn)”顯示。
如圖3 所示,在屏幕上指定位置畫點(diǎn),水平軸就是x,與LCD 坐標(biāo)一致,垂直軸需要將點(diǎn)坐標(biāo)變成字節(jié)為單位的坐標(biāo),我們先按習(xí)慣將y 軸64 點(diǎn)從下至上編號0 到63,其中0 到7 點(diǎn)為字節(jié)0,8 到15 點(diǎn)為字節(jié)1,依此類推對應(yīng)8 字節(jié)。
圖3 LCD“點(diǎn)”顯示示例
第一點(diǎn)y 軸為30,應(yīng)該對應(yīng)垂直哪個字節(jié)的哪個比特呢?
實(shí)際30 點(diǎn)應(yīng)該在第4 字節(jié)(24 到31)的Bit 6 上,拿30/8 取整為3,剛好是應(yīng)該跳過的前3 字節(jié)(對應(yīng)0 到23),那么30%8(30 除8 取余數(shù))呢,余數(shù)是6,不是剛好是Bit 位嗎?所以可以這樣將y 值映射到某字節(jié)的某點(diǎn)上,如果y 軸64 點(diǎn)對應(yīng)8 字節(jié)變量Da[n],n從0 到7,則:
da[y/8] = 1 《 (y%8);或da[y》3]=0x01《(y&0x07);后一種算法更優(yōu)。
關(guān)鍵字:LCD12864 數(shù)字示波器
通過總結(jié)規(guī)律,用以上算法可以將任意0 到63 之間的數(shù)據(jù)作為坐標(biāo)描點(diǎn)到對應(yīng)的8 個字節(jié)中,然后將8個字節(jié)全部寫入LCD,則通過剛才算法就會有一點(diǎn)與所給坐標(biāo)一致。
第一點(diǎn):da[30/8] = 1 《 30%8; 即da[3] = 0x40;
第二點(diǎn):da[10/8] = 1 《10%8; 即da[1] = 0x04;
首先給出列顯示子函數(shù),在任意列顯示y 值對應(yīng)點(diǎn)。
{
unsigned char j;
if(x<64) { // 根據(jù)列坐標(biāo)選擇左右半屏
for(j=0;j<8;j++) { // 寫左半屏
lcd_cmd_wr(ORGY+j,0);
lcd_cmd_wr(ORGX+x,0);
lcd_data_wr(da[j],0);
}
}
else {
x-=64;// 坐標(biāo)調(diào)整
for(j=0;j<8;j++) { // 寫右半屏
lcd_cmd_wr(ORGY+j,1);
lcd_cmd_wr(ORGX+x,1);
lcd_data_wr(da[j],1);
}
}
}
有了列顯示函數(shù),LCD 任何坐標(biāo)位置上描點(diǎn)繪圖函數(shù)為:
void lcd_disp(unsigned char x,y) //x 水平坐標(biāo),y 垂直坐標(biāo)
{
unsigned char dat[8];
unsigned char j;
y=63-y;// 使xy 坐標(biāo)符合習(xí)慣
for(j=0;j<8;j++) dat[j] = 0x0;
dat[y/8] |= 0x01《(y&0x07);
lcd_row_wr(x,dat);
}
以上函數(shù)能夠在指定坐標(biāo)(x,y)上描點(diǎn),下面嘗試將ADC 的值采集后送LCD 顯示。再按時間軸x 軸順序?qū)⒉煌瑫r刻采集到的y 值順序?qū)懭隠CD,這是我們就可以在LCD 上看到隨時間變化的電壓曲線了。主程序?yàn)椋?/p>
void main()
{
unsigned char i;
lcd12864_init();
for(i=0;i<128;i++) {
lcd_disp(i,read_adc(0)/16);// 從10bit 映射到6bit,要除16
}
while(1);
}
調(diào)整輸入給ADC 的信號頻率,可以得到滿意的波形圖了,效果如圖4 所示。
圖4 LCD實(shí)時顯示ADC圖
如果你成功做到了這一步,可喜可賀,已經(jīng)掌握了繪圖基礎(chǔ)了,不過程序還要繼續(xù)完善。
三、圖形液晶LCD12864繪圖驅(qū)動設(shè)計(jì)提高
如何在現(xiàn)實(shí)波形顯示的基礎(chǔ)上,同時將定標(biāo)網(wǎng)格也顯示出來呢?
首先我們看一種C 語法“A = 0x05; A |= 0x50;”運(yùn)行以上指令后,A = 0x55 ;也可以說第二個數(shù)據(jù)0x50 是疊加到第一個數(shù)據(jù)上的,我們可以用這種算法把需要顯示的亮點(diǎn)(也就是“1”)按一定的算法疊加在一起,送LCD 顯示,就出現(xiàn)了我們希望的在波形上增加背景網(wǎng)格的效果。
因?yàn)榫W(wǎng)格與水平x 軸是嚴(yán)格關(guān)聯(lián)的,所以我們可以對x 軸數(shù)據(jù)進(jìn)行判斷,有規(guī)律的將邊框和背景格點(diǎn)加入。
改進(jìn)帶背景格的lcd_disp() 函數(shù)。
void lcd_disp(unsigned char x,unsigned char y)
{
unsigned char da[8];
unsigned char j;
y = 63-y;
for(j=1;j<7;j++) da[j] = 0x0;
da[0]=0x01;
da[7]=0x80;
if(x%5==0) {
da[21/8] |= 0x01《(21%8);
da[42/8] |= 0x01《(42%8);
}
if((x==0)||(x==127)) {// 加兩端邊框
for(j=0;j<8;j++) da[j] = 0xff;
}
da[y/8] |= 0x01《(y%8);
lcd_row_wr(x,da);
}
效果如圖5 所示。
圖5 帶網(wǎng)格的LCD顯示圖
四、圖形液晶LCD12864移動游標(biāo)線繪圖驅(qū)動設(shè)計(jì)
聲明控制水平線的變量“unsigned charpointY=0;” 范圍0 到63, 聲明控制垂直線的變量“unsigned char pointX=0;”范圍0 到127。
void lcd_disp(unsigned char x,unsigned char y)
{
unsigned char da[8];
unsigned char j;
y = 63-y;
for(j=1;j<7;j++) da[j] = 0x0;
{// 繪制邊框
da[0]=0x01;
da[7]=0x80;
if((x==0)||(x==127)) {
for(j=0;j<8;j++) da[j] = 0xff;
}
}
if(x%5==0) da[pointY>>3] |= 0x01 《 (pointY&0x07);
// 繪制由變量pointY 控制的水平游標(biāo)線
if(x==pointX) // 繪制由變量pointX 控制的垂直游標(biāo)線
for(j=0;j<64;j++)
if(j%5==0) da[j>>3] |= 0x01 《(j&0x07);
da[y/8] |= 0x01《(y%8); // 繪制信號波形
lcd_row_wr(x,da);
}
運(yùn)行效果如圖6 所示。
圖6 水平垂直移動游標(biāo)線示例
五、圖形液晶LCD12864數(shù)字符號顯示
圖形點(diǎn)陣LCD 顯示數(shù)字,原理是把數(shù)字以點(diǎn)陣的形式取模,再把點(diǎn)陣模寫入特定的LCD 空間即可,首先來看數(shù)字取模,如圖7 所示,對數(shù)字“0”按8×5點(diǎn)取模。
圖7 數(shù)字取模示例圖
縱向看,8 點(diǎn)一列,從上至下對應(yīng)bit0 到bit7,我們用1 表示“亮”,0 表示“暗”,從左至右,依次確定為0111 1100,即0x7c ;1000 0010,即0x82 ;10000010, 即0x82 ;1000 0010, 即0x82 ;0111 1100,即0x7c ;如果我們依次將這5 個字節(jié)寫入LCD 某頁連續(xù)5個地址空間,LCD 上就會顯示“0”。
下面我們把數(shù)字變量在LCD 上動態(tài)顯示,就是數(shù)值變了,顯示跟著變。
字符顯示LCD 驅(qū)動函數(shù),實(shí)現(xiàn)8×n 點(diǎn)陣字符寫入函數(shù)。
void lcd_put_xyns(unsigned char x,y,n,unsigned char *s)
{
unsigned char i;
for(i=0;i
if((x+i)>63) {
lcd_cmd_wr(ORGY+y,1);
lcd_cmd_wr(ORGX+x+i-64,1);
lcd_dat_wr(s[i],1);
}
else {
lcd_cmd_wr(ORGY+y,0);
lcd_cmd_wr(ORGX+x+i,0);
lcd_dat_wr(s[i],0);
}
}
}
參數(shù):“x, y”是坐標(biāo),這里y 是頁坐標(biāo),取值從0 到7,“n”是點(diǎn)陣模字節(jié)數(shù),“*s”是點(diǎn)陣模起始地址。
將字模生成字模表:
unsigned char code number[]={
0x7C,0x82,0x82,0x7C,0x84,0xFE,0x80,0x00,0xCC,0xA2,0x
92,0x8C,0x44,0x92,0x92,0x6C,
0x38,0x24,0xFE,0x20,0x9E,0x92,0x92,0x62,0x7C,0x92,0x9
2,0x64,0x06,0xF2,0x0E,0x02,
0x6C,0x92,0x92,0x6C,0x4C,0x92,0x92,0x7C,0x80,
};
lcd_put_xyns(0,0,4,number+0*4); // 直接顯示字符0
lcd_put_xyns(4,0,4,number+1*4); // 直接顯示字符1
……
tmp=3421; // 以下代碼顯示變量tmp
lcd_put_xyns(80,2,4,number+(tmp/1000)*4);
lcd_put_xyns(84,2,4,number+(tmp/100%10)*4);
lcd_put_xyns(88,2,4,number+(tmp/10%10)*4);
lcd_put_xyns(92,2,4,number+(tmp%10)*4);
關(guān)鍵字:LCD12864 數(shù)字示波器
運(yùn)行效果如圖8 所示。
圖8 LCD顯示數(shù)字圖
基于以上原理,我們還可以將其他字符取模顯示。
最終完成的效果如圖9 所示。
圖9 完整示波器效果圖
六、數(shù)字示波器幾個參數(shù)
1. 數(shù)據(jù)采樣率與時間軸定標(biāo)
數(shù)據(jù)采樣率也就是每秒鐘連續(xù)采集到的數(shù)據(jù)個數(shù),或者說兩個有效數(shù)據(jù)之間的時間間隔。為了在LCD 上還原波形圖,時間軸與采樣率之間要有嚴(yán)格的換算關(guān)系。
為了還原波形圖,采樣率與被測量信號頻率之間有一定的關(guān)聯(lián),不同的采樣率,對應(yīng)不同頻段的信號,高采樣率適合高頻信號,低采樣率適合變化緩慢的低頻信號。
從顯示效果來看,為了在屏幕上再現(xiàn)信號的頻率特征,根據(jù)Nyquist 頻率特征定理,一般要保證信號的每個周期內(nèi)至少有2 點(diǎn),用LCD 再現(xiàn),2 點(diǎn)效果已經(jīng)很差了。最高采樣率一般由ADC 器件決定,這里用的TLC1549 最快只能做到88μs 采集一點(diǎn),按2 點(diǎn)每周期計(jì)算的話,可以做到對5.6K(1000000/88/2) 信號采樣,實(shí)際效果已經(jīng)很差了,1K 以內(nèi)效果最好。
實(shí)際在設(shè)計(jì)采樣率檔位時,最快檔按100μs 每點(diǎn),屏幕每5 點(diǎn)(500μs)一格,第一檔就是0.5ms/div,這一檔位需要連續(xù)采樣,用少量延時控制采樣率為10k,第二檔就是1ms/div,第三檔2ms/div,第四檔5ms/div,第五檔10ms/div,后面四檔分別用定時器控制完成。
2. 電壓軸分檔定標(biāo)
由于這版沒做模擬電路,沒有信號電調(diào)理電路,所謂電壓檔定標(biāo)是純軟件算法實(shí)現(xiàn),屏幕同樣取5 點(diǎn)一格,共10 格,當(dāng)輸入信號從0 到5V 變化時,LCD 剛好滿屏顯示,每格0.5V,另外還設(shè)計(jì)了0.1v/div,0.2v/div,1v/div,2v/div,5v/div。共6 檔。
3. 交流直流切換
功能完善的示波器應(yīng)該從硬件上實(shí)現(xiàn)交直流切換,本版先從軟件上實(shí)現(xiàn)交流直流顯示,設(shè)計(jì)思想是根據(jù)采樣得到的數(shù)據(jù)的最大值和最小值確定交流信號幅度和中值,把直流部分減掉,顯示只顯示交流部分,這就是交流檔,直流檔就是不做去直流處理。
4. 運(yùn)行停止切換
該功能實(shí)現(xiàn)起來很容易,所謂停止,就是停止新的數(shù)據(jù)采集,重復(fù)顯示同一幀數(shù)據(jù),顯示的效果就是波形穩(wěn)定無抖動,便于對信號電壓和頻率進(jìn)行測量。
5. 電壓測量
根據(jù)當(dāng)前電壓檔位(每格表示多少伏),計(jì)算出兩測量線之間的電壓值,在LCD 上顯示。
6. 頻率測量
根據(jù)當(dāng)前時間軸檔位(每格表示多長時間),計(jì)算出兩測量線之間的時間,假定兩測量線之間剛好是一個周期,轉(zhuǎn)換成頻率值在LCD 上顯示。
7. 波形平移
用變量控制波形在LCD 上顯示的相對位置,實(shí)現(xiàn)波形上下左右平移。
8. 幀同步
我們使用的數(shù)字示波器,對有規(guī)律的周期信號能夠穩(wěn)定顯示,要想實(shí)現(xiàn)穩(wěn)定顯示,需要在起始點(diǎn)顯示信號的不同周期的同一點(diǎn),如何做到這點(diǎn)呢?
如圖10 所示,我們對比兩幀數(shù)據(jù),波形起點(diǎn)不在同一點(diǎn),我們沒辦法保證每次數(shù)據(jù)采樣剛好在同一點(diǎn)(硬件觸發(fā)的除外),我需要按一定的規(guī)律,在LCD 上繪圖時,總是從同一點(diǎn)開始,例如過零點(diǎn),這里我們首先用統(tǒng)計(jì)的辦法找出信號的最大值和最小值,計(jì)算出信號中交流部分的中點(diǎn),也就是過零點(diǎn),然后逐點(diǎn)比較,搜尋過零點(diǎn)。
圖10 不同幀數(shù)據(jù)起點(diǎn)不同
程序設(shè)計(jì)思想是逐點(diǎn)比較過濾,我們?nèi)≈兄?,如圖7-1 中第5 點(diǎn)所示,信號從小于中值到大于中值的上升沿為起點(diǎn),不同幀信號可能從1、2、3、4 任何一點(diǎn)開始,這就要用程序判斷把設(shè)定的起點(diǎn)5 以前的數(shù)據(jù)丟掉。
程序是這樣的:
while(da_buffer[i] > dam) if(++i > (DATA_SIZE/2)) break;
while(da_buffer[i] <= dam) if(++i > (DATA_SIZE/2)) break;
while(da_buffer[i] > dam) if(++i > (DATA_SIZE/2)) break;
這里的dam 就是信號交流部分的中值。
程序用幾個while 語句,看上去很呆板,實(shí)際運(yùn)行效果很好喔。第一句把比設(shè)定值大的數(shù)據(jù)過濾,如圖10 中的1、2、3 點(diǎn),如果數(shù)據(jù)是從4 開始的,第一句會直接跳過;第二句把比設(shè)定值小的數(shù)據(jù)跳過,找出過零點(diǎn)的上升沿5 點(diǎn)。
9. 矢量繪圖
前面我們介紹的LCD 描點(diǎn)繪圖不是矢量繪圖,圖形由一系列的“點(diǎn)”組成,在波形繪圖區(qū),每列只有1個點(diǎn),拿方波繪圖來看,描點(diǎn)繪圖圖形如圖12 所示,矢量繪圖在點(diǎn)與點(diǎn)之間填充“直線”,方波繪圖效果如圖11 所示。對比兩種繪圖效果,最好選擇矢量繪圖,LCD 波形顯示效果較點(diǎn)繪圖好很多。
圖11 點(diǎn)繪圖
圖12 矢量繪圖
矢量繪圖需要按時間軸連續(xù)兩點(diǎn)同時考慮,根據(jù)兩點(diǎn)之間的差值補(bǔ)點(diǎn),這就是繪圖程序disp(x, y, l)中參數(shù)l 的作用。
七、數(shù)字示波器程序流程圖設(shè)計(jì)分析
表達(dá)程序設(shè)計(jì)思想的關(guān)鍵就是程序流程圖,下面將重點(diǎn)分析本程序設(shè)計(jì)的幾個關(guān)鍵流程圖。
主程序流程圖如圖13 所示,初始化完成系統(tǒng)初始化設(shè)置,包括全局變量初始化,紅外按鍵中斷、定時器中斷初始化,在主循環(huán)程序中,處理外部按鍵輸入,由于數(shù)據(jù)采集是在定時中斷中完成的,主循環(huán)中需要等待一幀數(shù)據(jù)采集完成后,才能對數(shù)據(jù)進(jìn)行同步處理,包括直流濾波,數(shù)據(jù)到LCD 映射,調(diào)用LCD 顯示函數(shù)完成波形繪圖,一幀數(shù)據(jù)處理完成后,重新開中斷,等待下一幀數(shù)據(jù)采集。
圖13 主程序流程圖圖
LCD 波形顯示流程圖如圖14 所示,LCD 波形顯示區(qū)需要根據(jù)新采集到的數(shù)據(jù)不斷刷新,這里以LCD列為單位,一次繪圖一個(x, y)坐標(biāo)點(diǎn),這里的編程思想是定義7 字節(jié)內(nèi)存變量,與LCD 的第x 列對應(yīng),首先對參數(shù)進(jìn)行檢查,例如y 參數(shù)就不能超過LCD 顯示去最大值,超過需要修正,確保點(diǎn)到點(diǎn)映射不出錯,需要顯示的內(nèi)容除了波形信號外,還有窗口邊框,背景格,游標(biāo)線等,都要同時考慮。這里的l 參數(shù)是兩點(diǎn)之間的間隔,采用矢量顯示,兩點(diǎn)之間用直線矢量繪圖代替描點(diǎn),顯示效果要好很多。
圖14 LCD波形顯示
數(shù)據(jù)采集流程圖如圖15 所示,數(shù)據(jù)采集需要嚴(yán)格等時,而且根據(jù)不同頻率的信號,從低頻到高頻需要用不同的采樣率,所以這里用定時器控制采樣時間間隔。
圖15 數(shù)據(jù)采集流程圖
由于定時器中斷任務(wù)調(diào)用需要額外時間,對于高速中斷是有一定時間間隔限制的,通過計(jì)算,100μs 周期采樣用定時器實(shí)現(xiàn)不了,所以這里定時數(shù)據(jù)采樣分高速和低速兩檔,高速檔一次一幀,低速檔一次一點(diǎn),一幀數(shù)據(jù)采集完成后,回到主程序?qū)?shù)據(jù)進(jìn)行處理。
按鍵任務(wù)流程圖如圖16 所示,由于示波器控制需要很多按鍵,涉及電壓檔位,采樣時間檔位,游標(biāo)線等,這些檔位都由一個全局變量控制,在按鍵任務(wù)模塊,就是根據(jù)有效按鍵,對這些檔位全局變量進(jìn)行加減調(diào)整,變量調(diào)整后,有些效果會在波形顯示刷新中體現(xiàn)出來,還有的需要單獨(dú)刷新顯示。
圖16 按鍵任務(wù)流程圖
八、經(jīng)驗(yàn)總結(jié)與心得體會
STC89C52RC 內(nèi)部擴(kuò)展RAM 只有256 字節(jié),不像數(shù)據(jù)手冊上介紹的512 字節(jié),外部RAM 超量使用會導(dǎo)致意想不到的后果,經(jīng)常莫名其妙的死機(jī)。
窗口波形顯示有兩種模式,逐點(diǎn)顯示(采集一個數(shù)據(jù)馬上刷新顯示)和逐幀顯示(采集完成一幀數(shù)據(jù)后刷新顯示),經(jīng)過測試論證,低頻逐點(diǎn)視覺效果好,高頻沒區(qū)別,但是逐點(diǎn)沒辦法同步。
由于版面限制,這里發(fā)表的只是一個壓縮版,詳細(xì)資料見“大學(xué)生電子實(shí)驗(yàn)室論壇” http://www.ceet.hbnu.edu.cn/bbs/。
初學(xué)者在學(xué)習(xí)他人程序時,不要僅僅對程序代碼下功夫看懂,其實(shí)真正學(xué)習(xí)的捷徑是看懂程序流程圖,學(xué)習(xí)他人的程序設(shè)計(jì)思想,在吸收他人程序設(shè)計(jì)思想的前提下,具體編程實(shí)現(xiàn)特定的功能,程序可以有多種“寫法”。
作為終端輸出顯示裝置,并不需要很高的刷新速度,因?yàn)樗墙o“人”看的,每秒超過10 次的變化,對人眼睛產(chǎn)生不了有用的效果。
我寫這篇教程,特別注重解決問題方法的總結(jié),我認(rèn)為掌握一種解決問題的方法比掌握一種具體的知識要重要得多。