完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第十二章 定时器中断实验 这一章,我们将向大家介绍如何使用 STM32F767 的通用定时器,STM32F767 的定时器功能十分强大,有 tiM1 和 TIM8 等高级定时器,有 LPTIM1 低功耗定时器,也有 TIM2~TIM5,TIM9~TIM14 等通用定时器,还有 TIM6 和 TIM7 等基本定时器,总共达 15 个定时器之多。在本章中,我们将使用 TIM3 的定时器中断来控制 DS1 的翻转,在主函数用 DS0 的翻转来提示程 序正在运行。本章,我们选择难度适中的通用定时器来介绍,本章将分为如下几个部分: 12.1 STM32F7 通用定时器简介 12.2 硬件设计 12.3 软件设计 12.4 下载验证 12.5 STM32CubeMX 配置定时器更新中断功能 12.1 STM32F7 通用定时器简介 STM32F767 的通用定时器包含一个 16 位或 32 位自动重载计数器(CNT),该计数器由可 编程预分频器(PSC)驱动。STM32F767 的通用定时器可以被用于:测量输入信号的脉冲长度 (输入捕获)或者产生输出波形(输出比较和 PWM)等。 使用定时器预分频器和 RCC 时钟控制器 预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32F767 的每个通用定 时器都是完全独立的,没有互相共享的任何资源。 STM32 的通用 TIMx (TIM2~TIM5 和 TIM9~TIM14)定时器功能包括: 1)16 位/32 位(仅 TIM2 和 TIM5)向上、向下、向上/向下自动装载计数器(TIMx_CNT),注 意:TIM9~TIM14 只支持向上(递增)计数方式。 2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~ 65535 之间的任意数值。 3)4 个独立通道(TIMx_CH1~4,TIM9~TIM14 最多 2 个通道),这些通道可以用来作为: A.输入捕获 B.输出比较 C.PWM 生成(边缘或中间对齐模式) ,注意:TIM9~TIM14 不支持中间对齐模式 D.单脉冲模式输出 4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外 一个定时器)的同步电路。 5)如下事件发生时产生中断/DMA(TIM9~TIM14 不支持 DMA): A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发) B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数) C.输入捕获 D.输出比较 E.支持针对定位的增量(正交)编码器和霍尔传感器电路(TIM9~TIM14 不支持) F.触发输入作为外部时钟或者按周期的电流管理(TIM9~TIM14 不支持) 由于 STM32F767 通用定时器比较复杂,这里我们不再多介绍,请大家直接参考《STM32F7 中文参考手册》第 650 页,通用定时器一章。下面我们介绍一下与我们这章的实验密切相关的 几个通用定时器的寄存器(以下均以 TIM2~TIM5 的寄存器介绍,TIM9~TIM14 的略有区别, 具体请看《STM32F7 中文参考手册》对应章节)。 首先是控制寄存器 1(TIMx_CR1),该寄存器的各位描述如图 12.1.1 所示: 图 12.1.1 TIMx_CR1 寄存器各位描述 在本实验中,我们只用到了 TIMx_CR1 的最低位,也就是计数器使能位,该位必须置 1, 才能让定时器开始计数。接下来介绍第二个与我们这章密切相关的寄存器:DMA/中断使能寄 存器(TIMx_DIER)。该寄存器是一个 16 位的寄存器,其各位描述如图 12.1.2 所示: 图 12.1.2 TIMx_ DIER 寄存器各位描述 这里我们同样仅关心它的第 0 位,该位是更新中断允许位,本章用到的是定时器的更新中 断,所以该位要设置为 1,来允许由于更新事件所产生的中断。 接下来我们看第三个与我们这章有关的寄存器:预分频寄存器(TIMx_PSC)。该寄存器用 设置对时钟进行分频,然后提供给计数器,作为计数器的时钟。该寄存器的各位描述如图 12.1.3 所示: 图 12.1.3 TIMx_ PSC 寄存器各位描述 这里,定时器的时钟来源有 4 个: 1)内部时钟(CK_INT) 2)外部时钟模式 1:外部输入脚(TIx) 3)外部时钟模式 2:外部触发输入(ETR),仅适用于 TIM2、TIM3、TIM4 4)内部触发输入(ITRx):使用 A 定时器作为 B 定时器的预分频器(A 为 B 提供时钟)。 这些时钟,具体选择哪个可以通过 TIMx_SMCR 寄存器的相关位来设置。这里的 CK_INT 时钟是从 APB1 倍频的来的,除非 APB1 的时钟分频数设置为 1(一般都不会是 1),否则通用 定时器 TIMx 的时钟是 APB1 时钟的 2 倍,当 APB1 的时钟不分频的时候,通用定时器 TIMx 的时钟就等于 APB1 的时钟。这里还要注意的就是高级定时器以及 TIM9~TIM11 的时钟不是来 自 APB1,而是来自 APB2 的。 这里顺带介绍一下 TIMx_CNT 寄存器,该寄存器是定时器的计数器,该寄存器存储了当前 定时器的计数值。 接着我们介绍自动重装载寄存器(TIMx_ARR),该寄存器在物理上实际对应着 2 个寄存器。 一个是程序员可以直接操作的,另外一个是程序员看不到的,这个看不到的寄存器在《STM32F7 中文参考手册》里面被叫做影子寄存器。事实上真正起作用的是影子寄存器。根据 TIMx_CR1 寄存器中 APRE 位的设置:APRE=0 时,预装载寄存器的内容可以随时传送到影子寄存器,此 时 2 者是连通的;而 APRE=1 时,在每一次更新事件(UEV)时,才把预装载寄存器(ARR) 的内容传送到影子寄存器。 自动重装载寄存器的各位描述如图 12.1.4 所示: 图 12.1.4 TIMx_ ARR 寄存器各位描述 最后,我们要介绍的寄存器是:状态寄存器(TIMx_SR)。该寄存器用来标记当前与定时 器相关的各种事件/中断是否发生。该寄存器的各位描述如图 12.1.5 所示: 图 12.1.5 TIMx_ SR 寄存器各位描述 关于这些位的详细描述,请参考《STM32F7 中文参考手册》第 699 页。 只要对以上几个寄存器进行简单的设置,我们就可以使用通用定时器了,并且可以产生中 断。 这一章,我们将使用定时器产生中断,然后在中断服务函数里面翻转 DS1 上的电平,来指 示定时器中断的产生。接下来我们以通用定时器 TIM3 为实例,来说明要经过哪些步骤,才能 达到这个要求,并产生中断。这里我们就对每个步骤通过库函数的实现方式来描述。首先要提 到 的 是 , 定 时 器 相 关 的 库 函 数 主 要 集 中 在 HAL 库文件 stm32f7xx_hal_tim.h 和 stm32f7xx_hal_tim.c 文件中。定时器配置步骤如下: 1)TIM3 时钟使能。 HAL 中定时器使能是通过宏定义标识符来实现对相关寄存器操作的,方法如下: __HAL_RCC_TIM3_CLK_ENABLE(); //使能 TIM3 时钟 2)初始化定时器参数,设置自动重装值,分频系数,计数方式等。 在 HAL库中,定时器的初始化参数是通过定时器初始化函数 HAL_TIM_Base_Init 实现的: HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim); 该函数只有一个入口参数,就是 TIM_HandleTypeDef 类型结构体指针,结构体类型为下面 我们看看这个结构体的定义: typedef struct { TIM_TypeDef *Instance; TIM_Base_InitTypeDef Init; HAL_TIM_ActiveChannel Channel; DMA_HandleTypeDef *hdma[7]; HAL_LockTypeDef Lock; __IO HAL_TIM_StateTypeDef State; }TIM_HandleTypeDef; 第一个参数 Instance 是寄存器基地址。和串口,看门狗等外设一样,一般外设的初始化结 构体定义的第一个成员变量都是寄存器基地址。这在HAL中都定义好了,比如要初始化串口1, 那么 Instance 的值设置为 TIM1 即可。 第二个参数 Init 为真正的初始化结构体 TIM_Base_InitTypeDef 类型。该结构体定义如下: typedef struct { uint32_t Prescaler; //预分频系数 uint32_t CounterMode; //计数方式 uint32_t Period; //自动装载值 ARR uint32_t ClockDivision; //时钟分频因子 uint32_t RepetitionCounter; } TIM_Base_InitTypeDef; 该初始化结构体中,参数 Prescaler 是用来设置分频系数的,刚才上面有讲解。参数 CounterMode 是用来设置计数方式,可以设置为向上计数,向下计数方式还有中央对齐计数方 式 , 比 较 常 用 的 是 向 上 计 数 模 式 TIM_CounterMode_Up 和 向 下 计 数 模 式 TIM_CounterMode_Down。参数 Period 是设置自动重载计数周期值。参数 ClockDivision 是用来 设置时钟分频因子,也就是定时器时钟频率 CK_INT 与数字滤波器所使用的采样时钟之间的分 频比。参数 RepetitionCounter 用来设置重复计数器寄存器的值,用在高级定时器中。 第三个参数 Channel 用来设置活跃通道。前面我们讲解过,每个定时器最多有四个通道可 以用来做输出比较,输入捕获等功能之用。这里的 Channel 就是用来设置活跃通道的,取值范 围为:HAL_TIM_ACTIVE_CHANNEL_1~ HAL_TIM_ACTIVE_CHANNEL_4。 第四个 hdma 是定时器的 DMA 功能时用到,为了简单起见,我们暂时不讲解太复杂。 第五个参数 Lock 和 State,是状态过程标识符,是 HAL库用来记录和标志定时器处理过程。 定时器初始化范例如下: TIM_HandleTypeDef TIM3_Handler; //定时器句柄 TIM3_Handler.Instance=TIM3; //通用定时器 3 TIM3_Handler.Init.Prescaler=8999; //分频系数 TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上计数器 TIM3_Handler.Init.Period=4999; //自动装载值 TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子 HAL_TIM_Base_Init(&TIM3_Handler); 3)使能定时器更新中断,使能定时器 HAL 库 中 , 使 能 定 时 器 更 新 中 断 和 使 能 定 时 器 两个操作可以在 函 数HAL_TIM_Base_Start_IT()中一次完成的,该函数声明如下: HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim); 该 函 数 非 常 好 理 解 , 只 有 一 个 入 口 参 数 。 调 用 该 定 时 器 之 后 , 会 首 先 调 用 __HAL_TIM_ENABLE_IT 宏定义使能更新中断,然后调用宏定义__HAL_TIM_ENABLE 使能 相应的定时器。这里我们分别列出单独使能/关闭定时器中断和使能/关闭定时器方法: __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);//使能句柄指定的定时器更新中断 __HAL_TIM_DISABLE_IT (htim, TIM_IT_UPDATE);//关闭句柄指定的定时器更新中断 __HAL_TIM_ENABLE(htim);//使能句柄 htim 指定的定时器 __HAL_TIM_DISABLE(htim);//关闭句柄 htim 指定的定时器 4)TIM3 中断优先级设置。 在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,设置中 断优先级。之前多次讲解到中断优先级的设置,这里就不重复讲解。 和串口等其他外设一样,HAL 库为定时器初始化定义了回调函数 HAL_TIM_Base_MspInit。 一般情况下,与 MCU 有关的时钟使能,以及中断优先级配置我们都会放在该回调函数内部。 函数声明如下: void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim); 对于回调函数,这里我们就不做过多讲解,大家只需要重写这个函数即可。 5)编写中断服务函数。 在最后,还是要编写定时器中断服务函数,通过该函数来处理定时器产生的相关中断。通 常情况下,在中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。然后执 行相关的操作,我们这里使用的是更新(溢出)中断,所以在状态寄存器 SR 的最低位。在处 理完中断之后应该向 TIM3_SR 的最低位写 0,来清除该中断标志。 跟串口一样,对于定时器中断,HAL 库同样为我们封装了处理过程。这里我们以定时器 3 的更新中断为例来讲解。 首先,中断服务函数是不变的,定时器 3 的中断服务函数为: TIM3_IRQHandler(); 一般情况下我们是在中断服务函数内部编写中断控制逻辑。但是 HAL 库为我们定义了 新 的定时器中断共用处理函数 HAL_TIM_IRQHandler,在每个定时器的中断服务函数内部,我们 会调用该函数。该函数声明如下: void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim); 而函数 HAL_TIM_IRQHandler 内部,会对相应的中断标志位进行详细判断,判断确定中断 来源后,会自动清掉该中断标志位,同时调用不同类型中断的回调函数。所以我们的中断控制 逻辑只用编写在中断回调函数中,并且中断回调函数中不需要清中断标志位。 比如定时器更新中断回调函数为: void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim); 跟串口中断回调函数一样,我们只需要重写该函数即可。对于其他类型中断,HAL 库同样 提供了几个不同的回调函数,这里我们列出常用的几个回调函数: void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);//更新中断 void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim);//输出比较 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);//输入捕获 void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim);//触发中断 对于这些回调函数的使用方法我们在后面用到的时候会给大家详细讲解。 通过以上几个步骤,我们就可以达到我们的目的了,使用通用定时器的更新中断,来控制 DS1 的亮灭。 12.2 硬件设计 本实验用到的硬件资源有: 1) 指示灯 DS0 和 DS1 2) 定时器 TIM3 本章将通过 TIM3 的中断来控制 DS1 的亮灭,DS1 是直接连接到 PB0 上的,这个前面已经 有介绍了。而 TIM3 属于 STM32F767 的内部资源,只需要软件设置即可正常工作。 12.3 软件设计 打开我们光盘实验 7 定时器中断实验可以看到,我们的工程中的 HARDWARE 下面比以前 多了一个 time.c 文件(包括头文件 time.h),这两个文件是我们自己编写。同时还引入了定时器 相关的 HAL 文件 stm32f7xx_hal_tim.c 和头文件 stm32f7xx_hal_tim.h。timer.c 文件代码如下: TIM_HandleTypeDef TIM3_Handler; //定时器句柄 //通用定时器 3 中断初始化 //arr:自动重装值。 psc:时钟预分频数 //定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us. //Ft=定时器工作频率,单位:Mhz //这里使用的是定时器 3!(定时器 3 挂在 APB1 上,时钟为 HCLK/2) void TIM3_Init(u16 arr,u16 psc) { TIM3_Handler.Instance=TIM3; //通用定时器 3 TIM3_Handler.Init.Prescaler=psc; //分频系数 TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上计数器 TIM3_Handler.Init.Period=arr; //自动装载值 TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; //时钟分频因子 HAL_TIM_Base_Init(&TIM3_Handler); //初始化定时器 3 HAL_TIM_Base_Start_IT(&TIM3_Handler); //使能定时器 3 和定时器 3 更新中断 } //定时器底册驱动,开启时钟,设置中断优先级 //此函数会被 HAL_TIM_Base_Init()函数调用 void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) { __HAL_RCC_TIM3_CLK_ENABLE(); //使能 TIM3 时钟 HAL_NVIC_SetPriority(TIM3_IRQn,1,3); //设置中断优先级,抢占 1,子优先级 3 HAL_NVIC_EnableIRQ(TIM3_IRQn); //开启 ITM3 中断 } //定时器 3 中断服务函数 void TIM3_IRQHandler(void) { HAL_TIM_IRQHandler(&TIM3_Handler); } //定时器 3 中断服务函数调用 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim==(&TIM3_Handler)) { LED1_Toggle; //LED0 反转 } } 该文件一共有 4 个函数。第一个函数 TIM3_Init 用来初始化定时器 3,使能定时器 3 更新中 断以及使能定时器,实现的是 12.1 小节讲解的步骤 2 和步骤 3 配置功能。该函数的 2 个参数用 来设置 TIM3 的溢出时间。因为我们在 Stm32_Clock_Init 函数里面已经初始化 APB1 的时钟为 4 分频,所以 APB1 的时钟为 54M,而从 STM32F7 的内部时钟树图(图 4.3.1.1)得知:当 APB1 的时钟分频数为 1 的时候,TIM2~7 以及 TIM12~14 的时钟为 APB1 的时钟,而如果 APB1 的时 钟分频数不为 1,那么 TIM2~7 以及 TIM12~14 的时钟频率将为 APB1 时钟的两倍。因此,TIM3 的时钟为 108M,再根据我们设计的 arr 和 psc 的值,就可以计算中断时间了。计算公式如下: Tout= ((arr+1)*(psc+1))/Tclk; 其中: Tclk:TIM3 的输入时钟频率(单位为 Mhz)。 Tout:TIM3 溢出时间(单位为 us)。 第二个函数 HAL_TIM_Base_MspInit 是定时器初始化回调函数,主要是使能定时器 3 时钟 以及定时器 3 的 NVIC 配置,实现的是 12.1 小节讲解的步骤 1 和步骤 4 功能。第三个函数 TIM3_IRQHandler 是中断服务入口函数,该函数内部只有一行代码就是调用定时器中断共用处 理函数 HAL_TIM_IRQHandler。根据前面的讲解,函数 HAL_TIM_IRQHandler 内部会判断中断 来源,根据中断来源调用不同的中断处理回调函数。这里我们开启的是定时器 3 的更新中断, 所以我们需要重定义更新中断回调函数 HAL_TIM_PeriodElapsedCallback。第四个函数 HAL_TIM_PeriodElapsedCallback 就是更新中断回调函数,也就是真正的中断处理函数,该函数 内部通过判断中断是定时器 3 之后,然后控制 LED1 翻转。 timer.h 头文件内容比较简单,这里我们就不做讲解。 最后,我们看看主函数代码如下: : int main(void) { Cache_Enable(); //打开 L1-Cache HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz delay_init(216); //延时初始化 uart_init(115200); //串口初始化 LED_Init(); //初始化 LED TIM3_Init(5000-1,10800-1); //定时器 3 初始化,定时器时钟为 108M,分频系数 //为 10800-1,//所以定时器 3 的频率为 108M/10800=10K,自动重装载为//5000-1,那么定时器周期就是 500ms while(1) { LED0_Toggle; //LED0 翻转 delay_ms(200); //延时 200ms } } 这里的代码和之前大同小异,此段代码对 TIM3 进行初始化之后,进入死循环等待 TIM3 溢出中断,当 TIM3_CNT 的值等于 TIM3_ARR 的值的时候,就会产生 TIM3 的更新中断,然 后在中断里面取反 LED1,TIM3_CNT 再从 0 开始计数。 这里定时器定时时长500ms是这样计算出来的,定时器的时钟为108Mhz,分频系数为10799, 所以分频后的计数频率为 108Mhz/(10799+1)=10KHz,然后计数到 4999,所以时长为(4999+1) /10000=0.5s,也就是 500ms。 12.4 下载验证 在完成软件设计之后,我们将编译好的文件下载到阿波罗 STM32 开发板上,观看其运行 结果是否与我们编写的一致。如果没有错误,我们将看 DS0 不停闪烁(每 400ms 闪烁一次), 而 DS1 也是不停的闪烁,但是闪烁时间较 DS0 慢(1s 一次)。 12.5 STM32CubeMX 配置定时器更新中断功能 经过前面多个章节的学习,大家对 STM32CubeMX 配置已经非常熟悉。从本章开始,出于 篇幅考虑,我们将不再像之前章节一样讲解那么详细,我们将只会列出配置的关键点,然后生 成工程,大家自行与光盘中提供的实验代码对照学习。 定时器 3 中断配置非常简单。配置步骤如下: ① 在 Pinout->TIM3 配置项中,配置 Clock Source 为 Internal Clock,如下图 12.5.1 所示: 图 12.5.1 TIM3 配置 ② 进入 Configuration 选项卡会发现,在 Control 栏下多出了 TIM3 按钮。点击 TIM3 按钮 进入 TIM3 配置页,在弹出的界面中点击 Parameter Settings 选项卡,Counter Settings 配置栏下面的四个选项就是用来配置定时器的预分频系数,自动装载值,计数模式以 及时钟分频因子。操作方法和配置值如下图 12.5.2 所示: 图 12.5.2 TIM3 参数设置界面 ③ 进入 Configuration->NVIC 配置页,在弹出的界面中点击 NVIC 选项卡,配置 Interrupt Table 中的 TIM3 global interrupt,使能中断,配置抢占优先级为 1 和响应优先级为 3。 经过上面三个步骤,生成代码,大家对比生成的代码和实验工程的区别。这里需要说明的 是,默认情况下,TIM3 的时钟来源是内部时钟 CK_INT,所以在我们实验中使用的是默认配置, 没有额外在程序中体现。 |
|
相关推荐
|
|
812 浏览 0 评论
使用stm32l451片子,对ad7606进行3通道100k采样值跳动问题?
1357 浏览 1 评论
STM32进行DMA数据转运,为什么只有A组最后一位数据被转运到B组去?
1807 浏览 2 评论
2102 浏览 3 评论
1692 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-9-2 05:21 , Processed in 0.625429 second(s), Total 66, Slave 48 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191