输出比较部分框图
⑤ 输入捕获和输出比较公用部分
该部分需要结合第④部分或者第⑥部分共同完成相应功能。
⑥ 输出比较
图21.1.1.1中的第⑥部分是输出比较,一般应用是要和第⑤部分一起完成定时器输出功能。 TIMx_CH1~ TIMx_CH4 表示定时器的 4 个通道,这 4 个通道都是可以独立工作的。IO 端口通 过复用功能与这些通道相连。
图 21.1.9 中,灰色阴影部分是输入捕获功能部分。这里我们看到右边没有 阴影部分就是输出比较功能部分了。下面以通道 1 输出比较功能为例给大家介绍定时器如何实 现输出功能的。
首先程序员写 CCR1 寄存器,即写入比较值。这个比较值需要转移到对应的捕获/比较影子 寄存器后才会真正生效。什么条件下才能转移?图 21.1.9 中可以看到 compare_transfer 旁边的 与门,需要满足三个条件:CCR1 不在写入操作期间、CC1S[1:0] = 0 配置为输出、OC1PE 位置 0(或者 OC1PE 位置 1,并且需要发生更新事件,这个更新事件可以软件产生或者硬件产生)。
当 CCR1 寄存器的值转移到其影子寄存器后,新的值就会和计数器的值进行比较,它们的 比较结果将会通过第⑥部分影响定时器的输出。
上图中,可以看到输出模式控制器,由 OC1M[2:0]位配置输出比较模式,该位的描述请参 考《STM32F10xxx 参考手册_V10(中文版).pdf》相关定时器章节的 TIMx_CCMR1 寄存器。 F1 系列有 8 种输出比较模式之多,后面用到再来介绍。
oc1ref 是输出参考信号,高电平有效,为高电平时称之为有效电平,为低电平时称之为无 效电平。它的高低电平受到三个方面的影响:OC1M[3:0]位配置的输出比较模式、第⑤部分比较 器的比较结果、还有就是 OC1CE 位配置的 ETRF 信号。ETRF 信号可以将 Oc1ref 电平强制清 零,该信号来自 IO 外部。
一般来说,当计数器的值和捕获/比较寄存器的值相等时,输出参考信号 oc1ref 的极性就会 根据我们选择的输出比较模式而改变。如果开启了比较中断,还会发生比较中断。
CC1P 位用于选择通道输出极性。
CC1E 位置 1 使能通道输出。
OC1 信号就会从 TIMx_CH1 输出到 IO 端口,再到 IO 外部。
通用定时器的 PWM 输出模式
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微 处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。我们可以让定时器产生PWM, 在计数器频率固定时,PWM 频率或者周期由自动重载寄存器(TIMx_ARR)的值决定,其占空 比由捕获/比较寄存器(TIMx_CCRx)的值决定。PWM 产生原理示意图如下图所示:
上图中,定时器工作在递增计数模式,纵轴是计数器的计数值 CNT,横轴表示时。当 CNT=CCRx 时,IO 输出高电平(逻辑 1);当 CNT=ARR 时,定时器溢出,CNT 的值被清零,然后继续递增,依次循环。在这个循环中,改 变 CCRx 的值,就可以改变 PWM 的占空比,改变 ARR 的值,就可以改变 PWM 的频率,这就 是 PWM 输出的原理。
定时器产生 PWM 的方式有许多种,下面我们以边沿对齐模式(即递增计数模式/递减计数 模式)为例,PWM 模式 1 或者 PWM 模式 2 产生 PWM 的示意图,如下图所示:
STM32F103 的定时器除了 TIM6 和 TIM7,其他的定时器都可以用来产生 PWM 输出。其 中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产 生多达 4 路的 PWM 输出!本实验我们以使用 TIM3 的 CH2 产生一路 PWM 输出为例进行学习。
配置步骤
相关寄存器
⚫ 捕获/比较模式寄存器 1/2(TIMx_CCMR1/2)
⚫ 捕获/比较使能寄存器(TIMx_CCER)
⚫ 捕获/比较寄存器 1/2/3/4(TIMx_CCR1/2/3/4)
捕获/比较寄存器(TIMx_CCR1/2/3/4),该寄存器总共有 4 个,对应 4 个通道 CH1~CH4。 我们使用的是通道 2,所以来看看 TIMx_CCR2 寄存器,描述如图 21.3.1.3 所示:
相关HAl库函数介绍
1. HAL_TIM_PWM_Init 函数
定时器的 PWM 输出模式初始化函数,其声明如下:
HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim);
⚫ 函数描述: 用于初始化定时器的 PWM 输出模式。
⚫ 函数形参: 形参 1 是 TIM_HandleTypeDef 结构体类型指针变量,基本定时器的时候已经介绍。
⚫ 函数返回值: HAL_StatusTypeDef 枚举类型的值。
⚫ 注意事项:该函数实现的功能以及使用方法和 HAL_TIM_Base_Init 类似,作用都是初始化定时器的 ARR 和 PSC 等参数。为什么 HAL 库要提供这个函数而不直接让我们使用 HAL_TIM_Base_Init 函数呢?这是因为 HAL 库为定时器的针对 PWM 输出定义了单独的 MSP 回调函数 HAL_TIM_PWM_MspInit,所以当我们调用 HAL_TIM_PWM_Init 进行 PWM 初始化之后,该函数内部会调用 MSP 回调函数 HAL_TIM_PWM_MspInit。当我们使用 HAL_TIM_Base_Init 初始 化定时器参数的时候,它内部调用的回调函数是 HAL_TIM_Base_MspInit,这里大家注意区分。
2. HAL_TIM_PWM_ConfigChannel 函数
定时器的 PWM 通道设置初始化函数。其声明如下:
HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim,
TIM_OC_InitTypeDef *sConfig, uint32_t Channel);
⚫ 函数描述: 该函数用于设置定时器的 PWM 通道。
⚫ 函数形参: 形参 1 是 TIM_HandleTypeDef 结构体类型指针变量,用于配置定时器基本参数。 形参 2 是 TIM_OC_InitTypeDef 结构体类型指针变量,用于配置定时器的输出比较参数。 重点了解一下 TIM_OC_InitTypeDef 结构体指针类型,其定义如下:
typedef struct
{
uint32_t OCMode; /* 输出比较模式选择,寄存器的时候说过了,共 8 种模式 */
uint32_t Pulse; /* 设置比较值 */
uint32_t OCPolarity; /* 设置输出比较极性 */
uint32_t OCNPolarity; /* 设置互补输出比较极性 */
uint32_t OCFastMode; /* 使能或失能输出比较快速模式 */
uint32_t OCIdleState; /* 选择空闲状态下的非工作状态(OC1 输出) */
uint32_t OCNIdleState; /* 设置空闲状态下的非工作状态(OC1N 输出) */
} TIM_OC_InitTypeDef;
我们重点关注前三个结构体成员。成员变量 OCMode 用来设置模式,这里我们设置为 PWM 模式 1。成员变量 Pulse 用来设置捕获比较值。成员变量 TIM_OCPolarity 用来设置输出极性。 其他成员 TIM_OutputNState,TIM_OCNPolarity,TIM_OCIdleState 和 TIM_OCNIdleState 后面 用到再介绍。
形参 3 是定时器通道,范围:TIM_CHANNEL_1 到 TIM_CHANNEL_4。这里我们使用的 是定时器 3 的通道 2,所以取值为 TIM_CHANNEL_2 即可。
⚫ 函数返回值: HAL_StatusTypeDef 枚举类型的值。
3. HAL_TIM_PWM_Start 函数
定时器的 PWM 输出启动函数,其声明如下:
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
⚫ 函数描述: 用于使能通道输出和启动计数器,即启动 PWM 输出。
⚫ 函数形参: 形参 1 是 TIM_HandleTypeDef 结构体类型指针变量。 形参 2 是定时器通道,范围:TIM_CHANNEL_1 到 TIM_CHANNEL_4。
⚫ 函数返回值: HAL_StatusTypeDef 枚举类型的值。
⚫ 注意事项: 对于单独使能定时器的方法,在上一章定时器实验我们已经讲解。实际上,HAL 库也同样 提供了单独使能定时器的输出通道函数,函数为:
void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel,
uint32_t ChannelState);
HAL_TIM_PWM_Start 函数内部也调用了该函数。
4. HAL_TIM_ConfigClockSource 函数
配置定时器时钟源函数,其声明如下:
HAL_StatusTypeDef HAL_TIM_ConfigClockSource(TIM_HandleTypeDef *htim,
TIM_ClockConfigTypeDef *sClockSourceConfig);
⚫ 函数描述: 用于配置定时器时钟源。
⚫ 函数形参:
形参 1 是 TIM_HandleTypeDef 结构体类型指针变量。
形参 2 是 TIM_ClockConfigTypeDef 结构体类型指针变量,用于配置定时器时钟源参数。 TIM_ClockConfigTypeDef 定义如下:
typedef struct
{
uint32_t ClockSource; /* 时钟源 */
uint32_t ClockPolarity; /* 时钟极性 */
uint32_t ClockPrescaler; /* 定时器预分频器 */
uint32_t ClockFilter; /* 时钟过滤器 */
} TIM_ClockConfigTypeDef;
⚫ 函数返回值: HAL_StatusTypeDef 枚举类型的值。
⚫ 注意事项: 该函数主要配置 TIMx_SMCR 寄存器。默认情况下,定时器的时钟源是内部时钟。本实验 就是使用内部时钟的,所以我们不用对时钟源就行初始化,默认即可。这里只是让大家知道有 这个函数可以设定时器的时钟源。比如用 HAL_TIM_ConfigClockSource 初始化选择内部时钟, 方法如下:
TIM_HandleTypeDef timx_handle; /* 定时器 x 句柄 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; /* 选择内部时钟 */
HAL_TIM_ConfigClockSource(&timx_handle, &sClockSourceConfig);
后面的定时器初始化凡是用到内部时钟我们都没有去初始化,系统默认即可。
代码
gtim.c
#include "./BSP/GTIM/gtim.h"
TIM_HandleTypeDef g_timx_pwm_chy_handle;
/* 通用定时器PWM输出初始化函数 */
void gtim_timx_pwm_chy_init(uint16_t psc, uint16_t arr)
{
TIM_OC_InitTypeDef timx_oc_pwm_chy;
g_timx_pwm_chy_handle.Instance = TIM3;
g_timx_pwm_chy_handle.Init.Prescaler = psc;
g_timx_pwm_chy_handle.Init.Period = arr;
g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle);
timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;
timx_oc_pwm_chy.Pulse = arr / 2;
timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW;
HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, TIM_CHANNEL_2);
}
/* 定时器输出PWN MSP初始化函数 */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_TIM3_CLK_ENABLE();
__HAL_RCC_AFIO_CLK_ENABLE();
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin = GPIO_PIN_5;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &gpio_init_struct);
__HAL_AFIO_REMAP_TIM3_PARTIAL();
}
}
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/GTIM/gtim.h"
extern TIM_HandleTypeDef g_timx_pwm_chy_handle; /* 定时器x句柄 */
int main(void)
{
uint16_t ledrpwmval = 0; /* CCR比较值 */
uint8_t dir = 1; /* CCR比较值的改变方向 */
HAL_Init(); /* 初始化 HAL 库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init();
gtim_timx_pwm_chy_init(72 -1, 500 -1); /* 72*500/72M=0.0005s,2KHz */
while(1)
{
delay_ms(10);
if(dir)
{
ledrpwmval++; /* dir==1 ledrpwmval递增 */
}
else
{
ledrpwmval--; /* dir==0 ledrpwmval递减 */
}
if(ledrpwmval > 500 - 1)
{
dir = 0; /* ledrpwmval到达500后,方向为递减 */
}
if(ledrpwmval == 0)
{
dir = 1; /* ledrpwmval到达0后,方向改为递增 */
}
/* 修改比较值控制占空比 */
__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, TIM_CHANNEL_2, ledrpwmval);
}
}