前言
一、内部结构
1、分类
2、框图
二、定时器输出的原理
1、计数(基本定时器)
1、过程
2、举例
2、PWM波输出
1、名词解释
2、举例
3、输出比较
三、代码示例(基本定时器)
1、cubemx配置
2、上代码( 水灯)
四、代码示例(高级定时器)
1、cubemx配置
2、上代码
五、问题
1、注意点
1、PSC和ARR寄存器的值为什么最后一位常常是9
2、既然周期是由PSC和ARR寄存器共同决定的,我们能不能调节PSC寄存器来改变周期
总结
前言
注:本系列基于2023年蓝桥杯实战情况就行编写,所有软件均采用2023年赛点资源包
其实定时器本质上应该属于一种特殊的计数器,其本质是按照特定的频率和计数方式进行计数,当计数值达到预设值的时候就会产生一个事件触发中断或者DMA请求。
因为定时器部分内容较多,因此本章节只讨论输出部分。因方波也是一种特殊的pwm波,因此下文中主要以pwm波为主。
一、内部结构
1、分类


这边需要注意只有定时器2是32位的。
关于计数方式,可以分为中心计数,向上计数和向下计数三种。在手册里已经给出了示意图。

注意:图中的RCR寄存器(重复次数寄存器)是高级定时器才有的,对于其他类型的定时器,我们认为其就是这种情况
2、框图

看不清的话可以去参考手册28.3.1节查询。图中粉色荧光 笔部分为输入捕获,放入下一节中讲述。

这个是通用定时器

这个是基本定时器。其中CK_CNT=CK_PSC/(PSC[15:0]+1)。
注意:PSC和ARR寄存器的框下面有阴影,表示存在影子寄存器,只有当更新事件发生的时候,重装载寄存器的值才会传递到影子寄存器,影子寄存器才是真正起作用的寄存器
其实高级定时器-刹车部分=通用定时器,通用定时器-中间荧光笔标记的部分=基本定时器。因此本文直接用高级定时器做分析。
二、定时器输出的原理
1、计数(基本定时器)
1、过程
我们看上面基本定时器的结构图,可以发现来自APB总线的时钟信号进入了定时器的触发控制器,每来一个时钟信号,计数器加一,然后计数器的值来到了下方的预分频器,输出分频后的计数值进入 CNT counter即计数器。而计数器上方是自动重装载寄存器,当CNT内的值与ARR内的值相等的时候,就会触发更新事件,停止计数,并清空CNT。以此往复,便实现了定时的功能。
2、举例
这里以定时器6定时1s举例。
我一般使用100M主频,因此APB1总线上的频率为100Mhz。查阅手册得知(不会的去看第三节中的时钟树),定时器6挂载在APB1总线上,因此输入定时器的时钟信号也就为100M。
通过手册我们可以知道,定时器6是一个16位的向上计数的定时器,一秒将产生100M个事件,也就是ARR寄存器至少要27位才能装下,但是显然不可能做到。因此,我们需要预分频,预分频寄存器也是16位的。也就是说,我最多可以计2^32个数了,满足要求了。那么接下来对于PSC和ARR两个寄存器的值我们就需要好好考虑一下了(尤其是在资源受限的条件下,我们不可能一个定时器只用一个引脚)。
PSC*ARR 计数间隔
65534*1525 655us
39999*2499 400us
19999*4999 200us
9999*9999 100us
4999*1999 9 50us
2499*399 99 25us
1525*6553 4 15us
至于为什么PSC和ARR不是整数,请看第一个问题
通过计算,我们可以发现在当前主频下,我们计数间隔(产生事件的间隔)在15~655us间,我们最长只能计时42.9s。但是这个时候的间隔为655us,将导致之后调用这个定时器的时候很难实现精确的定时,甚至整个1s也是有偏差的。相对来说上图标注的两个值比较合理,也方便后期计算时间。
2、PWM波输出
在上面的基础上,我们需要引入两个概念,分别是占空比和周期。
1、名词解释
周期:PWM波可以认为是由一段又一段重复的波形组成的,而每段波持续的时间就是周期,上一节中我们实现的1s定时,周期为1s。也就是通过改变ARR寄存器的值调节周期(一般情况下我们程序运行过程中修改频率是一件很危险的事,当然也有例外,看下方问题区)。
务必注意:一个定时器共用一个PSC和ARR寄存器,也就是各个通道的计数的时间间隔是一样的,我们只能通过CCR寄存器来调整各个通道的输出周期
占空比:在一个周期内有效电平(一般我们认为高电平有效)持续时间占总周期的比值。
2、举例
在程序中,假设我们需要输出一个1hz高电平有效30%占空比的PWM波。我们通过预分频,将计数频率降低到10000hz,此时我们数10000个数就是1s。而当计数器数到3000的时候,就会翻转电平,这样我们就获得了想要的PWM波。
根据傅里叶变换,方波是由基波和无数个奇次谐波组成。而方波正是一种特殊的PWM波,也就是说,理论上我们产生PWM波后,就可以通过多个PWM波来合成我们所需要的任意的波形了。
3、输出比较
输出比较一共可以分为5种模式,区别如下
模式 说明
冻结模式 作为普通定时器使用
比较输出模式1 匹配时输出有效/无效电平模式
比较输出模式2 也叫电平翻转模式,方波输出
强制输出模式 强制为无效/有效电平模式
PWM模式 PWM1和PWM2
关于PWM1和PWM2的区别
一般我们都使用PWM1模式,简单的说就是当cnt回到初始值后,在到达ccr设定的值之前为有效电平的就是PWM1,否则就是PWM2。
三、代码示例(基本定时器)
1、cubemx配置


