打印

软件无累积误差的定时中断程序模板,供初学者学习和行家

[复制链接]
7541|61
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
xwj|  楼主 | 2007-7-26 10:17 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
//--------------------------------------------------------------------------//
//针对论坛近期讨论激烈的“用51做实时时钟,不能有算法上带来的误差”问题特整理此贴//
//  以供供初学者学习和行家参考                                              //
//--------------------------------------------------------------------------//
//  软件无累积误差的定时中断程序,修改晶振频率定义FREQ即可直接使用          //
//  可以 时间周期 或 频率  为单位直接定义                                   //
//                                                             Edit By xwj  //
//--------------------------------------------------------------------------//
#include <REGX52.H>
#define uchar unsigned char
#define uint unsigned int
#define ulong unsigned long



#define FREQ  12000000              // 晶振频率12MHz,换不同晶振改这个参数即可//
#define MODIFY  14                  // 从变量加载时的修正值//
//#define MODIFY  13                  // 从常量加载时的修正值//

                        //按时间计算定时重载值//    // f单位:100uS,f<=655   //
#define TIMEdata(f) (65536-(uint)((ulong)(1200000000)/FREQ*(f))+MODIFY)
#define TIMEN(f) (500/f)                    // x次50mS//
                        //按频率计算定时重载值//   // f单位:Hz, f>25//
#define FREQdata(f) (65536-(FREQ/12)/(f)+MODIFY) 
#define FREQdataH(f) ((FREQdata(f))/256)    // 定时重载值高,f单位:Hz   //
#define FREQdataL(f) ((FREQdata(f))%256)    // 定时重载值低,f单位:Hz   //
#define FREQN(f) (f/20)                     // x次50mS,有误差,f单位:Hz  //
#define FREQdata3(f)  FREQdataH(f),FREQdataL(f),FREQN(f)
/************************************** /
unsigned char code FREQ_TAB[]=              //频率 -- 定时器初值
{
    FREQdata3(450),           //450Hz
    FREQdata3(500),           //500Hz
    FREQdata3(1000),          //1KHz
    FREQdata3(1300),          //1.3KHz
};
/**************************************/



                                 //  全局变量   //
unsigned int timeload;              //定时器重载值
unsigned char timen_50mS;           //50mS计数次数
unsigned char timeload_50mS;        //50mS需要的次数
unsigned char time1s;               //1S计数次数


/************************************/
void ext_int0() interrupt 0
{
    ;       //原  中断
}

/**************************************/
void ext_int1() interrupt 2
{
    ;       //原  中断
}

/**************************************/
void time1() interrupt 3
{
    ;       //原  中断
}

/**************************************/
//--------//串口中断处理
void serial_int () interrupt 4 
{
    ;
}

/************************************/
void time0() interrupt 1  using 1       //25mS或1mS
{
    unsigned int t0buf;
    EA=0;
    TR0=0;
    t0buf = timeload+TL0;         //蜂鸣频率表
//    t0buf = TIMEdata(250)+TL0;   //蜂鸣频率表
    TH0   = t0buf/256;
    TL0   = t0buf%256;
    TR0=1;
    EA=1;
    
    if((--timen_50mS)==0)            //50mS计数次数      //50mS
    {
        timen_50mS=timeload_50mS;       //重载50mS需要的次数
//        timen_50mS=TIMEN(250);        //重载50mS需要的次数

//        KeyScan();            //键扫描,自己去增加你要的功能

        if(--time1s==0)         //1S
        {
            time1s=20;          //改成10,就是每秒2次(500mS)
//            sec_add();          //加1秒,自己去增加你要的功能
        }
    }
}

/**************************************/
void system_init(void)             //系统初始化          
{

    //------------------------------------------------//自己去根据需要修改  //
    SCON  = 0x50;       // SCON: 设串口模式1,并打开接受:1起始位,8数据位,1停止位//
    TMOD  = 0x21;       // TMOD: 定时器1:模式2:8位自加载;定时器2:模式1:16位 //
    TH1   = 0xEA;       // TH1:  2400波特串口的自加载值,晶振频率:     20MHz //
    IP    = 0x10;       // IP:   串口中断优先                               //
    IE    = 0x92;       // IE:   开串口中断,开定时器 1中断                 //
//  TCON  = 0x50;       // TCON: 定时器 1运行;定时器 2运行                 //
    TR1=1;
    
    timeload_50mS=TIMEN(250);           //50mS需要的次数
    timeload=TIMEdata(250);             //定时器重载值
    TH0=TIMEdata(250)/256;TL0=TIMEdata(250)%256;           //25ms
    TR0=1;
    timen_50mS=timeload_50mS;           //50mS计数次数
    time1s=20;                          //1S计数次数
}

