- 有关于定时器 输出PWM功能 不了解的可以看这篇文章 :HAL库STM32常用外设教程(一)—— 定时器 输出PWM
- 有关于定时器 定时功能不了解的可以看这篇文章 :HAL库STM32常用外设教程(四)—— 定时器 基本定时
前言
1、STM32F407ZGT6
2、STM32CubeMx软件
3、keil5
内容简述:
通篇文章将涉及以下内容,如有错误,欢迎指出:
1、通用定时器特性
2、通用定时器的结构和功能
3、定时器有关输出比较的HAL库驱动程序
(1)CubeMx配置
(2)TIM驱动程序
①输出频率、占空比相同,相位不同的PWM
②输出频率、占空比都不同的PWM
一、通用定时器特性概述
通用定时器TIM2-TIM5以及TIM9-TIM14的功能如表1-1所示,它们的区别主要在于计数器的位数,捕获/比较通道的个数不同,通用定时器具有以下特性。
表1.1定时器基本特性
- 16位或32位自动重载计数器
- 16位可编程预分频器,分频系数位1-65536,分频系数可在运行时修改
- 有1、2、4个独立通道 ,可用于
(1)输入捕获
(2)输出比较
(3)PWM生成(边沿对齐或中心对齐)
(4)单脉冲模式输出 - 可使用外部信号控制定时器,可实现多个定时器互联的同步电路
- 可发生如下事件时产生中断或DMA请求
(1)更新——计数器上溢/下溢、计数器初始化(用过软件或内部/外部触发)
(2)触发事件(计数器启动、停止、初始化或通过内部/外部触发计数)
(3)输出比较
(4)输入捕获
注:在STM32F407的参考手册上,TIM2-TIM5可以使用外部时钟信号驱动计数器,而TIM9-TIM14只能使用内部时钟信号。
二、通用定时器框图
2.1 通用定时器框图
通过学习通用定时器框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。
图2.1 通用定时器结构框图
① 时钟源
通用定时器时钟可以选择下面四类时钟源之一:
1)内部时钟(CK_INT)
2)外部时钟模式 1:外部输入引脚(TIx)
3)外部时钟模式 2:外部触发输入(ETR)
4)内部触发输入(ITRx):使用一个定时器作为另一定时器的预分频器
注意:触发控制器输出时钟信号CK_PSC,若选择使用内部时钟,则CK_PSC就等同于CK_INT。
② 控制器
控制器包括:从模式控制器、编码器接口和触发控制器(TRGO)。从模式控制器可以控制计数器复位、启动、递增/递减、计数。编码器接口针对编码器计数。触发控制器用来提供触发信号给别的外设,比如为其它定时器提供时钟或者为 DAC/ADC 的触发转换提供信号。
③ 时基单元
时基单元包括:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器
(TIMx_ARR)。这部分内容和基本定时器基本一样的,大家可以参考基本定时器里的介绍。
不同点:
Ⅰ、通用定时器的计数模式有三种: 递增计数模式、 递减计数模式和中心对齐模式。
- 递增计数模式就是来了一个计数脉冲,计数器就加 1,直到计数器寄存器的值加到 ARR,加到ARR 时定时器溢出,由于是递增计数,故而称为定时器上溢,定时器溢出就会伴随着更新事件的发生,然后计数器又从自动重载寄存器影子寄存器的值开始继续递减计数,如此循环。
- 递减计数模式就是来了一个计数脉冲,计数器就减 1,直到计数器寄存器的值减到 0,减到 0 时定时器溢出,由于是递减计数,故而称为定时器下溢,定时器溢出就会伴随着更新事件的发生。然后计数器又从自动重载寄存器影子寄存器的值开始继续递减计数,如此循环。
-
中心对齐模式计数器先从 0 开始递增计数,直到计数器的值等于自动重载寄存器影子寄存器的值减 1 时,定时器上溢,同时生成更新事件,然后从自动重载寄存器影子寄存器的值开始递减计算,直到计数值等于 1 时,定时器下溢,同时生成更新事件,然后又从 0 开始递增计数,依此循环。每次定时器上溢或下溢都会生成更新事件。
Ⅱ、TIM2 和 TIM5 的计数器是 32 位的。
下面通过一张图展示定时器工作在不同计数模式下,更新事件发生的情况:
图2.2 更新事件发生条件
上图中,纵轴表示计数器的计数值,横轴表示时间, ARR 表示自动重载寄存器的值,小红点就是更新事件发生的时间点。举个例子,递增计数模式下,当计数值等于 ARR 时,计数器的值被复位为 0,定时器溢出,并伴随着更新事件的发生,后面继续递增计数。
④ 输入捕获
图 2-1中的第④部分是输入捕获,一般应用是要和第⑤部分一起完成测量功能。TIMx_CH1~ TIMx_CH4 表示定时器的 4 个通道,这 4 个通道都是可以独立工作的。 IO 端口通过复用功能与这些通道相连。配置好 IO 端口的复用功能后,将需要测量的信号输入到相应的IO 端口,输入捕获部分可以对输入的信号的上升沿,下降沿或者双边沿进行捕获。常见的测量有:测量输入信号的脉冲宽度、测量 PWM 输入信号的频率和占空比等。
⑤ 输入捕获和输出比较公用部分
该部分需要结合第④部分或者第⑥部分共同完成相应功能。
⑥ 输出比较
图2-1中的第⑥部分是输出比较,一般应用是要和第⑤部分一起完成定时器输出功能。TIMx_CH1~ TIMx_CH4 表示定时器的 4 个通道,这 4 个通道都是可以独立工作的。 IO 端口通过复用功能与这些通道相连。
2.2 捕获/比较通道
通用定时器有1、2或4个捕获/比较通道,每个通道都是独立工作的。图2-1的定时器有2个捕获/比较通道:一个通道要么作为捕获输入通道,要么作为比较通道;每个通道有一个复用的引脚,如TIM9_CH1、TIM9_CH2复用引脚。
捕获/比较通道由输入阶段、比较阶段和输出阶段组成。
- 输入阶段。通道作为输入引脚,例如图2-1中④左侧的TIMx_CH1,从复用引脚输入时钟信号TI1,输入阶段可以对输入信号TI1进行滤波和边沿检测,在经过选择器和预分频器后得到时钟信号IC1PS。
- 捕获/比较阶段。有一个具有预装载功能的捕获/比较寄存器(Capture/Compare Register),以及相关的影子寄存器,可以读写CCR。在捕获模式下,捕获实际发生在影子寄存器中,然后将影子寄存器的内容复制到CRR中。在比较模式下,CRR的内容将复制到影字寄存器中,然后将影子寄存器的内容与计数器值进行比较。
-
输出阶段。输出阶段就是根据设置的工作模式和控制逻辑,控制输出引脚的电平。使用捕获/比较通道,通用定时器可以实现如下功能。
①输入捕获,可用于测量一个时钟信号的频率,脉冲宽度等。
②输出比较,将计数器CNT的值与CRR的值比较,控制输出引脚的电平。
③PWM生成,通过设置ARR和CRR的值,在计数器的值CNT变化过程中输出PWM波。PWM波的频率由ARR决定,占空比由CRR决定。
④单脉冲模式输出。
三、输出比较原理及HAL库驱动
3.1 输出比较
3.1.1 输出比较原理
输出比较(output compare)用于控制输出波形,或指示经过了某一段时间。它的工作原理是:用捕获/比较寄存器的值CCR与计数器值CNT比较,如果两个寄存器的值匹配,产生输出比较结果OCyREF,这个值有比较模式和输出极性决定,这个比较结果可以输出到通道的引脚。
注:
①简单来说就是在输出比较这块电路会比较CNT和CCR的值。CNT计数自增,CCR是我们给定的一个值,当CNT大于CCR、小于CCR时,引脚就会输出高电平或者是低电平。
②这个捕获/比较寄存器是输入捕获和输出比较共用的,当使用输入捕获时,他就是捕获寄存器,当时用输出比较时,它就是比较寄存器。
先回顾一下前面文章《HAL库STM32常用外设教程(一)—— 定时器 输出PWM》中提到的PWM模式(图3.1):
图 3.1 PWM模式
图3.2 PWM频率计算
其中当CNT计数值 = pulse(也就是CCR值)时,电平就会翻转,当CNT计数达到ARR后,再计数时CNT的值就会清0。总结来说,PWM模式下,PWM的频率由自动重装载值(ARR)和预分频值(PSC)共同决定,CCR决定的是PWM的占空比。
那我们再来看输出比较模式的特点,下图3.3是输出比较比较模式的原理示意图。
图3.3 输出比较模式
在CubeMx里面配置完输出比较模式后,PWM的周期也是固定的,是一个计数周期两倍,频率是周期的倒数,所以频率也是固定的,从图3.3可以看出占空比也是固定的,都是50%,因为 此时是CNT = CCR的时候进行翻转电平(Toggle on match模式下),CNT是从0到65535之间不断变化(递增模式下计数计到最大值65535时,会重新从0开始计数)。CCR的值如果不变,那么CNT计数记到CCR的周期就是固定的。
注:
以STM32F407为例,假设ARR值设置的是65535,预分频器PSC值是168-1,TIM1的通道1输出,那么就可以计算出此时TIM1_CH1输出比较模式下的频率。
计数器的频率 = 168MHz / (168-1 +1) = 1MHz ,得出 计一个数用时为 1/1000 000 秒
ARR数值为65535,一个计数周期计65535个,得出一个计数周期时间 = 65535 * 1 /1000 000 秒 =0.065535 秒
输出比较模式下周期是计数周期的2倍,得出 Toc = 2 * 0.065535 = 0.13107 秒 = 131.07 ms
频率是周期的倒数,所以得出该输出比较模式的下频率为 1 / 0.13107 = 7.6295HZ
上述计算得出的就是图3.5和图3.6中PWM的频率,从图中给出的数据也可以看出频率等参数。
那么此时CCR决定的是什么呢?设置CCR1和CCR2的值进行对比,通过图3.4可以看出,CCR值不同时 其占空比依旧是50%,周期也是计数周期的两倍,并没有变化,但是从红色的虚线可以看出PWM的相位发生了变化,所以在输出比较中 每个通道的初相位可以通过各通道的CCRx来确定。
通过图3.5和图3.6的实际输出 进一步观察,其中图3.5的CCR1 = 5,CCR2 = 30000,两个PWM的相位差约为四分之一的周期,将CCR2的值增大到65530,此时从图3.6可以看出相位差已将接近二分之一个周期。
图3.4 输出比较模式对比
图3.5 不同CCR下的PWM(1)
图3.6 不同CCR下的PWM(2)
输出比较模式下,当比较匹配时,可以产生中断或DMA请求,引起输出引脚发生如下几种变化,如图3.7。
- 冻结(Frozen),即保持其电平。
- 有效电平(Active level),有效电平由设置的通道极性决定。
- 无效电平(Inactive Level)。
- 翻转(Toggle on match)。
图3.7 输出比较变化模式
如图3.8所示,STM32CubeMx里面“Output compare preload”对应的是输入捕获/比较寄存器TIMx_CCMRy的OCyPE(输出比较预装载使能)位。
如果输入捕获/比较寄存器TIMx_CCMR1或TIMx_CCMR2中的OCyPE位设置为0,则捕获/比较寄存器TIMx_CCRy无预装载功能,对TIMx_CCRy寄存器的修改立刻生效;如果设置OCyPE位为1,对TIMx_CCRy寄存器的修改需要等到下一个UEV事件时才能生效,如图5-4。
注:文章中提到的TIMx_CCRy中的 x表示定时器,y表示通道编号。
图3.8 输出比较预装载使能
3.1.2 输出比较相关的驱动函数
输出比较相关的HAL函数如表3.1所示,这里仅列出了相关函数名,简要说明其功能相关函数在stm32f4xx_hal_tim.h中。
表3.1 输出比较相关的HAL函数
函数名 | 功能描述 |
---|---|
HAL_TIM_OC_Init() | 输出比较初始化,需先执行HAL_TIM_Base_Init()进行定时器初始化 |
HAL_TIM_OC_ConfigChannel() | 输出比较通道配置 |
HAL_TIM_OC_Start() | 启动输出比较,需要先执行HAL_TIM_Base_start()启动定时器 |
HAL_TIM_OC_Stop() | 停止输出比较 |
HAL_TIM_OC_Start_IT() | 以中断方式启动输出比较,需要先执行HAL_TIM_Base_start_IT()启动定时器 |
HAL_TIM_OC_Stop_IT() | 停止输出比较 |
__HAL_TIM_GET_COMPARE() | 获取基础定时器的当前状态 |
__HAL_TIM_ENABLE_OCxPRELOAD() | 使能CCR的预装载功能,为CCR设置的新值下个UEV事件发生时才更新到CCR寄存器 |
__HAL_TIM_DISABLE_OCxPRELOAD() | 禁止CCR的预装载功能,为CCR设置d额新值立刻更新到CCR寄存器 |
__HAL_TIM_SET_COMPARE() | 设置比较寄存器CCR的值 |
__HAL_TIM_GET_COMPARE() | 读取比较寄存器CCR的值 |
HAL_TIM_OC_DelayElapsedCallback() | 产生比较事件的回调函数 |
四、输出比较应用实例
4.1 示例1
示例内容:通过输出比较的模式,在TIM1的通道1和通道2上输出两种相位不同的PWM。
见图3.5和3.6的结果显示
4.4.1 STM32CubeMx配置
定时器1的模式和参数设置结果如图4.1所示。在模式设置中,设置Clock Source(时钟源)为“Internal Clock”(内部时钟;设置Channel1工作模式为“Output Compare CH1”,设置Channel2工作模式为“Output Compare CH2”,也就是使用输出比较功能。
Counter Settings组用于设置定时器的基本参数,主要设置结果如下。
- PSC(预分频器数值)的值设置为168-1,所以计数器的时钟频率为 168M / ( 168 - 1 +1) = 1MHZ。
-
ARR(自动重装载值)设置为65535(最大),因为在输出比较模式中ARR的值没起作用,定义成最大的原因是不用频繁的更新CNT为0了。
图4.1 定时器TIM1的模式和参数设置(1)
Output Compare Channel 组是通道1和通道2的输出比较参数,配置如图4.2,各个参数的意义和设定值如下。
-
Mode,输出比较模式,有冻结、有效电平、翻转灯多种选择。这里设置为“Toggle on match”,也就是在计数器的值与CCR的值相等时,使CH1输出翻转。
-
Pulse,脉冲宽度,就是CCR的值,这里通道1的CCR值设置为5,所以通道1的脉冲宽度为 = 5 / (1 / 1MHz) = 5 / 1Mhz 秒
通道2的CCR值为30000,通道2的脉冲宽度为 30000 *(1 / 1MHz) = 30 ms -
Output compare preload,设置CCR是否使用预装载功能。本示例不动态的修改CCR的值,设置为Enable和Disable无影响。
-
CH Polarity,通道极性。如果参数Mode设置为Active level on match 或Intactive level on match等与通道极性有关的模式,此参数就是输出的有效电平。本示例模式设置为匹配时输出翻转,此参数无关。
图4.2 Pulse脉冲宽度设置
4.4.2 代码设置
生成工程并打开,通过图4.3中的代码打开输出比较模式即可。
图4.3 打开定时器TIM1输出比较模式(2)
HAL_TIM_OC_Start(&htim1,TIM_CHANNEL_1); /* 启动TIM1通道1的输出比较 */
HAL_TIM_OC_Start(&htim1,TIM_CHANNEL_2); /* 启动TIM1通道2的输出比较 */
4.1 示例2
示例内容:在TIM1的通道1和通道2上输出两种频率不同的PWM。
见表4.1和图4.6的结果显示
4.4.1 示例框架及原理概述
PWM模式下同一定时器不同通道能够输出占空比不同,频率相同的PWM,输出比较模式下 同一定时器不同通道可以输出占空比不同,频率不同的PWM。在本示例程序设置的整体思路如下:
输出比较模式在CNT与CCR不断做比较的过程中,若CNT等于CCR,产生的则是电平翻转,并且会产生中断,通过对中断回调函数HAL_TIM_OC_DelayElapsedCallback()的编写,就能够实现多路不同频率信号的输出。
例如,我们进行如下操作:
- 定义OC_Channel1_Duty作为TIM1通道1的占空比,将OC_Channel1_Duty的值设置为 70%。
- 定义OC_Channel1_Pulse作为周期计数数目,OC_Channel1_Pulse y的值设置为 1000。
- 取ARR为最大值65535(在输出比较模式中ARR没起作用,CNT从ARR值溢出后,会重新从0开始计时,所以就ARR设置成最大后CNT就不用经常更新了)。
- 系统时钟为84MHz,预分频系数PSC设置为168-1(和示例1的值一样),则分频后的时钟频率为1MHz,那么当计pulse个数目时,所用的时间为1ms,占空比为70%,即所需高电平时间为0.7ms(如图)。
注:示例2是在回调函数里面自己重新配置的输出比较翻转方式,所以和示例1的周期计算略有差别,应当注意此细节变化。
当第一次CNT等于CCR时,进入中断回调函数HAL_TIM_OC_DelayElapsedCallback(),让CCR加上(pulse-pulse*duty),即0.3ms后再次进行反转电平,此时PWM持续了0.3ms的低电平;
当第二次CNT等于CCR是,进入中断回调函数,让CCR加上(pulse*duty),即0.7ms后再进行反转电平,此时PWM持续了0.7ms的高电平;这样即可完成输出比较PWM的配置。
4.4.2 STM32CubeMx配置
在示例示例1中的配置下STM32CubeMx需要进行两处修改。
1、在TIM1的“ NVIC Setting”配置里面打开定时器1中断设置,如图4.4。
图4.4 定时器TIM1的中断设置
2、TIM中的两个通道的pulse数值可以设置为0,如图4.5所示,因为在程序中对Pulse的又进行了赋值,此处需要提到程序中用到的一个函数__HAL_TIM_SET_COMPARE()(设置比较寄存器CCR的值),该函数的第三个参数就是设置的Pulse的值,就是在该函数的参数中对其进行了赋值。(也可以保持示例1的数值不变)
图4.5 定时器TIM的pulse设置
4.4.3 代码设置
STM32CubeMx按4.4.1配置完成后就开始按如下步骤编写代码。
1、定义占空比和脉冲宽度。
uint32_t OC_Channel1_Pulse = 1000; /* TIM1通道1的脉冲宽度 */
uint32_t OC_Channel1_Duty = 70; /* TIM1通道1的占空比 */
uint32_t OC_Channel2_Pulse = 1200; /* TIM1通道2的脉冲宽度 */
uint32_t OC_Channel2_Duty = 50; /* TIM1通道2的占空比 */
2、以中断凡是开始定时1的通道1和通道2输出比较模式。
HAL_TIM_OC_Start_IT(&htim1,TIM_CHANNEL_1); /* 以中断方式启动TIM1通道1的输出比较 */
HAL_TIM_OC_Start_IT(&htim1,TIM_CHANNEL_2); /* 以中断方式启动TIM1通道2的输出比较 */
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
uint32_t OC_Count = 0;
OC_Count = __HAL_TIM_GET_COUNTER(htim); /* 读取定时器的当前计数值,就是读取TIM1_CNT寄存器的值 */
if(htim->Instance == TIM1){ /* 判断是否是定时器1 */
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1){ /* 判断是否是通道1 */
if(GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_9)){ /* 判断此时的电平是否为低电平 */
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,OC_Count + OC_Channel1_Pulse - OC_Channel1_Duty * OC_Channel1_Pulse/100);/* 设置比较寄存器CCR的值--0.3ms的低电平 */
}else{
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,OC_Count + OC_Channel1_Duty * OC_Channel1_Pulse/100);/* 设置比较寄存器CCR的值--0.7ms的高电平 */
}
}else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2){ /* 判断是否是通道2 */
if(GPIO_PIN_RESET == HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_11)){
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,OC_Count + OC_Channel2_Pulse - OC_Channel2_Duty * OC_Channel2_Pulse/100);
}else{
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,OC_Count + OC_Channel2_Duty * OC_Channel2_Pulse/100);
}
}
}
}
进入该回调函数,假如此时读到的OC_Count为100,读到的电平为低电平,则低电平的脉冲宽度会一直持续到计数值为400,计算公式(与程序中对应):
100 + 1000 - 70 * 1000/100 = 400
当OC_Count计到400时,会进行翻转电平,并且再次进入回调函数,判断此时电平为高电平此时OC_Count为400,读取到的此时电平为高电平,则高电平的脉冲宽度会一直持续到计数值为1100,计算公式(与程序中对应):
400 + 70 * 1000/100 = 1100
当OC_Count计到400时,会进行翻转电平,进入回调函数,重复上面的流程以此实现输出不同频率的占空比。
图4.6 最终输出结果
表4.1 两种PWM比较
内容 | PWM1(蓝线) | PWM2(红线) |
---|---|---|
频率 | 1Khz | 833.3Hz |
周期 | 1ms | 1.2ms |
高电平占空比 | 70% | 50% |
低电平占空比 | 30% | 50% |
参考书籍和文章:
《STM32Cube高效开发教程(基础篇)》王维波
《STM32F4xx中文参考手册》
《STM32F407 探索者开发指南》
《stm32输出比较模式与PWM模式总结》
《STM32-HAL库08-TIM的输出比较模式(输出PWM的另一种方式)》
《STM32 定时器输出比较模式和PWM输出模式的区别》文章来源:https://www.toymoban.com/news/detail-843678.html
勇敢是当你还未开始就已经知道自己会输,可你依然要去做,而且无论如何都要把它坚持到底,你很少能赢,但有时也会。
——《杀死一只知更鸟》文章来源地址https://www.toymoban.com/news/detail-843678.html
到了这里,关于HAL库STM32常用外设教程(五)—— 定时器 输出比较的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!