一.STM32F429定时器介绍
STM32F429 一共有14个定时器,其中有 2 个高级定时器(TIM1 和 TIM8),具体如下表所示:
二.高级定时器输出指定个数PWM
1.输出PWM原理
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用
微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。我们可以让定时器产生
PWM,在计数器频率固定时,PWM 频率或者周期由自动重载寄存器(TIMx_ARR)的值决定,其占空比由捕获/比较寄存器(TIMx_CCRx)的值决定。PWM 产生原理示意图如下图所示:
上图中,定时器工作在递增计数模式,纵轴是计数器的计数值 CNT,横轴表示时。当
CNT<CCRx 时,IO 输出低电平(逻辑 0);当 CNT>=CCRx 时,IO 输出高电平(逻辑 1);当CNT=ARR 时,定时器溢出,CNT 的值被清零,然后继续递增,依次循环。在这个循环中,改变 CCRx 的值,就可以改变 PWM 的占空比,改变 ARR 的值,就可以改变 PWM 的频率,这就是 PWM 输出的原理。
定时器产生 PWM的方式有许多种,下面我们以边沿对齐模式(即递增计数模式/递减计数模式)为例,PWM 模式 1 或者 PWM 模式 2 产生 PWM 的示意图,如下图所示:
STM32F429 的定时器除了 TIM6 和 TIM7 外,其他的定时器都可以产生 PWM 输出。其中
高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4 路的 PWM 输出!
2.TIMER8输出指定个数PWM设置
要清楚重复计数器特性,设置重复计数器寄存器 RCR 的值为 N,那么更新事件将
在定时器发生 N+1 次上溢或下溢时发生。换句话来说就是,想要指定输出 N 个 PWM,只需要把 N-1写入 RCR寄存器。因为在边沿对齐模式下,定时器溢出周期对应着 PWM周期,我们只要在更新事件发生时,停止输出 PWM 就行。
为了保证定时器输出指定个数的 PWM 后,定时器马上停止继续输出,我们使能更
新中断,并在定时器中断里关闭计数器。
三.PWM输出例程
1.代码功能
通过 TIM8_CH1(由 PC6 复用)输出 PWM,为了指示 PWM 的输出情况,我们用杜邦线连接 PC6 和 PB0,来实现 PWM 输出控制 LED1 的亮灭。上电默认输出 5 个 PWM 控制 LED1亮灭五次。之后按一下按键 KEY0,就会输出 5 个 PWM 控制 LED1 亮灭五次
2.代码流程图
3.代码
(1)main.c
int main(void)
{
uint8_t key = 0;
uint8_t t = 0;
GPIO_InitTypeDef gpio_init_struct;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(360, 25, 2, 8); /* 设置时钟,180Mhz */
delay_init(180); /* 延时初始化 */
usart_init(115200); /* 初始化USART */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
atim_timx_npwm_chy_init(10000 - 1, 9000 - 1); /* 20Khz的计数频率,2Hz的PWM频率 */
/* 将 LED1 引脚设置为输入模式, 避免和PC6冲突 */
gpio_init_struct.Pin = LED1_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* LED1引脚设置浮空输入模式 */
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);
ATIM_TIMX_NPWM_CHY_CCRX = 5000; /* 设置PWM占空比为50%,这样可以控制每一个PWM周期,LED1(GREEN)
有一半时间是亮的,一半时间是灭的,LED1亮灭一次,表示一个PWM波
*/
atim_timx_npwm_chy_set(5); /* 输出5个PWM波(控制LED1(GREEN)闪烁5次) */
while (1)
{
key = key_scan(0);
if (key == KEY0_PRES) /* KEY0按下 */
{
atim_timx_npwm_chy_set(5); /* 输出5个PWM波(控制TIM8_CH1, 即PC6输出5个脉冲) */
}
t++;
delay_ms(10);
if (t > 50) /* 控制LED0闪烁, 提示程序运行状态 */
{
t = 0;
LED0_TOGGLE();
}
}
}
(2)timer.h
#ifndef __ATIM_H
#define __ATIM_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 高级定时器 定义 */
/* TIMX 输出指定个数PWM 定义
* 这里输出的PWM通过PC6(TIM8_CH1)输出, 我们用杜邦线连接PC6和PB0, 然后在程序里面将PB0设置成浮空输入
* 就可以 看到TIM8_CH1控制LED1(GREEN)的亮灭, 亮灭一次表示一个PWM波
*/
#define ATIM_TIMX_NPWM_CHY_GPIO_PORT GPIOC
#define ATIM_TIMX_NPWM_CHY_GPIO_PIN GPIO_PIN_6
#define ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define ATIM_TIMX_NPWM_CHY_GPIO_AF GPIO_AF3_TIM8
#define ATIM_TIMX_NPWM TIM8
#define ATIM_TIMX_NPWM_IRQn TIM8_UP_TIM13_IRQn
#define ATIM_TIMX_NPWM_IRQHandler TIM8_UP_TIM13_IRQHandler
#define ATIM_TIMX_NPWM_CHY TIM_CHANNEL_1 /* 通道Y,1<= Y <=4 */
#define ATIM_TIMX_NPWM_CHY_CCRX TIM8->CCR1 /* 通道Y的输出比较寄存器 */
#define ATIM_TIMX_NPWM_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0) /* TIM8 时钟使能 */
/******************************************************************************************/
void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc); /* 高级定时器 输出指定个数PWM初始化函数 */
void atim_timx_npwm_chy_set(uint32_t npwm); /* 高级定时器 设置输出PWM的个数 */
#endif
(3)timer.c
TIM_HandleTypeDef g_timx_npwm_chy_handle; /* 定时器x句柄 */
/* g_npwm_remain表示当前还剩下多少个脉冲要发送
* 每次最多发送256个脉冲
*/
static uint32_t g_npwm_remain = 0;
/**
* @brief 高级定时器TIMX 通道Y 输出指定个数PWM 初始化函数
* @note
* 高级定时器的时钟来自APB2,当PPRE2≥2分频的时候
* 高级定时器的时钟为APB2时钟的2倍, 而APB2为90M, 所以定时器时钟 = 180Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值
* @param psc: 时钟预分频数
* @retval 无
*/
void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
TIM_OC_InitTypeDef timx_oc_npwm_chy = {0}; /* 定时器输出 */
ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE(); /* TIMX 通道IO口时钟使能 */
ATIM_TIMX_NPWM_CHY_CLK_ENABLE(); /* TIMX 时钟使能 */
g_timx_npwm_chy_handle.Instance = ATIM_TIMX_NPWM; /* 定时器x */
g_timx_npwm_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
g_timx_npwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_npwm_chy_handle.Init.Period = arr; /* 自动重装载值 */
g_timx_npwm_chy_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能TIMx_ARR进行缓冲 */
g_timx_npwm_chy_handle.Init.RepetitionCounter = 0; /* 重复计数器初始值 */
HAL_TIM_PWM_Init(&g_timx_npwm_chy_handle); /* 初始化PWM */
gpio_init_struct.Pin = ATIM_TIMX_NPWM_CHY_GPIO_PIN; /* 开启通道y的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = ATIM_TIMX_NPWM_CHY_GPIO_AF; /* 端口复用 */
HAL_GPIO_Init(ATIM_TIMX_NPWM_CHY_GPIO_PORT, &gpio_init_struct);
timx_oc_npwm_chy.OCMode = TIM_OCMODE_PWM1; /* 选择PWM1模式 */
timx_oc_npwm_chy.Pulse = arr / 2; /* 设置比较值,此值用来确定占空比
这里默认设置比较值为自动重装载值的一半,即占空比为50%
*/
timx_oc_npwm_chy.OCPolarity = TIM_OCPOLARITY_HIGH; /* 输出比较极性为高 */
HAL_TIM_PWM_ConfigChannel(&g_timx_npwm_chy_handle, &timx_oc_npwm_chy, ATIM_TIMX_NPWM_CHY); /* 配置TIMx通道y */
HAL_NVIC_SetPriority(ATIM_TIMX_NPWM_IRQn, 1, 3); /* 设置中断优先级,抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(ATIM_TIMX_NPWM_IRQn); /* 开启ITMx中断 */
__HAL_TIM_ENABLE_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE); /* 允许更新中断 */
HAL_TIM_PWM_Start(&g_timx_npwm_chy_handle, ATIM_TIMX_NPWM_CHY); /* 开启对应PWM通道 */
}
/**
* @brief 高级定时器TIMX NPWM设置PWM个数
* @param npwm : PWM的个数, 1~2^32次方个
* @retval 无
*/
void atim_timx_npwm_chy_set(uint32_t npwm)
{
if (npwm == 0) return;
g_npwm_remain = npwm; /* 保存脉冲个数 */
HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE); /* 产生一次更新事件,在中断里面处理脉冲输出 */
__HAL_TIM_ENABLE(&g_timx_npwm_chy_handle); /* 使能定时器TIMX */
}
/**
* @brief 高级定时器TIMX NPWM中断服务函数
* @param 无
* @retval 无
*/
void ATIM_TIMX_NPWM_IRQHandler(void)
{
uint16_t npwm = 0;
/* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
if (__HAL_TIM_GET_FLAG(&g_timx_npwm_chy_handle, TIM_FLAG_UPDATE) != RESET)
{
if (g_npwm_remain >= 256) /* 还有大于256个脉冲需要发送 */
{
g_npwm_remain = g_npwm_remain - 256;
npwm = 256;
}
else if (g_npwm_remain % 256) /* 还有位数(不到256)个脉冲要发送 */
{
npwm = g_npwm_remain % 256;
g_npwm_remain = 0; /* 没有脉冲了 */
}
if (npwm) /* 有脉冲要发送 */
{
ATIM_TIMX_NPWM->RCR = npwm - 1; /* 设置重复计数寄存器值为npwm-1, 即npwm个脉冲 */
HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE); /* 产生一次更新事件,在中断里面处理脉冲输出 */
__HAL_TIM_ENABLE(&g_timx_npwm_chy_handle); /* 使能定时器TIMX */
}
else
{
ATIM_TIMX_NPWM->CR1 &= ~(1 << 0); /* 关闭定时器TIMX,使用HAL Disable会清除PWM通道信息,此处不用 */
}
__HAL_TIM_CLEAR_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE); /* 清除定时器溢出中断标志位 */
}
}
四.STM32单片机输出PWM应用场景
PWM通过改变信号的脉冲宽度(即占空比)来模拟不同的电压或电流水平,从而实现对设备的精确控制。以下是一些具体的应用场景:
1.电机控制
PWM信号常用于电机的速度调节。通过改变PWM信号的占空比,可以控制电机的平均电压,从而调节电机的转速。这种方式比直接控制电压或电流更为高效和精确。
2.灯光控制
在LED灯、呼吸灯等照明设备中,PWM信号可以用来调节灯光的亮度。通过改变PWM信号的占空比,可以控制LED灯的平均电流,从而调节其亮度。这种方式可以实现平滑的亮度调节,避免直接调节电压可能带来的闪烁问题。
3.音频控制
在音频设备中,PWM信号可以用于模拟音频信号的音量控制。通过改变PWM信号的占空比,可以控制音频信号的等效电压,从而调节音量。然而,需要注意的是,由于PWM信号本身是离散的,因此在音频领域通常使用更高级的DAC(数模转换器)来实现更平滑的音量控制。
4.温度控制
在需要精确控制温度的设备中,如电加热器、空调等,PWM信号可以用来调节加热元件的功率。通过改变PWM信号的占空比,可以控制加热元件的平均功率输出,从而实现对温度的精确控制。
5.舵机控制
在机器人、智能车等项目中,PWM信号常用于控制舵机的角度。通过改变PWM信号的占空比,可以控制舵机内部电机的转动角度,从而实现对机械臂、车轮等部件的精确控制。
6.电源管理
在需要精确控制电源输出的场合,如开关电源、电池充电器等,PWM信号可以用来调节输出电压或电流。通过改变PWM信号的占空比和频率,可以实现对电源输出的精确控制,提高电源效率和稳定性。