前言:
现在咱们对“定时器计数汇编语言”都比较着重,同学们都需要剖析一些“定时器计数汇编语言”的相关内容。那么小编在网上汇集了一些对于“定时器计数汇编语言””的相关知识,希望各位老铁们能喜欢,同学们快快来学习一下吧!SysTick定时器和基本定时器
实验目的掌握STM32基本定时器和SysTick定时器的原理。掌握SysTick定时器和基本定时器相关应用程序设计。实验内容编写程序,配置SysTick定时器并打开中断,实现中断服务函数中改变指示灯D1状态。编写程序,配置基本定时器并打开中断,实现中断服务函数中改变指示灯D1状态。硬件设计TIMER概念介绍
定时器(timer)几乎是每个MCU必有的重要外设之一,可用于定时、精确延时、计数等等,在检测、控制领域有广泛应用。
定时器运行时不占用CPU时间,配置好之后,可以与CPU并行工作,实现精确的定时和计数,并且可以通过软件控制其是否产生中断,使用起来灵活方便。通常MCU在介绍定时器外设时,总是和计数器(counter)一起出现,所以我们有必要先了解一下定时器和计数器的区别。
定时器和计数器实际都是通过计数器来计数,定时器是对周期不变的脉冲计数(一般来自于系统时钟),由计数的个数和脉冲的周期即可计算出时间,同时,通过一个给定的预期值(即比较值,对应预期的计数值,也就是预期时间),当计数值达到预期值时产生中断,这样就实现了定时,应用程序通过设置不同的预期值实现不同时长的定时。
计数器是对某一事件进行计数,这个事件每发生一次,计数值加/减1,而这个事件的产生可能是没有规律的。也就是计数器的用途是对事件的发生次数进行计数,由计数值来反映事件产生的次数。
注:不同MCU定时器外设使用的计数器位数是个重要的参数,STM32F103系列MCU的基本定时器、通用定时器和高级定时器所用的计数器均是16位计数器。STM32基本定时器和SysTick定时器介绍
STM32F103系列MCU提供定时器有SysTick定时器(系统滴答定时器)、基本定时器、通用定时器和高级定时器,在STM32的参考手册中没有提及SysTick定时器。
ARM Cortex-M3内核的处理器内部都包含了一个SysTick定时器,该定时器是一个24 位的倒计数定时器。当计到0时该定时器就会从 LOAD 寄存器中自动重装载定时初值。只要不把CTRL寄存器中的ENABLE位清0,SysTick定时器就一直工作下去。所以SysTick定时器可用于系统中的时钟节拍,比如不断产生1us的中断。
STM32F103系列MCU时钟系统中清晰地描述了各定时器的时钟来源,如下图所示。
图1:STM32时钟树示意图
注:STM32F103系列MCU通用定时器和高级定时器的时钟源有多种选择,上图中介绍的通用定时器和高级定时器的时钟源是指来源于内部时钟的情况。
下面再介绍下STM32F103系列基本定时器的内部结构图:
图2:STM32基本定时器内部结构图
注:STM32F103系列MCU基本定时器用计数器只可向上计数,向上计数最大到65535。计数器从0累加计数到自动重装载数值(TIMx_ARR寄存器)后重新从0开始计数并产生一个计数器溢出事件。基本定时器时钟源:
基本定时器的时钟(TIMxCLK)只能由内部时钟(CK_INT)提供,从STM32时钟树中可知,TIMxCLK是经APB1预分频器后再处理得到的。如果APB1预分频系数等于1,则APB1预分频器后的频率直接提供给TIMxCLK,如果APB1预分频系数不等于1,则APB1预分频器后的频率需乘以2(再分频)提供给TIMxCLK。
注:STM32F103系列MCU通用定时器和高级定时器的时钟源选择内部时钟(CK_INT)时,TIMx_CLK都是由APB1或APB2预分频器后再处理得到的。处理情况取决于APB1预分频系数或APB2预分频系数是否等于1,方法同上。基本定时器溢出时间计算:
基本定时器用计数器的时钟频率计算公式:
图3:计数器时钟频率计算公式
基本定时器溢出时间计算公式:
图4:TIM溢出时间计算公式
举例:假如预分频器赋值PSC[15:0]为7199,自动重装载器赋值ARR[15:0]为9999,内部时钟CK_INT默认为72MHz,CK_PSC与CK_INT相同。代入上述TIM溢出时间公式,很容易得出TIM溢出时间为1s。即开启定时器中断的情况下,每1s会进入一次定时器中断服务函数。软件设计SysTick定时器寄存器汇集
SysTick定时器有4个寄存器,如下表所示:
表1:SysTick相关寄存器
序号
寄存器名
读/写
功能描述
1
CTRL
读/写
SysTick控制及状态寄存器。
2
LOAD
读/写
SysTick重装载寄存器。
3
VAL
读/写
SysTick当前值寄存器。
4
CALIB
读/写
SysTick校准寄存器。
注:Cortex‐M3处理器内部都包含了Systick定时器(即都有上面4个寄存器),因此Systick定时器的程序设计可很方便实现不同Cortex‐M3处理器芯片的程序移植。在STM32F103ZET6的例程中,Systick定时器相关函数定义是在core_cm3.h文件中。CTRL:SysTick控制及状态寄存器
表2: CTRL寄存器
位
Field
RW
复位值
描述
位16
COUNTFLAG
只读
如果在上次读取本寄存器后,SysTick已经数到了0,则该位为 1。如果读取该位,该位将自动清零。
位2
CLKSOURCE
读/写
选择时钟源。
0:外部时钟源(STCLK)。
1:内核时钟(FCLK)。
位1
TICKINT
读/写
SysTick中断(异常请求)使能位。
0:SysTick计数器递减到0时无中断。
1:SysTick计数器递减到0时产生中断。
位0
ENABLE
读/写
SysTick定时器使能位。
0:禁止SysTick定时器。
1:使能SysTick定时器。
注:上述是《Cortex-M3权威指南》中对寄存器各位进行的描述。针对STM32F103系列MCU控制及状态寄存器的CLKSOURCE位选择时钟源具体情况是:为1时选择的是HCLK,为0时选择的是HCLK/8。LOAD:SysTick重装值寄存器
表3: LOAD寄存器
位
Field
RW
复位值
描述
位23~位0
RELOAD
读/写
当SysTick计数器递减到0时,将被重装载的值。
CNT:SysTick当前值寄存器
表4: VAL寄存器
位
Field
RW
复位值
描述
位23~位0
CURRENT
读/写
读取时返回当前计数器的值,写则使之清零,同时还会清除在SysTick控制及状态寄存器中的COUNTFLAG标志。
CALIB:SysTick校准寄存器
表5: CALIB寄存器
位
Field
RW
复位值
描述
位31
NOREF
只读
---
判断外部参考时钟(STCLK)是否可用。0:有外部参考时钟(STCLK可用)。
1:没有外部参考时钟(STCLK不可用)。
位30
SKEW
只读
---
判断校准值是否是10ms。
0:校准值是准确的10ms。
1:校准值不是准确的10ms。
位23~位0
TENMS
读/写
10ms的时间内计数器的数值变化量。芯片设计者应该通过Cortex‐M3的输入信号提供该数值。若该值读回零,则表示无法使用校准功能。
基本定时器寄存器汇集
STM32F103提供了8个用于操作基本TIM的寄存器,如下表所示:
表6:基本TIM相关寄存器
序号
寄存器名
读/写
功能描述
1
TIMx_CR1
读/写
TIM6和TIM7控制寄存器1。
2
TIMx_CR2
读/写
TIM6和TIM7控制寄存器2。
3
TIMx_DIER
读/写
TIM6和TIM7 DMA/中断使能寄存器。
4
TIMx_SR
读/写
TIM6和TIM7状态寄存器。
5
TIMx_EGR
读/写
TIM6和TIM7事件产生寄存器。
6
TIMx_CNT
读/写
TIM6和TIM7计数器寄存器。
7
TIMx_PSC
读/写
TIM6和TIM7预分频寄存器。
8
TIMx_ARR
读/写
TIM6和TIM7自动重装载寄存器。
关于TIMx_CR1寄存器:
TIMx_CR1寄存器是TIM6和TIM7控制寄存器1,设置自动重装载预装载使能、单脉冲模式允许、更新请求源、计数器使能等。
图5:TIMx_CR1寄存器
注:如果ARPE=0,即不使用ARR的预装功能(没有缓存),则修改TIMx_ARR 寄存器的值就是操作影子寄存器,新的ARR的值立即生效。否则,如果ARPE=1,即使用ARR的预装功能(具有缓存),则修改TIMx_ARR 寄存器的值就是操作预装寄存器,直到发生更新事件后,ARR预装载寄存器的值才被拷贝到影子寄存器中。关于影子寄存器:
在基本定时器内部结构图中可以看到预分频器和自动重装载寄存器下面有一个“阴影”,这个“阴影”就是影子寄存器,预分频器和自动重装载寄存器可以统称为预装载寄存器。影子寄存器是无法直接对其读写操作的(程序直接操作的是预装载寄存器),但确是真正起作用的寄存器。
设计预装载寄存器和影子寄存器的好处:所有真正需要起作用的寄存器(影子寄存器)可以在同一个时间(发生更新事件时)被更新为所对应的预装载寄存器的内容,这样可以保证多个通道的操作能够准确地同步。
如果没有影子寄存器,软件更新预装载寄存器时,则同时更新了真正操作的寄存器,因为软件不可能在一个相同的时刻更新多个寄存器,结果造成多个通道的时序不能同步。如果程序中还有中断等因素影响,多个通道的时序关系有可能会混乱,造成是不可预知的结果。
关于TIMx_ARR寄存器:
TIMx_ARR寄存器设置TIM6和TIM7自动重装载值(范围0~65535)。
图6:TIMx_ARR寄存器
关于TIMx_PSC预分频寄存器:
TIMx_PSC预分频寄存器设置TIM6和TIM7预分频器值(范围0~65535)。该注释中已经给出计数器时钟频率CK_CNT的计算公式,前面给出的定时器溢出时间计算公式也是基于此。
图7:TIMx_PSC预分频寄存器
关于TIMx_SR寄存器:
TIMx_SR寄存器位0存放TIM6和TIM7更新中断标志,由软件清除。
图8:TIMx_SR寄存器
其他寄存器详细的描述在这里不做具体的介绍,大家可以参考目录:“第1部分:开发板硬件资料”“2 - 芯片资料”中“STM32英文参考手册_V15”或“STM32中文参考手册_V10”对应的基本定时器章节的寄存器部分认真研读。
库函数使用
SysTick定时器有关库函数是在core_cm3.h中定义的,下面版本V5.0.1来介绍下对SysTick定时器的初始化函数。
关于SysTick_Config库函数:
SysTick_Config库函数是用于设置基本定时器预分频器值和自动重装载值。
图9:SysTick_Config库函数
注:SysTick_Config库函数的入参只有一个,就是定时器重装值,因为SysTick定时器的计数器是24位的,故重装值范围为0x000000~0xFFFFFF(转为十进制是0~16777215)。由SysTick_Config库函数对重装载寄存器操作语句可知,入参ticks减一后赋值给重装载寄存器,所以入参ticks的取值范围是1~16777216。另外,该库函数对SysTick定时器中断、时钟源及定时器使能位均进行了配置。
ST官方提供的最终库函数版本是V3.5版本,该版本与定时器相关库函数很多,不再一一列举。下面针对几个比较常用的库函数做下介绍。
关于TIM_TimeBaseInit库函数:
TIM_TimeBaseInit库函数是用于设置基本定时器预分频器值和自动重装载值。
图10:TIM_TimeBaseInit库函数
关于TIM_TimeBaseInitTypeDef结构体:
TIM_TimeBaseInitTypeDef结构体定义如下图,这个结构图是针对基本定时器、通用定时器和高级定时器声明的,比如基本定时器在配置时并不需要配置结构体的所有成员,具体如下图所示。
图11:TIM_TimeBaseInitTypeDef结构体
关于TIM_ITConfig库函数:
TIM_ITConfig库函数是TIM中断使能函数,作用是使能或禁止指定的TIM中断。
图12:TIM_ITConfig库函数
其他库函数详细的描述在这里不做具体的介绍,大家可以参考目录:“第1部分:开发板硬件资料”--->“2 - 芯片资料”中“STM32固件库使用手册的中文翻译版”对应的TIM章节的库函数部分认真研读,需要提醒大家的是该手册库函数针对的并不是3.5版本的,所以仅可作为参考,不能完全依赖。
基本定时器配置过程
针对STM32F103的基本定时器,软件的配置过程一般如下:
图13:基本定时器配置步骤
SysTick定时器中断实验注:本节的实验源码是在“实验2-1-3:流水灯”的基础上修改。本节对应的实验源码是:“实验2-12-1:SysTick定时器中断”。工程需要用到的库文件
本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。
表7:实验需要用到的c文件
序号
文件名
后缀
功能描述
1
stm32f10x_rcc
.c
复位与时钟控制器。
2
stm32f10x_gpio
.c
通用输入输出。
注:因为SysTick定时器是Cortex‐M3处理器内部包含的, Systick定时器相关函数定义是在core_cm3.h文件中,所以此处不需要添加misc.c文件。
按下图所示将需要的c文件添加到工程。
图14:在新建工程中添加所需库函数c文件
头文件引用和路径设置需要引用的头文件
因为在“main.c”文件中使用了标准库和我们自己建的软件延时函数,所以需要引用下面的头文件。
#include "stm32f10x.h" //delay这里报错的原因是:delay函数用汇编实现的,导致了MDK误报。 #include "delay.h" 需要包含的头文件路径
本例需要包含的头文件路径如下表:
表8:头文件包含路径
序号
路径
描述
1
..\Lib\F10x_FWLIB\inc
标准库头文件路径。
2
..\User
stm32f10x_conf.h头文件在该路径,所以要包含。
3
..\User\bsp
自建的板卡相关的驱动文件路径。
MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。
图15:添加头文件包含路径
编写代码
首先是对SysTick定时器进行初始化操作,进行该操作时调用了SysTick_Config函数,配置SysTick定时器时钟是高速外设时钟HCLK(与系统时钟相等72MHz),自动重装值是71999,则SysTick定时器的溢出时间是(71999+1)/72000000 s为1ms。
代码清单: SysTick定时器初始化
/************************************************************************** * 描 述 : 初始化SysTick定时器 * 入 参 : 无 * 返回值 : 无 注:重装值不能大于16777215(24位计数器FFFFFF) **************************************************************************/ void SysTick_Init(void) { /* SysTick_Config会选择SysTick定时器时钟是HCLK,打开SysTick定时器并开启中断 */ SysTick_Config(72000); //重装值设为71999
}
调用SysTick_Config函数默认打开SysTick定时器时钟是HCLK时钟,在SysTick控制及状态寄存器中有对时钟源的选择,下面对SysTick_Init函数重新设计下,改变了SysTick时钟源及自动重装值。大家自己去计算下,修改后的SysTick定时器的溢出时间还是1ms。
代码清单:SysTick定时器初始化
/************************************************************************ * 描 述 : 初始化SysTick定时器 * 入 参 : 无 * 返回值 : 无 注:重装值不能大于16777215(24位计数器FFFFFF) ************************************************************************/ void SysTick_Init(void) { /* SysTick_Config会选择SysTick定时器时钟是HCLK,打开SysTick定时器并开启中断 */ SysTick_Config(72000/8); //重装值设为8999 SysTick->CTRL &= ~SysTick_CTRL_CLKSOURCE_Msk; //选择SysTick定时器时钟是HCLK/8 }
然后,因为对SysTick定时器初始化时使能了中断,每1ms便会进入一次中断服务函数,进入1000次中断服务函数时间长度是1s。故可以观察到指示灯1s闪一次。
代码清单:SysTick中断服务函数
/************************************************************************** * 描 述 : SysTick中断服务函数 * 入 参 : 无 * 返回值 : 无 ************************************************************************/ void SysTick_Handler(void) { if(timecount>1000) { timecount=0; //变量timecount清零 led_toggle(LED_1); //1s执行一次翻转指示灯D1的操作 } else { timecount++; //变量timecount累加1 } }
最后,主函数中初始化指示灯和SysTick定时器,主循环中没有对用户指示灯进行操作,说明指示灯动作来源于中断,代码如下。
代码清单:主函数
int main(void) { //初始化用于驱动指示灯D1、D2、D3、D4的引脚,并熄灭4个用户LED leds_init(); //初始化SysTick SysTick_Init();
//主循环 while(1) { ; //空命令,说明用户LED的闪烁来自于SysTick中断
} } 实验步骤解压“…\第3部分:标准库教程和实验源码\ 1 - 基础实验程序\”目录下的压缩文件“实验2-12-1:SysTick定时器中断”,将解压后得到的文件夹拷贝到合适的目录,如“D\STM32F103ZET6”。启动MDK5.23。在MDK5中执行“Project→Open Project”打开“…\SYSTICK\projec”目录下的工程“SYSTICK.uvproj”。点击编译按钮编译工程。注意查看编译输出栏,观察编译的结果,如果有错误,修改程序,直到编译成功为止。编译后生成的HEX文件“SYSTICK.hex”位于工程目录下的“Objects”文件夹中。点击下载按钮下载程序。如果需要对程序进行仿真,点击Debug按钮,即可将程序下载到STM32F103ZET6中进行仿真。程序运行后,用户指示灯D1每1s状态翻转一次。基本定时器中断实验注:本节的实验源码是在“实验2-12-1:SysTick定时器中断”的基础上修改。本节对应的实验源码是:“实验2-12-2:基本定时器中断”。工程需要用到的库文件
本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。
表9:实验需要用到的c文件
序号
文件名
后缀
功能描述
1
stm32f10x_rcc
.c
复位与时钟控制器。
2
stm32f10x_gpio
.c
通用输入输出。
3
stm32f10x_tim
.c
定时器。
4
misc
.c
中断向量控制器。
注:基本定时器、通用定时器和高级定时器都是用的tim.c文件。
按下图所示将需要的c文件添加到工程。
图16:在新建工程中添加所需库函数c文件
头文件引用和路径设置需要引用的头文件
因为在“main.c”文件中使用了标准库和我们自己建的软件延时函数,所以需要引用下面的头文件。
#include "stm32f10x.h" //delay这里报错的原因是:delay函数用汇编实现的,导致了MDK误报。 #include "delay.h" 需要包含的头文件路径
本例需要包含的头文件路径如下表:
表10:头文件包含路径
序号
路径
描述
1
..\Lib\F10x_FWLIB\inc
标准库头文件路径。
2
..\User
stm32f10x_conf.h头文件在该路径,所以要包含。
3
..\User\bsp
自建的板卡相关的驱动文件路径。
MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。
图17:添加头文件包含路径
编写代码
首先是按照基本定时器配置步骤对TIM进行初始化和中断优先级设置,将配置的预分频值和自动重装载值代入溢出时间计算公式可得溢出时间是1s,具体代码如下。
代码清单:TIM6初始化
/************************************************************************* * 描 述 : 初始化TIM6并配置TIM6中断优先级 * 入 参 : 无 * 返回值 : 无 ***************************************************************************/ void TIM6_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM6 , ENABLE); //打开外设TIM6时钟
TIM_DeInit(TIM6); //将外设TIM6寄存器重设为缺省值
//TIM6是基本定时器,只需配置TIM_Period和TIM_Prescaler即可 TIM_TimeBaseInitStruct.TIM_Period = 9999 ; //设置计数器自动重装载值,取值范围0x0000~0xFFFF TIM_TimeBaseInitStruct.TIM_Prescaler = 7199 ; //设置预分频器数值,取值范围0x0000~0xFFFF TIM_TimeBaseInit(TIM6, &TIM_TimeBaseInitStruct ) ;
TIM_ClearFlag(TIM6, TIM_FLAG_Update); //清除TIM6的待处理标志位 TIM_ITConfig(TIM6, TIM_IT_Update,ENABLE); //使能TIM6中断 TIM_Cmd(TIM6, ENABLE); //使能TIM6外设
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //NVIC_Group:先占优先级2位,从优先级2位 NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn; //配置为TIM6中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级为1 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级为2 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断通道 NVIC_Init(&NVIC_InitStructure); }
中断服务函数中设计翻转用户指示灯D1的操作,代码如下。
代码清单:TIM中断服务函数
/********************************************************************** * 描 述 : TIM6中断服务函数 * 入 参 : 无 * 返回值 : 无 ***********************************************************************/ void TIM6_IRQHandler(void) { led_toggle(LED_1); //1s执行一次翻转指示灯D1的操作 TIM_ClearFlag(TIM6, TIM_FLAG_Update); //清除中断标志位 }
然后主函数中会对指示灯和TIM6进行初始化,在主循环中没有用户代码,可以判断如果用户指示灯D1闪烁则来自于中断服务函数。
代码清单:主函数
int main(void) { //初始化用于驱动指示灯D1、D2、D3、D4的引脚,并熄灭4个用户LED leds_init(); //初始化TIM6并配置TIM6中断优先级,配置TIM6溢出时间是1s TIM6_Init();
//主循环 while(1) { ; //空命令,说明用户LED的闪烁来自于TIM6中断 } }实验步骤解压“…\第3部分:标准库教程和实验源码\ 1 - 基础实验程序\”目录下的压缩文件“实验2-12-2:基本定时器中断”,将解压后得到的文件夹拷贝到合适的目录,如“D\STM32F103ZET6”。启动MDK5.23。在MDK5中执行“Project→Open Project”打开“…\TIM\projec”目录下的工程“TIM.uvproj”。点击编译按钮编译工程。注意查看编译输出栏,观察编译的结果,如果有错误,修改程序,直到编译成功为止。编译后生成的HEX文件“TIM.hex”位于工程目录下的“Objects”文件夹中。点击下载按钮下载程序。如果需要对程序进行仿真,点击Debug按钮,即可将程序下载到STM32F103ZET6中进行仿真。程序运行后,用户指示灯D1每1s状态翻转一次。
标签: #定时器计数汇编语言 #定时器中断延时一秒汇编程序