前言
本文主要介绍使用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秒
}
}
这是整个系统的主函数,内容十分简单。
- 对于使用到的外设初始化。
- 操作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); //开启基本定时器的中断计时
}
该函数为用户自定义,基本定时器参数配置,重点注意:
- 开启自动加载寄存器的影子寄存器。
- 设置时钟分频参数。此处需要注意,取值范围为[0-65535] 。我原来想设置为90000,将分频后的时钟为1KHz,但是已经超过了取值范围。
- 开启时钟计时。
此处和正点原子代码不同的地方在于,开启了影子寄存器。这并不是正点原子代码的疏忽,而是因为在低版本的程序代码中,没有设置该寄存器的选项。
其余的初始化程序和正点原子大同小异,此处不再赘述。
中断响应
/**
* @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; //功能反转
}
}
中断的响应函数直接定义在中断服务函数中。主要是因为:
- 功能比较简单。
- 通过寄存器直接操作,效率比较高。
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以及时钟相关。其生成的代码和自定义的基本一致。
需要注意的是:
- 在GPIO初始化中,还开启了GPIOH的时钟,这是因为外部晶振用了其引脚。
- 在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库自带的程序,直接在中断响应函数中将功能实现,提高代码的运行效率。