定时器

定时器(Timer)是一种嵌入式系统中常见的外设(是集成在芯片内部的一组功能模块),通常由一个晶体振荡器提供时钟信号,可以计时一定的时间后执行相应的操作;它是基于硬件计数器的一种机制,通常集成在微控制器(MCU)或处理器中;定时器的基本功能是精确测量时间间隔,或者根据时间周期触发特定任务

在嵌入式系统中,被称为外设(Peripheral)是因为它相对于中央处理器(CPU)的核心执行逻辑来说,是一种辅助功能模块

基本组成

  • 时钟源:定时器依赖于外部时钟或内部时钟源(通常是微控制器的主时钟或分频时钟)来进行计时。
  • 计数器:定时器内部有一个计数器,它根据时钟源的频率进行递增或递减。计数器的大小可以是 8 位、16 位、32 位或其他大小,决定了它的计数范围。
  • 中断机制:定时器通常具有中断功能,当计数器达到设定的值或溢出时,定时器可以触发中断,通知处理器执行特定任务。

工作模式

嵌入式定时器一般有几种常见的工作模式:

  1. 计时/定时模式(Timer Mode)
    • 定时器以固定的频率计数,计数器的值随时间递增。当计数器达到预设值(或溢出)时,产生一个定时中断。这种模式用于周期性任务,例如定时任务、PWM 信号的产生、或周期性事件的触发。
  2. 计数模式(Counter Mode)
    • 定时器可以用作事件计数器,它在某个外部信号(如 GPIO 脉冲输入)发生时进行计数。每检测到一个外部脉冲信号,计数器递增一次。这种模式适合对外部事件进行计数,比如脉冲信号的测量、转速计数等。
  3. 捕获模式(Capture Mode)
    • 定时器在捕获模式下可以记录某个输入信号的变化时刻。比如,当一个外部信号到达时,定时器将当前的计数器值保存起来,从而记录信号发生时的精确时间。捕获模式常用于脉宽测量、外部信号的精确计时等。
  4. 比较模式(Compare Mode)
    • 定时器在比较模式下,当计数器的值与预设的比较值相等时,定时器可以产生中断或者输出一个信号。该模式常用于生成 PWM(脉宽调制)信号或周期性输出信号。

工作原理

定时器启动后,根据配置的模式和速率进行计数。在定时模式下,定时器对内部系统时钟进行计数;在计数模式下,定时器对外部引脚输入的脉冲进行计数。

当定时器的计数值达到其容量上限(如16位定时器的上限为65535)时,会发生溢出。此时,定时器的溢出标志位(如TF0、TF1等)会被硬件置位,并向CPU发出中断请求(如果中断被允许)。

如果CPU允许中断,并且没有其他更高优先级的中断正在处理,那么CPU会响应定时器的中断请求,并跳转到对应的中断服务程序执行。

单片机复位后,程序计数器(PC)从 0000H 单元开始执行程序。另外中断服务程序的入口地址(又称中断向量)也位于程序存储器单元。在程序存储器中,每个中断都有一个固定的入口地址,当中断发生并得到响应后,单片机就会自动跳转到相应的中断入口地址去执行程序。外部中断 0(INTO) 的中断服务程序的入口地址是 0003H,定时器/计数器 0(TIMERO) 中断服务程序的入口地址是 000BH,外部中断 1(INT1) 的中断服务程序的入口地址是 0013H,定时器/计数器 1(TIMER1) 的中断服务程序的入口地址是 001BH 等。

在 51 系列单片机中这些地址由 keil 编译器进行维护;

中断向量表

中断被激活时,硬件会尝试跳转到我们提供的中断处理函数。而硬件怎么找到我们的中断处理函数呢?每种架构的 CPU 都会有一个固定的中断向量表,中断向量表按照中断号顺序存储中断处理函数的函数地址,当发生中断时,硬件会顺序查找对应的函数地址并跳转。

当程序被编译时,编译器会识别出中断服务函数的声明,并根据中断号在生成的代码中将中断服务函数的入口地址放置在中断向量表中对应的位置。中断向量表是一个特殊的存储区域,它记录了所有中断服务函数的入口地址,以便在中断发生时能够快速找到并跳转到相应的中断服务函数执行。

那么咱们写代码时在函数后加 interrupt n;Keil 编译后会将此函数首地址放入对应的寄存器中(中断向量),请问这个中断号与中断向量的对应表在keil 中什么地方配置的呢,或者是在代码中如何实现的呢?这是由 keil 编译器所维护的一组对应关系表,写interrupt n,keil 会自动在中断向量处插入跳转到该函数入口点的 LJMP 单片机中断被允许且发生中断请求事件,CPU由硬件向PC写入中断向量实现中断跳转

LJMP是一种远跳转指令,用于在保护模式或实地址模式下跳转到不同的代码段执行程序。

中断向量不是寄存器,是 FLASH 中的 code 区域靠前的一部分

