The post is intened to be used as a record with the STC-89C52RC development board.
<!-- more -->
環境介紹:硬件為KST-51 開發板 v1.3.2,編譯環境KeilV5 9.54,程序燒錄工具 STC-ISP_v6.94L
資料: [KST-51_DataSheet] [STC89C52RC-en]
## 寄存器
### 定時器:
**八位重裝載模式** 定時器定時時間計算:CNT(255 - TL1) = 921600 * T(s)
同時在八位重裝載模式下,TL1 從零開始計數,TL1為八位,也就是累積至255,當255 + 1 時 TF1 = 1,同時需要注意 TF1 這里需要手動清零。
對于**查詢法/輪詢法**實現定時器,以下是示例程序
// Realize the 10ms LED repeatedly light, e.g. LED = 1 sustain 10ms, later LED = 0 sustain 10ms continuously repeating.
// The the procedure, the LED signal is at a frequency of 50 hertz.
// Implementation of timer using query method
#include <reg52.h>
sbit LED = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
void main(void)
{
unsigned int CNT = 0;
ADDR3 = 1;
ADDR2 = 1;
ADDR1 = 1;
ADDR0 = 0;
ENLED = 0;
TH1 = 0xB7;
TL1 = 0xB7;
TMOD = 0x20; //0010 0000
LED = 1;
TR1 = 1;
while(1) {
if (TF1){
if(++CNT == 128) { //TIMING_LED(s) = (255 - TL1) * CNT * 12 / 11059200
LED = ~LED; //CNT(255 - TL1) = 921600 * T(s)
CNT = 0;
}
TF1 = 0;
}
}
}
```
上面這個程序通過Proteus 仿真出來測得的頻率大約是50hertz,但是會出現**49hertz**的情況,如下圖所示。因本人才疏學淺,暫未分析影響該結果真正原因。如讀者對此問題有自己的想法,可以聯系通過文章開頭的郵箱聯系我,感謝讀者的反饋,還請讀者多多擔待!

**中斷法實現**
通過中斷可以實現硬件自動通知CPU,極大減少CPU占用,同時使得響應延遲固定,避免不確定的響應延遲
以下是示例程序
**硬件自動翻轉**
**軟件 + 硬件(定時器觸發 + 外設聯動)**
**硬件介紹**
PNP:
- [三極管典型開關電路]
- 三極管是CCCS器件(電流控制電流源),添加**基極限流電阻**防止損壞MCU管腳,
- 第二,三極管基極輸入阻抗(基極未被驅動,也就是懸空/高阻態時的等效對地阻抗)較高,當上電初始化容易受到噪聲干擾,因此需要確保三極管截止,避免不必要的動作。對于NPN三極管,當基極高電平時導通,因此需要**基極下拉電阻**,確保三極管可靠截止。而PNP三極管,需要加**基極上拉電阻**。因為三極管內部存在電荷存儲效應,基區殘留的電荷需要釋放,并聯在B和E之間的電阻提供了放電回路,因此基極上/下拉電阻還可以縮短管段時間,提高開關速度。
- 第三,集電極電阻Rc用于限流,限制流過負載的電流,防止過流損壞。集電極電阻Rc還可以實現電流-電壓轉換,可以應用到放大器或邏輯反相器,Vout = Vcc - Ic * Rc,集電極電阻壓降隨Ic 增大而增大,成正比。
- 這里補充一下,為什么高基極輸入阻抗會導致易受噪聲干擾。當BE結未正偏導通時,基極對GND幾乎沒有直流通路,有極小的漏電流,此時該節點的等效阻抗可達M Ohms甚至更高。這時外界耦合的干擾電流即使只有1nA,經過10M Ohms的懸空阻抗也會產生10mV的電壓波動。而三極管導通后,BE結正偏,動態阻抗很低,不會易受噪聲干擾
有關Proteus 仿真反思,我以為在Proteus 里對于PNP 不需要上拉電阻了,但是在仿真實現中遇到了只有仿真會出現的問題,關于詳細信息,您可以點擊以下兩個鏈接獲取詳細信息,[Keil-code.pdf],[Proteus.pdf]。該工程通過配置74HC138,選中六位八段數碼管。您可以在代碼中觀察到,我只配置了LEDS0 = 0,也就是只指定了Q2 PNP導通,但是結果Q3,Q5卻也導通了。這里的問題是沒有上拉電阻,原因在前面提到了,盡管是在仿真中實現。
解決方案如下
[proteus-resoltion.pdf]
[proteus工程文件]
這兩份文件可以實現六位定時器功能,方法是通過定時器中斷實現動態掃描。代碼這里部分參考**《手把手教你學51單片機-C語言版》P68**
#include <reg52.h>
typedef unsigned char U8;
typedef unsigned int U16;
#define T0_RELOAD_HIGH 0xFC
#define T0_RELOAD_LOW 0x67
#define T0_COUNT_1S 1000
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
U8 code LedChar[] = {
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E
};
U8 data LedBuff[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
U8 data sec_buf[6] = {0};
U16 data cnt = 0;
bit flag1s = 0;
void SecAddOne(void) {
U8 k = 0;
sec_buf[0]++;
while (sec_buf[k] >= 10) {
sec_buf[k] = 0;
k++;
if (k < 6) sec_buf[k]++;
}
LedBuff[0] = LedChar[sec_buf[0]];
LedBuff[1] = LedChar[sec_buf[1]];
LedBuff[2] = LedChar[sec_buf[2]];
LedBuff[3] = LedChar[sec_buf[3]];
LedBuff[4] = LedChar[sec_buf[4]];
LedBuff[5] = LedChar[sec_buf[5]];
}
void main() {
ENLED = 0;
ADDR3 = 1;
TMOD = 0x01;
TH0 = T0_RELOAD_HIGH;
TL0 = T0_RELOAD_LOW;
ET0 = 1;
EA = 1;
TR0 = 1;
while(1) {
if (flag1s) {
flag1s = 0;
SecAddOne();
}
}
}
void Timer0_ISR() interrupt 1 {
static U8 i = 0; // Here, the"Static" keyword is used to archieve the effect of being assigned only once.
TH0 = T0_RELOAD_HIGH;
TL0 = T0_RELOAD_LOW;
cnt++;
if (cnt >= T0_COUNT_1S) {
cnt = 0;
flag1s = 1;
}
P0 = 0xFF; // 消隱
P1 = (P1 & 0xF8) | i; // 一次性更新位選
P0 = LedBuff[i ];
i++;
if (i >= 6) i = 0;
}
在該代碼中使用了16位定時器模式,這里要注意因為使用的 IC 是stc89c52rc,機器周期是12/11059200。通過 ISR 實現1ms 定時,當累積到1s,也就是1000次 ISR,置位flag1s。在main函數中調用SecAddOne函數,該函數的作用是每1s在個位進位1次,也就是個位加一。在達到999999+1,根據SecAddOne 函數中的if (k < 6) sec_buf[k]++; 這一句配合while(sec_buf[k] >= 10)實現000000的顯示。
**特殊篇章**
在Proteus中的數碼管仿真顯示,是比較麻煩的事情,跟實物還是有所不同的。舉個例子說,當PNP三極管截止時,你可以在集電極測得3.33V的電壓,此時發射極電壓是+5V,基極電壓等于發射極電壓。這跟實際萬用表測量到的3.33V是有所不同的,
1. 集電極電阻電路可以實現數碼管定時器秒表顯示并且不必使用中斷功能,只需要消隱一下。Proteus工程文件基于集電極電阻原理圖。
P0= 0xFF;
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //數碼管顯示字符轉換表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = { //數碼管顯示緩沖區,初值 0xFF 確保啟動時都不亮
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
void main()
{
unsigned char i = 0; //動態掃描的索引
unsigned int cnt = 0; //記錄 T0 中斷次數
unsigned long sec = 0; //記錄經過的秒數
ENLED = 0; //使能 U3,選擇控制數碼管
ADDR3 = 1; //因為需要動態改變 ADDR0-2 的值,所以不需要再初始化了
TMOD = 0x01; //設置 T0 為模式 1
TH0 = 0xFC; //為 T0 賦初值 0xFC67,定時 1ms
TL0 = 0x67;
TR0 = 1; //啟動 T0
while (1)
{
if (TF0 == 1) //判斷 T0 是否溢出
{
TF0 = 0; //T0 溢出后,清零中斷標志
TH0 = 0xFC; //并重新賦初值
TL0 = 0x67;
cnt++; //計數值自加 1
if (cnt >= 1000) //判斷 T0 溢出是否達到 1000 次
{
cnt = 0; //達到 1000 次后計數值清零
sec++; //秒計數自加 1
//以下代碼將 sec 按十進制位從低到高依次提取并轉為數碼管顯示字符
LedBuff[0] = LedChar[sec%10];
LedBuff[1] = LedChar[sec/10%10];
LedBuff[2] = LedChar[sec/100%10];
LedBuff[3] = LedChar[sec/1000%10];
LedBuff[4] = LedChar[sec/10000%10];
LedBuff[5] = LedChar[sec/100000%10];
}
//以下代碼完成數碼管動態掃描刷新
P0= 0xFF;
if (i == 0)
{ ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; }
else if (i == 1)
{ ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; }
else if (i == 2)
{ ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; }
else if (i == 3)
{ ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; }
else if (i == 4)
{ ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; }
else if (i == 5)
{ ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; }
}
}
}
```
不過這以上都需要使用名為PNP的PNP元件,就是字面意義上面的叫做PNP。
換個方法就可以實現2N2907實現定時器秒表顯示。并且不用集電極電阻也可以,用一個[**下拉阻排**](https://zhidao.baidu.com/question/557402039.html)就可以實現。
下面這個圖是名為PNP的PNP Transistors

下面這個圖是名為2N2907的PNP Transistors實現

轉自網絡
|