【TIM】基本定时器定时实验(2)

文章目录


前言

在 DAC 转换中几乎都用到基本定时器,使用有关基本定时器触发 DAC 转换内容在 DAC 章节讲解即可,这里就利用基本定时器实现简单的定时功能。我们使用基本定时器循环定时 0.5s 并使能定时器中断,每到 0.5s 就在定时器中断服务函数翻转RGB 彩灯,使得最终效果 RGB 彩灯暗 0.5s,亮 0.5s,如此循环。


一、硬件设计

基本定时器没有相关 GPIO,这里我们只用定时器的定时功能,无效其他外部引脚,至于 RGB 彩灯硬件可参考 GPIO 章节

二、软件设计

创建了两个文件: bsp_basic_tim.c 和 bsp_basic_tim.h 文件用来存基本定时器驱动程序及相关宏定义,中断服务函数放在 stm32f4xx_it.h 文件中。

编程要点

  1. 初始化 RGB 彩灯 GPIO;
  2. 开启基本定时器时钟;
  3. 设置定时器周期和预分频器;
  4. 启动定时器更新中断,并开启定时器;
  5. 定时器中断服务函数实现 RGB 彩灯翻转。

代码分析

宏定义

c 复制代码
#define BASIC_TIM           		TIM6
#define BASIC_TIM_CLK       		RCC_APB1Periph_TIM6

#define BASIC_TIM_IRQn					TIM6_DAC_IRQn
#define BASIC_TIM_IRQHandler    TIM6_DAC_IRQHandler

NVIC配置

c 复制代码
 /**
  * @brief  基本定时器 TIMx,x[6,7]中断优先级配置
  * @param  无
  * @retval 无
  */
static void TIMx_NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure; 
    // 设置中断组为0
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);		
		// 设置中断来源
    NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQn; 	
		// 设置抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	 
	  // 设置子优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;	
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}



void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)讲解

