[英]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.