PWM是一种应用广泛的利用微处理器的数字输出来对模拟电路进行控制的一种技术(即对脉冲宽度的控制)PWM同时也是驱动蜂鸣器,驱动舵机,通信等重要的一环,而对于初学者而言,点完灯的下一个程序就是驱动蜂鸣器,本篇将讲述如何使用及调整PWM输出频率,占空比
工程文件可入Q群:659512171获取
对于STM32,PWM输出依靠定时器,而在STM32F103c8t6中,共有4个定时器可供输出,每个都可以配置4路输出,所以总共可以输出16路PWM,本篇只介绍和应用单路PWM输出。
打开STM32CubeMX,新建工程,配置外部高速时钟:
输入需要的频率,敲击回车,STM32CubeMX会自动配置(在这里我把时钟频率设为了最高72,但其实不设也行,在后面的分频改一下就好了)
这里一定要选好,不然会导致芯片自锁
下面对这其中的3个值进行说明:
预分频(psc):CPU运行频率先经过它分频再进入计时器,如CPU运行在 x Mhz 下,预分频为 y,那进入计时器的频率也就为 x/(y+1) Mhz(因为从0计数,所以是y+1)。
自动重装值(arr):指一次周期的计数长度
脉冲长度:指输出脉冲的计数长度
在这里就可以知道怎么调节频率于占空比了,首先,在预分频(y)和主频(x)一定的情况下,要保持输出 a% 占空比的 b Hz 的方波,那么自动重装值就应该是 x / (y+1) / b ,而脉冲长度也就应该是 (x / (y+1) / b) * a%,所以只需要写一个函数调节自动重装值和脉冲长度就可以实现调节频率和占空比的目的了。
这里我分别把预分频、自动重装值、脉冲长度设置为了 999、1440、720,所以频率和占空比分别应为:72,000,000 / (999+1)/1440 = 50 (Hz),1440 / 720 = 50%
然后点击右上角的GENERATE CODE生成文件,然后打开。
打开main.c,tim.c,tim.h文件:
可以看到我们设置的参数都在这个初始化函数里了,接下来只需要设置2个变量替换其中2个具体值。
将上面这一段代码复制到下面的 /* USER CODE BEGIN 1 */ 和 /* USER CODE END 1 */ 区间里,并把上面这一段代码注释掉(删掉也可以),
接着,更改一下后面括号中的数据(原本是 void,无类型,现在要换成数据),只需要设置自动重装值和脉冲长度的变量:
/* USER CODE BEGIN 1 */
void MX_TIM2_Init(uint16_t arr, uint16_t pul)
{
/* USER CODE BEGIN TIM2_Init 0 */
/* USER CODE END TIM2_Init 0 */
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM2_Init 1 */
/* USER CODE END TIM2_Init 1 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 1000;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = arr;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = pul;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM2_Init 2 */
/* USER CODE END TIM2_Init 2 */
HAL_TIM_MspPostInit(&htim2);
}
/* USER CODE END 1 */
另外,还需要在tim.h中更改对此函数的声明,不然是报错不通过的,也无法在主程序中引用,同样把函数声明移到用户区域里,并注释掉:
/* USER CODE BEGIN Prototypes */
void MX_TIM2_Init(uint16_t arr, uint16_t pul);
/* USER CODE END Prototypes */
如果你觉得每次都写 HAL_TIM_PWM_Start 和 HAL_TIM_PWM_Stop 太麻烦,可以再加一个PWM开启/关闭函数,同样是在用户区域写如下函数:
void PWM (int onoff)
{
if (onoff == 0){
HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);
}else if (onoff == 1){
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
}
}
以及函数声明:
void PWM (int onoff);
在这里当数据为0时,停止PWM,当为1时,开启PWM
最后,tim.c中的用户端应是如下代码:
/* USER CODE BEGIN 1 */
void MX_TIM2_Init(uint16_t arr, uint16_t pul)
{
/* USER CODE BEGIN TIM2_Init 0 */
/* USER CODE END TIM2_Init 0 */
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM2_Init 1 */
/* USER CODE END TIM2_Init 1 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 1000;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = arr;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = pul;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM2_Init 2 */
/* USER CODE END TIM2_Init 2 */
HAL_TIM_MspPostInit(&htim2);
}
void PWM (int onoff)
{
if (onoff == 0){
HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);
}else if (onoff == 1){
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
}
}
/* USER CODE END 1 */
整个tim.h应是如下代码:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file tim.h
* @brief This file contains all the function prototypes for
* the tim.c file
******************************************************************************
* @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 */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __TIM_H__
#define __TIM_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
extern TIM_HandleTypeDef htim2;
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
//void MX_TIM2_Init(void);
void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim);
/* USER CODE BEGIN Prototypes */
void MX_TIM2_Init(uint16_t arr, uint16_t pul);
void PWM (int onoff);
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /* __TIM_H__ */
最后,在main.c中引用就行了,记得要把初始化的参数填上去,这里给出一个实例(整个int main函数) :
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(1440,36);
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
PWM(1);
HAL_Delay(1500);
PWM(0);
MX_TIM2_Init(1440,180);
PWM(1);
HAL_Delay(1500);
PWM(0);
MX_TIM2_Init(1440,36);
}
/* USER CODE END 3 */
}
这里先是初始化PWM输出为50HZ,脉宽0.5ms(即2.5%占空比)的信号,然后打开PWM输出,延时1.5秒后关闭,再配置为50Hz,脉宽2.5ms(即12.5%占空比)的信号,然后再次打开输出,延时1.5秒后关闭并重新配置为50HZ,脉宽1ms的信号,这两个分别是舵机的0°,180°信号,可以让连接在tim2的一通道(即PA0)的舵机在0°到180°间重复旋转,效果如下:
相信大家也看到了,这种方式来调节频率和占空比麻烦且会有一段时间的空缺,会影响到对波形比较敏感的设备,所以接下来介绍第二种方式
前面过程的都与第一种方式相同,可以完全保留,第一种方式的调节函数可以作为初始化使用,此方式无需编写任何函数,调用了HAL库中的修改CCR(储存脉冲长度数值)和ARR(储存自动重装值)寄存器的函数,接下来直接上代码:
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(1440,36);
/* USER CODE BEGIN 2 */
PWM(1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(1500);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,180); //修改CCR寄存器
HAL_Delay(1500);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,36);
/*
__HAL_TIM_SET_AUTORELOAD(&htim2,1440); //修改ARR寄存器,没有通道限制,整个定时器一起改
*/
}
/* USER CODE END 3 */
}
上面程序实现效果与第一种相同,但此程序可以在PWM运行时更改,无需关闭PWM,没有空缺
另外,对于频率的修改还可以通过改预分频系数来解决,但由于自动重装值与预分频之间是连除的关系,所以一般只修改自动重装值,修改预分频系数的函数如下:
__HAL_TIM_SET_PRESCALER(TIM_TypeDef* TIMx, uint32_t Prescaler);
/*举一个例子:
__HAL_TIM_SET_PRESCALER(&htim2, 1000);
这条语句就是把定时器二的预分频系数设为1000
*/
都看到这里了点点关注再走呗
交流Q群:659512171