找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 6|回复: 0
打印 上一主题 下一主题
收起左侧

8051单片机定时器程序

[复制链接]
跳转到指定楼层
楼主
ID:1171124 发表于 2026-5-10 19:50 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
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实现

转自网络
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享淘帖 顶 踩
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表