打印
[AVR单片机]

GCC,用定时器0不能准确定时(有图

[复制链接]
4177|14
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
小fan|  楼主 | 2007-9-22 23:14 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
我采集0~5V电压(工频),在5V时,纹波电压为1V。<br />我采用的方案是用8位定时器0,产生1ms中断,每间隔1ms采集一次。<br /><br />在我使用的时候发现一个问题,为了简化问题,我举一个我亲手做的实验来说明问题。<br />主频8M<br /><br />30uS定时程序<br /><br />程序如下:<br />#include<avr/io.h><br />#include<avr/delay.h><br />#include<stdio.h><br />#include<avr/interrupt.h><br />#include<avr/signal.h><br />#include<avr/pgmspace.h><br /><br />SIGNAL(SIG_OVERFLOW0)  // 即使换成INTERRUPT(SIG_OVERFLOW0),波形一样<br />{<br />//产生周期 T=(255-225)*(8/8MHz)=30*1uS=30uS<br />        //SREG=0x00;        //关总中断<br />        ;PORTC|=_BV(PC1);   //测试点<br />        DDRC|=_BV(PC1);<br />        _delay_us(10);<br />        //SREG=0x80;          //开总中断<br />        TCNT0=225;            //设初始值为229,记数范围:255-225=30<br />    <br />}<br />void main(void)<br />{   <br />    DDRC=0x00;<br />    TCNT0=225;            //设初始值为225<br />    TCCR0|=_BV(CS01);    //预分频,CK/8,<br />    TIMSK|=_BV(TOIE0); //定时器0中断使能<br />    SREG=0x80;          //开总中断    <br />    While(1)<br />{<br />        ;PORTC&=~_BV(PC1);<br />        DDRC|=_BV(PC1);<br />}<br />}<br />为了达到准确计时,我只能一次一次重设初始值来改变定时时间<br />

相关帖子

沙发
小fan|  楼主 | 2007-9-22 23:17 | 只看该作者

图1

PC1口波形如下,

使用特权

评论回复
板凳
小fan|  楼主 | 2007-9-22 23:19 | 只看该作者

30uS延时的时候

使用特权

评论回复
地板
小fan|  楼主 | 2007-9-22 23:21 | 只看该作者

问题是

1、    为什么定时不准?
2、    在执行中断服务程序时,定时器为什么关闭了?

我在中断服务程序并没有设置关闭定时器呀??

使用特权

评论回复
5
zsmbj| | 2007-9-23 09:08 | 只看该作者

是你的程序的问题,和gcc和单片机没有任何关系。

定时器是准的,是你的程序结构有问题。
你进入中断后延迟了10us,退出中断后再给timer重新赋了值。当然timer要重新计时了。这个进入中断,中断里的实行时间,出中断的总时间 就是你看到的30us多出的时间了。

中断要准请用比较中断,自动重装。

使用特权

评论回复
6
小fan|  楼主 | 2007-9-23 14:07 | 只看该作者

还是不太明白

定时大概多出了3uS左右的时间,这是入中断和出中断的时间?

我的程序里没有涉及任何变量,所以在执行中断程序的时候根本不需要“压栈”和“出栈”。

我就不明白了,这3uS,单片机究竟在做什么?

使用特权

评论回复
7
zsmbj| | 2007-9-23 15:37 | 只看该作者

谁说没有压栈操作?看看.lss文件再说吧。

还有你以下这些语句都是需要时间的。
看看汇编再说吧。

PORTC|=_BV(PC1);   //测试点
DDRC|=_BV(PC1);
_delay_us(10);
TCNT0=225;            //设初始值为229,记数范围:255-225=30

使用特权

评论回复
8
小fan|  楼主 | 2007-9-23 17:06 | 只看该作者

谢谢你,答案太让我满意了,嘿嘿

INTERRUPT(SIG_OVERFLOW0) //INTERRUPT
{
  5c:    78 94           sei
  5e:    1f 92           push    r1
  60:    0f 92           push    r0
  62:    0f b6           in    r0, 0x3f    ; 63
  64:    0f 92           push    r0
  66:    11 24           eor    r1, r1
  68:    8f 93           push    r24
//产生周期 T=250*(8/8MHz)=256*1uS=250uS


        PORTC|=_BV(PC1);    //示波器测试点开
  6a:    a9 9a           sbi    0x15, 1    ; 21
        DDRC|=_BV(PC1);
  6c:    a1 9a           sbi    0x14, 1    ; 20
        __ticks = 1;
    else if (__tmp > 255)
        __ticks = 0;    /* i.e. 256 */
    else
        __ticks = (uint8_t)__tmp;
  6e:    80 e5           ldi    r24, 0x50    ; 80
  70:    8a 95           dec    r24
  72:    f1 f7           brne    .-4          ; 0x70
        _delay_us(30);

        
        PORTC&=~_BV(PC1);    //示波器测试点关
  74:    a9 98           cbi    0x15, 1    ; 21

        //SREG=0x80;          //开总中断
        TCNT0=225;            //设初始值为1f(31),记数范围:255-30(0X82)=225
  76:    81 ee           ldi    r24, 0xE1    ; 225
  78:    82 bf           out    0x32, r24    ; 50
  7a:    8f 91           pop    r24
  7c:    0f 90           pop    r0
  7e:    0f be           out    0x3f, r0    ; 63
  80:    0f 90           pop    r0
  82:    1f 90           pop    r1
  84:    18 95           reti

00000086 <main>:

    
}


int main(void)

  86:    cf e5           ldi    r28, 0x5F    ; 95
  88:    d4 e0           ldi    r29, 0x04    ; 4
  8a:    de bf           out    0x3e, r29    ; 62
  8c:    cd bf           out    0x3d, r28    ; 61
    TCNT0=225;            //设初始值为1f(31),记数范围:255-30(0X82)=225
  8e:    81 ee           ldi    r24, 0xE1    ; 225
  90:    82 bf           out    0x32, r24    ; 50
    TCCR0|=_BV(CS01);    //预分频,CK/8,
  92:    83 b7           in    r24, 0x33    ; 51
  94:    82 60           ori    r24, 0x02    ; 2
  96:    83 bf           out    0x33, r24    ; 51
    TIMSK|=_BV(TOIE0);
  98:    89 b7           in    r24, 0x39    ; 57
  9a:    81 60           ori    r24, 0x01    ; 1
  9c:    89 bf           out    0x39, r24    ; 57
    SREG=0x80;          //开总中断    
  9e:    80 e8           ldi    r24, 0x80    ; 128
  a0:    8f bf           out    0x3f, r24    ; 63

    
    while(1)
    {    
        PORTC&=~_BV(PC1);
  a2:    a9 98           cbi    0x15, 1    ; 21
        DDRC|=_BV(PC1);
  a4:    a1 9a           sbi    0x14, 1    ; 20
  a6:    fd cf           rjmp    .-6          ; 0xa2


一共14条“压栈”和“出栈”指令,pop和push都是双周期指令。

我有空的时候用CTC模式拭一拭。

使用特权

评论回复
9
龙在天涯| | 2007-9-23 21:19 | 只看该作者

路过,实践出真知

使用特权

评论回复
10
xwj| | 2007-9-23 21:41 | 只看该作者

呵呵,不要轻易怀疑编译器,多想想是不是自己的问题


特别是初学者

使用特权

评论回复
11
小fan|  楼主 | 2007-9-23 23:05 | 只看该作者

还有个问题请帮忙回答

数据手册里有这样一段话:

“当T/C0 溢出时, TOV0 置位。执行相应的中断服务程序时此位硬件清零。此外, TOV0也可以通过写1 来清零。当SREG 中的位I、TOIE0(T/C0 溢出中断使能) 和TOV0 都置位时,执行中断服务程序。”

“TOV0也可以通过写1 来清零”
写1,不就是置位 ?
置位,怎么又叫清零那?

郁闷那


程序从新写一下,中断程序改为:
INTERRUPT(SIG_OVERFLOW0) //INTERRUPT
{
        TIFR&=~_BV(TOV0);   //TIFR|=_BV(TOV0) ,两句话的效果是一样的 
        TCNT0=225;      
        PORTC|=_BV(PC1);    
        DDRC|=_BV(PC1);
        _delay_us(10);
}

使用特权

评论回复
12
小fan|  楼主 | 2007-9-23 23:55 | 只看该作者

波形

使用特权

评论回复
13
zsmbj| | 2007-9-24 08:57 | 只看该作者

改成CTC模式吧

不需要在中断中装载初值的。这样最准了。
另外中断标志在进入中断后自动清零了,不需要软件处理。

包括arm等cpu都是写“1”来清零的,这个位是状态位。写“1”并不是把1写到这个状态上。而是利用这个写,把状态位清除。

这个方法很好用。比如要清除一个状态寄存器的第7位,那么只需要写入0x80即可,因为其他位写入的是0,所以不会改变其他位的状态。只有第7位被清除了。

使用特权

评论回复
14
小fan|  楼主 | 2007-9-24 16:57 | 只看该作者

CTC模式只能叫基本准确

CTC模式只能叫基本准确,不能称之为精确

/******************************************************
    功能:T/C2 CTC模式产生中断
    编译器:WinAVR-20050214
    目标芯片:mega8
    时钟:8M
    作者:huangfan_1983
    日期:2007-9-24
******************************************************/
#include<avr/io.h>
#include<avr/delay.h>
#include<stdio.h>
#include<avr/interrupt.h>
#include<avr/signal.h>
#include<avr/pgmspace.h>

SIGNAL(SIG_OUTPUT_COMPARE2) //INTERRUPT
{
    //TCNT2=0x00;            //设初始值为0,
    //OCR2=30;            //计数计30*8M/8=30uS
    //TCCR2|=_BV(WGM21)|_BV(CS21);    //CTC模式,clk/8
    PORTC|=_BV(PC1);    //示波器测试点开
    DDRC|=_BV(PC1);
    _delay_us(10);
}


int main(void)

    //产生周期 T=25*(8/8MHz)=256*1uS=25uS 频率为1/25uS=40K
    TCNT2=0x00;            //设初始值为0,
    OCR2=25;
    TCCR2|=_BV(WGM21)|_BV(CS21);    //CTC模式,clk/8
    TIMSK|=_BV(OCIE2);    //
    SREG=0x80;          //开总中断        
    while(1)
    {    
        PORTC&=~_BV(PC1);
        DDRC|=_BV(PC1);
    }    
}

我的定时频率为40K,实际是39.0996K

使用特权

评论回复
15
zsmbj| | 2007-9-26 08:50 | 只看该作者

CTC模式是精确的。

至于你的频率不准确,是振荡的问题吧。你是采用内部的RC振荡吧。换个外部的晶振试试。39.099k*25*8=7.819M。

使用特权

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

本版积分规则

3

主题

33

帖子

1

粉丝