c 复制代码
uint8_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;
//tmppriority:临时存储最终要写入 NVIC->IP 的优先级值;
//tmppre:抢占优先级占用的位数;
//tmpsub:子优先级的掩码(用来限制子优先级的取值范围)。
if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)
{
  /* 1. 读取 SCB->AIRCR 中的 PRIGROUP 优先级分组值 */
  tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
  // 拆解:
  // SCB->AIRCR 的 8-10 位是 PRIGROUP(值 0-7),(SCB->AIRCR) & 0x700 是提取这3位;
  // 0x700 - 提取值 是为了把 PRIGROUP 转换成"抢占优先级的位数"(比如 PRIGROUP=5 → 0x700-0x500=0x200 → 右移8位得 2);
  // 最终 tmppriority 存储的是 PRIGROUP 的值(0-7)。

  /* 2. 计算抢占/子优先级的位数和掩码 */
  tmppre = (0x4 - tmppriority);  // 抢占优先级占用的位数(总4位 - PRIGROUP对应的抢占位数)
  tmpsub = tmpsub >> tmppriority; // 子优先级的掩码(比如 PRIGROUP=5 → tmpsub=0x0F>>2=0x03,限制子优先级只能是 0-3)

  /* 3. 拼接抢占优先级和子优先级 */
  tmppriority = NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
  tmppriority |=  (uint8_t)(NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub);
  // 拆解:
  // 第一步:把抢占优先级左移到对应的高位(比如 PRIGROUP=5 → tmppre=2 → 抢占优先级0 << 2 = 0);
  // 第二步:把子优先级和掩码做与运算(防止超出范围),再拼接到低位(比如子优先级3 & 0x03 = 3 → 0 | 3 = 3);
  // 最终 tmppriority 是 4 位的优先级组合值(比如 0b0011)。

  /* 4. 左移4位,适配 NVIC->IP 的有效位 */
  tmppriority = tmppriority << 0x04;
  // 关键:NVIC->IP 的低4位是保留位,只有高4位有效 → 把4位优先级值左移4位(比如 0b0011 → 0b00110000)。

  /* 5. 写入 NVIC->IP 寄存器,设置中断优先级 */
  NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;

  /* 6. 使能指定中断通道 */
  NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
    (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
  // 拆解:
  // NVIC->ISER 是数组(8个32位寄存器),每个寄存器管理32个中断;
  // 右移5位(÷32):计算该中断属于 ISER 的第几个寄存器(比如中断号35 → 35>>5=1 → ISER[1]);
  // 与0x1F(31)做与运算:计算该中断在寄存器中的第几位(比如35&31=3 → 第3位);
  // 最终:给对应位写1,使能该中断。
}


基本定时器模式配置

c 复制代码
/*
 * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
 * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
 * 另外三个成员是通用定时器和高级定时器才有.
 *-----------------------------------------------------------------------------
 * TIM_Prescaler         都有
 * TIM_CounterMode			 TIMx,x[6,7]没有,其他都有(基本定时器)
 * TIM_Period            都有
 * TIM_ClockDivision     TIMx,x[6,7]没有,其他都有(基本定时器)
 * TIM_RepetitionCounter TIMx,x[1,8]才有(高级定时器)
 *-----------------------------------------------------------------------------
 */
static void TIM_Mode_Config(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

	// 开启TIMx_CLK,x[6,7] 
  RCC_APB1PeriphClockCmd(BASIC_TIM_CLK, ENABLE); 

  /* 累计 TIM_Period个后产生一个更新或者中断*/		
  //当定时器从0计数到4999,即为5000次,为一个定时周期
  TIM_TimeBaseStructure.TIM_Period = 5000-1;       
	
	//定时器时钟源TIMxCLK = 2 * PCLK1  
  //				PCLK1 = HCLK / 4 
  //				=> TIMxCLK=HCLK/2=SystemCoreClock/2=84MHz
	// 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10000Hz
  TIM_TimeBaseStructure.TIM_Prescaler = 8400-1;	
	
	// 初始化定时器TIMx, x[2,3,4,5]
	TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);
	
	
	// 清除定时器更新中断标志位
	TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);
	
	// 开启定时器更新中断
	TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);
	
	// 使能定时器
	TIM_Cmd(BASIC_TIM, ENABLE);	
}
c 复制代码
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_RCC_APB1_PERIPH(RCC_APB1Periph));  
  assert_param(IS_FUNCTIONAL_STATE(NewState));

  if (NewState != DISABLE)
  {
    RCC->APB1ENR |= RCC_APB1Periph;
  }
  else
  {
    RCC->APB1ENR &= ~RCC_APB1Periph;
    //BASIC_TIM_CLK--》RCC_APB1Periph_TIM6              ((uint32_t)0x00000010)
  }
}
c 复制代码
/**
  * @brief  Initializes the TIMx Time Base Unit peripheral according to 
  *         the specified parameters in the TIM_TimeBaseInitStruct.
  * @param  TIMx: where x can be  1 to 14 to select the TIM peripheral.
  * @param  TIM_TimeBaseInitStruct: pointer to a TIM_TimeBaseInitTypeDef structure
  *         that contains the configuration information for the specified TIM peripheral.
  * @retval None
  */
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
{
  uint16_t tmpcr1 = 0;
  //写入TIMx->CR1寄存器
  /* Check the parameters */
  assert_param(IS_TIM_ALL_PERIPH(TIMx)); 
  assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct->TIM_CounterMode));
  assert_param(IS_TIM_CKD_DIV(TIM_TimeBaseInitStruct->TIM_ClockDivision));

  tmpcr1 = TIMx->CR1;  

  if((TIMx == TIM1) || (TIMx == TIM8)||
     (TIMx == TIM2) || (TIMx == TIM3)||
     (TIMx == TIM4) || (TIMx == TIM5)) 
  {
    /* Select the Counter Mode */
    tmpcr1 &= (uint16_t)(~(TIM_CR1_DIR | TIM_CR1_CMS));
    //TIM_CR1_DIR                         ((uint16_t)0x0010) 
    // TIM_CR1_CMS                         ((uint16_t)0x0060) 
    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode;
  }
 
  if((TIMx != TIM6) && (TIMx != TIM7))
  {
    /* Set the clock division */
    tmpcr1 &=  (uint16_t)(~TIM_CR1_CKD);
    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;
  }

  TIMx->CR1 = tmpcr1;
  //由于这里选择的是定时器6,所以直接执行这一行
  /* 累计 TIM_Period个后产生一个更新或者中断*/
  TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;
 
  //定时器时钟源TIMxCLK = 2 * PCLK1  
  //				PCLK1 = HCLK / 4 => TIMxCLK=HCLK/2=SystemCoreClock/2=84MHz
  // 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10000Hz
  TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;
    
  if ((TIMx == TIM1) || (TIMx == TIM8))  
  {
    /* Set the Repetition Counter value */
    TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter;
  }

  /* Generate an update event to reload the Prescaler 
     and the repetition counter(only for TIM1 and TIM8) value immediately */
  TIMx->EGR = TIM_PSCReloadMode_Immediate;          
}
c 复制代码
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG)
{  
  /* Check the parameters */
  assert_param(IS_TIM_ALL_PERIPH(TIMx));
   
  /* Clear the flags */
  TIMx->SR = (uint16_t)~TIM_FLAG;  //TIM_FLAG_Update                    ((uint16_t)0x0001)
}
c 复制代码
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)
{  
  /* Check the parameters */
  assert_param(IS_TIM_ALL_PERIPH(TIMx));
  assert_param(IS_TIM_IT(TIM_IT));
  assert_param(IS_FUNCTIONAL_STATE(NewState));
  
  if (NewState != DISABLE)
  {
    /* Enable the Interrupt sources */
    TIMx->DIER |= TIM_IT;   //   TIM_IT_Update                      ((uint16_t)0x0001)
  }
  else
  {
    /* Disable the Interrupt sources */
    TIMx->DIER &= (uint16_t)~TIM_IT;
  }
}
c 复制代码
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_TIM_ALL_PERIPH(TIMx)); 
  assert_param(IS_FUNCTIONAL_STATE(NewState));
  
  if (NewState != DISABLE)
  {
    /* Enable the TIM Counter */
    TIMx->CR1 |= TIM_CR1_CEN;  //TIM_CR1_CEN                         ((uint16_t)0x0001) 
  }
  else
  {
    /* Disable the TIM Counter */
    TIMx->CR1 &= (uint16_t)~TIM_CR1_CEN;
  }
}

