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