在嵌入式系统开发中,定时器是非常重要的外设之一。通过定时器中断,我们可以精确地控制任务的执行时间,比如LED的闪烁频率。本文介绍了如何使用PIC16F1719微控制器的定时器0来控制LED的闪烁。
在编写程序之前,首先需要设置PIC的配置位(Configuration Bits)。配置位用于配置微控制器的系统行为,比如振荡器的类型、看门狗定时器、低电压复位等。在本文的代码中,配置位的设置如下:
c展开代码#pragma config FOSC = INTOSC    /* 内部振荡器,使用内部时钟 */
#pragma config WDTE = OFF       /* 禁用看门狗定时器 */
#pragma config PWRTE = OFF      /* 禁用上电定时器 */
#pragma config MCLRE = ON       /* MCLR 引脚功能使能 */
#pragma config CP = OFF         /* 禁用代码保护 */
#pragma config BOREN = OFF      /* 禁用低电压复位 */
#pragma config CLKOUTEN = OFF   /* 禁用时钟输出 */
#pragma config IESO = ON        /* 启用内部/外部切换模式 */
#pragma config FCMEN = ON       /* 启用故障安全时钟监控 */
#pragma config WRT = OFF        /* 禁用写保护 */
#pragma config PPS1WAY = OFF    /* 允许多次更改外设引脚选择 */
#pragma config ZCDDIS = ON      /* 禁用零交叉检测 */
#pragma config PLLEN = ON       /* 启用锁相环(PLL) */
#pragma config STVREN = ON      /* 启用栈溢出/下溢复位 */
#pragma config BORV = LO        /* 选择低电压复位触发点 */
#pragma config LPBOR = OFF      /* 禁用低功耗低电压复位 */
#pragma config LVP = ON         /* 启用低电压编程 */
为了使系统时钟达到32MHz,我们使用了内部振荡器(INTOSC)并启用了4倍的PLL(Phase-Locked Loop)。使用下面的代码可以完成系统时钟的配置:
c展开代码OSCCON = 0xF0;  // 设置INTOSC为8MHz,使用4x PLL,最终系统时钟为32MHz
在这段代码中,我们使用了RD1引脚来控制LED的状态,因此需要将其配置为输出模式:
c展开代码TRISDbits.TRISD1 = 0;  // 设置RD1为输出
LATDbits.LATD1 = 0;    // 初始LED关闭
此外,我们将所有模拟输入引脚设置为数字模式以避免干扰:
c展开代码ANSELA = 0x00;  // 将所有A端口引脚设置为数字模式
ANSELB = 0x00;  // 将所有B端口引脚设置为数字模式
ANSELC = 0x00;  // 将所有C端口引脚设置为数字模式
定时器0是8位定时器,使用内部时钟并配置为256分频:
c展开代码OPTION_REGbits.TMR0CS = 0;  // 使用内部时钟
OPTION_REGbits.PSA = 0;     // 启用预分频器
OPTION_REGbits.PS0 = 1;     // 预分频器设置为256
OPTION_REGbits.PS1 = 1;
OPTION_REGbits.PS2 = 1;
TMR0 = 0;                   // 初始化定时器0
在这里,定时器溢出后会触发中断,因此我们需要使能定时器0中断:
c展开代码INTCONbits.TMR0IE = 1;  // 使能定时器0中断
INTCONbits.PEIE = 1;    // 使能外设中断
INTCONbits.GIE = 1;     // 使能全局中断
在中断服务程序中,我们对计数器进行累加,当计数器达到一定值(比如122次溢出,对应约1秒时间)时,切换LED的状态,并重置计数器:
c展开代码void __interrupt() timer0_isr(void) {
    if (INTCONbits.TMR0IF) {
        INTCONbits.TMR0IF = 0;  // 清除中断标志
        count++;                // 增加计数
        if (count == 122) {     // 当计数器达到122时
            LATDbits.LATD1 ^= 1;  // 切换LED状态
            count = 0;          // 重置计数
        }
    }
}
为了确保系统在启动时稳定工作,我们使用了一个小的延时函数来等待PLL的启动时间:
c展开代码__delay_ms(10);  // 允许PLL启动时间大约2ms
c展开代码/* PIC16F1719 配置位设置 */
#pragma config FOSC = INTOSC    /* 振荡器选择位 (INTOSC 振荡器: CLKIN 引脚上的 I/O 功能) */
#pragma config WDTE = OFF       /* 看门狗定时器使能 (WDT 禁用) */
#pragma config PWRTE = OFF      /* 上电定时器使能 (PWRT 禁用) */
#pragma config MCLRE = ON       /* MCLR 引脚功能选择 (MCLR/VPP 引脚功能为 MCLR) */
#pragma config CP = OFF         /* 闪存程序存储器代码保护 (程序存储器代码保护禁用) */
#pragma config BOREN = OFF      /* 低电压复位使能 (低电压复位禁用) */
#pragma config CLKOUTEN = OFF   /* 时钟输出使能 (CLKOUT 功能禁用。CLKOUT 引脚上的 I/O 或振荡器功能) */
#pragma config IESO = ON        /* 内外部切换模式 (内外部切换模式使能) */
#pragma config FCMEN = ON       /* 故障安全时钟监控器使能 (故障安全时钟监控器使能) */
#pragma config WRT = OFF        /* 闪存自写保护 (写保护关闭) */
#pragma config PPS1WAY = OFF    /* 外设引脚选择单向控制 (PPSLOCK 位可以通过软件反复设置和清除) */
#pragma config ZCDDIS = ON      /* 零交叉检测禁用 (ZCD 禁用。ZCD 可以通过设置 ZCDCON 的 ZCDSEN 位来启用) */
#pragma config PLLEN = ON       /* 锁相环使能 (4x PLL 使能) */
#pragma config STVREN = ON      /* 栈溢出/下溢复位使能 (栈溢出或下溢将导致复位) */
#pragma config BORV = LO        /* 低电压复位电压选择 (低电压复位电压 (Vbor),选择低触发点。) */
#pragma config LPBOR = OFF      /* 低功率低电压复位 (低功率低电压复位禁用) */
#pragma config LVP = ON         /* 低电压编程使能 (低电压编程使能) */
/* 定义系统振荡器频率 */
#define _XTAL_FREQ (32000000ul)
#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//counter variable
int count = 0;
void main(void) {
    unsigned char index = 0;
    /* 程序初始化设置 */
    OSCCON = 0xF0;                      /* 设置 INTOSC 为 8MHz,并使用 4xPLL,使系统振荡器达到 32MHz */
    INTCON = 0;                         /* 禁用所有中断源 */
    PIE1 = 0;
    PIE2 = 0;
    PIE3 = 0;
    // Allow PLL startup time ~2 ms
    __delay_ms(10);
    ANSELA = 0x00;  // 将所有A端口引脚设置为数字模式
    ANSELB = 0x00;  // 将所有B端口引脚设置为数字模式
    ANSELC = 0x00;  // 将所有C端口引脚设置为数字模式
    // 配置D1为输出
    TRISDbits.TRISD1 = 0;
    LATDbits.LATD1 = 0;
    // 配置定时器0
    OPTION_REGbits.TMR0CS = 0;  // 使用内部时钟
    OPTION_REGbits.PSA = 0;     // 使用预分频器
    OPTION_REGbits.PS0 = 1;     // 预分频器 256
    OPTION_REGbits.PS1 = 1;
    OPTION_REGbits.PS2 = 1;
    TMR0 = 0;                   // 定时器0计数器初始化
    // 使能定时器中断
    INTCONbits.TMR0IE = 1;
    INTCONbits.TMR0IF = 0;
    INTCONbits.PEIE = 1;        // 使能外设中断
    INTCONbits.GIE = 1;         // 使能全局中断
    while (1) {
    }
}
void __interrupt() timer0_isr(void) {
    // 检查是否是 Timer0 溢出中断
    if (INTCONbits.TMR0IF) {
        // 重置 Timer0 溢出标志
        INTCONbits.TMR0IF = 0;
        // 增加计数
        count++;
        // 1 秒周期
        if (count == 122) {
            LATDbits.LATD1 ^= 1;  // 切换LED状态
            count = 0;            // 重置计数
        }
        // 重装载 Timer0 (根据需要,可以添加)
    }
}
通过本文的代码,我们可以使用PIC16F1719的定时器0实现精确的时间控制,达到LED每秒闪烁一次的效果。通过定时器中断,避免了轮询的方式,提高了系统的效率和实时性。


本文作者:Dong
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC。本作品采用《知识共享署名-非商业性使用 4.0 国际许可协议》进行许可。您可以在非商业用途下自由转载和修改,但必须注明出处并提供原作者链接。 许可协议。转载请注明出处!