STM32—第四章定时器—第二部分PWM输出
1 PWM 简介
脉冲宽度调制( PWM),是英文 Pulse Width Modulation 的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术 。简单一点,就是对脉冲宽度的控制。
STM32的定时器除了 TIM6 和 7 。其他的定时器都可以用来产生 PWM 输出。其中高级定时器TIM1 和 TIM8可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,这样, STM32 最多可以同时产生 30 路 PWM 输出!这里我们仅利用 TIM3的 CH2 产生一路 PWM 输出。如果要产生多路输出,大家可以根据我们的代码稍作修改即可。
2 相关寄存器
要使STM32 的通用定时器 TIMx 产生 PWM 输出,除了上一部分介绍的寄存器外,我们还会用到 3 个寄存器,来控制 PWM 的。这三个 寄存器分别是:捕获 /比较模式寄存器TIMx_CCMR1/2 )、捕获/比较使能寄存器( TIMx_CCER )、捕获/比较寄存器( TIMx_CCR1~4 )。接下来我们简单介绍一下这三个寄存器。
2.1 捕获/比较模式寄存器( TIMx_CCMR1/2 )
该寄存器总共有 2 个, TIMx _CCMR1和 TIMx CCMR2 。TIMx_CCMR1 控制 CH1 和 2 而 TIMx CCMR 2 控制 CH3 和 4 。该寄存器的各位描述如下图 所示:
该寄存器的有些位在不同模式下,功能不一样,所以在图中,我们把寄存器分了2层,上面一层对应输出而下面的则对应输入。关于该寄存器的详细说明,请参考《 STM32 参考手册》第 288 页, 14 .4.7 一节。这里我们需要说明的是模式设置位 OCxM ,此部分由 3 位组成。总共可以配置成7种模式,我们使用的是 PWM 模式,所以这 3 位必须设置为 110/111 。这两种PWM 模式的区别就是输出电平的极性相反。
2.2捕获比较使能寄存器( TIMx_CCER )
该寄存器控制着各个输入输出通道的开关。该寄存器的各位描述如下图所示:
该寄存器比较简单,我们这里只用到了 CC2E 位,该位是输入/捕获 2 输出使能位,要想PWM 从 IO 口输出,这个位必须设置为 1 ,所以我们需要设置该位为 1 。该寄存器更详细的介绍了,请参考《 STM32 参考手册》第292页, 14.4.9 这一节。
2.3 捕获/比较寄存器( TIMx_CCR1~4 )
该寄存器总共有 4 个,对应 4 个输通道 CH1~4 。因为这 4 个寄存器都差不多,我们仅以 TIMx_CCR1 为例介绍,该寄存器的各位描述如下图所示:
在输出模式下,该寄存器的值与CNT 的值比较,根据比较结果产生相应动作。利用这点,我们通过修改这个寄存器的值,就可以控制 PWM 的输出脉宽了。 本章,我们使用的是 TIM3的通道 2 ,所以我们需要修改 TIM3_CCR2 以实现脉宽控制 DS0 的亮度。
2.4 端口重映射
我们要利用TIM3 的 CH2 输出 PWM 来控制 DS0 的亮度 ,但是 TIM3_CH2 默认是接在 PA7上面的,而我们的 DS0 接在 PB5 上面,如果普通 MCU ,可能就只能用飞线把 PA7 飞到 PB5上来实现了,不过,我们用的是 STM32 ,它比较高级 ,可以通过重映射功能,把 TIM3_CH2映射到 PB5 上 。
STM32的重映射控制是由复用重映射和调试 IO 配置寄存器( AFIO_MAPR )控制的,该寄存器的各位描述如下图所示:
我们这里用到的是TIM3 的重映射,从上图可以看出, TIM3_REMAP 是由 [11:10] 这 2 个位控制的。 TIM3_REMAP[1:0] 重映射控制表如下表所示:
默认条件下,TIM3_REMAP[1:0] 为 00 ,是没有重映射的,所以TIM3_CH1~TIM3_CH4 分别是接在 PA6 、 PA7 、 PB0 和 PB1 上的,而我们想让 TIM3_CH2 映射到 PB5 上, 则 需要设置TIM3_REMAP[1:0]=10 ,即部分重映射,这里需要注意,此时 TIM3_CH1 也被映射到 PB4 上了。
至此,我们把本章要用的几个相关寄存器都介绍完了,本章要实现通过重映射TIM3_CH2到 PB5 上,由 TIM3_CH2 输出PWM来控制DS0的亮度。下面我们介绍 通过库函数来配置该功能的步骤 。
3 相关库函数
首先要提到的是,PWM 相关的函数设置在库函数文件 stm32f 10x_tim.h 和 stm32f10x_tim.c文件中。(即定时器模块)
3.1 设置 TIM3_CH2 重映射到 PB5
因为TIM3_CH2 默认是接在 PA7 上的,所以我们需要设置 TIM3_REMAP 为部分重映射(通过 AFIO_MAPR 配置),让 TIM3_CH2 重映射到 PB5 上面。 在库函数函数 里面设置重映射的函数是:
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
STM32 重映射只能重映射到特定的端口 。 第一个入口参数可以 理解为 设置重映射的类型 ,比如 TIM3 部分重映射入口参数为GPIO_PartialRemap_TIM3 ,这点可以顾名思义了。 所以TIM3部分重映射的库函数实现方法是:
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
3.2 初始化定时器参数函数TIM_TimeBaseInit
在开启了TIM3 的时钟之后,我们要设置 ARR 和 PSC 两个寄存器的值来控制输出 PWM 的周期。当 PWM 周期太慢(低于 50Hz )的时候,我们就会明显感觉到闪烁了。因此 PWM 周期在这里不宜设置的太小。 这在库函数是通过 TIM_TimeBaseInit 函数实现的,在上一部分定时器中断章节我们已经有讲解,这里就不详细讲解,调用的格式为:
TIM_TimeBaseStructure.TIM_Period = arr; // 设置自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler =psc; // 设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 设置时钟分割 :TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 根据指定的参数初始化 TIMx 的
3.3 定时器通道设置函数TIM_OC2Init
接下来,我们要设置TIM3_CH2 为 PWM 模式(默认是冻结的),因为我们的 DS0 是低电平亮,而我们希望当 CCR2 的值小的时候, DS0 就暗,CCR2 值大的时候, DS0 就亮,所以我们要通过配置 TIM3_CCMR1 的相关位来控制 TIM3_CH2 的模式。 在库函数中, PWM通道设置是通过函数 TIM_OC 1 Init TIM_OC 4 Init 来设置的, 不同的通道的设置函数不一样, 这里我们使用的是通道 2 ,所以使用的函数是 TIM_OC 2 Init 。
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
来看看结构体 TIM_OCInitTypeDef的定义:
typedef struct
{
uint16_t TIM_OCMode;
uint16_t TIM_OutputState;
uint16_t TIM_OutputNState;
uint16_t TIM_Pulse;
uint16_t TIM_OCPolarity;
uint16_t TIM_OCNPolarity;
uint16_t TIM_OCIdleState;
uint16_t TIM_OCNIdleState;
} TIM_OCInitTypeDef;
这里我们讲解一下与我们要求相关的几个成员变量:
- 参数 TIM_OCMode 设置模式是 PWM 还是输出比较,这里我们是 PWM 模式。
- 参数TIM_OutputState 用来设置比较输出使能,也就是使能PWM输出到端口 。
- 参数TIM_OCPolarity 用来设置极性是高还是低。
- 其他的参数TIM_OutputNState TIM_OCNPolarity TIM_OCIdleState 和TIM_OCNIdleState 是高级定时器 TIM1 和 TIM8 才用到的。
要实现我们上面提到的场景,方法是:
TIM_OCInitTypeDef TIM_OCInitStructure;
TI M_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; // 选择 PWM 模式 2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); // 初始化 TIM3 OC2
3.4 修改TIM3_CCR2 占空比函数
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint1 6_t Compare2);
理所当然,对于其他通道,分别有一个函数名字,函数格式为TIM_SetCompare x(x=1,2,3,4) 。
4 输出PWM的流程与思路
需要注意的是,用不到中断。
- 1 开启 TIM3 时钟 以及复用功能时钟 ,配置 P B5 为复用输出。
- 2 设置 TIM3_CH2 重映射到 PB5 上 。
- 3 初始化 TIM3, 设置 TIM3 的 ARR 和 PSC 。
- 4 设置 TIM3_CH2 的 PWM 模式 ,使能 TIM3 的 CH2 输出 。
- 5 使能TIM3。
- 6 修改 TIM3_CCR2 来控制占空比。
5 项目代码与实现
和上一部分不同的是,我们在 timer.c 里面加入了如下代码:
//TIM3 PWM 部分初始化
//PWM 输出初始化
//arr :自动重装值
//psc :时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);// ① 使能定时器 3 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|
RCC_APB2Periph_AFIO, ENABLE); ① 使能 GPIO 和 AFIO 复用功能时钟
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); // ② 重映射 TIM3_CH2 -->PB5
//设置该引脚为复用输出功能 输出 TIM3 CH2 的 PWM 脉冲波形 GPIOB.5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;// 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //① 初始化 GPIO
//初始化 TIM3
TIM_TimeBaseStructure.TIM_Period = arr;// 设置在自动重装载周期值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 设置时钟分割 :TDTS = Tck_tim
TIM_TimeBaseStructure.T IM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);// ③ 初始化 TIMx
//初始化 TIM3 Channel2 PWM 模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;// 选择 PWM 模式 2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputSta te_Enable; // 比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //④ 初始化外设 TIM3 OC2
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); // 使能预装载寄存器
TIM_Cmd(TIM3, ENABLE);// ⑤ 使能 TIM3
接下来,我们修改主程序里面的 main 函数如下:
int main(void)
{
u16 led0pwmval=0;
u8 dir=1;
delay_in it(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2) 2); //设置 NVIC 中断分组 2
uart_init( 115200) ;// 串口初始化波特率为 115200
LED_Init(); //LED 端口初始化
TIM3_PWM_Init(899,0); //不分频 PWM 频率 =72000/900=8 0 Khz
while(1)
{
delay_ms(10);
if(dir)led0pwmval++;
else led0pwmval--;
if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;
TIM_SetCompare2(TIM3,led0pwmval);
}
}
这里,我们从死循环函数可以看出,我们将led0pwmval这个值设置为 PWM比较值 ,也就是通过led0pwmval来控制PWM的占空比 ,然后 控制 led0pwmval的值从0变到300 ,然后又从300变到0如此循环,因此 DS0 的亮度也会跟着从暗变到亮,然后又从亮变到暗。至于这里的值,我们为什么取 300 ,是因为PWM的输出占空比达到这个值的时候,我们的LED亮度变化就不大了(虽然最大值可以设置到 899 ),因此设计过大的值在这里是没必要的。至此,我们的软件设计就完成了。