历史上的今天
返回首页

历史上的今天

今天是:2024年09月20日(星期五)

正在发生

2019年09月20日 | 第31章 TIM—基本定时器—零死角玩转STM32-F429系列

2019-09-20 来源:eefocus

本章参考资料:《STM32F4xx 中文参考手册》、《STM32F4xx规格书》、库帮助文档《stm32f4xx_dsp_stdperiph_lib_um.chm》。


学习本章时,配合《STM32F4xx 中文参考手册》基本定时器章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。


特别说明,本书内容是以STM32F42x系列控制器资源讲解。


31.1 TIM简介

定时器(Timer)最基本的功能就是定时了,比如定时发送USART数据、定时采集AD数据等等。如果把定时器与GPIO结合起来使用的话可以实现非常丰富的功能,可以测量输入信号的脉冲宽度,可以生产输出波形。定时器生产PWM控制电机状态是工业控制普遍方法,这方面知识非常有必要深入了解。


STM32F42xxx系列控制器有2个高级控制定时器、10个通用定时器和2个基本定时器,还有2个看门狗定时器。看门狗定时器不在本章讨论范围,有专门讲解的章节。控制器上所有定时器都是彼此独立的,不共享任何资源。各个定时器特性参考表 01。


表 01 各个定时器特性

image.png?imageView2/2/w/550

其中最大定时器时钟可通过RCC_DCKCFGR寄存器配置为90MHz或者180MHz。


定时器功能强大,这一点透过《STM32F4xx中文参考手册》讲解定时器内容就有160多页就显而易见了。定时器篇幅长,内容多,对于新手想完全掌握确实有些难度,特别参考手册是先介绍高级控制定时器,然后介绍通用定时器,最后才介绍基本定时器。实际上,就功能上来说通用定时器包含所有基本定时器功能,而高级控制定时器包含通用定时器所有功能。所以高级控制定时器功能繁多,但也是最难理解的,本章我们先选择最简单的基本定时器进行讲解。


31.2 基本定时器

基本定时器比高级控制定时器和通用定时器功能少,结构简单,理解起来更容易,我们就开始先讲解基本定时器内容。基本定时器主要两个功能,第一就是基本定时功能,生成时基,第二就是专门用于驱动数模转换器(DAC)。关于驱动DAC具体应用参考DAC章节。


控制器有两个基本定时器TIM6和TIM7,功能完全一样,但所用资源彼此都完全独立,可以同时使用。在本章内容中,以TIMx统称基本定时器。


基本上定时器TIM6和TIM7是一个16位向上递增的定时器,当我在自动重载寄存器(TIMx_ARR)添加一个计数值后并使能TIMx,计数寄存器(TIMx_CNT)就会从0开始递增,当TIMx_CNT的数值与TIMx_ARR值相同时就会生成事件并把TIMx_CNT寄存器清0,完成一次循环过程。如果没有停止定时器就循环执行上述过程。这些只是大概的流程,希望大家有个感性认识,下面细讲整个过程。


31.3 基本定时器功能框图

基本定时器的功能框图包含了基本定时器最核心内容,掌握了功能框图,对基本定时器就有一个整体的把握,在编程时思路就非常清晰,见图 01。


首先先看图 01中绿色框内容,第一个是带有阴影的方框,方框内容一般是一个寄存器名称,比如图中主体部分的自动重载寄存器(TIMx_ARR)或PSC预分频器(TIMx_PSC),这里要特别突出的是阴影这个标志的作用,它表示这个寄存器还自带有影子寄存器,在硬件结构上实际是有两个寄存器,源寄存器是我们可以进行读写操作,而影子寄存器我们是完全无法操作的,有内部硬件使用。影子寄存器是在程序运行时真正起到作用的,源寄存器只是给我们读写用的,只有在特定时候(特定事件发生时)才把源寄存器的值拷贝给它的影子寄存器。多个影子寄存器一起使用可以到达同步更新多个寄存器内容的目的。


接下来是一个指向右下角的图标,它表示一个事件,而一个指向右上角的图标表示中断和DMA输出。这个我们把它放在图中主体更好理解。图中的自动重载寄存器有影子寄存器,它左边有一个带有"U"字母的事件图标,表示在更新事件生成时就把自动重载寄存器内容拷贝到影子寄存器内,这个与上面分析是一致。寄存器右边的事件图标、中断和DMA输出图标表示在自动重载寄存器值与计数器寄存器值相等时生成事件、中断和DMA输出。


图 01 基本定时器功能框图


1.    ①时钟源

