STM32通用定时器(HAL库)定时中断、PWM呼吸灯
定时器和延时函数的区别
通用定时器结构
时钟信号:
触发控制器选择时钟信号:
时基单元:
基本定时器已经讲过,下面节选字stm32cubemx的教程基础篇
捕获/比较通道
定时器的捕获/比较通道可以用于多种应用,包括测量信号的频率、脉宽调制(PWM)信号的生成和控制等。
输入捕获:用于捕获外部信号的事件时间,可用于测量频率和脉宽。
输出比较:用于生成特定的PWM信号或控制外部设备(如伺服电机)。
捕获:
通过检测边沿信号,在边沿信号跳转时,将定时器的TIMx_CNT的值放到TIMx_CCR(捕获/比较寄存器)里。
输入捕获模式
当外部信号沿(上升沿或下降沿)到达时,当前计数器的值会被捕获并存储在相应的CCR寄存器中。这可以用来计算信号的频率、脉宽等信息。可以用来测量脉冲宽度或者测量频率。
t1-t2 时间就是我们需要测量的高电平时间,假如定时器工作在向上计数模式,测量方法是:首先设置定时器通道 x 为上升沿捕获,这样在 t1 时刻,就会捕获到当前的 CNT 值,然后立即清零 CNT,并设置通道 x 为下降沿捕获,这样到 t2 时刻,又会发生捕获事件,得到此时的 CNT 值,记为 CCRx2。根据定时器的计数频率,我们就可以算出 t1-t2 的时间,从而得到高电平脉宽。在 t1-t2 时间内可能会出现 N 次定时器溢出,因此我们还需要对定时器溢出进行处理,防止因高电平时间过长发生溢出导致测量数据不准。CNT计数的次数等于:N*ARR+CCRx2,有了这个计数次数,再乘以 CNT 的计数周期,即可得到 t2-t1 的时间长度,即高电平持续时间。
PWM输入模式
在PWM模式下,TIMx_CCR寄存器的值决定了输出信号的高电平持续时间,即占空比。
设定比较值后,当计数器的值达到该比较值时,触发相应的事件,比如切换输出引脚状态。
比较
计数器的值和与装载的值进行比较。
在PWM模式下,TIMx_CCR寄存器的值决定了输出信号的高电平持续时间,即占空比。改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的频率。
设定比较值后,当计数器的值达到该比较值时,触发相应的事件,比如切换输出引脚状态。
相关HAL函数
初始化
HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim);
HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim)
{
/* 检查定时器句柄是否分配 */
if (htim == NULL)
{
return HAL_ERROR;
}
/* 检查参数的有效性 */
assert_param(IS_TIM_INSTANCE(htim->Instance));
assert_param(IS_TIM_COUNTER_MODE(htim->Init.CounterMode));
assert_param(IS_TIM_CLOCKDIVISION_DIV(htim->Init.ClockDivision));
assert_param(IS_TIM_AUTORELOAD_PRELOAD(htim->Init.AutoReloadPreload));
if (htim->State == HAL_TIM_STATE_RESET)
{
/* 分配锁资源并进行初始化 */
htim->Lock = HAL_UNLOCKED;
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
/* 将中断回调重置为传统的弱回调函数 */
TIM_ResetCallback(htim);
if (htim->PWM_MspInitCallback == NULL)
{
htim->PWM_MspInitCallback = HAL_TIM_PWM_MspInit;
}
/* 初始化底层硬件:GPIO、时钟、NVIC */
htim->PWM_MspInitCallback(htim);
#else
/* 初始化底层硬件:GPIO、时钟、NVIC 及 DMA */
HAL_TIM_PWM_MspInit(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
/* 设置定时器状态 */
htim->State = HAL_TIM_STATE_BUSY;
/* 初始化PWM的基本时间 */
TIM_Base_SetConfig(htim->Instance, &htim->Init);
/* 初始化DMA突发操作状态 */
htim->DMABurstState = HAL_DMA_BURST_STATE_READY;
/* 初始化定时器通道状态 */
TIM_CHANNEL_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);
TIM_CHANNEL_N_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);
/* 初始化定时器状态 */
htim->State = HAL_TIM_STATE_READY;
return HAL_OK;
}
HAL_TIM_PWM_DeInit(TIM_HandleTypeDef *htim);
反初始化定时器PWM功能,将定时器的设置重置为其默认状态,并释放相关资源。
HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim, TIM_OC_InitTypeDef *sConfig, uint32_t Channel);
sConfig为指向 PWM 配置结构体的指针,该结构体中包含了 PWM 模式下的输出比较初始化参数。这些参数通常包括:
Pulse: 定义在一个计数周期内的脉冲宽度(即占空比)。
OCMode: 定义输出比较模式(如 PWM 模式 1 或 2)。
Polarity: 定义输出的极性(如高或低有效)。
FastMode: 加速模式的启用/禁用。
OCFastMode: 是否启用快速模式。
操作函数
HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
启动PWM输出。
HAL_StatusTypeDef HAL_TIM_PWM_Stop(TIM_HandleTypeDef *htim, uint32_t Channel);
停止PWM输出。
HAL_StatusTypeDef HAL_TIM_PWM_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
启动PWM输出并使能中断。
HAL_StatusTypeDef HAL_TIM_PWM_Stop_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
停止PWM输出并禁用中断。
HAL_TIM_PWM_SetCompare(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t CompareValue);
设置PWM通道的比较值(占空比),以动态调整PWM输出。
HAL_TIM_OCxPreloadConfig(TIM_HandleTypeDef *htim, uint32_t Channel, FunctionalState NewState);
用于配置输出比较通道的预装载功能,这个功能可以增强PWM信号的稳定性和一致性。OCx 代表输出比较通道(如 OC1、OC2 等),其中 x 是通道的编号。
HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
当 PWM 脉冲完成时调用的回调函数,允许用户执行自定义操作。
输入捕获
HAL_TIM_IC_Init(&htim);
初始化定时器的输入捕获功能。
HAL_TIM_IC_ConfigChannel(TIM_HandleTypeDef *htim, TIM_IC_InitTypeDef *sConfig, uint32_t Channel)
TIM_IC_InitTypeDef sConfigIC;
sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING; // 设置捕获极性
sConfigIC.ICSelection = TIM_IC_SELECTION_DIRECTTI; // 直接连接
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; // 不分频
sConfigIC.ICFilter = 0; // 无滤波
HAL_TIM_IC_ConfigChannel(&htim, &sConfigIC, TIM_CHANNEL_1);
HAL_TIM_IC_ConfigChannel(&htim, &sConfigIC, TIM_CHANNEL_1);
HAL_TIM_IC_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
启动指定的输入捕获通道。
HAL_TIM_IC_Stop(TIM_HandleTypeDef *htim, uint32_t Channel);
停止指定的输入捕获通道。
HAL_TIM_IC_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
HAL_TIM_IC_Stop_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
启动\停止输入捕获通道并使能中断。
HAL_TIM_IC_GetState(TIM_HandleTypeDef *htim);
获取输入捕获通道的状态。
HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
输入捕获事件发生时调用的回调函数,用户可以在这里处理捕获数据。
PWM输入
PWI模式(输入捕获的特例)是专为捕获PWM信号设计的输入捕获模式。在该模式下,定时器的两个输入捕获通道(通常为CH1和CH2)协同工作,通过捕获PWM信号的两个不同特征(通常是上升沿和下降沿)来实现对信号频率和占空比的测量。
CH1通常用于捕获PWM信号的上升沿,以此来测量信号的周期。
CH2则用于捕获PWM信号的下降沿,用于计算占空比。
HAL库的定时器中断结构
HAL_TIM_IRQHandler()框架,根据不同的中断标志位和中断事件调用不同的中断回调函数。
定时器同步
参考下文介绍
多个定时器同步输出的主从配置示例 – STM32团队 ST意法半导体中文论坛 (stmicroelectronics.cn)
生成PWM波和定时中断
PWM原理
基础设置
配置Timer的时钟信号
将TIM2配置基本定时功能,
设置NVIC
配置PWM输出
设置参数
设置PWM通道的GPIO配置
代码
定时器的函数结构
main.c
HAL_Delay()改变了PWM 占空比调节的速度
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2024 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){//定时器中断回调函数
if(htim==(&htim2))//判断产生中断的定时器
{
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);//LED状态反转
}
}
/* USER CODE END 0 */
/**
* @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_TIM2_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4);
uint16_t a=0;//定义占空比变量并给出初始值
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
while(a < 500){
a++;
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_4,a);//设置占空比函数(参数3是PWM比值,范围0~ARR计数周期)
HAL_Delay(10);
}
while(a)
{
a--;
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_4,a);
HAL_Delay(10);
}
/* 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_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != 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_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
tim.c
/**
******************************************************************************
* @file tim.c
* @brief This file provides code for the configuration
* of the TIM instances.
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2024 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "tim.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;
/* TIM2 init function */
void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 9999;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 7199;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
/* TIM3 init function */
void MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 499;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_MspPostInit(&htim3);
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspInit 0 */
/* USER CODE END TIM2_MspInit 0 */
/* TIM2 clock enable */
__HAL_RCC_TIM2_CLK_ENABLE();
/* TIM2 interrupt Init */
HAL_NVIC_SetPriority(TIM2_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
/* USER CODE BEGIN TIM2_MspInit 1 */
/* USER CODE END TIM2_MspInit 1 */
}
else if(tim_baseHandle->Instance==TIM3)
{
/* USER CODE BEGIN TIM3_MspInit 0 */
/* USER CODE END TIM3_MspInit 0 */
/* TIM3 clock enable */
__HAL_RCC_TIM3_CLK_ENABLE();
/* TIM3 interrupt Init */
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
/* USER CODE BEGIN TIM3_MspInit 1 */
/* USER CODE END TIM3_MspInit 1 */
}
}
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(timHandle->Instance==TIM3)
{
/* USER CODE BEGIN TIM3_MspPostInit 0 */
/* USER CODE END TIM3_MspPostInit 0 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/**TIM3 GPIO Configuration
PB1 ------> TIM3_CH4
*/
GPIO_InitStruct.Pin = LED4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(LED4_GPIO_Port, &GPIO_InitStruct);
/* USER CODE BEGIN TIM3_MspPostInit 1 */
/* USER CODE END TIM3_MspPostInit 1 */
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspDeInit 0 */
/* USER CODE END TIM2_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_TIM2_CLK_DISABLE();
/* TIM2 interrupt Deinit */
HAL_NVIC_DisableIRQ(TIM2_IRQn);
/* USER CODE BEGIN TIM2_MspDeInit 1 */
/* USER CODE END TIM2_MspDeInit 1 */
}
else if(tim_baseHandle->Instance==TIM3)
{
/* USER CODE BEGIN TIM3_MspDeInit 0 */
/* USER CODE END TIM3_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_TIM3_CLK_DISABLE();
/* TIM3 interrupt Deinit */
HAL_NVIC_DisableIRQ(TIM3_IRQn);
/* USER CODE BEGIN TIM3_MspDeInit 1 */
/* USER CODE END TIM3_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
也可以在回调函数里写:
这个函数实现了按脉宽递增或递减方向修改CCR 的值,因为开启了CCR 预装载功能,所以新设置的CCR的值在下一个UEV事件时才生效。
可以观察到LED1由明到暗,再由暗到明的循环往复变化效果。
作者:zhi_beiyou