定时器及PWM
1 定时器
1.1 定时器分类
对于STM32来说,定时器可分为基本定时器、通用定时器、高级定时器三类,后者包括前者的全部功能。以stm32f1系列为例,TIM6和TIM7为基本定时器,TIM2~TIM5为通用定时器,TIM和TIM8为高级控制定时器。
基本定时器(TIM6/TIM7)【精简型】
● 16位自动重装载累加计数器
● 16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频
● 触发DAC的同步电路,TIM6/7独有功能
● 在更新事件(计数器溢出)时产生中断/DMA请求
通用定时器(TIM2、TIM3、TIM4和TIM5)【通用型】
● 16位向上、向下、向上/向下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意数值
● 4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 使用外部信号控制定时器和定时器互连的同步电路
● 如下事件发生时产生中断/DMA:
─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
─ 输入捕获
─ 输出比较
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
高级定时器(TIM1和TIM8)【增强型】
● 16位向上、向下、向上/下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值
● 4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 使用外部信号控制定时器和定时器互联的同步电路
● 如下事件发生时产生中断/DMA:
─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
─ 输入捕获
─ 输出比较
─ 刹车信号输入
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
● 死区时间可编程的互补输出
● 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
● 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
通用定时器挂载在APB1总线,高级定时器挂载在APB2总线。
1.2 计数模式
(1)向上计数模式
计数器从0计数到自动加载值(TIMx_ARR计数器的内容),然后重新从0开始计数并且产生一个计数器向上溢出时间,每次溢出时可以产生更新事件。
(2)向下计数模式
计数器从自动加载值(TIMx_ARR计数器的内容)向下计数到0,然后从自动装载值重新开始并且产生一个计数器向下溢出时间,每次溢出时可以产生更新事件。
(3)中央对齐模式(向上/向下计数)
计数器从0开始计数到自动加载值(TIMx_ARR寄存器)-1,产生一个计数器向上溢出事件,最后向下计数到1并产生一个计数器向下溢出时间,最后再从0开始重新计数。
1.3 相关结构及函数
本节基于stm32f1系列基本定时器进行相关讲解,如下图所示为基本定时器结构框图
查阅参考手册RCC章节的时钟树可以知道,RCC的定时器时钟TIMxCLK,即内部时钟CK_INT是由APB1预分频器分频后提供。如下图所示,如果APB1预分频系数为1,则频率不变,否则频率为2倍。即此时用于分频的APB1的预分频系数为2,所以TIMxCLK = 36 * 2 = 72MHz。
看第一个圆圈内容,APB1的时钟,最大是36M,由分频系数决定,当分频系数是2的时候,APB1的时钟就是36MHz。
看第二个圆圈内容,当APB1的分频系数不为1的时候,TIMXCLK的时钟就是APB1的时钟乘以2,所以TIM2的时钟就是72MHz了。为什么可以乘以2?答:手册上就是这么说的,至于为什么,你得去问STM32芯片厂商的IC工程师了。
system_stm32f10x.c文件的SetSysClockTo72()函数,默认就是配置APB1位2分频,如下图所示:
(1)TIM_TimeBaseInitTypeDef结构体
TIM_Prescaler:指定定时器预分频器数值,由TIMx_PSC寄存器配置,可设置范围为0x0000~0xFFFF,即0~65535;
TIM_CounterMode:计数模式,可分为向上计数、向下计数以及三种中心对齐模式。而基本定时器只能向上计数;
TIM_Period:计数器周期,即自动重装载寄存器TIMx_ARR的值,在事件生成时更新到影子寄存器,由TIMx_CR1寄存器的ARPE位配置是否使能缓冲;
TIM_ClockDivision:时钟分频,配置定时器时钟CK_INT频率与数字滤波器采样时钟频率分频比,基本定时器没有这个功能,不用设置;
TIM_RepetitionCounter:重复计数器,属于高级控制寄存器专用寄存器位,利用它可以很容易控制输出PWM个数,这里不用设置。
计一个数的时间是1/CK_CNT,产生一次中断的时间为(ARR+1)/CK_CNT。如果在中断服务程序里设置一个变量time用于记录中断次数,则time定时时间为:(ARR+1)/CK_CNT*time。
(2)定时1s实验
例如,需要做一个1s的定时,CK_PSC=72MHz,则PSC=71,那么CK_CNT=1MHz,
计一个数时间:1/CK_CNT = 1/1MHz = 1us,
中断一次的时间:(ARR+1)/CK_CNT = (999+1)/1MHz = 1ms,
则定时时间:(ARR+1)/CK_CNT*time = 1ms*1000 = 1s
初始化TIM_TimeBaseInitTypeDef
1 /** 2 * @brief 基本定时器配置 3 * @param 无 4 * @retval 无 5 */ 6 static void BASIC_TIM_Mode_Config(void) 7 { 8 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; 9 10 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); // 内部时钟72MHz 11 12 TIM_TimeBaseStructure.TIM_Period = 999; // 自动重装载寄存器的值 13 TIM_TimeBaseStructure.TIM_Prescaler= 71; // 预分频器数值 14 TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure); 15 16 TIM_ClearFlag(TIM6, TIM_FLAG_Update); // 清除计数器中断标志位 17 TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); 18 19 TIM_Cmd(TIM6, ENABLE); 20 }
中断优先级配置
/** * @brief 中断优先级配置 * @param 无 * @retval 无 */ static void BASIC_TIM_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
中断函数
1 extern volatile uint32_t time; // 该变量定义在main()函数里 2 3 void TIM6_IRQHandler(void) 4 { 5 if(TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) 6 { 7 time++; // 每中断一次,time值加1,中断一次时间为1ms,需要中断1000次才可定时1s,即time值为1000 8 TIM_ClearITPendingBit(TIM6, TIM_FLAG_Update); 9 } 10 }
在main()函数里调用led和定时器的初始化配置函数,在一个循环里判断time变量的值是否为1000,如果已经达到1000,则led灯状态变化(亮或灭)一次,并且time变量值重赋为0,以便继续判断及定时。
2 PWM
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。
2.1 PWM工作过程
将寄存器值和计数器值比较,通过比较结果输出高低电平,实现PWM信号
如图为向上计数:
定时器重装载值为ARR,比较值CCRx
t时刻对计数器值和比较值进行比较
如果计数器值小于CCRx值,输出低电平
如果计数器值大于CCRx值,输出高电平
PWM的一个周期
定时器从0开始向上计数
0-t1段,定时器计数器TIMx_CNT值小于CCRx值,输出低电平
t1-t2段,定时器计数器TIMx_CNT值大于CCRx值,输出高电平
当TIMx_CNT值达到ARR时,定时器溢出,重新向上计数...循环此过程
至此一个PWM周期完成
影响因素
ARR:决定PWM周期(在时钟频率一定的情况下,当前为默认内部时钟CK_INT)
CCRx:决定PWM占空比(高低电平所占整个周期比例)
1 TIMx_CCMR1寄存器的OC1M[2:0]位,设置输出模式控制器
110:TIM_OCMode_PWM1(向上计数时,CNT<CCR为有效电平,oc1ref=1,否则为无效电平;向下计数时,CNT>CCR为无效电平,oc1ref=1,否则为有效电平)
111:TIM_OCMode_PWM2(向上计数时,CNT<CCR为无效电平,oc1ref=1,否则为有效电平;向下计数时,CNT>CCR为有效电平,oc1ref=1,否则为无效电平)
两种模式的有效无效正好相反。
2 计数器值TIMx_CNT与通道1捕获比较寄存器CCR1进行比较,通过比较结果输出有效电平和无效电平
OC1REF=0 无效电平
OC1REF=1 有效电平
3 通过输出模式控制器产生的信号
TIMx_CCER寄存器的CC1P位,设置输入/捕获通道1输出极性
0:高电平有效
1:低电平有效
4 TIMx_CCER:CC1E位控制输出使能电路,信号由此输出到对应引脚
0:关闭
1:开启
计数器值TIMx_CNT与捕获比较寄存器值CCRx比较后,由TIMx_CCMR1:OC1M位和TIMx_CCER:CC1P位共同决定最终的输出结果。
下图中,通过配置TIMx_CCMR1可以配置相应通道为输入(捕获模式)还是输出(比较模式),OCxx描述了输出模式下的含义,ICxx描述了输入模式下的含义。
通过设置模式1或模式2,决定了比较结果输出为有效电平(OC1REF=1高电平)或无效电平(OC1REF=0低电平)
CC1P设置输入/捕获1极性,确定最终输出为高电平还是低电平,CC1P=0,则在OC1REF为高电平时输出高电平,CC1P=1,则在OC1REF为低电平时输出高电平,从图2中可以看出在CC1P=1时会有一个反相器,将OC1REF输入进行反相。
CC1E设置输入/捕获1输出使能,0:关闭-OC1禁止输出;1:开启-OC1信号输出到对应的输出引脚。
2.2 PWM相关库函数
在本人使用的板子上TIM3_CH1对应的GPIO是PB4,以此为例进行说明,以下为PWM相关的主要函数
1 使能定时器3和相关IO时钟(LED-PB4)
使能定时器3时钟:RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
使能GPIOB时钟:RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
2 初始化IO口为复用功能输出GPIO_Init();
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOB, &GPIO_InitStructure);
3 PB4输出PWM(定时器3通道1),需要对PB4进行映射
GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_TIM3);
4 初始化定时器 (重装载值ARR,与分频系数PSC等)
PrescalerValue = (uint16_t) (SystemCoreClock / 1000000) - 1; // 100Mhz->1Mhz
TIM_TimeBaseStructure.TIM_Period = 100-1;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_PrescalerConfig(TIM3, PrescalerValue, TIM_PSCReloadMode_Immediate);
5 初始化输出比较参数:
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
6 使能预装载寄存器
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
7 使能定时器
TIM_Cmd(TIM3, ENABLE);
8 不断改变比较值CCRx,达到不同的占空比效果
TIM_SetCompare1(TIM3, pule);
在main函数中实现如下
while (1)
{
Delay(10);
if(i)
pule++;
else
pule--;
if(pule==0)
i=1;
if(pule>100)
i=0;
TIM_SetCompare1(TIM3, pule);
}