STM32CubeMX第四篇之基本定时器

前言

本文主要介绍使用keil和STM32CubeMX两种工具实现基本定时器功能。

与正点原子的代码相比,本文的代码实现有以下几个特点:

  • 在中断响应中,不再使用回调函数处理,而是直接使用响应函数,且在响应函数中直接操作寄存器,以提高代码的效率。
  • 使用的时钟为基本时钟TIM6。与通用时钟相比,该时钟具有简单易用的特点。

关于基本时钟的更多内容,可以参考博客<STM32F429第十八篇之基本定时器>。
关于正点原子的代码详解,可以参考博客<STM32F429第十九篇之基本定时器实验详解>。

本文使用的HAL库的版本为:STM32Cube_FW_F4_V1.25.0
本文使用的STM32CubeMX版本为:6.1.1

该工程的下载地址为:
keil版本:https://github.com/zhenhaiyang/keil
Cube版本:https://github.com/zhenhaiyang/STM32CUBE

keil

主函数

/**
  ******************************************************************************
  * @file    main.c
  * @author  zhy
  * @version 1.0
  * @date    2021-02-04
  * @brief   实现定时器中断
  ******************************************************************************
  */

#include "stm32f4xx_hal.h"
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "timer.h"

int main()
{
    /* 1.初始化配置 */
    HAL_Init();           //HAL初始化
    SystemClock_Config(); //系统时钟初始化
    LedInit();            //LED初始化
    Tim6Init();           //TIM6初始化

    /* 2.while循环 */
    while (1)
    {
        LED0 = !LED0;   //LED反转
        delay_ms(1000); //延时1秒
    }
}

这是整个系统的主函数,内容十分简单。

  1. 对于使用到的外设初始化。
  2. 操作LED灯反转。

此处需要注意的是,为了不改变库函数,若想要重设中断优先级的组数,可以修改stm32f4xx_hal_msp.c文档。该文档中的函数由用户自定义。其中

/**
  * @brief  Initializes the Global MSP.
  * @param  None
  * @retval None
  */
void HAL_MspInit(void)
{
    /* NOTE : This function is generated automatically by STM32CubeMX and eventually  
            modified by the user
   */
    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);//修改中断组的分配
}

该函数在HAL_Init()中自动调用。

时钟配置

/** 
 * @brief TIM6的初始化
 * @note 无
 * @param {*}无
 * @retval 无
 */
void Tim6Init(void)
{
    /* 1.时钟使能 */
    __HAL_RCC_TIM6_CLK_ENABLE();

    /* 2.参数配置 */
    TIM_HandleTypeDef htim6;
    htim6.Instance = TIM6;                                     //tim6
    htim6.Init.AutoReloadPreload = TIM_AUTOMATICOUTPUT_ENABLE; //自动加载寄存器影子寄存器使能
    htim6.Init.Prescaler = 9000 - 1;                           //预分频系数为9000,分频后的时钟为10KHz
    htim6.Init.Period = 5000 - 1;                              //计数周期为5000,即500ms触发一次中断
    htim6.Lock = HAL_UNLOCKED;                                 //不上锁
    htim6.State = HAL_TIM_STATE_RESET;                         //状态初值
    HAL_TIM_Base_Init(&htim6);

    /* 3.初始化 */
    HAL_TIM_Base_Start_IT(&htim6); //开启基本定时器的中断计时
}

该函数为用户自定义,基本定时器参数配置,重点注意:

  1. 开启自动加载寄存器的影子寄存器。
  2. 设置时钟分频参数。此处需要注意,取值范围为[0-65535] 。我原来想设置为90000,将分频后的时钟为1KHz,但是已经超过了取值范围。
  3. 开启时钟计时。

此处和正点原子代码不同的地方在于,开启了影子寄存器。这并不是正点原子代码的疏忽,而是因为在低版本的程序代码中,没有设置该寄存器的选项。

其余的初始化程序和正点原子大同小异,此处不再赘述。

中断响应

/** 
 * @brief TIM6中断响应函数
 * @note 中断发生时,自动调用
 * @param {*}无
 * @retval 无
 */
void TIM6_DAC_IRQHandler(void)
{
    if ((TIM6->SR | TIM_SR_UIF) && (TIM6->DIER | TIM_DIER_UIE)) //判断中断类别
    {
        TIM6->SR = ~TIM_SR_UIF; //清除中断标记
        LED1 = !LED1;           //功能反转
    }
}

