STM32定时器&PWM应用编程

本文围绕STM32展开,介绍了其定时器的类型、通用定时器计数模式及原理框图,阐述了PWM的基本原理、优点和控制方法。还通过实例,详细说明了如何使用定时器计数方式控制LED周期性亮灭,以及利用PWM实现呼吸灯,并展示了使用STM32CubeMX创建项目、生成Keil代码等操作过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一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.仿真结果
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值