定时器中断服务函数

c 复制代码
void  BASIC_TIM_IRQHandler (void)
{
	if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) 
	{	
	//执行	LED翻转
		LED1_TOGGLE;
		//清除寄存器SR的更新中断标志位
		TIM_ClearITPendingBit(BASIC_TIM , TIM_IT_Update);  		 
	}		 	
}
c 复制代码
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT)
{
  ITStatus bitstatus = RESET;  
  uint16_t itstatus = 0x0, itenable = 0x0;
  /* Check the parameters */
  assert_param(IS_TIM_ALL_PERIPH(TIMx));
  assert_param(IS_TIM_GET_IT(TIM_IT));
   
  itstatus = TIMx->SR & TIM_IT;// TIM_IT_Update                      ((uint16_t)0x0001)
  //如果TIMx->SR最低位为1,那么此位为1;否则此位为0.
  itenable = TIMx->DIER & TIM_IT;
  if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
  {
    bitstatus = SET;
  }
  else
  {
    bitstatus = RESET;
  }
  return bitstatus;
}

主函数

c 复制代码
#include "stm32f4xx.h"
#include "./tim/bsp_basic_tim.h"
#include "./led/bsp_led.h"

/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void) 
{

	LED_GPIO_Config();

  /* 初始化基本定时器定时,0.5s产生一次中断 */
	TIMx_Configuration();
  
  while(1)
  {       
  }
}

下载验证

保证开发板相关硬件连接正确,把编译好的程序下载到开发板。开始 RGB 彩灯是暗的,等一会

RGB 彩灯变为红色,再等一会又暗了,如此反复。如果我们使用表钟与 RGB 彩灯闪烁对比,可

