欢迎光临
我们一直在努力

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

文章目录

  • 前言
  • 一、硬件设计
  • 二、软件设计
    • 编程要点
    • 代码分析
      • 宏定义
      • NVIC配置
      • 基本定时器模式配置
      • 定时器中断服务函数
      • 主函数
      • 下载验证

前言

在 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 彩灯翻转。
  • 代码分析

    宏定义

    #define BASIC_TIM TIM6
    #define BASIC_TIM_CLK RCC_APB1Periph_TIM6

    #define BASIC_TIM_IRQnTIM6_DAC_IRQn
    #define BASIC_TIM_IRQHandler TIM6_DAC_IRQHandler

    NVIC配置

    /**
    * @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)讲解

    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,使能该中断。
    }

    在这里插入图片描述

    在这里插入图片描述 在这里插入图片描述

    基本定时器模式配置

    /*
    * 注意: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 = 50001;

    //定时器时钟源TIMxCLK = 2 * PCLK1
    //PCLK1 = HCLK / 4
    //=> TIMxCLK=HCLK/2=SystemCoreClock/2=84MHz
    // 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10000Hz
    TIM_TimeBaseStructure.TIM_Prescaler = 84001;

    // 初始化定时器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);
    }

    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)
    }
    }

    在这里插入图片描述

    /**
    * @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;
    }

    在这里插入图片描述

    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)
    }

    在这里插入图片描述

    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;
    }
    }

    在这里插入图片描述

    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;
    }
    }

    在这里插入图片描述

    定时器中断服务函数

    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);
    }
    }

    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;
    }

    在这里插入图片描述

    主函数

    #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 彩灯状态的。

    赞(0)
    未经允许不得转载:171主机测评 » 【TIM】基本定时器定时实验(2)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址