/**************************************/
void main (void)
{
    P0=0xff;                        //自己去根据需要修改  //
    P1=0xff;                        //自己去根据需要修改  //
    P2=0xff;                        //自己去根据需要修改  //
    P3=0xff;                        //自己去根据需要修改  //
    system_init();             //系统初始化
    
//    beepn(1);

    while(1)
    {
        ;       //改成你自己的程序
//        if (key_ok)         //按键处理程序
//            do_key();
//        if (inbufsign)
//            do_serial(getbyte ());      //串口数据处理程序
//        delayms(10);            //延时n mS
    }
}
/**************************************/


/************************************** /
//汇编指令分析:
//从TR0=0;到TR0=1;之间漏计14指令周期,故#define MODIFY  14--这就是没有误差的原理了
;     unsigned int t0buf;
;     EA=0;
            ; SOURCE LINE # 76
    CLR      EA
;     TR0=0;
            ; SOURCE LINE # 77
    CLR      TR0
;     t0buf = timeload+TL0;         //蜂鸣频率表
            ; SOURCE LINE # 78
    MOV      R7,TL0
    MOV      R6,#00H
    MOV      A,timeload+01H
    ADD      A,R7
    MOV      R7,A
    MOV      A,R6
    ADDC     A,timeload
;---- Variable 't0buf?440' assigned to register 'R4/R5' ----
    MOV      R5,AR7
//    t0buf = TIMEdata(250)+TL0;   //蜂鸣频率表
;     TH0   = t0buf/256;
            ; SOURCE LINE # 80
    MOV      TH0,A
;     TL0   = t0buf%256;
            ; SOURCE LINE # 81
    MOV      TL0,R7
;     TR0=1;
            ; SOURCE LINE # 82
    SETB     TR0
;     EA=1;
            ; SOURCE LINE # 83
    SETB     EA
;     



//本程序由xwj设计的UltraEdit脚本加亮显示,如需要脚本访问我的Blog 或发送邮件至:xwjfile@21cn.com(xwjfile@21cn.com)
沙发
一朝成名| | 2007-7-26 10:27 | 只看该作者

沙发 留个脚印下班了好好看下

使用特权

评论回复
板凳
hqgboy| | 2007-7-26 10:43 | 只看该作者

板凳....收.

使用特权

评论回复
地板
jasonell| | 2007-7-26 13:34 | 只看该作者

收藏了,

使用特权

评论回复
5
wjy1107| | 2007-7-26 15:56 | 只看该作者

收了。

嘻嘻!

使用特权

评论回复
6
古道热肠| | 2007-7-26 16:31 | 只看该作者

拿回家学习学习

  有C有汇编,这种东西我喜欢。

使用特权

评论回复
7
gyt| | 2007-7-26 17:52 | 只看该作者

好帖子

值得顶!

使用特权

评论回复
8
cjf512| | 2007-7-26 18:27 | 只看该作者

好!

收藏了!

使用特权

评论回复
9
谈的元| | 2007-7-26 18:37 | 只看该作者

顶哈

如果解析哈原理,会比写个程序好

使用特权

评论回复
10
xwj|  楼主 | 2007-7-26 18:41 | 只看该作者

原理说的很明白了,你自己不看能怪谁啊?

使用特权

评论回复
11
mannerfh| | 2007-7-26 18:45 | 只看该作者

好方法

的确是个好方法,但用C时,还得先进行编译,然后算出准确的时间吧!

使用特权

评论回复
12
5880527| | 2007-7-26 20:45 | 只看该作者

定时有误差,但无累计误差

   

使用特权

评论回复
13
aolin| | 2007-7-26 20:56 | 只看该作者

这个方法不提倡

还是不提倡这种方法:
1, 用了C,还要产生汇编输出来分析汇编指令,同时还要去查指令表,看哪条指令需要几个指令周期,麻烦!
2, 移植性不好.不同的C编译器输出不同,就算是同一个C编译器,不同版本输出都有可能不同.就算同一跟版本的C编译器,不同程度的优化输出也都不同.

所以这种算法不提倡,还有更好的方法!

使用特权

评论回复
14
fsaok| | 2007-7-26 21:51 | 只看该作者

没有抓到问题的关键

楼主没有抓到问题的关键,

关于误差,我在10多年前专门研究。

误差问题的关键是:51 的指令是有单周期、两周期和三周期。

中断的时候,可能是两周期和三周期的指令周期中,

比如,在程序中,刚好在定时器的走到ffff时候,刚好执行一个djnz指令(2周期指令),于是进入中断程序时,刚好定时器是0001,

这样和定时器走到fffe时候,51执行一个djnz指令,产生了所谓积累误差。

这个误差是不能用中断中关中断来解决的。

所以,论坛上流行所谓自重载定时器T1才没有误差,

对于T0,

其实,对于T0,可以采用这样的算法,

解决方法一
所有程序都可以用
 PCON |= 0x01;

让处理器计数器停止。

解决方法二、
自动重载

解决方法三
软件自动重载

TL0 += CONSTL; //CONST 是 某个常数。

使用特权

评论回复
15
xwj|  楼主 | 2007-7-27 07:32 | 只看该作者

fsaok ,你根本就没仔细看,拜托你看了再说!

使用特权

评论回复
16
xwj|  楼主 | 2007-7-27 07:42 | 只看该作者

To aolin

你说的两个问题,根本就不是什么问题

1, 用了C,还要产生汇编输出来分析汇编指令,同时还要去查指令表,看哪条指令需要几个指令周期,麻烦!
--看下汇编有什么大不了的?而且一次搞定以后照搬就行了,一劳永逸的事都不干???

2, 移植性不好.不同的C编译器输出不同,就算是同一个C编译器,不同版本输出都有可能不同.就算同一跟版本的C编译器,不同程度的优化输出也都不同.
--本来就是针对51的,定时器的操作不同系列IC当然会完全不一样,难道别的定时器程序移植就能直接用?
--对于编译器优化,就只一条加指令,不管怎么优化都基本不会变的

--这里提供的是一个解决问题的方法和思路,就算以上顾虑都存在,但是针对强调误差的情况,为了解决人为误差,多花这么点精力也是应该的,何况是我都帮你写好了呢?
--一个合格的程序员不会这么懒吧???

呵呵:-)

使用特权

评论回复
17
农民讲习所| | 2007-7-27 07:50 | 只看该作者

这种方法要坚决淘汰掉

 unsigned int t0buf;
    EA=0;
    TR0=0;
    t0buf = timeload+TL0;         //蜂鸣频率表
//    t0buf = TIMEdata(250)+TL0;   //蜂鸣频率表
    TH0   = t0buf/256;
    TL0   = t0buf%256;
    TR0=1;
    EA=1;

这种方法要坚决淘汰掉,一定使用自动装载或高位手动装载。

使用特权

评论回复
18
ayb_ice| | 2007-7-27 08:20 | 只看该作者

反对17L

专家不比你强...
TINY用的就是这种...

使用特权

评论回复
19
xwj|  楼主 | 2007-7-27 08:44 | 只看该作者

呵呵,那么,当你的晶振频率凑不出合适的自动装载值时怎

能自动装载或高位手动装载当然最好,比如AVR,就根本不需要考虑这个问题;

但是,当你的晶振频率凑不出合适的自动装载值时怎么办???
用非整数次中断加上余数累加来计秒?
还是直接停掉中断和定时器把TL0加上带补偿值的常数,那种更简单方便?



这个贴只是针对网友aolin提出的“用51做实时时钟,不能有算法上带来的误差”问题而特别考虑的,在他的这个前提下,当然要把误差的考虑放在最高位,其他的影响当然就成为次要的了


而对于实际的系统,在有多个中断同时产生时,无论如何都会有中断不能实时触发,需要等待的可能,也就是说不管怎么样,都会有中断存在误差!
这是,误差怎么分配?到底优先考虑谁? 这就是一个取舍的问题,要看你的系统强调的是什么了


任何的“要坚决淘汰掉什么”或 “要坚决使用什么”的说法都是武断的,
很可能一种环境这样说是对的,但换一种环境,就可能是错误的哦


PS:
EA不一定需要停掉,除非有别的更高优先级的中断,但是针对aolin所提出的前提,是不太应该出现这种情况的。当然,特殊情况下更多的考虑也未尝不可,但何不再多考虑下程序时隙和优先级的分配呢?


使用特权

评论回复
20
古道热肠| | 2007-7-27 08:52 | 只看该作者

楼主的方法可行的

  有误差修正值,还可以修正晶振不准确带来的误差,可能产生误差会发生在中断嵌套的应用中,但把这时钟设定为最高优先级,是可以消除的。分析C语言的精确执行时间只能查看汇编代码来计算。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

xwj

288

主题

22800

帖子

35

粉丝