一文读懂单片机的定时器

目录

引言

单片机定时器的基本原理

定时器的组成

定时器的工作原理

单片机定时器的配置方法

选择时钟源

设置预分频器

选择工作模式

配置中断

单片机定时器的应用实例

实例1:简单的延时程序

实例2:频率测量

实例3:PWM信号生成

代码解释


引言

单片机(Microcontroller Unit,MCU)是一种集成了处理器、存储器、输入输出接口等组件的微型计算机系统。在嵌入式系统设计中,单片机被广泛应用于各种控制和处理任务中。定时器是单片机中一个非常重要的组件,它能够提供精确的时间控制和事件触发功能,广泛应用于计时、频率测量、PWM(脉宽调制)生成等场合。本文将详细介绍单片机定时器的工作原理、配置方法以及应用实例,帮助读者快速掌握单片机定时器的使用技巧。

单片机定时器的基本原理
定时器的组成

单片机的定时器通常由以下几个部分组成:

  1. 计数器(Counter):用于记录时钟脉冲的个数。计数器可以是8位、16位或32位的寄存器,其大小决定了定时器的计数范围。
  2. 预分频器(Prescaler):用于对输入时钟进行分频,以降低计数器的计数频率。预分频器的分频比可以是固定的,也可以是可编程的。
  3. 时钟源(Clock Source):为定时器提供时钟信号。常见的时钟源包括内部时钟、外部时钟和外部事件触发等。
  4. 控制寄存器(Control Registers):用于配置定时器的工作模式、中断使能、预分频器设置等。
  5. 中断服务程序(Interrupt Service Routine, ISR):当定时器溢出或达到设定值时,会触发中断,中断服务程序用于处理定时器事件。
定时器的工作原理

定时器的工作过程可以简单概括为以下步骤:

  1. 初始化配置:通过控制寄存器设置定时器的工作模式、时钟源、预分频器等参数。
  2. 启动定时器:使能定时器开始计数。计数器开始接收时钟信号,并根据预分频器的设置对时钟信号进行分频。
  3. 计数过程:计数器在每个时钟周期内增加1,直到达到其最大值(例如,8位计数器的最大值为255)。
  4. 溢出处理:当计数器溢出时,会触发中断,中断服务程序会被调用。中断服务程序可以执行一些特定的任务,如更新计数器值、发送信号等。
  5. 循环计数:计数器溢出后,通常会自动重置为初始值,继续计数。
单片机定时器的配置方法
选择时钟源