定时器要实现计数必须有个时钟源,基本定时器时钟只能来自内部时钟,高级控制定时器和通用定时器还可以选择外部时钟源或者直接来自其他定时器等待模式。我们可以通过RCC专用时钟配置寄存器(RCC_DCKCFGR)的TIMPRE位设置所有定时器的时钟频率,我们一般设置该位为默认值0,使得表 01中可选的最大定时器时钟为90MHz,即基本定时器的内部时钟(CK_INT)频率为90MHz。


基本定时器只能使用内部时钟,当TIM6和TIM7控制寄存器1(TIMx_CR1)的CEN位置1时,启动基本定时器,并且预分频器的时钟来源就是CK_INT。对于高级控制定时器和通用定时器的时钟源可以来找控制器外部时钟、其他定时器等等模式,较为复杂,我们在相关章节会详细介绍。


2.    ②控制器

定时器控制器控制实现定时器功能,控制定时器复位、使能、计数是其基础功能,基本定时器还专门用于DAC转换触发。


3.    ③计数器

基本定时器计数过程主要涉及到三个寄存器内容,分别是计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR),这三个寄存器都是16位有效数字,即可设置值为0至65535。


首先我们来看图 01中预分频器PSC,它有一个输入时钟CK_PSC和一个输出时钟CK_CNT。输入时钟CK_PSC来源于控制器部分,基本定时器只有内部时钟源所以CK_PSC实际等于CK_INT,即90MHz。在不同应用场所,经常需要不同的定时频率,通过设置预分频器PSC的值可以非常方便得到不同的CK_CNT,实际计算为:fCK_CNT等于fCK_PSC/(PSC[15:0]+1)。


图 02是将预分频器PSC的值从1改为4时计数器时钟变化过程。原来是1分频,CK_PSC和CK_CNT频率相同。向TIMx_PSC寄存器写入新值时,并不会马上更新CK_CNT输出频率,而是等到更新事件发生时,把TIMx_PSC寄存器值更新到影子寄存器中,使其真正产生效果。更新为4分频后,在CK_PSC连续出现4个脉冲后CK_CNT才产生一个脉冲。


图 02 基本定时器时钟源分频


在定时器使能(CEN置1)时,计数器COUNTER根据CK_CNT频率向上计数,即每来一个CK_CNT脉冲,TIMx_CNT值就加1。当TIMx_CNT值与TIMx_ARR的设定值相等时就自动生成事件并TIMx_CNT自动清零,然后自动重新开始计数,如此重复以上过程。为此可见,我们只要设置CK_PSC和TIMx_ARR这两个寄存器的值就可以控制事件生成的时间,而我们一般的应用程序就是在事件生成的回调函数中运行的。在TIMx_CNT递增至与TIMx_ARR值相等,我们叫做为定时器上溢。


自动重载寄存器TIMx_ARR用来存放于计数器值比较的数值,如果两个数值相等就生成事件,将相关事件标志位置位,生成DMA和中断输出。TIMx_ARR有影子寄存器,可以通过TIMx_CR1寄存器的ARPE位控制影子寄存器功能,如果ARPE位置1,影子寄存器有效,只有在事件更新时才把TIMx_ARR值赋给影子寄存器。如果ARPE位为0,修改TIMx_ARR值马上有效。


4.    定时器周期计算

经过上面分析,我们知道定时事件生成时间主要由TIMx_PSC和TIMx_ARR两个寄存器值决定,这个也就是定时器的周期。比如我们需要一个1s周期的定时器,具体这两个寄存器值该如何设置内。假设,我们先设置TIMx_ARR寄存器值为9999,即当TIMx_CNT从0开始计算,刚好等于9999时生成事件,总共计数10000次,那么如果此时时钟源周期为100us即可得到刚好1s的定时周期。


接下来问题就是设置TIMx_PSC寄存器值使得CK_CNT输出为100us周期(10000Hz)的时钟。预分频器的输入时钟CK_PSC为90MHz,所以设置预分频器值为(9000-1)即可满足。


31.4 定时器初始化结构体详解

标准库函数对定时器外设建立了四个初始化结构体,基本定时器只用到其中一个即TIM_TimeBaseInitTypeDef,该结构体成员用于设置定时器基本工作参数,并由定时器基本初始化配置函数TIM_TimeBaseInit调用,这些设定参数将会设置定时器相应的寄存器,达到配置定时器工作环境的目的。这一章我们只介绍TIM_TimeBaseInitTypeDef结构体,其他结构体将在相关章节介绍。


初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在stm32f4xx_tim.h文件中,初始化库函数定义在stm32f4xx_tim.c文件中,编程时我们可以结合这两个文件内注释使用。