2、上代码(流水灯)
记得在开始的时候开启定时器6的中断 。
void HAL_Tim_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint16_t counter = 0;
static uint16_t led = 0x0001, i = 0, dir = 0;
if (++counter == 100)
{
counter = 0;
GPIOD->BSRR |= 0x0004; // PD2置1
GPIOC->ODR = ~(led << (8 + i)); // pc8~15输出反相的led
__nop(); // delay一个cpu时钟周期
GPIOD->BRR |= 0x0004; // PD2置0
if (dir == 0)
{
if (++i == 7)
dir = 1;
}
else
{
if (--i == 0)
dir = 0;
}
}
HAL_TIM_Base_Start_IT(&htim6);
}
四、代码示例(高级定时器)
1、cubemx配置


2、上代码
按需组合使用下面几行代码就可以
TIM3->PSC=99;//AHB clock prescaler/100 TIM3->ARR=999;//auto reload register TIM3->CCR2=500;//compare capture register,duty=CCR2/ARR=50% HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);//start pwm HAL_Delay(1000) HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_2);//stop pwm
五、问题
1、注意点
1、PSC和ARR寄存器的值为什么最后一位常常是9
务必注意这个寄存器值为0时,实际是1。也就是如果我们需要100分频,PSC的值应该设置成100-1即99。ARR同理。
2、既然周期是由PSC和ARR寄存器共同决定的,我们能不能调节PSC寄存器来改变周期
一般情况下,我们习惯调整ARR寄存器的值来改变周期,因为相对计算简单,就像上面100M主频,PSC=10000的情况下,每次CNT+1需要经过0.1ms,我想要1hz,只需要ARR=10000,想要10hz,只需要ARR=1000。
但是,就是14届比赛的时候,碰到一个很神奇的东西。题目要求pwm波以不超过200hz的变化幅度在规定时间内变换至指定周期,占空比还不能变。这个时候你会发现占空比是CCR和ARR的比值,ARR变了,CCR的值就要重算,虽然就是多一行程序,但是实际上示波器测量发现占空比还是有波动的。这个时候,我们调整PSC相对来说就容易的多了。
那么怎么选择呢,很简单,记住一句话改周期就改ARR,改频率就改PSC
总结
定时器部分我个人偏向于寄存器编程,因为只需要记住9个字母(PSC、ARR、CCR)就可以完成大部分功能的编写,比又臭又长的hal库函数好记多了,重点是keil调试的时候是可以直接查看这三个寄存器的值的,这极大的方便了找bug 。