單片機控制的定時計數(shù)繼電器 - 軟件部分
這個 軟件部分按照開發(fā)過程的順序,分為顯示部分、定時部分和外部觸發(fā)信號的檢測部分。
1. 顯示驅(qū)動:
這個電路中使用的3位共陰極7段式LED(LD-3361AS)共11個管腳,經(jīng)過網(wǎng)上搜索,查找了其基本電路連接關系。為了便于在Proteus中進行仿真,因此就利用Proteus元件庫中帶點的1位數(shù)碼管3只,外加一個接口,自己構(gòu)成了這個顯示元件。這樣就方便使用Proteus直接仿真運行。

下一步是顯示字模的組成。因為它的字段a-g與單片機P3口的連接順序并不是按照P3.0-P3.7這樣的順序一一對應,因此,需要按照連接的順序,建立在P3口上顯示每個字符時的字模表。
上圖是一般7段數(shù)碼管的筆畫定義,如果要顯示字符0,則需要a,b,c,d,e,f都要點亮,標記為1. 如果顯示字符1,則需要b、c點亮即可。其它可顯示的字符也是按照這個方法定義。因此,在這個實際電路中,字符的定義如下:
LED顯示控制端口連接及字符字形定義:
/* 顯示字符字模定義表
端口P3: 7 6 5 4 3 2 1 0
7SEG: G B F A C DP D E
字符 字碼定義
0: 0 1 1 1 1 0 1 1 7B
1: 0 1 0 0 1 0 0 0 48
2: 1 1 0 1 0 0 1 1 D3
3: 1 1 0 1 1 0 1 0 DA
4: 1 1 1 0 1 0 0 0 E8
5: 1 0 1 1 1 0 1 0 BA
6: 1 0 1 1 1 0 1 1 BB
7: 0 1 0 1 1 0 0 0 58
8: 1 1 1 1 1 0 1 1 FB
9: 1 1 1 1 1 0 0 0 F8
a: 1 1 1 1 1 0 0 1 F9
b: 1 0 1 0 1 0 1 1 AB
c: 1 0 0 0 0 0 1 1 83
d: 1 1 0 0 1 0 1 1 CB
e: 1 0 1 1 0 0 1 1 B3
f: 1 0 1 1 0 0 0 1 B1
g: 1 1 1 1 1 0 1 0 FA
H: 1 0 1 0 1 0 0 1 A9
I: 0 0 1 0 0 0 0 1 21
J: 0 1 0 0 1 0 1 0 4A
K: 0 0 0 0 0 0 0 0 00
L: 0 0 1 0 0 0 1 1 23
M: 0 0 0 0 0 0 0 0 00
N: 0 0 0 0 0 0 0 0 00
o: 1 0 0 0 1 0 1 1 8B
P: 1 1 1 1 0 0 0 1 F1
Q: 0 0 0 0 0 0 0 0 00
r: 1 0 0 0 0 0 0 1 81
S: 1 0 1 1 1 0 1 0 BA //和5相同
t: 1 0 1 0 0 0 1 1 A3
u: 0 0 0 0 1 0 1 1 0B
v: 0 0 0 0 0 0 0 0 00
W: 0 0 0 0 0 0 0 0 00
X: 0 0 0 0 0 0 0 0 00
y: 1 0 1 0 1 0 1 0 AA
Z: 0 0 0 0 0 0 0 0 00
-: 1 0 0 0 0 0 0 0 80
_: 0 0 0 0 0 0 1 0 02
[: 0 0 1 1 0 0 1 1 33
]:0 1 0 1 1 0 1 0 5A
~: 0 0 0 1 0 1 0 0 14 //表示錯誤字符。
字符表結(jié)束*/
轉(zhuǎn)換成 C語言的標準定義如下:
static unsigned char code CharCode[] = {0x7B, 0x48, 0xD3, 0xDA, 0xE8, 0xBA, 0xBB, 0x58, // 0 - 7
0xFB, 0xF8, 0xF9, 0xAB, 0x83, 0xCB, 0xB3, 0xB1, // 8 - f
0xFA, 0xA9, 0x21, 0x4A, 0x00, 0x23, 0x00, 0x00, // g - N
0x8B, 0xF1, 0x00, 0x81, 0xBA, 0xA3, 0x0B, 0x00, // o - v
0x00, 0x00, 0xAA, 0x00, 0x80, 0x02, 0x33, 0x5A, // w - z,-, _, [, ],
0x14}; //~.
不過,在上表中,有不少是用0x00表示的,表明這個字符用數(shù)碼管無法顯示,因此全部滅掉,變成空白了。如果你要顯示"W,M,N,K,V,X,Z ,....都無法顯示的。因此在使用時,盡量避免用到以上的顯示內(nèi)容。但是常用的如:”Start“,"Hlt","Stop" "End" ”0-9“,”A-F“等都可以正常顯示。
在字模表定義好以后,就要顯示兩類信息,一類是字符串,另一類是整數(shù)數(shù)字。我們通常使用的字符串是用ASCII(American Standard Code for Information Interchange, 美國標準信息交換碼)碼表示的,并不是我們這里的字模。因此需要把常用的ASCII字符串,轉(zhuǎn)換成所需要顯示對應的字模數(shù)據(jù),然后把字模數(shù)據(jù)再放到對應的P3端口上,就可以在數(shù)碼管上顯示出來了。
下面是ASCII字符到字模表的轉(zhuǎn)換過程。
1. 首先定義了一個顯示緩沖區(qū),就是PC里面的顯存。把要顯示的內(nèi)容放到顯存中,然后由顯示程序自動顯示出來就好了。因為這個數(shù)碼管只有3位,無法顯示很多內(nèi)容,因此這里的顯存僅定義為10個字節(jié)。其中8個字節(jié)可以用來顯示內(nèi)容,另外2個字節(jié)用作循環(huán)時的間隔符使用。在這個基礎上,最多顯示的字符長度是8位ASCII碼,對應于數(shù)字,則最大是99999999。超過這個范圍的顯示內(nèi)容,會被處理掉而不予處理。
#define BUFLEN 10 //顯示緩沖區(qū), 顯示起始指針和終止指針,要顯示的數(shù)據(jù)的長度(1-8)。
unsigned char DispBuf[BUFLEN]; //數(shù)據(jù)長度8位,加2位隔離空格
unsigned char DISPLEN; //要顯示的數(shù)據(jù)的長度,1-8.
2. 將單個 ASCII字符轉(zhuǎn)換為LED對應的字模代碼。其中CharCode[]就是前面定義的字模表。
unsigned char CChar(char c)
{
unsigned char i;
if (c >= '0' && c <= '9') i=c-'0';
else if (c >='A' && c<='Z') i=c-'A'+10;
else if (c >='a' && c<='z') i=c-'a'+10;
else if (c == '-') i=36;
else if (c == ' ') i=35; //空格
else if (c == '_') i=37;
else if (c == '[') i=38;
else if (c == ']') i=39;
else i=40; //其它字符全部轉(zhuǎn)換為小數(shù)點
return CharCode[i];
}
這個轉(zhuǎn)換過程比較簡單,就是將ASCII字符與0-9,A-Z, a-z分別比較,然后找到位置值,轉(zhuǎn)換到字模表中的順序值,最后返回字模表該位置對應的字模值即可。因此無法區(qū)分大小寫,識別主要靠想象力了。
3. 顯示字符串的程序是顯示的一個核心部分。因為只能顯示3位,因此當要顯示的長度不超過3位時,靠右顯示。前面顯示為空白(不顯示)。而對于超過3位長度的部分,則是按順序存放在顯存中,由顯示程序自動顯示(顯示程序后面介紹)。
void Disp(char *str)
{
unsigned char i;
DISPLEN =sLen(str);
if (DISPLEN==1) { //長度為1,前面2個空白,最后一個數(shù)值。
DispBuf[2]=CChar(str[0]);
DispBuf[1]=0;
DispBuf[0]=0;
DISPLEN=3;
} else if (DISPLEN ==2) { //長度為2,前面1個空白,后面兩個數(shù)值。
DispBuf[1]=CChar(str[0]);
DispBuf[2]=CChar(str[1]);
DispBuf[0]=0;
DISPLEN=3;
}
else
for (i=0;i<DISPLEN;i++){
DispBuf[i]=CChar(str[i]); //按順序放進顯存。
}
//顯示區(qū)的最大長度是8,最后增加2個0,作為空格,隔離循環(huán)顯示。
for (i=DISPLEN;i<10;i++) DispBuf[i]=0; //如果dCount =0, 則立即開始刷新(Timer0的下次中斷開始就刷新)。
//否則等這次正常顯示完成才刷新。可能會丟失部分顯示內(nèi)容,但是看著正常。
dCount=0;
}
上面用到了一個字符串長度函數(shù)sLen。因為不想使用C標準庫,因此自己定義這個函數(shù),并且限定最大長度是8。
unsigned char sLen(char *str)
{
unsigned char i=0;
while(*str != 0) {i++;str++;}
if (i>8) i=8; //獲取字符串長度,不大于8。大于8時等于8.
return i;
}
4. 對于長整數(shù)的顯示。范圍 0 - 9999,9999。雖然程序內(nèi)部能夠處理無符號長整數(shù)(0-2(32)范圍),但是顯示時只處理這個較小的范圍。
/*--------------------------------------------------------------------------------------------
無符號長整數(shù)顯示程序
限制:只能顯示8位,否則顯示 OFL - 表示 Overflow,溢出。
----------------------------------------------------------------------------------------------*/
void DispLInt(unsigned long m)
{
unsigned long i;
unsigned char j,k;
unsigned char Res[8];
//對于超大數(shù)據(jù)的處理,顯示“---”,閃爍。"OFL"
if (m>99999999) {
DispBuf[0]=0x8B; // O
DispBuf[1]=0xB1; // F
DispBuf[2]=0x23; // L
DISPLEN = 3;
return;
}
//將整數(shù)轉(zhuǎn)換成十進制字符數(shù)組Res[], 如873245轉(zhuǎn)換成{'8','7','3','2','4','5'}。
i=m/10;
j=m%10;
k=0;
Res[k]=j;
while(i>0) {
j=i%10;
i=i/10;
k++;
Res[k]=j;
}
//對于僅1位數(shù)字的顯示,前面填充2個空格,最后一位顯示數(shù)字。
if (m<10) {
DispBuf[0]=0;
DispBuf[1]=0;
DispBuf[2]=CharCode[Res[0]];
DISPLEN=3;
}
//對于2為數(shù)字顯示,前面填1個空格,后面兩位填數(shù)字。
if (m >=10 && m<100) {
DispBuf[0]=0;
DispBuf[1]=CharCode[Res[1]];
DispBuf[2]=CharCode[Res[0]];
DISPLEN=3;
}
//對于3位及以上的數(shù)字處理。按照順序填充在顯存中。
if (m>=100) {
for (j=k;j>0;j--) DispBuf[k-j]=CharCode[Res[j]]; //uchar i-- 會導致死循環(huán),無法停下來的。
DispBuf[k]=CharCode[Res[0]];
DISPLEN=k+1;
}
if (k<3) k=3;
//對于長度超過3位的數(shù)字,需要循環(huán)顯示,并且在需要循環(huán)顯示的緩沖區(qū)中增加兩個空格(0)。
DispBuf[k+1]=0;
DispBuf[k+2]=0;
//是否需要立即刷新?影響顯示的美觀程度。
dCount = 0;
}
至此,要顯示的內(nèi)容存放到顯示緩沖區(qū)的工作已經(jīng)完成。剩下就是如何把顯示緩沖區(qū)的內(nèi)容放到P3端口上,讓LED數(shù)碼管點亮了。這里為了讓程序獨立的工作,使用了Timer0服務中斷作為定時掃描顯示使用。
5. LED的掃描頻率設定。因為使用I/O口直接驅(qū)動LED的管腳進行顯示,因此任何時候,都只可能在3個數(shù)碼管中,僅有一個是點亮的。但是因為人眼的視覺暫留現(xiàn)象,我們看到多次循環(huán)掃描點亮后的數(shù)碼管是一直亮著的。根據(jù)電影和電視的顯示頻率來看,一般每個數(shù)碼管最少掃描頻率在24次/秒以上時,才能感覺到一直亮著。掃描頻率再低時,就會感覺到數(shù)字在閃爍。
另外,因為我們要顯示的字符最多是8位,因此每3個字符顯示一段時間后,必須移動一下,流水形式顯示下一位,這個流水的速度不能太快,太快了一樣會感到字符閃爍。因此,根據(jù)感受,這個移動的頻率在1.5-2次/秒左右。這就是下面確定掃描程序的頻率的依據(jù)。
為了精確確定掃描的周期,使用系統(tǒng)定時器Timer0作為基準。
另外,因考慮到后期還會使用Timer0復用做UART通訊時的波特率發(fā)生器,因此選擇系統(tǒng)的主時鐘在11.0592MHz或大于它的0.5倍的整倍數(shù)。這樣可以較為精準的產(chǎn)生115200Baud的通訊速率。這里選擇基準時鐘為11.0592MHz(因STC15F204EA使用內(nèi)部RC振蕩器工作,會有0.2%以內(nèi)的實際誤差)。
Timer0的初始化如下:
/*------------------------------------------------
定時器中斷程序/用于顯示
------------------------------------------------*/
void Init_Timer0(void)
{
TMOD |= 0x01; //定時模式1,16位定時方式
TH0=0xEE; //定時器初值: 11.0592MHz晶振, 中斷周期5ms
TL0=0x04;
PT0=1; //Timer0中斷優(yōu)先
EA=1; //總中斷打開
ET0=1; //定時中斷打開
TR0=1; //定時開始
cp=0;
dispPtr =0; //顯示位置指針清零
TIMER0INIT=1; //TIMER0初始化標志,其它地方用到。
}
/*-------------------------------------------------------------------------
顯示服務中斷:TIMER0_ISR
說明: LED的刷新頻率在30Hz以上時,沒有閃爍感。 此時中斷的刷屏頻率要大于90Hz。
在時鐘M=11.0592MHz時,TH0=0xEC,TL0=0x04即可。
--------------------------------------------------------------------------*/
void Timer0_isr(void) interrupt 1
{
unsigned char t;
unsigned char i;
TH0=0xEE; //0xFD,0x57 :32.954MHz --->2.00527KHz
TL0=0x04; //0xFA,0xB0 :32.954MHz --->1.00686KHz
cp++; //cp為刷新周期計數(shù),每中斷幾次,刷新一次。如果系統(tǒng)主頻很高,此時的刷新頻率數(shù)值可以調(diào)整到更大。
if (cp == LED_REFRESH_FREQ) { //5ms * 4 = 20ms LED_REFRESH_FREQ 拖過調(diào)整顯示延遲常數(shù),
cp =0;
dispPtr++; //顯示指針指向下一個要顯示的字段
if (dispPtr ==1) { //顯示第一位,先關閉上一個顯示位,更新要顯示的數(shù)值,然后打開要顯示的字段。實際顯示時間等于2次中斷的間隔。
DIG_3=LED_OFF;
P3=DispBuf[dispPtr-1];
DIG_1=LED_ON;
}
if (dispPtr ==2) {//顯示第二位,做法同上。
DIG_1=LED_OFF;
P3=DispBuf[dispPtr-1];
DIG_2=LED_ON;
}
if (dispPtr ==3) {//顯示第三位,完成后把顯示指針dispPtr變成0,表示一次顯示完成。
DIG_2=LED_OFF;
P3=DispBuf[dispPtr-1];
DIG_3=LED_ON;
dispPtr=0;
}
dCount++; //是否要做流水移動的計數(shù)器。這里每刷新50次,移動一次。移動頻率=(單字刷新頻率*3)/50,約1.8-2.0Hz
if (dCount>50 && DISPLEN>3) { //循環(huán)次數(shù),每個位置顯示相同次數(shù)后,開始更新下一輪字符。字符移位開始。
//在兩次循環(huán)中間插入段行識別2個空格或上劃線? 否則首尾相連無法識別。
dCount=0; //流水移動計數(shù)器清零
t=DispBuf[0]; //保存顯存中的第一位
for (i=0; i<DISPLEN+1;i++)
DispBuf[i]=DispBuf[i+1]; //循環(huán)引動第2位到DISPLEN+1位,向前移動一位
DispBuf[i]=t; //將第一位補在循環(huán)隊列的尾部
}
}
}
經(jīng)過以上動作,數(shù)據(jù)就可以正常在LED上顯示了。
6. Timer0參數(shù)設置,主頻與中斷頻率的測量值。
對于STC15F204EA的Timer0, 其它主頻下可參照調(diào)整。具體的測試數(shù)據(jù)如下(使用P1.7端口電平翻轉(zhuǎn)進行測量的實際頻率,非計算值):當TH0=0xEE, TL=0x11時,主頻變化與P1.7的端口翻轉(zhuǎn)頻率的關系如下:
32.958MHz 輸出:298.82Hz
5.995MHz 輸出:54.375Hz
12.011MHz 輸出:108.955Hz
19.966MHz 輸出:181.082Hz
23.980MHz 輸出:217.476Hz
29.988MHz 輸出:271.944Hz
當系統(tǒng)的主頻固定設置到33MHz(實際值32.950MHz)時,定時參數(shù)設置對輸出頻率的變化:
TH0=0xFF, TL0=0x01 :5.307KHz
TH0=0xEF TL0=0xA0 :327.236
TH0=0xF6 TL0=0xA0 :571.172
TH0=0xF6 TL0=0x00 :535.521
TH0=0xF5 TL0=0x00 :486.952
TH0=0xF5 TL0=0x40 :498.271
TH0=0xF5 TL0=0x48 :499.761
TH0=0xF5 TL0=0x4A :500.080
TH0=0xCA TL0=0x62 :100.006
TH0=0xE5 TL0=0x33 :200.000
TH0=0xEA TL0=0x90 :250.040
TH0=0xEE TL0=0x23 :300.040
TH0=0xF2 TL0=0x9C :400.043
在主頻固定設置在M=11.0592MHz, 并且同步打開Timer1 ON(4KHz速率中斷,輕負載)時,TH0,TL0與輸出頻率的關系如下:
TH0, TL0, 翻轉(zhuǎn)頻率
0xF2, 0x9C, 134.267Hz
0xEE, 0x04, 100.003Hz LED_REFRESH_FREQ=2時,LED閃爍,LED_REFRESH_FREQ=1, 則不再閃爍。
0xE2, 0x08, 60.016, LED_REFRESH_FREQ=1,輕微閃爍。
0xE8, 0x03, 75.000, LED_REFRESH_FREQ=1.
0xEC, 0x04, 89.904, LED_REFRESH_FREQ=1, 此時單個字的刷新周期為29.994Hz(實際測試值)。
這些測量數(shù)據(jù)和計算值之間有一定的差距。特別是當顯示的負載增大時,點亮LED和不點亮LED的中斷速率是不一樣的。實際使用時,如果需要準確的數(shù)據(jù),最好用示波器測量實際值為準。
7. LED的顯示亮度問題。
對于普通的51單片機,無法直接驅(qū)動共陰極LED數(shù)碼管直接顯示。因此需要將端口設置為推挽輸出模式。
//關閉所有顯示。
LED_Init();
//STC單片機特殊寄存器 PxM0,PxM1設置工作狀態(tài)。 M1M0=01 推挽式輸出,可以直接點亮LED。但是需要增加470ohm分壓電阻限流。
P3M0=0xFF;
P3M1=0x0;
(因QQ文字編輯器異常,無法編寫下去了。這一節(jié)就此結(jié)束。)
作者: admin 時間: 2015-10-29 12:30
制作非常精美,介紹十分詳細,當然要加分. 51黑有你更精彩。!
作者: sanchao123007 時間: 2015-11-2 11:22
問一下:樓主是如何用Proteus仿真STC的單片機,如是元件庫更新,發(fā)一下sanchao123007@163.com
作者: ludaijin 時間: 2015-11-16 11:35
diy的精神讓人欽佩
作者: ljh67 時間: 2016-2-19 08:11
有沒有hex文件
作者: raymondau 時間: 2017-4-3 14:57
樓主能發(fā)一下完整的程序嗎?謝謝
歡迎光臨 (http://www.torrancerestoration.com/bbs/) |
Powered by Discuz! X3.1 |