代码清单 01 定时器基本初始化结构体


1 typedef struct {


2 uint16_t TIM_Prescaler; // 预分频器


3 uint16_t TIM_CounterMode; // 计数模式


4 uint32_t TIM_Period; // 定时器周期


5 uint16_t TIM_ClockDivision; // 时钟分频


6 uint8_t TIM_RepetitionCounter; // 重复计算器


7 } TIM_TimeBaseInitTypeDef;


(1)    TIM_Prescaler:定时器预分频器设置,时钟源经该预分频器才是定时器时钟,它设定TIMx_PSC寄存器的值。可设置范围为0至65535,实现1至65536分频。


(2)    TIM_CounterMode:定时器计数方式,可是在为向上计数、向下计数以及三种中心对齐模式。基本定时器只能是向上计数,即TIMx_CNT只能从0开始递增,并且无需初始化。


(3)    TIM_Period:定时器周期,实际就是设定自动重载寄存器的值,在事件生成时更新到影子寄存器。可设置范围为0至65535。


(4)    TIM_ClockDivision:时钟分频,设置定时器时钟CK_INT频率与数字滤波器采样时钟频率分频比,基本定时器没有此功能,不用设置。


(5)    TIM_RepetitionCounter:重复计数器,属于高级控制寄存器专用寄存器位,利用它可以非常容易控制输出PWM的个数。这里不用设置。


虽然定时器基本初始化结构体有5个成员,但对于基本定时器只需设置其中两个就可以,想想使用基本定时器就是简单。


31.5 基本定时器定时实验

在DAC转换中几乎都用到基本定时器,使用有关基本定时器触发DAC转换内容在DAC章节讲解即可,这里就利用基本定时器实现简单的定时功能。


我们使用基本定时器循环定时0.5s并使能定时器中断,每到0.5s就在定时器中断服务函数翻转RGB彩灯,使得最终效果RGB彩灯暗0.5s,亮0.5s,如此循环。


31.5.1 硬件设计

基本定时器没有相关GPIO,这里我们只用定时器的定时功能,无效其他外部引脚,至于RGB彩灯硬件可参考GPIO章节。


31.5.2 软件设计

这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本章配套的工程。我们创建了两个文件:bsp_basic_tim.c和bsp_basic_tim.h文件用来存基本定时器驱动程序及相关宏定义,中断服务函数放在stm32f4xx_it.h文件中。


1.    编程要点

(1)    初始化RGB彩灯GPIO;


(2)    开启基本定时器时钟;


(3)    设置定时器周期和预分频器;


(4)    启动定时器更新中断,并开启定时器;


(5)    定时器中断服务函数实现RGB彩灯翻转。


2.    软件分析

宏定义

代码清单 02 宏定义


1 #define BASIC_TIM TIM6


2 #define BASIC_TIM_CLK RCC_APB1Periph_TIM6


3


4 #define BASIC_TIM_IRQn TIM6_DAC_IRQn


5 #define BASIC_TIM_IRQHandler TIM6_DAC_IRQHandler


使用宏定义非常方便程序升级、移植。


NCIV配置

代码清单 03 NVIC配置


1 static void TIMx_NVIC_Configuration(void)


2 {


3 NVIC_InitTypeDef NVIC_InitStructure;


4 // 设置中断组为0


5 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);


6 // 设置中断来源


7 NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQn;


8 // 设置抢占优先级


9 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;


10 // 设置子优先级


11 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;


12 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;


13 NVIC_Init(&NVIC_InitStructure);


14 }


实验用到定时器更新中断,需要配置NVIC,实验只有一个中断,对NVIC配置没什么具体要求。


基本定时器模式配置

代码清单 04 基本定时器模式配置


1 static void TIM_Mode_Config(void)


2 {


3 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;


4


5 // 开启TIMx_CLK,x[6,7]


6 RCC_APB1PeriphClockCmd(BASIC_TIM_CLK, ENABLE);


7


8 /* 累计 TIM_Period个后产生一个更新或者中断*/


9 //当定时器从0计数到4999,即为5000次,为一个定时周期


10 TIM_TimeBaseStructure.TIM_Period = 5000-1;


11


12 //定时器时钟源TIMxCLK = 2 * PCLK1


13 // PCLK1 = HCLK / 4


14 // => TIMxCLK=HCLK/2=SystemCoreClock/2=90MHz


15 // 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10000Hz


16 TIM_TimeBaseStructure.TIM_Prescaler = 9000-1;


17


18 // 初始化定时器TIMx, x[2,3,4,5]


19 TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);