STC8H

STC8H内置了5个16位定时器:T0,T1,T2,T3,T4;

中断向量表

STC8Hzhongduanxiangliangbiao

实现

使用定时器,控制板载LED高低电平输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// main.c
#include "Config.h"
#include "Timer.h"
#include "GPIO.h"
#include "NVIC.h"

void GPIO_config(void) {

GPIO_InitTypeDef GPIO_Init; //结构定义
GPIO_Init.Pin = GPIO_Pin_3; //指定要初始化的IO,
//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Init.Mode = GPIO_PullUp;

GPIO_Inilize(GPIO_P5, &GPIO_InitStructure); //初始化
}

void TIMER_config(void) {
TIM_InitTypeDef Timer_Init //结构定义

//定时器0做16位自动重装, 中断频率为100000HZ,中断函数从P6.7取反输出50KHZ方波信号.

//指定工作模式,TIM_16BitAutoReload, TIM_16Bit, TIM_8BitAutoReload, TIM_16BitAutoReloadNoMask
Timer_Init.TIM_Mode = TIM_16BitAutoReload;
//指定时钟源, TIM_CLOCK_1T,TIM_CLOCK_12T,TIM_CLOCK_Ext
Timer_Init.TIM_ClkSource = TIM_CLOCK_1T;
//是否输出高速脉冲, ENABLE或DISABLE,如果配置ENABLE,则P3.5端口会同步输出时钟脉冲
Timer_Init.TIM_ClkOut = DISABLE;
//初值,指定Timer频率 1000hz (每秒执行1000次,每次1ms(周期))
//不要小于367hz (2.7ms周期)
//不要大于1 000 000hz 一百万 (1us周期)
Timer_Init.TIM_Value = 65536UL - (MAIN_Fosc / 1000UL);
//是否初始化后启动定时器, ENABLE或DISABLE
Timer_Init.TIM_Run = ENABLE;

//初始化Timer0 Timer0,Timer1,Timer2,Timer3,Timer4
Timer_Inilize(Timer0,&TIM_InitStructure);

//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
NVIC_Timer0_Init(ENABLE,Priority_0);
}

void main(){
GPIO_config();
TIMER_config();
// 开启全局中断
EA = 1;

P53 = 0; // 熄灯

while(1);
}

=============================================================================
// Timer_lsr.c
// 函数: Timer0_ISR_Handler
// 描述: Timer0中断函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2020-09-23
//========================================================================
void Timer0_ISR_Handler (void) interrupt TMR0_VECTOR //进中断时已经清除标志
{
// TODO: 在此处添加用户代码
P53 = ~P53;
}

配置

工作模式

工作模式指的是计数方式,timer的计数是在主频计数的基础上,来进行数数的。timer有16位的计数器,通过计数器来计数来确定定时器运行的时长,在关键位置触发定时中断。

  • 16位自动重装载模式:可以被设置成定时或者计数两种模式,每当定时器溢出时就会触发中断或者输出信号。
  • 16位不可重装载模式:计数值达到设定值后,定时器就会停止计数,需要重新初始化才能继续计数。
  • 8位自动重装载模式:8位计数器溢出时触发中断或输出信号。
  • 不可屏蔽中断的16位自动重装载模式:16位计数器溢出时触发中断或输出信号,并且可以通过软件或硬件方式清除定时器计数器的值。

通常使用16位自动重装载模式.

时钟源

可配置的是重要有两个:说白了就是如何去数数

  • 1T: 跟随主频:每个时钟频率加一,速度是传统 8051 单片机的 12 倍
  • 12T: 进行12分频:每 12 个时钟频率加一,与传统 8051 单片机相同

初值

从初值开始数数,数多久触发一次中断;

中断使能

中断配置是为了打开中断开关的,从而可以触发中断回调的,如果不配置,将无法触发中断回调。

问题

一、在定时器计算中为什么是 65536ul 减一,而不是 16 位的最大值 65535 减一

定时器的溢出原理:定时器计数器的工作原理是从一个预设的初始值开始,递增至最大计数值后发生溢出,随即重新开始从 0 计数。对于一个 16 位定时器,其最大计数范围是 0 到 65535,共有 65536 个可能的计数值。

在设置定时器预加载值时,选择 65536 作为基准是基于以下原因:

  • 从 0 计数:定时器从 0 开始计数,达到预设值后回到 0。如果定时器从预设值开始计数,并且计数到 65535 之后发生溢出回到 0,则整个周期实际上包含了 65536 个计数步骤。
  • 完整周期:使用 65536 作为基数,允许定时器完整地经历从预设值到 65535 的所有计数状态,确保溢出时计数周期的完整性。

从 0 开始计数到达 16 位计数器的最大值 65535 之后发生溢出回到 0 ,然后在开始重新计数;也就是说加到 65535 之后还要再加一,才是一个完整的周期,也就是 65536 个数;