一STM32定时器
(1)定时器
在stm32单片中,定时器是存在于stm32中的一个外设。分别有高级定时器、通用定时器、基本定时器,也有些型号的单片机没有高级定时器,具体要去查看芯片手册。
(2)通用定时器计数模式
- 向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
- 向下计数模式:计数器自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并且产生一个计数器向下溢出事件。
- 中央对齐模式:计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1,并且产生一个计数器溢出事件;然后再从0开始重新计数。
(3)原理框图
- 时基单元
1.计数器寄存器(TIMx_CNT) :计数器由预分频器的时钟输出 CK_CNT 驱动,仅当设置了计数器 TIMX_CR1 寄存器中的计数器使能位(CEN)时,CK_CNT 才有效。当计数器达到溢出条件(向下计数时的下溢条件)并当
TIMX_CR1 寄存器中的 UDIS 位等于 0 时,产生更新事件。
2.预分频器寄存器 (TIMx_PSC):预分频器可以将计数器的时钟频率按 1 到 65536 之间的任意值分频。它是基于一个(在 TIMx_PSC 寄存器中的)16 位寄存器控制的 16 位计数器。因为这个控制寄存器带有缓冲器,它能够在工作时被改变。新的预分频器的参数在下一次更新事件到来时被采用。
3.自动装载寄存器 (TIMx_ARR):自动装载寄存器是预先装载的。根据在 TIMX_CR1 寄存器中的自动装载预装载使能位(ARPE)的设置,预装载寄存器的内容被永久地或在每次的更新事件 UEV 时传送到影子寄存器。 - 输入捕获通道
在输入捕获模式下,当检测到 ICx 信号上相应的边沿后,捕获/比较寄存器
(TIMx_CCRx)被用来锁存计数器的值。当一个捕获事件发生时,相应的CCXIF 标志(TIMx_SR 寄存器)被置 1,如果开放了中断或者 DMA 操作,则将产生中断或者DMA 操作。如果一个捕获事件发生时 CCxIF 标志已经为高,那么重复捕获标志CCxOF(TIMx_SR 寄存器)被置 1。写 CCxIF=0 可清除 CCxIF,或读取存储在TIMx_CCRx 寄存器中的捕获数据也可清除 CCxIF。写 CCxOF=0 可清除CCxOF。
二 PWM(脉冲宽度调制)
(1)基本原理
脉冲宽度调制模式可以产生一个由 TIMx_ARR 寄存器确定频率、由 TIMx_CCRx寄存器确定占空比的信号。在 TIMx_CCMRx 寄存器中的 OCxM 位写入“110"(PWM 模式 1)或“111"(PWM 模式 2),能够独立地设置每个通道工作在 PWM 模式,每个 OCx 输出一路PWM。必须通过设置 TIMx_CCMRx 寄存器 OCxPE 位使能相应的预装载寄存器,最后还要设置 TIMx_CR1 寄存器的 ARPE 位使能自动重装载的预装载寄存器。
控制方式就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也就是在输出波形的半个周期产生多个脉冲,使各个脉冲的等值电压为正弦波形,所获得的输出平滑且低次谐波少,按一定的规则对各脉冲的宽度进行调制,既可改变逆变电路输出电压的大小,也可以改变输出频率。
(2)优点
- PWM的一个优点是从处理器到被控系统信号都是数字形式的,无需进行数模转换。让信号保持为数字形式可将噪声影响降到最小。噪声只有在强到足以将逻辑1改变为逻辑0或将逻辑0改变为逻辑1时,也才能对数字信号产生影响。
- 对噪声抵抗能力的增强是PWM相对于模拟控制的另外一个优点,而且这也是在某些时候将PWM用于通信的主要原因。从模拟信号转向PWM可以极大地延长通信距离。在接收端,通过适当的RC或LC网络可以滤除调制高频方波并将信号还原为模拟形式。总之,PWM既经济、节约空间、抗噪性能强,是一种值得广大工程师在许多设计应用中使用的有效技术。
(3)PWM的控制方法
- 等脉宽PWM法
等脉宽PWM法是PWM法中最为简单的一种,它是把每一脉冲的宽度均相等的脉冲列作为PWM波,通过改变其周期,达到调频的效果,改变脉冲的宽度或占空比可以调压,采用适当控制方法即可使电压与频率协调变化。 - SPWM法
SPWM法是一种比较成熟的,如今使用较广泛的PWM法,前面提到的采样控制理论中的一个重要结论:冲量相等而形状不同的窄脉冲加在具有惯性的环节上时,其效果基本相同的。 - 电流控制PWM
电流控制PWM的基本思想是把希望输出的电流波形作为指令信号,把实际的电流波形作为反馈信号,通过两者瞬时值的比较来决定各开关器件的通断,使实际输出随指令信号的改变而改变。
三 定时器计数方式,控制LED以2s的频率周期性地亮-灭
(1)使用STM32CubeMX创建项目
1.选择RCC,在右侧弹出的菜单栏中选Crystal/Ceramic Resonator
2.选择SYS。,在右侧弹出的菜单栏中选Serial Wire。
3.这里选择PA0作为LED灯的输出,将其选为GPIO-OUT,这里我们只使用一个灯。
4.配置定时器2和定时器3
这里我们使用定时器2和定时器3来实现定时的功能。如图所示,定时器2配置:依次点击位置2,选中定时器2;位置3,配置定时器2的时钟源为内部时钟;位置4,分频系数为71;位置5,向上计数模式,计数周期为5000,使能自动重载模式。
注:分频系数那里虽然写的是71,但系统处理的时候会自动加上1,所以实际进行的是72分频。由于时钟我们一般会配置为72MHZ,所以72分频后得到1MHZ的时钟。1MHZ的时钟,计数5000次,得到时间5000/1000000=0.005秒。也就是每隔0.005秒定时器2会产生一次定时中断。
5.配置中断
6.点开USART1,Mode选择异步通信Asynchronous
7.进入CLK Configuration (时钟配置)中,进行时钟配置
(2)生成Keil 代码
1.在main.c中添加启动定时器代码:
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_Base_Start_IT(&htim3);
2.在main.c中添加定时器中断回调函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint32_t time_cnt =0;
static uint32_t time_cnt3 =0;
if(htim->Instance == TIM2)
{
if(++time_cnt >= 400)
{
time_cnt =0;
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_1);
}
}
if(htim->Instance == TIM3)
{
if(++time_cnt3 >= 1000)
{
time_cnt3 =0;
HAL_UART_Transmit(&huart1,hello,20,100000);
}
}
}
该函数为定时器的中断回调函数,当产生定时中断的时候,会自动调用这个函数。在函数内部定义了定时器的一个静态变量:time_cnt与定时器3 的time_cnt3。
3.完整main.c代码如下:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2022 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.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);
static void MX_NVIC_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
uint8_t hello[20]="hello windows!\r\n";
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();
MX_USART1_UART_Init();
/* Initialize interrupts */
MX_NVIC_Init();
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_Base_Start_IT(&htim3);
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* 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};
/** 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.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
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_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief NVIC Configuration.
* @retval None
*/
static void MX_NVIC_Init(void)
{
/* TIM2_IRQn interrupt configuration */
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
/* TIM3_IRQn interrupt configuration */
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
}
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint32_t time_cnt =0;
static uint32_t time_cnt3 =0;
if(htim->Instance == TIM2)
{
if(++time_cnt >= 400)
{
time_cnt =0;
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_0);
}
}
if(htim->Instance == TIM3)
{
if(++time_cnt3 >= 1000)
{
time_cnt3 =0;
HAL_UART_Transmit(&huart1,hello,20,100000);
}
}
}
/* 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 */
4.电路链接如图
(3)结果展示
四 使用PWM完成呼吸灯
(1)使用STM32CubeMX创建项目
1.选择RCC,在右侧弹出的菜单栏中选Crystal/Ceramic Resonator
2.选择SYS。,在右侧弹出的菜单栏中选Serial Wire。
3.将PA0点作为输出
4.配置定时器3和定时器4
这里我们选择定时器3和定时器4来实现定时的功位置3,分频系数为71,向上计数模式,计数周期为500,使能自动重载模式。通道1选择:PWM Generation CH1(PWM输出通道1)
设置分频系数为71,计数周期为500,其它默认。
5.进入CLK Configuration (时钟配置)中,进行时钟配置:
最后生成keil项目
(2)Keil代码
1.设置占空比
打开工程main.c文件。首先定义一个变量,用来存储占空比:初值设为10:
uint16_t duty_num3 = 10;
uint16_t duty_num4 = 10;
2.开启PWM信道
开始TIM3的通道3,输出PWM。
开始TIM4的通道4,输出PWM。
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);
3.调用代码
这里我们设置为每隔50毫秒,占空比加10,如果超过500(也就是PWM周期),自动变成0。(即灯会从亮倒暗,逐渐变化)
while (1)
{
/* USER CODE END WHILE */
HAL_Delay(50);
duty_num3 = duty_num3 + 10;
duty_num4 = duty_num4 + 10;
if(duty_num3 > 500)
{
duty_num3 = 0;
}
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,duty_num3);
if(duty_num4 > 500)
{
duty_num4 = 0;
}
__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_1,duty_num4);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
4.整体代码
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* 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 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
uint16_t duty_num3 = 10;
uint16_t duty_num4 = 10;
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_TIM3_Init();
MX_TIM4_Init();
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_Delay(50);
duty_num3 = duty_num3 + 10;
duty_num4 = duty_num4 + 10;
if(duty_num3 > 500)
{
duty_num3 = 0;
}
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,duty_num3);
if(duty_num4 > 500)
{
duty_num4 = 0;
}
__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_1,duty_num4);
/* 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};
/** 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.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
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_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != 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 */
(3)编译并烧录
1.电路链接如图
2.结果展示
(4)Keil虚拟示波器,观察 pwm输出波形
1.点击第一步,Target界面中,选择跟正确的晶振大小,使用8MHz的外部晶振:
2.接着进行Debug页的设置:
3.点击Setup设置添加要进行观察的引脚
4.仿真结果