20


21


22 // 清除定时器更新中断标志位


23 TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);


24


25 // 开启定时器更新中断


26 TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);


27


28 // 使能定时器


29 TIM_Cmd(BASIC_TIM, ENABLE);


30 }


使用定时器之前都必须开启定时器时钟,基本定时器属于APB1总线外设。


接下来设置定时器周期数为4999,即计数5000次生成事件。设置定时器预分频器为(9000-1),基本定时器使能内部时钟,频率为90MHz,经过预分频器后得到10KHz的频率。然后就是调用TIM_TimeBaseInit函数完成定时器配置。


TIM_ClearFlag函数用来在配置中断之前清除定时器更新中断标志位,实际是清零TIMx_SR寄存器的UIF位。


使用TIM_ITConfig函数配置使能定时器更新中断,即在发生上溢时产生中断。


最后使用TIM_Cmd函数开启定时器。


定时器中断服务函数

代码清单 05 定时器中断服务函数


1 void BASIC_TIM_IRQHandler (void)


2 {


3 if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) {


4 LED1_TOGGLE;


5 TIM_ClearITPendingBit(BASIC_TIM , TIM_IT_Update);


6 }


7 }


我们在TIM_Mode_Config函数启动了定时器更新中断,在发生中断时,中断服务函数就得到运行。在服务函数内我们先调用定时器中断标志读取函数TIM_GetITStatus获取当前定时器中断位状态,确定产生中断后才运行RGB彩灯翻转动作,并使用定时器标志位清除函数TIM_ClearITPendingBit清除中断标志位。


主函数

代码清单 06 主函数


1 int main(void)


2 {


3


4 LED_GPIO_Config();


5


6 /* 初始化基本定时器定时,1s产生一次中断 */


7 TIMx_Configuration();


8


9 while (1) {


10 }


11 }


实验用到RGB彩灯,需要对其初始化配置。LED_GPIO_Config函数是定义在bsp_led.c文件的完成RGB彩灯GPIO初始化配置的程序。


TIMx_Configuration函数是定义在bsp_basic_tim.c文件的一个函数,它只是简单的先后调用TIMx_NVIC_Configuration和TIM_Mode_Config两个函数完成NVIC配置和基本定时器模式配置。


31.5.3 下载验证

保证开发板相关硬件连接正确,把编译好的程序下载到开发板。开始RGB彩灯是暗的,等一会RGB彩灯变为红色,再等一会又暗了,如此反复。如果我们使用表钟与RGB彩灯闪烁对比,可以发现它是每0.5s改变一次RGB彩灯状态的。

推荐阅读

史海拾趣

台湾远翔(Feeling Technology)公司的发展小趣事

2015年,乔光电子经历了重大股权变更,现任总经理颜非接手公司后,开启了二次创业之路。2018年,公司在漳平市选址购地,并于2019年开始建设新工厂,同时决定构建自己的品牌。这一决策虽然对原有客户造成了一定冲击,但颜非坚信品牌是企业的灵魂,坚持走品牌化道路。经过几年的努力,乔光电子不仅成功打造了“FTR”自主品牌,还获得了“UL&TUV&VDE&CQC”等安规认证和多项国家专利证书,品牌影响力显著提升。

Franz Binder GmbH & Co公司的发展小趣事

乔光电子在全球拥有广泛的销售网络,不仅在国内的上海和深圳设有销售公司,还在韩国、印度、新加坡、巴西、意大利等国家设立了销售据点。随着市场的不断拓展和技术的不断创新,乔光电子的销售额从2015年的2200万元增长到2022年的27000万元,实现了跨越式发展。未来,乔光电子计划进一步扩展导热导电胶生产线业务,并可能涉足传感器和安规电容领域。同时,公司还将继续加大研发投入,提升产品性能和质量,为全球客户提供更加优质、可靠的电子产品和服务。

HAHN - Elektrobau GmbH & Co KG公司的发展小趣事

在高通的发展历程中,公司多次面临来自竞争对手的专利纠纷。面对这些挑战,高通积极应诉,通过长达数年的法律斗争,最终捍卫了自己的CDMA创新成果。这些胜利不仅彰显了高通在通信技术领域的深厚积累,也进一步巩固了其在行业中的技术领导地位。高通持续投入大量资源进行研发创新,确保了公司在全球蜂窝通信技术领域的领先地位。

Fujisoku Corporation公司的发展小趣事