定时器的时钟源决定了计数器的计数频率。常见的时钟源包括:

  • 内部时钟:来自单片机内部振荡器的时钟信号。通常用于简单的定时任务。
  • 外部时钟:来自外部振荡器或时钟源的信号。适用于需要精确时钟的应用。
  • 外部事件触发:由外部事件(如按钮按下、传感器信号等)触发定时器计数。
  • 设置预分频器

    预分频器用于降低计数器的计数频率,以延长定时器的计时范围。例如,如果时钟源频率为1MHz,预分频器设置为128,则计数器的计数频率为1MHz / 128 = 7.8125kHz。预分频器的设置通常通过控制寄存器中的相应位来实现。

    选择工作模式

    定时器的工作模式决定了其计数方式和应用场合。常见的工作模式包括:

  • 模式0(13位定时/计数器):适用于8051单片机等。将8位计数器和5位预分频器组合成13位计数器。
  • 模式1(16位定时/计数器):适用于需要较长计时范围的应用。
  • 模式2(8位自动重载定时/计数器):适用于需要周期性定时任务的场合。
  • 模式3(两个独立的8位定时/计数器):适用于需要同时进行两个独立定时任务的应用。
  • 配置中断

    定时器溢出或达到设定值时,可以触发中断。中断的配置包括:

  • 中断使能:通过控制寄存器中的中断使能位来启用定时器中断。
  • 中断优先级:设置中断的优先级,以确定中断的响应顺序。
  • 中断服务程序:编写中断服务程序来处理定时器事件。
  • 单片机定时器的应用实例
    实例1:简单的延时程序

    下面是一个使用定时器实现简单延时的示例代码(以8051单片机为例):

    #include <reg51.h>
    
    #define DELAY_TIME 1000  // 延时时间(单位:ms)
    
    void delay_ms(unsigned int ms) {
        unsigned int i;
        TMOD = 0x01;  // 设置定时器0为模式1(16位定时/计数器)
        TH0 = 0xFC;   // 设置定时器初值,定时1ms
        TL0 = 0x18;
        for (i = 0; i < ms; i++) {
            TF0 = 0;  // 清除定时器溢出标志
            TR0 = 1;  // 启动定时器
            while (!TF0);  // 等待定时器溢出
            TR0 = 0;  // 停止定时器
        }
    }
    
    void main() {
        while (1) {
            P1 = 0xFF;  // 点亮所有LED
            delay_ms(DELAY_TIME);  // 延时1秒
            P1 = 0x00;  // 熄灭所有LED
            delay_ms(DELAY_TIME);  // 延时1秒
        }
    }
    实例2:频率测量

    定时器可以用于测量外部信号的频率。下面是一个使用定时器测量频率的示例代码(以STM32为例):

    #include "stm32f10x.h"
    
    #define TIM_CLOCK_FREQ 72000000  // 定时器时钟频率
    #define TIM_PRESCALER 7199       // 预分频器值,定时器频率为10kHz
    
    void TIM_Config(void) {
        TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
    
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);  // 使能TIM2时钟
    
        TIM_TimeBaseStructure.TIM_Period = 0xFFFF;  // 自动重载寄存器的值
        TIM_TimeBaseStructure.TIM_Prescaler = TIM_PRESCALER;  // 预分频器值
        TIM_TimeBaseStructure.TIM_ClockDivision = 0;  // 时钟分割
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  // 向上计数模式
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
        NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  // TIM2中断
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  // 抢占优先级
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  // 响应优先级
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  // 使能TIM2中断通道
        NVIC_Init(&NVIC_InitStructure);
    
        TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);  // 使能TIM2更新中断
        TIM_Cmd(TIM2, ENABLE);  // 启动定时器
    }
    
    void TIM2_IRQHandler(void) {
        if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
            static unsigned int count = 0;
            static unsigned int freq = 0;
            static unsigned int last_count = 0;
    
            TIM_ClearITPendingBit(TIM2, TIM_IT_Update);  // 清除TIM2更新中断待处理位
    
            count++;
            if (count >= 100) {
                freq = (count - last_count) * 10000;  // 计算频率
                last_count = count;
                count = 0;
            }
        }
    }
    
    int main(void) {
        TIM_Config();  // 配置定时器
    
        while (1) {
            // 可以在此处处理频率测量结果
        }
    }
    实例3:PWM信号生成

    定时器可以用于生成PWM信号。下面是一个使用定时器生成PWM信号的示例代码(以AVR单片机为例):

    #include <avr/io.h>
    #include <avr/interrupt.h>
    
    #define PWM_FREQ 1000  // PWM频率(单位:Hz)
    #define PWM_DUTY_CYCLE 75  // PWM占空比(单位:百分比)
    
    void PWM_Init(void) {
        // 设置定时器1为快速PWM模式,8位分辨率
        TCCR1A = (1 << WGM11) | (1 << COM1A1);
        TCCR1B = (1 << WGM13) | (1 << CS11);  // 预分频器设置为8
    
        // 计算定时器的自动重载值,以达到所需的PWM频率
        // 例如,假设系统时钟为16MHz,预分频为8,则定时器时钟为2MHz
        // 8位定时器的最大值为255,因此频率为2MHz / 256 = 7.8125kHz
        // 为了达到1kHz,需要调整自动重载值
        ICR1 = (F_CPU / (PWM_FREQ * 8 * 256)) - 1;
    
        // 设置占空比
        OCR1A = (ICR1 * PWM_DUTY_CYCLE) / 100;
    
        // 设置PB1为输出
        DDRB |= (1 << PB1);
    }
    
    int main(void) {
        PWM_Init();  // 初始化PWM
    
        while (1) {
            // 可以在此处添加其他代码
        }
    }

    代码解释

    1. 定时器配置

    2. TCCR1A 和 TCCR1B 寄存器用于配置定时器1的工作模式和时钟源。
    3. WGM11 和 WGM13 位设置定时器为快速PWM模式,8位分辨率。
    4. COM1A1 位设置为非反转模式,即当计数器值小于 OCR1A 时,输出为高电平.
    5. CS11 位设置预分频器为8,假设系统时钟为16MHz,则定时器时钟为2MHz.
    6. 自动重载值计算

    7. ICR1 寄存器用于设置定时器的最大计数值,以达到所需的PWM频率.
    8. 计算公式为 ICR1 = (F_CPU / (PWM_FREQ * 预分频 * 256)) - 1,其中 F_CPU 是系统时钟频率.
    9. 占空比设置

    10. OCR1A 寄存器用于设置PWM的占空比,计算公式为 OCR1A = (ICR1 * PWM_DUTY_CYCLE) / 100.
    11. 端口配置

    12. DDRB |= (1 << PB1); 将PB1设置为输出模式,以便将PWM信号输出到该引脚.

    通过以上配置,定时器1将生成一个频率为1kHz、占空比为75%的PWM信号,并将其输出到PB1引脚.

    作者:厉昱辰

    物联沃分享整理
    物联沃-IOTWORD物联网 » 一文读懂单片机的定时器

    发表回复