來自:中國計(jì)量測(cè)控網(wǎng) 本文針對(duì)LCD12864 特性,完成了數(shù)字示波器顯示必須的繪圖驅(qū)動(dòng)程序設(shè)計(jì),這個(gè)教程定位給初學(xué)者使用,立足從簡(jiǎn)單到復(fù)雜一步一步介紹設(shè)計(jì)過程,甚至是調(diào)試的過程,還包括一些經(jīng)驗(yàn)總結(jié),特別是提供了完整的keil 工程附件。希望讀者立足示波器項(xiàng)目,學(xué)到更多軟硬件設(shè)計(jì)經(jīng)驗(yàn)技巧。 一、簡(jiǎn)易數(shù)字示波器原理 數(shù)字示波器基本原理可以簡(jiǎn)單理解為:數(shù)據(jù)采集+ 圖形顯示,該過程循環(huán)進(jìn)行,如圖1 所示。 圖1 簡(jiǎn)易數(shù)字示波器流程圖 LCD 圖形顯示需要根據(jù)LCD 特性設(shè)計(jì),不同LCD驅(qū)動(dòng)程序不同,本篇將結(jié)合不帶字庫的LCD12864 設(shè)計(jì)顯示程序。 二、圖形液晶LCD12864繪圖驅(qū)動(dòng)設(shè)計(jì)基礎(chǔ) 關(guān)于LCD 的硬件接口電路,在其他教程中有詳細(xì)介紹,涉及單片機(jī)總線知識(shí)和CPLD 內(nèi)部電路,需要認(rèn)真學(xué)習(xí),這里借助現(xiàn)成的驅(qū)動(dòng)函數(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ù)讀出 后面所有對(duì)LCD 的編程操作都是基于以上接口定義進(jìn)行的各種讀寫操作。 首先來看LCD12864 的點(diǎn)陣結(jié)構(gòu)圖,如圖2 所示。 圖2 LCD點(diǎn)陣分布結(jié)構(gòu)圖 此LCD 屏由水平128 列,垂直64 行組成。水平128 列分左右各64 列兩個(gè)半屏構(gòu)成。垂直64 行又分8 頁,每頁8 行(1 列8 點(diǎn)剛好1 字節(jié))。程序每次對(duì)LCD 的繪圖操作就是以最小單位1 字節(jié)進(jìn)行操作的。 理解這點(diǎn)至關(guān)重要。也就是每次只能針對(duì)8 點(diǎn)進(jìn)行操作,而不是1 點(diǎn)進(jìn)行操作。左右屏由單獨(dú)地址線控制(前面的接口定義就是分左右屏定義的)。實(shí)際打點(diǎn)只需往指定“位置”寫入數(shù)據(jù),“1”亮,“0”暗。 LCD 驅(qū)動(dòng)忙檢測(cè)函數(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; // 超時(shí)跳出 } while ((tmp|0x7f)==0xff); //bit7 為1 則表示LCD 內(nèi)部執(zhí)行命令,處于“忙”狀態(tài) } 對(duì)LCD 進(jìn)行讀寫操作時(shí),需要進(jìn)行“忙”檢測(cè),LCD 內(nèi)部也是由控制器來完成一系列刷屏操作的,執(zhí)行各種操作都是需要一定的時(shí)間,也就是說不是任何時(shí)候外部控制器都可以對(duì)LCD 發(fā)操作指令的,只有LCD為空閑狀態(tài)時(shí)才可以操作,忙檢測(cè)就是循環(huán)讀取LCD狀態(tài)標(biāo)志位,判斷是否空閑,關(guān)于命令的細(xì)節(jié)請(qǐng)參考數(shù)據(jù)手冊(cè)。 命令寫入函數(shù)void lcd_cmd_wr(unsigned char cmd,right)。 void lcd_cmd_wr(unsigned char cmd, right) { loop_lcd12864_is_busy(right); // 忙檢測(cè) 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() 兩個(gè)函數(shù)分別是給LCD 寫命令和寫數(shù)據(jù)函數(shù),通過寫命令函數(shù)設(shè)定地址。每個(gè)函數(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ù)對(duì)LCD 所有點(diǎn)陣寫0,完成一次清屏操作。這里的ORGY,PRGX 是設(shè)定光標(biāo)的命令,光標(biāo)指向(0,0)字節(jié),是一個(gè)固定值。實(shí)際在執(zhí)行數(shù)據(jù)寫入的時(shí),x 坐標(biāo)范圍從0 到63,在連續(xù)寫入過程中能夠?qū)崿F(xiàn)自動(dòng)加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)屏幕滾動(dòng)顯示效果 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í)行清屏,非必須操作 }
|
效果如圖5 所示。 圖5 帶網(wǎng)格的LCD顯示圖 四、圖形液晶LCD12864移動(dòng)游標(biāo)線繪圖驅(qū)動(dòng)設(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); // 繪制信號(hào)波形 lcd_row_wr(x,da); } 運(yùn)行效果如圖6 所示。 圖6 水平垂直移動(dòng)游標(biāo)線示例 五、圖形液晶LCD12864數(shù)字符號(hào)顯示 圖形點(diǎn)陣LCD 顯示數(shù)字,原理是把數(shù)字以點(diǎn)陣的形式取模,再把點(diǎn)陣模寫入特定的LCD 空間即可,首先來看數(shù)字取模,如圖7 所示,對(duì)數(shù)字“0”按8×5點(diǎn)取模。 圖7 數(shù)字取模示例圖 縱向看,8 點(diǎn)一列,從上至下對(duì)應(yīng)bit0 到bit7,我們用1 表示“亮”,0 表示“暗”,從左至右,依次確定為0111 1100,即0x7c ;1000 0010,即0x82 ;10000010, 即0x82 ;1000 0010, 即0x82 ;0111 1100,即0x7c ;如果我們依次將這5 個(gè)字節(jié)寫入LCD 某頁連續(xù)5個(gè)地址空間,LCD 上就會(huì)顯示“0”。 下面我們把數(shù)字變量在LCD 上動(dòng)態(tài)顯示,就是數(shù)值變了,顯示跟著變。 字符顯示LCD 驅(qū)動(dòng)函數(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,1); } else { lcd_cmd_wr(ORGY+y,0); lcd_cmd_wr(ORGX+x+i,0); lcd_dat_wr(s,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); 運(yùn)行效果如圖8 所示。 圖8 LCD顯示數(shù)字圖 基于以上原理,我們還可以將其他字符取模顯示。 最終完成的效果如圖9 所示。 六、數(shù)字示波器幾個(gè)參數(shù) 1. 數(shù)據(jù)采樣率與時(shí)間軸定標(biāo) 數(shù)據(jù)采樣率也就是每秒鐘連續(xù)采集到的數(shù)據(jù)個(gè)數(shù),或者說兩個(gè)有效數(shù)據(jù)之間的時(shí)間間隔。為了在LCD 上還原波形圖,時(shí)間軸與采樣率之間要有嚴(yán)格的換算關(guān)系。 為了還原波形圖,采樣率與被測(cè)量信號(hào)頻率之間有一定的關(guān)聯(lián),不同的采樣率,對(duì)應(yīng)不同頻段的信號(hào),高采樣率適合高頻信號(hào),低采樣率適合變化緩慢的低頻信號(hào)。 從顯示效果來看,為了在屏幕上再現(xiàn)信號(hào)的頻率特征,根據(jù)Nyquist 頻率特征定理,一般要保證信號(hào)的每個(gè)周期內(nèi)至少有2 點(diǎn),用LCD 再現(xiàn),2 點(diǎn)效果已經(jīng)很差了。最高采樣率一般由ADC 器件決定,這里用的TLC1549 最快只能做到88μs 采集一點(diǎn),按2 點(diǎn)每周期計(jì)算的話,可以做到對(duì)5.6K(1000000/88/2) 信號(hào)采樣,實(shí)際效果已經(jīng)很差了,1K 以內(nèi)效果最好。 實(shí)際在設(shè)計(jì)采樣率檔位時(shí),最快檔按100μs 每點(diǎn),屏幕每5 點(diǎn)(500μs)一格,第一檔就是0.5ms/div,這一檔位需要連續(xù)采樣,用少量延時(shí)控制采樣率為10k,第二檔就是1ms/div,第三檔2ms/div,第四檔5ms/div,第五檔10ms/div,后面四檔分別用定時(shí)器控制完成。 2. 電壓軸分檔定標(biāo) 由于這版沒做模擬電路,沒有信號(hào)電調(diào)理電路,所謂電壓檔定標(biāo)是純軟件算法實(shí)現(xiàn),屏幕同樣取5 點(diǎn)一格,共10 格,當(dāng)輸入信號(hào)從0 到5V 變化時(shí),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ù)的最大值和最小值確定交流信號(hào)幅度和中值,把直流部分減掉,顯示只顯示交流部分,這就是交流檔,直流檔就是不做去直流處理。 4. 運(yùn)行停止切換 該功能實(shí)現(xiàn)起來很容易,所謂停止,就是停止新的數(shù)據(jù)采集,重復(fù)顯示同一幀數(shù)據(jù),顯示的效果就是波形穩(wěn)定無抖動(dòng),便于對(duì)信號(hào)電壓和頻率進(jìn)行測(cè)量。 5. 電壓測(cè)量 根據(jù)當(dāng)前電壓檔位(每格表示多少伏),計(jì)算出兩測(cè)量線之間的電壓值,在LCD 上顯示。 6. 頻率測(cè)量 根據(jù)當(dāng)前時(shí)間軸檔位(每格表示多長時(shí)間),計(jì)算出兩測(cè)量線之間的時(shí)間,假定兩測(cè)量線之間剛好是一個(gè)周期,轉(zhuǎn)換成頻率值在LCD 上顯示。 7. 波形平移 用變量控制波形在LCD 上顯示的相對(duì)位置,實(shí)現(xiàn)波形上下左右平移。 8. 幀同步 我們使用的數(shù)字示波器,對(duì)有規(guī)律的周期信號(hào)能夠穩(wěn)定顯示,要想實(shí)現(xiàn)穩(wěn)定顯示,需要在起始點(diǎn)顯示信號(hào)的不同周期的同一點(diǎn),如何做到這點(diǎn)呢? |
如圖10 所示,我們對(duì)比兩幀數(shù)據(jù),波形起點(diǎn)不在同一點(diǎn),我們沒辦法保證每次數(shù)據(jù)采樣剛好在同一點(diǎn)(硬件觸發(fā)的除外),我需要按一定的規(guī)律,在LCD 上繪圖時(shí),總是從同一點(diǎn)開始,例如過零點(diǎn),這里我們首先用統(tǒng)計(jì)的辦法找出信號(hào)的最大值和最小值,計(jì)算出信號(hào)中交流部分的中點(diǎn),也就是過零點(diǎn),然后逐點(diǎn)比較,搜尋過零點(diǎn)。 圖10 不同幀數(shù)據(jù)起點(diǎn)不同 程序設(shè)計(jì)思想是逐點(diǎn)比較過濾,我們?nèi)≈兄,如圖7-1 中第5 點(diǎn)所示,信號(hào)從小于中值到大于中值的上升沿為起點(diǎn),不同幀信號(hào)可能從1、2、3、4 任何一點(diǎn)開始,這就要用程序判斷把設(shè)定的起點(diǎn)5 以前的數(shù)據(jù)丟掉。 程序是這樣的: while(da_buffer > dam) if(++i > (DATA_SIZE/2)) break; while(da_buffer <= dam) if(++i > (DATA_SIZE/2)) break; while(da_buffer > dam) if(++i > (DATA_SIZE/2)) break; 這里的dam 就是信號(hào)交流部分的中值。 程序用幾個(gè)while 語句,看上去很呆板,實(shí)際運(yùn)行效果很好喔。第一句把比設(shè)定值大的數(shù)據(jù)過濾,如圖10 中的1、2、3 點(diǎn),如果數(shù)據(jù)是從4 開始的,第一句會(huì)直接跳過;第二句把比設(shè)定值小的數(shù)據(jù)跳過,找出過零點(diǎn)的上升沿5 點(diǎn)。 9. 矢量繪圖 前面我們介紹的LCD 描點(diǎn)繪圖不是矢量繪圖,圖形由一系列的“點(diǎn)”組成,在波形繪圖區(qū),每列只有1個(gè)點(diǎn),拿方波繪圖來看,描點(diǎn)繪圖圖形如圖12 所示,矢量繪圖在點(diǎn)與點(diǎn)之間填充“直線”,方波繪圖效果如圖11 所示。對(duì)比兩種繪圖效果,最好選擇矢量繪圖,LCD 波形顯示效果較點(diǎn)繪圖好很多。 圖11 點(diǎn)繪圖 圖12 矢量繪圖 矢量繪圖需要按時(shí)間軸連續(xù)兩點(diǎn)同時(shí)考慮,根據(jù)兩點(diǎn)之間的差值補(bǔ)點(diǎn),這就是繪圖程序disp(x, y, l)中參數(shù)l 的作用。 七、數(shù)字示波器程序流程圖設(shè)計(jì)分析 表達(dá)程序設(shè)計(jì)思想的關(guān)鍵就是程序流程圖,下面將重點(diǎn)分析本程序設(shè)計(jì)的幾個(gè)關(guān)鍵流程圖。 主程序流程圖如圖13 所示,初始化完成系統(tǒng)初始化設(shè)置,包括全局變量初始化,紅外按鍵中斷、定時(shí)器中斷初始化,在主循環(huán)程序中,處理外部按鍵輸入,由于數(shù)據(jù)采集是在定時(shí)中斷中完成的,主循環(huán)中需要等待一幀數(shù)據(jù)采集完成后,才能對(duì)數(shù)據(jù)進(jìn)行同步處理,包括直流濾波,數(shù)據(jù)到LCD 映射,調(diào)用LCD 顯示函數(shù)完成波形繪圖,一幀數(shù)據(jù)處理完成后,重新開中斷,等待下一幀數(shù)據(jù)采集。 圖13 主程序流程圖圖 LCD 波形顯示流程圖如圖14 所示,LCD 波形顯示區(qū)需要根據(jù)新采集到的數(shù)據(jù)不斷刷新,這里以LCD列為單位,一次繪圖一個(gè)(x, y)坐標(biāo)點(diǎn),這里的編程思想是定義7 字節(jié)內(nèi)存變量,與LCD 的第x 列對(duì)應(yīng),首先對(duì)參數(shù)進(jìn)行檢查,例如y 參數(shù)就不能超過LCD 顯示去最大值,超過需要修正,確保點(diǎn)到點(diǎn)映射不出錯(cuò),需要顯示的內(nèi)容除了波形信號(hào)外,還有窗口邊框,背景格,游標(biāo)線等,都要同時(shí)考慮。這里的l 參數(shù)是兩點(diǎn)之間的間隔,采用矢量顯示,兩點(diǎn)之間用直線矢量繪圖代替描點(diǎn),顯示效果要好很多。 圖14 LCD波形顯示 數(shù)據(jù)采集流程圖如圖15 所示,數(shù)據(jù)采集需要嚴(yán)格等時(shí),而且根據(jù)不同頻率的信號(hào),從低頻到高頻需要用不同的采樣率,所以這里用定時(shí)器控制采樣時(shí)間間隔。 圖15 數(shù)據(jù)采集流程圖 由于定時(shí)器中斷任務(wù)調(diào)用需要額外時(shí)間,對(duì)于高速中斷是有一定時(shí)間間隔限制的,通過計(jì)算,100μs 周期采樣用定時(shí)器實(shí)現(xiàn)不了,所以這里定時(shí)數(shù)據(jù)采樣分高速和低速兩檔,高速檔一次一幀,低速檔一次一點(diǎn),一幀數(shù)據(jù)采集完成后,回到主程序?qū)?shù)據(jù)進(jìn)行處理。 按鍵任務(wù)流程圖如圖16 所示,由于示波器控制需要很多按鍵,涉及電壓檔位,采樣時(shí)間檔位,游標(biāo)線等,這些檔位都由一個(gè)全局變量控制,在按鍵任務(wù)模塊,就是根據(jù)有效按鍵,對(duì)這些檔位全局變量進(jìn)行加減調(diào)整,變量調(diào)整后,有些效果會(huì)在波形顯示刷新中體現(xiàn)出來,還有的需要單獨(dú)刷新顯示。 圖16 按鍵任務(wù)流程圖 八、經(jīng)驗(yàn)總結(jié)與心得體會(huì) STC89C52RC 內(nèi)部擴(kuò)展RAM 只有256 字節(jié),不像數(shù)據(jù)手冊(cè)上介紹的512 字節(jié),外部RAM 超量使用會(huì)導(dǎo)致意想不到的后果,經(jīng)常莫名其妙的死機(jī)。 窗口波形顯示有兩種模式,逐點(diǎn)顯示(采集一個(gè)數(shù)據(jù)馬上刷新顯示)和逐幀顯示(采集完成一幀數(shù)據(jù)后刷新顯示),經(jīng)過測(cè)試論證,低頻逐點(diǎn)視覺效果好,高頻沒區(qū)別,但是逐點(diǎn)沒辦法同步。 初學(xué)者在學(xué)習(xí)他人程序時(shí),不要僅僅對(duì)程序代碼下功夫看懂,其實(shí)真正學(xué)習(xí)的捷徑是看懂程序流程圖,學(xué)習(xí)他人的程序設(shè)計(jì)思想,在吸收他人程序設(shè)計(jì)思想的前提下,具體編程實(shí)現(xiàn)特定的功能,程序可以有多種“寫法”。 作為終端輸出顯示裝置,并不需要很高的刷新速度,因?yàn)樗墙o“人”看的,每秒超過10 次的變化,對(duì)人眼睛產(chǎn)生不了有用的效果。 |