从零开始学嵌入式之STM32——26.STM32的通用定时器-生成PWM方波
通用定时器的功能很强大,针对不同功能配置了众多的寄存器,我们在学习时可以按照功能来学习寄存器,这样更有利于对通用定时器的掌握。使用定时器的输入输出功能时,GPIO端口配置成复用推挽输出模式即可,因为在输出模式下,输入功能也是打开的。简称脉宽调制,通过调整信号中的高电平的占比时间(占空比)来调整输出功率的大小,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。当然也可以输出其他波形。
目录
一、通用定时器介绍
在整个STM32F10xxx系列中,通用定时器的数量和具体的系列相关,例如目前我手中的开发板的芯片有两种型号:STM32F103C8T6和STM32F103ZET6。这两种芯片的通用定时器的数量就有所不同:
下图是两款芯片的数据手册,从截图中可以看出:


STM32F103ZET6的通用定时器的数量为4个:TIM2/3/4/5
STM32F103C8T6的通用定时器的数量为3个:TIM2/3/4
通用定时器拥有基本定时器所有的功能,并在此基础上增加了如下功能:
- 多种时钟源
- 向上计数、向下计数、向上/向下计数
- 输入捕获
- 输出比较
- PWM生成
- 支持针对定位的增量(正交)编码器和霍尔传感器电路
- IM1、TIM8 是高级定时器
- F103高容量系列和互联型系列还有 基本定时器 TIM6、TIM7
1.通用定时器功能框图
2.通用定时器的时钟来源
3.外部时钟源的功能
- 一般用于定时器的级联
- 大部分情况下,内部时钟源足够使用
- 不配置时钟源的情况下,默认选择的就是内部时钟源
4.定时器相关的引脚统计
下面是根据芯片对应的数据手册统计的与通用定时器相关的通道引脚(仅统计了STM32F103C8T6和STM32F103ZET6两款常用芯片)
STM32F103C8T6(注意:下表仅统计了默认复用功能,未统计重映射)
|
引脚 |
定时器通道 |
|
PA0 |
TIM2_CH1_ETR |
|
PA1 |
TIM2_CH2 |
|
PA2 |
TIM2_CH3 |
|
PA3 |
TIM2_CH4 |
|
PA6 |
TIM3_CH1 |
|
PA7 |
TIM3_CH2 |
|
PB0 |
TIM3_CH3 |
|
PB1 |
TIM3_CH4 |
|
PB6 |
TIM4_CH1 |
|
PB7 |
TIM4_CH2 |
|
PB8 |
TIM4_CH3 |
|
PB9 |
TIM4_CH4 |
STM32F103ZET6(注意:下表仅统计了默认复用功能,未统计重映射)
|
引脚 |
定时器通道 |
|
PA0 |
TIM2_CH1_ETR/TIM5_CH1 |
|
PA1 |
TIM2_CH2/TIM5_CH2 |
|
PA2 |
TIM2_CH3/TIM5_CH3 |
|
PA3 |
TIM2_CH4/TIM5_CH4 |
|
PA6 |
TIM3_CH1 |
|
PA7 |
TIM3_CH2 |
|
PB0 |
TIM3_CH3 |
|
PB1 |
TIM3_CH4 |
|
PD2 |
TIM3_ETR |
|
PB6 |
TIM4_CH1 |
|
PB7 |
TIM4_CH2 |
|
PB8 |
TIM4_CH3 |
|
PB9 |
TIM4_CH4 |
|
PE0 |
TIM4_ETR |
二、计数器的3种计数模式
1.向上计数
2.向下计数
3.中央对齐模式
三、PWM介绍
PWM(pulse-width modulation,脉冲宽度调制)
简称脉宽调制,通过调整信号中的高电平的占比时间(占空比)来调整输出功率的大小,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。
STM32中通用定时器的脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器(自动重装载寄存器)确定频率、由TIMx_CCRx寄存器(捕获/比较寄存器)确定占空比的信号。
PWM通常用于控制电机转速、LED亮度调节等应用。要求被控制的电路必须要有一定的惯性。
所谓惯性指的是电路中的负载的状态不会发生突变。比如电机断电了也不会立即停止。LED灯是利用了人类眼睛的余晖(视觉暂留)效应(大概是0.1s)。
注意:PWM并不等同于数模转换。
1.PWM的3个参数
(1)周期
(2)频率
(3)占空比
五、使用通用定时器输出PWM波形
1.定时器的基础配置
在基本定时器的使用中,介绍了定时器的时基部分、触发部分的配置,主要用于设置定时器的输入频率(通过预分频器PSC控制)和计数器的溢出(通过自动重装载寄存器ARR控制)。具体的配置方法请参考上节内容。
2.控制计数方向
3.配置通道方向和输出比较模式
使用定时器的PWM输出时,GPIO端口配置成复用功能推挽输出模式(CNF-10;MODE-11)即可。
通用定时器中的通道可用于输入(捕获模式)或输出(比较模式),通道的方向由CCMRx寄存器的CCxS通道模式控制位定义,该控制位有2bit[1:0]。
在PWM模式时,需要配置为输出模式(比较模式)此时的CCxS位的值需配置为:00。
CCxS的不同,会导致CCMR1寄存器不同控制位的作用发生变化,在使用时需要特别注意,在配置通道时,需要先设定好通道的模式再做其他配置。
3.1.捕获/比较模式寄存器1(TIMx_CCMR1)
3.2.输出比较模式:CCxS为00
3.3.输入捕获模式:CCxS不为00
4.通用定时器的输出比较功能
- 假设计数器向上计数,重装载寄存器的值为99(计数100后溢出)
- 假设捕获/比较寄存器的值为60
- 比较寄存器的值和计数器的值进行大小比较
- 根据比较结果(> = < )不同,产生不同输出:高电平或低电平
4.1.输出比较模式下的比较值
5.输出比较的8种模式
由CCMR1寄存器的3个控制位OC1M[2,0]控制。假设计数器的值是CNT,比较寄存器1的值是CCR = 60。
模式1:OC1M[2,0]:000 输出冻结,啥都不输出,与比较结果无关。
模式2:OC1M[2,0]:001 强制输出高电平。一旦CNT==CCR,强制输出高电平,再也不改变了。
模式3:OC1M[2,0]:010 强制输出低电平,一旦CNT==CCR,强制输出低电平,再也不改变了。
模式4:OC1M[2,0]:011 输出翻转,一旦CNT==CCR,则翻转输出。高->低,低->高。
这个时候,得到的方波是一个占空比为50%的方波,这是为什么呢?为什么不是60%?
原因是:
电平每次发生翻转都是在CNT == CCR的时候,这个周期是固定的。
输出的波形是溢出频率的一半。
这种模式可以用于调整相位。
模式5:OC1M[2,0]:100 强制输出低电平,与比较结果无关。
模式6:OC1M[2,0]:101 强制输出高电平,与比较结果无关。
模式7:OC1M[2,0]:110 PWM模式1(重点)
CNT < CCR 输出高电平
CNT >= CCR 输出低电平
输出波形如下:
输出占空比为60%的PWM方波
溢出频率等于PWM频率
周期也相同
模式8:OC1M[2,0]:111 PWM模式2
CNT < CCR 输出低电平
CNT >= CCR 输出高电平
周期等于溢出周期
频率等于溢出率
占空比为40%
6.配置通道极性和使能
CC1P输入输出捕获1输出极性:
为0:OC1高电平有效
为1:OC1低电平有效
影响的是PWM模式时的通道输出的有效电平,严格来讲,PWM模式输出低电平有效更为合理,因为是低电平点亮LED。
7.通用定时器寄存器映像表
六、代码编写
以下代码基于STM32F103C8T6芯片实现,使用芯片的GPIOA1引脚连接LED实现PWM驱动LED实现呼吸灯效果。
文件名:tim2.h
#ifndef __TIME2_H
#define __TIME2_H
#include "stm32f10x.h"
// 函数声明
// 初始化
void TIM2_Init(void);
// 开启定时器,开始输出PWM方波
void TIM2_Start(void);
// 停止定时器,停止输出PWM方波
void TIM2_Stop(void);
// 设置PWM占空比
void SET_DutyCycle(uint8_t dutycycle);
#endif
文件名:tim2.c
#include "tim2.h"
// 初始化
void TIM2_Init(void)
{
// 1.开启时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 2.设置GPIOA1引脚的工作模式,PWM方波输出,引脚需要工作在复用功能推挽输出模式:CNF-10;MODE-11;
GPIOA->CRL &= GPIO_CRL_CNF1_0;
GPIOA->CRL |= GPIO_CRL_CNF1_1;
GPIOA->CRL |= GPIO_CRL_MODE1;
// 3.设置TIM2定时器,为了使得LED刷新频率可以达到合适的效果,需要考虑人眼的余晖效应,这里将频率定为100Hz
// 3.1.设置定时器的输入频率,假设ARR的值为99(100次计数),输出100Hz的频率,在输入的时钟频率为72MHz的前提下,PSC的值的计算过程为:
// 输出频率 = 1 / [(1 / (定时器输入时钟频率 / (PSC+1))) * (ARR + 1)]
// 由频率计算公式推到出周期计算公式:
// 输出时钟周期 = (1 / (定时器输入时钟频率 / (PSC+1))) * (ARR + 1)
// 已知:输如频率 = 72MHz ARR+1 = 100; 计算PSC
// 1/100Hz = (1 / (72MHz / (PSC+1))) * 100
// 72MHz / (PSC+1) = 100Hz * 100
// 72MHz / (100Hz * 100) = (PSC+1)
// PSC+1 = 7200
// PSC = 7199
// 不开启预装载功能(默认)
TIM2->CR1 &= ~TIM_CR1_ARPE;
TIM2->PSC = 7199;
// 3.2.设置预装载寄存器的值,为了方便调整PWM的占空比,这个值设定为99,也就是计数100次后溢出。
TIM2->ARR = 99;
// 3.3.设置定时器的计数方向,0代表向上计数
TIM2->CR1 &= ~TIM_CR1_DIR;
// 3.4.设置定时器的通道方向,输出,CCxS-00
TIM2->CCMR1 &= ~TIM_CCMR1_CC2S;
// 3.5.配置定时2器通道2的输出模式:PWM模式1
TIM2->CCMR1 &= ~TIM_CCMR1_OC2M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC2M_1;
TIM2->CCMR1 |= TIM_CCMR1_OC2M_2;
// 3.6.配置一个默认的占空比:99%,因为LED是共阳,因此占空比高代表LED暗。
TIM2->CCR2 = 99;
// 3.7.开启定时器通道输出使能
TIM2->CCER |= TIM_CCER_CC2E;
}
// 开启定时器,开始输出PWM方波
void TIM2_Start(void)
{
TIM2->CR1 |= TIM_CR1_CEN;
}
// 停止定时器,停止输出PWM方波
void TIM2_Stop(void)
{
TIM2->CR1 &= ~TIM_CR1_CEN;
}
// 设置PWM占空比
void SET_DutyCycle(uint8_t dutycycle)
{
TIM2->CCR2 = dutycycle;
}
文件名:main.c
#include "tim2.h"
#include "delay.h"
int main(void)
{
// 初始化定时器2
TIM2_Init();
// 开启定时器
TIM2_Start();
// 定义PWM占空比变化的方向,0代表向上计数,1代表向下计数
uint8_t dir = 0;
// 定义占空比的初始值
uint8_t dutycycle = 99;
while(1)
{
// 确定方向
if(dutycycle == 99)
{
dir = 1;
}
else if (dutycycle == 0)
{
dir = 0;
}
// 占空比变化
if(dir == 0)
{
dutycycle++;
}
else
{
dutycycle--;
}
// 占空比调整
SET_DutyCycle(dutycycle);
Delay_nms(20);
}
}
七、最终效果
更多推荐



所有评论(0)