timer
定时器简介
STM32微控制器提供了多种类型的定时器,以满足不同应用场景的需求。以下是STM32定时器的基本介绍、分类、功能特点及应用示例: 本篇主要是基本定时器(Basic Timer)的编程与配置,PWM等功能放在独立的章节中讲解。
基本分类
高级定时器(Advanced Timer)
- 典型型号:TIM1和TIM8(在某些高配型号中)
- 特点:
- 16位向上/向下计数器
- 多个捕获/比较通道(通常为4个或更多),用于多路PWM输出或信号捕获
- 互补输出(死区控制),适合无刷直流电机(BLDC)等应用
- DMA支持,用于在定时事件发生时无需CPU干预的数据传输
- 同步功能,可以与其他定时器或外部信号同步计数
- 应用:复杂的PWM生成、电机控制、多通道同步操作等
通用定时器(General Purpose Timer)
- 典型型号:TIM2、TIM3、TIM4、TIM5等
- 特点:
- 16位计数器
- 多个捕获/比较通道(通常为4个),支持PWM输出、输入捕获、脉冲宽度测量等
- 多种计数模式(向上计数、向下计数、中心对齐等)
- 中断与DMA支持
- GPIO关联,可以直接驱动GPIO进行PWM输出或边沿检测
- 应用:通用定时任务、信号频率测量、PWM输出、简单电机控制等
基本定时器(Basic Timer)
- 典型型号:TIM6和TIM7
- 特点:
- 功能简化,仅有计数器和定时中断功能
- 不支持输入捕获或PWM输出
- 适用于需要精确定时中断但无需复杂输出控制的应用
- 应用:周期性任务调度、简单延时、DAC触发信号等
通用功能与特性
时基单元
- 预分频器(PSC):对输入时钟进行分频,以设置合适的计数频率。
- 计数器(CNT):递增(或递减)计数,直到达到预设值后溢出。
- 自动重装载寄存器(ARR):设定计数器溢出时的重置值,用于确定定时周期。
时钟源
- 可选择内部时钟源(如HSI、LSI、HSE、LSE等)或外部时钟源。
- 经过AHB/APB总线预分频后供给定时器。
中断与DMA
- 支持各种定时器事件触发中断,如溢出、更新、捕获/比较匹配等。
- DMA请求可减少CPU干预,提高数据处理效率。
GPIO关联
- 通用定时器和高级定时器通过通道映射至GPIO引脚,实现PWM输出、边沿检测等功能。
应用示例
定时中断:设置定时器周期性溢出,触发中断服务程序执行周期性任务或更新状态。
脉宽调制(PWM):配置定时器比较通道,生成不同占空比的PWM波形,用于电机速度控制、LED亮度调节等。
信号捕获与测量:利用输入捕获功能捕捉外部信号的上升沿、下降沿或特定电平变化,用于测量信号频率、周期、脉冲宽度等。
同步操作:多个定时器之间或与外部事件同步计数,实现复杂的同步控制逻辑。
编程与配置
STM32定时器可以通过直接操作寄存器或使用STM32官方提供的HAL库、LL库等进行配置和编程。使用库函数可以简化开发过程,提供更高级别的抽象和统一的接口。编程步骤通常包括:
时钟使能:通过RCC(Reset and Clock Control)寄存器或库函数启用定时器所需的时钟源。
定时器初始化:配置定时器工作模式(向上计数、向下计数、中心对齐等)、预分频系数、自动重装载值、捕获/比较通道参数等。
中断/DMA配置:如果使用中断,需在NVIC(Nested Vectored Interrupt Controller)中设置优先级并使能中断;如果使用DMA,需配置DMA通道、传输参数等。
启动定时器:使能定时器计数,并在必要时启动捕获/比较通道。
中断服务程序编写:当定时器事件发生时,编写相应的中断服务程序来处理定时任务或捕获数据。
示例代码
/* -----------------------------------------
经过查看Data Sheet知道stm32f103c8t6
仅仅支持
TIM1/TIM8,TIM2~5
-----------------------------------------*/
#include "reg_tim.h"
// --------------------------------------
// 系统时钟SysClock = 72,000,000Hz的情况
void reg_tim2_init(void)
{
//TIM2时钟开启
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
TIM2->PSC = 7200 - 1; //0.1ms
TIM2->ARR = 5000; //0.5s
//ARPE=1,UDIS=0,CEN=1,CNT_EN=1
TIM2->CR1 |= TIM_CR1_ARPE; //reload
TIM2->CR1 &= ~TIM_CR1_CMS; //Edge-aligned mode
TIM2->CR1 |= TIM_CR1_DIR; //downcounter
//配置
//定时器使能
TIM2->CR1 |= TIM_CR1_CEN;
}
// --------------------------------------
// Time2中断配置
#define NVIC_ADDR 0x08000000 //Flash Addr
#define NVIV_OFFSET 0x0000 //偏移 = 0x200 * N
void reg_time2_intr_init(void)
{
//--------------------------------------------
// NVIC 配置
//--------------------------------------------
//配置中断向量表,Flash
SCB->VTOR = NVIC_ADDR | (NVIV_OFFSET & (uint32_t)0x1FFFFF80);
// 配置中断优先级,注意STM32用的是高4bit
// 配置抢占式优先级和响应式优先级各自占2bit
SCB->AIRCR |= 0x05FA0000 | 0x500;
// 配置抢占式中段级别为0,响应式中断级别为1
NVIC->IP[TIM2_IRQn] = 0x10;
//使能中断
NVIC->ISER[TIM2_IRQn >> 5] |= 1 << (TIM2_IRQn & 0x1f);
//清除中断标记
TIM2->SR &= ~TIM_SR_UIF;
//开启TIM2中断
TIM2->DIER |= TIM_DIER_UIE; // | TIM_DIER_TIE;
}
uint8_t flip_flag = 0;
void tim_intr_demo(void)
{
reg_gpio_init();
reg_uart_init();
//----------------------------------
// 感觉配置没问题,Debug的时候手动配置UIF,可以进入中断。
// 但程序自己运行,硬件中断挂起感觉一直没有作用。
// 后来调整了一下顺序,先配置tim,再开启中断。成功!!!
//----------------------------------
reg_tim2_init();
reg_time2_intr_init();
reg_uart_sendstr("tim2 init finished!\r\n",0);
while(1)
{
if(0 != flip_flag)
{
flip_flag = 0;
reg_uart_sendstr("tim2 irq_handle!\r\n",0);
}
}
}
void TIM2_IRQHandler(void)
{
// -------------------------------------
// 这里之所以不能按照预期产生闪烁效果
// 我认为是该中断函数的进入不仅仅有TIM_SR_UIF中断
// 还有其他情况下的中断我没有考虑到。
// 因此在处理各种不同功能的时候,要先判断其中断标志
// 然后再进行相应的操作
// --------------------------------------
// LED1_FLIP;
if (TIM2->SR & TIM_SR_UIF)
{
flip_flag = 1;
LED1_FLIP;
//清除相应的中断标识
TIM2->SR &= ~TIM_SR_UIF;
}
}
#include "stm32f10x_conf.h"
void timer_init(void)
{
TIM_TimeBaseInitTypeDef timerConfig;
// 0.使能定时器时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
// 1.定时器基础配置
TIM_TimeBaseStructInit(&timerConfig);
timerConfig.TIM_Period = 5000 - 1; //因此一个计数代表着0.1ms
timerConfig.TIM_Prescaler = 7200; //CoreClock is 72000000 = 72MHz
timerConfig.TIM_CounterMode = TIM_CounterMode_Up; //注意:无论向上或者向下计数,范围都是 0-自动加载值
TIM_TimeBaseInit(TIM2,&timerConfig);
//2. 中断配置
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除中断标志
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); // 定时器中断使能
//3. 使能定时器
TIM_Cmd(TIM2,ENABLE); // 开启定时器
}
//定时器中断配置
void timer_intr_init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
//配置中断向量表地址
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0000);
//指定抢占优先级为两位,响应优先级为2位
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//配置TIM2,抢占优先级为0,响应优先级为1
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
uint8_t sendflag = 0;
void timer_demo(void)
{
timer_init();
timer_intr_init();
while(1)
{
}
}
void TIM2_IRQHandler(void)
{
//当发生TIM2中断的时候
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
//中断处理
static int a = 0;
if(a == 0)
{
a = 1;
LED_OFF();
}
else
{
a = 0;
LED_ON();
sendflag = 1;
}
}
//清除中断标志
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}