中断的响应函数直接定义在中断服务函数中。主要是因为:

  1. 功能比较简单。
  2. 通过寄存器直接操作,效率比较高。

CUBE

主函数

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_TIM6_Init();
    /* USER CODE BEGIN 2 */

    
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
        /* USER CODE END WHILE */

        LED1 = !LED1;
        delay_ms(1000);
        /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
}

配置函数

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    /** Configure the main internal regulator output voltage
  */
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
    /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 25;
    RCC_OscInitStruct.PLL.PLLN = 360;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 4;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        Error_Handler();
    }
    /** Activate the Over-Drive mode
  */
    if (HAL_PWREx_EnableOverDrive() != HAL_OK)
    {
        Error_Handler();
    }
    /** Initializes the CPU, AHB and APB buses clocks
  */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
    {
        Error_Handler();
    }
}

/**
  * @brief TIM6 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM6_Init(void)
{

    /* USER CODE BEGIN TIM6_Init 0 */

    /* USER CODE END TIM6_Init 0 */

    TIM_MasterConfigTypeDef sMasterConfig = {0};

    /* USER CODE BEGIN TIM6_Init 1 */

    /* USER CODE END TIM6_Init 1 */
    htim6.Instance = TIM6;
    htim6.Init.Prescaler = 8999;
    htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim6.Init.Period = 4999;
    htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
    {
        Error_Handler();
    }

    HAL_TIM_Base_Start_IT(&htim6);
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }
    /* USER CODE BEGIN TIM6_Init 2 */

    /* USER CODE END TIM6_Init 2 */
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* GPIO Ports Clock Enable */
    __HAL_RCC_GPIOH_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0 | GPIO_PIN_1, GPIO_PIN_SET);

    /*Configure GPIO pins : PB0 PB1 */
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

此处列举了三个配置函数,分别与GPIO,RCC以及时钟相关。其生成的代码和自定义的基本一致。
需要注意的是:

  1. 在GPIO初始化中,还开启了GPIOH的时钟,这是因为外部晶振用了其引脚。
  2. 在TIM6配置过程,不会启动计数器,若要启动计数器,还需要自己增加。
/**
  * Initializes the Global MSP.
  */
void HAL_MspInit(void)
{
    /* USER CODE BEGIN MspInit 0 */

    /* USER CODE END MspInit 0 */

    __HAL_RCC_SYSCFG_CLK_ENABLE();
    __HAL_RCC_PWR_CLK_ENABLE();

    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);

    /* System interrupt init*/

    /* USER CODE BEGIN MspInit 1 */

    /* USER CODE END MspInit 1 */
}

/**
* @brief TIM_Base MSP Initialization
* This function configures the hardware resources used in this example
* @param htim_base: TIM_Base handle pointer
* @retval None
*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim_base)
{
    if (htim_base->Instance == TIM6)
    {
        /* USER CODE BEGIN TIM6_MspInit 0 */

        /* USER CODE END TIM6_MspInit 0 */
        /* Peripheral clock enable */
        __HAL_RCC_TIM6_CLK_ENABLE();
        /* TIM6 interrupt Init */
        HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 0, 1);
        HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
        /* USER CODE BEGIN TIM6_MspInit 1 */

        /* USER CODE END TIM6_MspInit 1 */
    }
}

以上代码为自动生成的底层配置。

中断响应

/**
  * @brief This function handles TIM6 global interrupt, DAC1 and DAC2 underrun error interrupts.
  */
void TIM6_DAC_IRQHandler(void)
{
    /* USER CODE BEGIN TIM6_DAC_IRQn 0 */

    /* USER CODE END TIM6_DAC_IRQn 0 */
    if ((TIM6->SR | TIM_SR_UIF) && (TIM6->DIER | TIM_DIER_UIE))
    {
        TIM6->SR = ~TIM_SR_UIF;
        LED0 = !LED0;
    }
    /* USER CODE BEGIN TIM6_DAC_IRQn 1 */

    /* USER CODE END TIM6_DAC_IRQn 1 */
}

类似的,不要使用HAL库自带的程序,直接在中断响应函数中将功能实现,提高代码的运行效率。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值