随着全球化进程的加速,“未来电子”意识到单一市场已无法满足公司的长远发展需求。于是,公司制定了全球化战略,通过设立海外研发中心、生产基地和销售网络,积极拓展国际市场。在欧洲、北美和亚洲等地,“未来电子”的产品和服务得到了广泛认可,市场份额逐年攀升。这一战略不仅提升了公司的国际影响力,还为公司的持续增长提供了强劲动力。

广东长利光电(Changli Optoelectronic)公司的发展小趣事

在公司成立之初,广东长利光电面临着巨大的市场压力和资金困难。然而,公司创始人凭借着对LED技术的深刻理解和市场前景的坚定信念,决定专注于LED产品的研发和生产。他们引进了先进的生产设备和技术,不断创新,逐渐在市场上站稳了脚跟。这一时期,公司的研发团队经常加班加点,反复试验,以确保产品的质量和性能达到最佳状态。

ETC1公司的发展小趣事

金溢科技是国内ETC行业的领军企业之一。公司自创立之初,就致力于ETC技术的研发与应用。通过不断的技术创新,金溢科技成功推出了多款具有自主知识产权的ETC设备,并在市场中获得了广泛应用。同时,公司还积极参与国家标准的制定和修订工作,推动ETC行业的规范化发展。金溢科技凭借其在ETC领域的卓越表现,逐渐成为了行业的标杆企业。

问答坊 | AI 解惑

求助:F2812通过CPLD外接A/D转换的问题

下面是我的连接示意图 :   图不太清楚,我说下:F2812 通过XA0-XA18连接CPLD,CPLD译码启动多路开关将模拟量送给A/D芯片,同时启动A/D芯片,然后A/D的数据接到F2812的XD0-XD15上   谁有这样的C语言程序?谢谢了  &n ...…

查看全部问答>

滤波电路

谁能帮我分析下这是一个什么样的滤波电路?是低通滤波吗?但是我觉得和我掌握的低通滤波有不太一样。…

查看全部问答>

WinCE5.0修改S3C2443LCD驱动,链接时出错,望大家赐教。

大家好~在下有个问题请教。 S3C2443开发板,WinCE5.0更换LCD屏幕,屏幕由320*240改为480*272。 修改驱动时:        修改C:\\WINCE500\\PLATFORM\\SMDK2443\\Src\\Inc\\s3c2443_lcd.h文件将        #d ...…

查看全部问答>

(回复有分)实现qq连连看

类似于qq连连看,是单击版的,有个朋友想找我做,问下大家,要是做的话预算多少rmb,还有需要多久 一个开发+一个美工…

查看全部问答>

注册表中IRQ该添加多少?

#define AT91C_ID_IRQ1   ((unsigned int) 30) // Advanced Interrupt Controller (IRQ1) 我用的是IRQ1,IRQ1定义为上. 那我注册表中的IRQ添加多少呢? 1E 还是30? 我试了1E不行啊!…

查看全部问答>

100求解关于线程同步问题

程序中的临界区 InitializeCriticalSection(&(pHWHead->RegCritSec)); .... DeleteCriticalSection(&(pHWHead->RegCritSec)); .... .... EnterCriticalSection(&(pHWHead->RegCritSec)); .... LeaveCriticalSection(&(pHWHead->RegCritSec) ...…

查看全部问答>

一个STM8S的库里的问题

使用stm8s_gpio.c在Raisonance下进行编译,出现没法找到stm8s_gpio.c文件的问题。寻找原因,出在下面的语句中,void GPIO_WriteLow(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef PortPins){  GPIOx->ODR &=&n ...…

查看全部问答>

STM8STIM1_CH4求助

STM8S TIM1_CH4 求助 请问我在OPTION 里设置的 PD7 输出 TIM1_CH4 功能,我想关闭原来PC4上的TIM1_CH4输出, 我查了半天手册没有看到说明,请问版主主,可以实现吗?目前的实验情况是两个通道的PD7 PC4 同时输出PWM信号,晕倒,PC4我还想当 ...…

查看全部问答>

gdb调试时怎样用watch 来看变量值

#include    main() { void my_print(char *string ); void my_print(char *string ); char string[]=\"hello world \"; my_print(string); my_print1(string); } void my_print(char *string) {    printf(\"The ...…

查看全部问答>

NVIDIA内部推荐(上海,北京和深圳职位)

大家好,现NVIDIA有上海、北京、深圳和天津的职位接收内部推荐,有兴趣的可以发送中英文简历到我的公司邮箱bradf@nvidia.com。有什么问题也可以欢迎发邮件过来咨询和交流,谢谢。具体职位信息如下:   ASIC-Physical Design Enginee ...…

查看全部问答>