繁体   English   中英

PIC16F877A timer1中断时间不符合预期

[英]PIC16F877A timer1 interrupt time is not as expected

PIC-DIP40开发板上的 PIC16F877A MCU 上的 TIMER1 上实现了中断 function。 将定时器预分频器配置为1并将自动预加载值配置为55536 ,以便中断时间为0.01s 使用100的计数器来计算1s的间隔。 Fosc 是4Mhz 所以我的计算是:

interrupt time = (4 / Fosc) * (65536 - 55536) = (4/4000000) * (65536 - 55536) = 0.01 s

并使用100的计数器来生成1s的间隔。 目前,我没有示波器来测试实际的 1s 间隔,所以我在定时器中断上闪烁一个 LED (LED2),在同一时间间隔1s上使用__delay_ms(1000); function。

因此,正如预期的那样,两个 LED 将同步闪烁(同时打开和关闭)。 但是对于某些第一次迭代,它们会同步闪烁。 经过一些迭代后,它们的闪烁时间(打开和关闭时间)之间存在明显的时间差异。 几分钟后,差异几乎是 1 秒。 所以定时器中断没有按预期工作。

那么我的中断时间计算是错误的还是我在 timer1 配置中遗漏了什么?

总体目标是在不使用示波器的情况下生成 1s 时间间隔并测试有效性。

这是我的代码:

// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

#include <xc.h>
#include <pic16f877a.h>
#define _XTAL_FREQ  4000000

#define LED1_ON PORTDbits.RD7 = 0
#define LED1_OFF PORTDbits.RD7 = 1 
#define LED2_ON PORTDbits.RD6 = 0
#define LED2_OFF PORTDbits.RD6 = 1 

#define LED2_TOGGLE PORTDbits.RD6 = ~PORTDbits.RD6

uint16_t preloadValue  = 55536 ; 
uint16_t counter  = 0 ; 
uint16_t secCounter1 = 100 ; 

void io_config() {
    TRISD  &= ~((1 << _PORTD_RD7_POSITION) | (1 << _PORTD_RD6_POSITION)) ; //RD7 and RD6 are output LEDs
}

void timer1_init(){
    TMR1 = preloadValue ; //loading the preload value
    T1CON &= ~((1 << _T1CON_T1CKPS1_POSN) | (1 << _T1CON_T1CKPS0_POSN) | (1 << _T1CON_TMR1CS_POSN)) ; //prescalar is 1 clock is Fosc
    T1CONbits.TMR1ON = 1 ; //timer 1 is ON
    LED2_ON ; 
}

void interrupt_en_configure(){
    INTCON |=  (1 << _INTCON_GIE_POSITION) | (1 << _INTCON_PEIE_POSITION) ; //global and peripheral interrupt on
    PIE1 |= _PIE1_TMR1IE_MASK ;  //timer 1 interrupt enable
    TMR1IF = 0 ; //clearing interupt flag
}

void __interrupt() ISR(){
    if(TMR1IF){
        counter ++  ; 
        if (counter == secCounter1){
            counter = 0 ;
            LED2_TOGGLE ; 
        }
        
        TMR1 = preloadValue ;
        TMR1IF = 0 ; 
    }
}

void main(void) {
    io_config();
    interrupt_en_configure() ; 
    timer1_init() ;
    
    while (1) {
        LED1_ON ; 
        __delay_ms(1000);
        LED1_OFF ;
        __delay_ms(1000);
    }
}

由于以下原因,您不应期望它们同步运行:

  • 首先,您不知道__delay_ms()是如何实现的,也不知道它可能做出的任何精度“承诺”——它肯定没有使用 TIMER1,因为您正在控制它。 事实上文档给出了一些实现细节,你真的不能指望精确。

  • 其次,即使__delay_ms()既准确又同步,您还是在循环中调用它,循环的软件开销、function 调用以及您为切换 LED 所做的任何操作。 这是每次迭代的几个周期,不会影响锁定到硬件的中断间隔,并且与软件时序无关。

__delay_ms()的精度问题实际上在这篇 Microchip 支持文章中得到了解决:

如果需要精确的延迟,或者在延迟期间有其他任务可以执行,那么使用定时器产生中断是最好的处理方式。

在这种情况下,您应该信任您的代码,而不是库提供的延迟,这是故意粗略的(因为它不会耗尽宝贵的 H/W 计时器资源)。

__delay_ms()通过运行一个空循环来延迟,但它通常不准确。 您需要查看运行的实际机器代码以计算实际延迟。 顺便说一句,这不是火箭科学,也不是一项伟大的学习任务。 (去过也做过。)

现在,您的循环(LED 开关、循环)的 rest 添加到此。 因此,您的纯软件驱动信号灯并不准确。

但是,您的中断驱动信号灯也不是。 在经过几个时钟周期后,您在 ISR 结束时重置定时器。 您需要考虑到这一点,并且不要忘记中断延迟。 更糟糕的是,根据条件语句,重置发生在定时器溢出后的不同时间。

产生准确的时间是很困难的,尤其是对于这样一个简单的设备。

解决方案是完全避免使用软件来重置定时器。 请阅读数据表第 8 章,并使用捕获/比较/PWM 模块将定时器重置为适当的值。

仍然可能发生的最糟糕的事情是一些抖动,只是因为 ISR 可能有不同的延迟。 但是计时器的运行与系统的晶体一样准确。 平均而言,您的 LED 会正确闪烁。

无论如何,如果您的时间要求不那么严格,请考虑接受一些不准确的情况。 然后使用您最喜欢的最简单的解决方案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM