本儀器的程序主要由鍵盤、顯示程序、AT24C01A讀寫程序、信號產生程序等部分組成。以下對部分功能作一些分析。
一、鍵盤程序
本儀器需要調整的數值范圍較大,因此,“增加”和“減少”鍵必須具有快速連加和快速連減的功能,否則調整速度太慢。這種鍵盤可以用多種方法來實現,關鍵在于設計一個正確的程序結構,圖1是一種實現方法的流程圖。
圖1 流程圖
程序工作時,不斷地掃描鍵盤,次掃描到有鍵按下后如常規鍵盤一樣,進行鍵值處理,處理完畢,不等待鍵盤釋放,直接退出鍵盤程序。當又一次執行到鍵盤程序,如果檢測到鍵還被按著,就不再直接去鍵值處理程序,而是將一個計數器加1,直接返回主程序,如此循環,直到計數到一個定值(如500,表示鍵盤程序已被執行了500次),如果鍵還被按著,說明用戶有連加(或連減)要求,程序即將計數器減去一個數值(如30),然后進行鍵值處理。這樣,以后鍵盤程序每執行30次,就執行一次鍵值處進程序,實現了次啟動時間較長,以后快速連續動作的要求。如果檢測到鍵已被釋放,則清除所有標志,將計數器清零,準備下一次按鍵處理。
程序開始時定義了兩個常量:Qdsj和Ljsj,如下所示
const uint Qdsj=500; /*與啟動連加(減)功能的時間有關*/
const uint Ljsj=30; /*與連加(減)的速度有關*/
這兩個常量與次啟動及連加、減的速度有關,具體數值應根據實際情況試驗后確定。下面是部分鍵處理程序,注意其中這兩個變量的使用。
void Key()/*鍵處理*/
{ ……
if(!KeyValue)
{
…無鍵按下,清除一切標志退出
}
if(KeyMark) /*次檢測到按鍵嗎?*/
{ KeyCounter++; /*不是次(KeyMark已是1了)*/
if(Qdsj==KeyCounter) /*連續按著已有Qdsj次了*/
{ KeyCounter-=Ljsj; /*減去Ljsj次*/
KeyProcess(KeyValue,1); /*鍵值處理*/
}
else{ return ; } /* 如果按著還沒有到Qdsj*/
}
else /*次檢測到有鍵按下*/
{ mDelay(10); /*延時10毫秒*/
…再次檢測
if(!KeyValue)
{… 清除一切標志并返回}
}
二、小數點的處理
要在LED數碼管上顯示小數點,可以有兩種選擇,一種方式是在顯示0.1~0.9時用小數顯示,而在顯示1~500時不顯示小數點,這種方式編程略麻煩一些;另一種是使用定點的方式顯示小數點,即不論是在0.1~0.9Hz段,還是1~500Hz段,均在倒數第二位點亮小數點,這種顯示方式比較簡單,本機采用了第二種方式。
通常,用語言編程時,可以用浮點型數據來表示小數,但本程序并沒有這樣來處理。因為單片機的資源有限,而浮點型數據的表達方式與其他數據的表達方式很不相同,無論是存儲還是運算,都相當占用資源,因而在單片機中能不用浮點型數據就盡量不要使用。這里我們將所有的頻率設定值擴大10倍,即所要求的頻率值是0.1~500Hz,但在單片機內部用1~5000來表示。如果頻率設定值小于10,每按一次鍵,頻率設定值就加或減1,如果頻率設定值大于等于10,每按一次按鍵就加或減10。例如,當前頻率設定值為100,按一下“增加”鍵,該值就會變為110,相當于頻率設定值由10變為11;如果當前設定值為9,按一下“減少”鍵,該值變為8,相當于頻率值由0.9變到了0.8。在根據頻率設定值計算定時常數時,只要將被除數擴大10倍即可,程序中是這樣表示的:
ltemp=1000000;
ltemp*=10; //由于plsd被放大了10倍,故被除數也放大10倍
……
在顯示頻率設定值時,點亮倒數第二位的數碼管上的小數點,顯示程序中有這樣的程序行:
if(Counter1==1) //如果當前正在顯示倒數第二位時
{ if(!PlSl) //如果是要求顯示頻率
DispCode=DispCode&0xbf; /*點亮小數點*/
}
由于P0.6與小數點位相連,所以不論待顯示的數是多少,該位被清零后,小數點就能被點亮。要將該位清零,只要將字形碼與0xbf(10111111)相與即可。
三、AT24C01A的讀寫
AT24C01A芯片是具有I2C接口的EEPROM,由于89C51單片機沒有I2C接口,因此,必須用I/O口模擬I2C時序。這里僅提供作者用C語言編寫的接口程序,不對此作更多的介紹。
使用這一接口程序,只要定義好寫常數、讀常數及根據硬件連線定義好三個引腳SDA、SCL和WP,然后直接調用讀、寫函數即可。
#define AddWr 0xa0 /*器件地址選擇及寫標志*/
#define AddRd 0xa1 /*器件地址選擇及讀標志*/
sbit Sda= P3^7; /*串行數據*/
sbit Scl= P3^6; /*串行時鐘*/
sbit WP= P3^5;
接口程序提供了多字節的讀、寫函數,其中讀函數需要用到三個參數:用于存放讀出數據的數組,待讀EEPROM的起始地址,字節數;寫函數也要用到三個參數:用于存放待寫入數據的數組,待寫入EEPROM的起始地址,字節數。下面是這兩個函數的用法參考:
RdFromROM(Number,10,2); //從地址10H開始處讀出2個字節,存入Numbre數組中。
WrToROM(Number,10,2); //將Number數組中的2個字節寫入EEPROM,地址從10H開始
四、信號產生
信號發生由定時中斷0完成,在定時時間到之后,重置定時常數,接著判斷究竟是較高頻率還是較低頻率,分別予以處理,如果是較高頻率,直接取反輸出端口即可返回,如果是較低頻率,則要進行計數,并判斷計數值是否到設定值,如果到了,則取反輸出端口,并清零計數器,然后再返回,這部分程序如下 :
void OutWave() interrupt 1 //定時0中斷用于波形輸出
{ static uint Count; //較低頻率時計數用
TH0=CTH0; //重裝時間常數
TL0=CTL0;
if(HighLow) //如果是較高頻率
{ WaveOut=!WaveOut;
Mczsl++; }
else { Count++;
if(Count>=Plcs)
{ WaveOut=!WaveOut;
Count=0;
Mczsl++;
} } }
其中Mczsl是脈沖輸出個數的計數值。從程序中還可以看出,每次輸出只能得到波形的一半,要么高電平,要么低電平,一個完整的波形需要兩次輸出才能完成。
定時中斷中所設定的定時常數,預設定計數值(Plcs)都由主程序根據頻率設定值計算得到,根據前述原理,對于較低頻率的信號和較高頻率的信號采用兩種不同的方法產生,對于較低頻率的信號,定時常數是一個定值,通過改變預設定計數值來達到定時時間,而對于較高頻率的信號,直接改變定時常數來改變定時時間。為此,在主程序中根據設定值的大小分別處理,如果設定值大于10Hz,那么是較高頻率的算法,只要計算出設定頻率值對應的時間,不難得到待設定值,程序中的處理方法是:
ltemp=1000000;
ltemp*=10; //由于plsd被放大了10倍,故被除數也放大10倍
ltemp/=Plsd; //獲得周期(單位微秒)
ltemp/=2; //獲得定時常數
根據t=1/f,計算定時時間,單位是s,而我們所要求的定時時間單位是μs,因此,首先讓ltemp等于1000000,又由于Plsd變量在單片機內部被放大10倍,故再將該值擴大10倍,然后用ltemp為被除數,去除以Plsd,得到周期數。由于每次定時中斷只能得到一半波形,因此定時數應該是周期數的一半,將周期數除以2,即得到了定時常數。顯然,這里沒有先計算時間到s,然后再換算為μs,其目的也是為了避免小數運算。
當所設定的頻率值小于10Hz時,程序是這樣處理的:
CTH0=(65536-1000)/256;
CTL0=(65536-1000)%256; //否則是在10HZ以下,定時器的定時常數是1ms
HighLow=0;
Plcs=5000/Plsd;
首先將定時常數確定為1000μs,然后將標志位HighLow置0,表示要進行較低頻率的處理,計算出中斷次數。中斷次數這樣來確定:用10000000/Plsd得到周期數,然后用這個值除以2000即得可,這時除以2000的原因同上述分析,即定時時間為1000μs,終得到的的周期是2000μs。