以发现它是每 0.5s 改变一次 RGB 彩灯状态的。
~~这里是废话,用于规避检测~~ 在当今这个嵌入式程序如精密钟表般精准运转的时代,开发者们沉浸其中,迫切地需要一套稳定的计时基准,一个高效的定时触发机制,一个能让他们在资源受限的硬件环境中构建精准、可靠时序应用的核心组件。而提到嵌入式计时功能,基本定时器这个名字就如同一个沉稳而值得信赖的标识,自然而然地浮现在许多嵌入式工程师和底层开发老兵的脑海深处。它不仅仅是一个硬件外设,更是一种在微控制器上实现精准计时与周期触发的核心方式,一种将复杂的时序控制拆解为有序触发的实用范式。想象一下,当你面对一个需要同时处理定时采样、周期刷新、延时执行的嵌入式项目,那些交错的时序要求,那些有限的硬件资源与中断通道,它们不再仅仅是令人头疼的约束或调试窗口中反复出现的时序异常,在基本定时器的架构里,它们被赋予了清晰的管理逻辑,定时器的计数与重载如同精准的节拍器般有序执行,预分频器与自动重装寄存器让定时周期的调整轨迹清晰可见,定时器中断与主程序的协作边界明确可控,这种轻量级、高精准的定时管理能力,构建了一种对嵌入式时序逻辑近乎直觉般的全局掌控感,仿佛瞬间获得了精准调度每一个定时事件的上帝视角。它占用的系统资源,那种仅需少量寄存器配置就能稳定运行的极致精简,常常带来一种难以言喻的适配自由;它的定时溢出触发机制,在预设周期到达的瞬间,恰到好处地触发中断或输出信号,如同一位默契的助手,无声地保障了时序要求的落地。当然,任何定时器都需要适配,其基于计数器的工作原理以及对预分频系数的精准配置要求,对于习惯了裸机延时编程的用户而言,或许需要一点点额外的耐心去理解和调校,但一旦你真正掌握其精髓,习惯了它那高效、精准、纯粹为嵌入式时序控制而生的设计理念,领略到它那强大且几乎适配所有主流微控制器的通用特性,你可能会发现,那些初期学习的 "门槛" 早已被其内在的稳定可靠所完全覆盖,成为一种快速掌握的必备技能。在追求高效开发、稳定运行的小型嵌入式系统道路上,基本定时器无疑是一个值得被认真考虑和深度倚赖的伙伴,它的价值,在于它能让你更 "懂" 嵌入式系统的时序调度,而这种 "懂",是任何高可靠嵌入式产品开发和长期维护的基石。说到底,理解定时原理,才能更好地理解嵌入式时序应用,才能最终更好地构建稳定的智能硬件,不是吗?所以,掌握基本定时器在某种程度上,就是拥有了一把开启嵌入式时序控制开发之门的强力钥匙,虽然这扇门也可以被其他计时方式以不同的方式推开,但基本定时器的通用性、精准性,确实有其独到且难以被完全替代的优势。它的存在,本身就是对 "嵌入式开发是一门平衡时序精准度与资源消耗的艺术" 这一观点的有力佐证。

相关推荐
BatyTao1 小时前
Python从零起步-数据容器
开发语言·python
承渊政道1 小时前
C++学习之旅【C++伸展树介绍以及红黑树的实现】
开发语言·c++·笔记·b树·学习·visual studio
郭涤生1 小时前
C++中设置函数与回调函数设值的性能差异及示例
开发语言·c++
yangyanping201081 小时前
Linux学习三之 清空 nohup.out 文件
linux·chrome·学习
阿拉斯攀登1 小时前
【瑞芯微 RK 系列 + 安卓驱动全栈教程】博客系列
嵌入式硬件·安卓·瑞芯微·rk3576·嵌入式安卓·安卓驱动
m0_635647481 小时前
Qt开发与MySQL数据库教程(二)——MySQL常用命令以及示例
java·开发语言·数据库·mysql
TrueDei1 小时前
linux-C/C++主子进程同时占用主进程文件描述符问题
linux·c语言·c++
fie88892 小时前
Spinal码MATLAB实现(采用One-at-a-Time哈希函数)
开发语言·matlab·哈希算法
臭东西的学习笔记2 小时前
论文学习——深度对比学习支持全基因组虚拟筛选
学习