基本计时器TIM4:STM8的定时器/计数器资源综述
计时器概述
计时器的定义
-
如果需要精确地按时间操作单片机,那么就需要使用定时/计数器(简称T/C资源),下文简称计时器
-
计时器资源是单片机控制芯片的标配,它的功能极为重要,既可以按字面意思用于定时,也可以用于计数
事实上,定时功能的本质就是依赖时钟源进行计数
计时器的功能原理
-
定时功能
单片机内部的时钟定期向计数器发送脉冲,计数器记录下接收脉冲的次数,在达到寄存器设定的计数值后产生相应事件或者触发中断,由于时钟脉冲周期固定,所以利用计数器进行延时比之前提到的空操作延时更精确
- 具体的定时时间=脉冲个数×时钟周期
-
计数功能
计数脉冲的来源为从时钟源改为外部引脚输入(脉冲到来的时间与个数是不确定的,因此不能定时)对外部引脚输入的电平脉冲进行计数,达到设定值时触发
计时器的分类
-
查阅数据手册 计时器 可知该型号的计时器资源
STM8S一般拥有四个计时器资源(有的型号有6个),分为三类,彼此相互独立(TIM1/TIM5/TIM6除外,它们三者可以级联):
-
TIM1:16-bit高级控制计时器
拥有16位(由于STM8是8位控制器,因此16位实际上由两个8位的寄存器分别存放高8位和低8位实现)计数值,分频系数为1~65536;功能最为强大:可以选择外部触发输入为计数脉冲源,有输入捕获、输出比较、PWM产生等功能,4个独立的捕获/比较通道,这些功能在之后会专门介绍
-
TIM2/3:16-bit通用计时器
类似TIM1,也可以进行定时、输入捕获、输出比较,产生PWM信号,不过功能相比TIM1有所缺失,只有一个控制寄存器,输出比较没有死区控制和刹车控制,计数时钟源只能是主时钟,计数值可以有16位,但分频系数为1~32768,捕获/比较通道也比较少,TIM2为3个,TIM3为2个
某些型号的STM8S还有第三个16位通用计时器TIM5,有3个独立通道,但是比较少见,不再单独介绍
-
TIM4:8-bit基本计时器
功能最为简单的计时器,计数值只有8位,分频系数为1~128
某些型号还有第二个8位基本计时器TIM6,但是比较少见,不再单独介绍
计时流程与相关概念
计数时钟CK_CNT初始化
-
计数脉冲源的选择
-
单片机内部有三种时钟可用作计时,详见时钟源一节;默认使用的主时钟源是HSI,频率为2MHz
-
TIM2/3/4资源的计数时钟源只能是主时钟输出的fmaster,这要求我们配置主时钟状态寄存器CLK_CMSR和主时钟切换寄存器CLK_SWR,以此指定一个主时钟
配置完成后,便确定了单片机的主时钟来源以及频率
-
如果想要计数而非计时,那就要选择外部的脉冲输入作为脉冲源,之后会在TIM1的章节中单独介绍
-
-
主时钟源的分频
-
计时器可以对主时钟输出的fmaster分频,能够分频的范围见各个计时器资源的分频系数(注意:和对HSI时钟源的分频不同,计时器是对已经经过分频而产生的fmaster再分频),通过改变时钟的频率与计时器的周期,让计时范围更宽、更灵活
-
信号处理过程如下:fmaster主时钟 -> fCK_PSC预分频时钟 -> fCK_CNT计数器时钟
在指定好主时钟后,把主时钟信号通过CK_PSC时钟线连接到预分频器(主时钟的频率即CK_PSC时钟的频率)
再将CK_PSC时钟(计数时钟源输入时钟)分频,最终得到CK_CNT时钟(TIM4资源计数时钟)
-
分频的值设定通过配置预分频寄存器TIMx_PSCR实现
配置值会在发生更新事件时送入预分频寄存器的缓冲器中,这个缓冲器相当于上次设定分频值的锁止单元
-
主时钟(CK_PSC时钟)与分频后得到的TIMx计数时钟(CK_CNT时钟)之间频率计算关系为fCK_CNT= FCK_PSC / 2PSC[2;0]
-
计时过程
-
得到CK_CNT时钟后并且被使能后,TIMx资源中的向上计数器从0开始递增计数
-
影子寄存器
计数过程中,计数器的计数值会与自动重装载寄存器(TIMx_ARR)的影子寄存器中的设定值比较
影子寄存器的设定值来自自动重装载寄存器ARR,不去直接使用自动重装载寄存器的好处是在修改溢出条件时,计数器溢出条件还能按照改变前的参数执行,因为寄存器的数值还未来得及送入影子寄存器
-
计数模式
包括上文提到的向上计数(计数值从0递增),STM8的定时/计数器资源(简称T/C资源)共有三种计数模式,它们分别为:
- 向上计数:计数值从0开始递增,直至达到TIMx_ARR影子寄存器中的计数最大值后产生“向上计数溢出”
- 向下计数:计数值从影子寄存器中的计数最大值开始递减,直至达到0时产生“向下计数溢出”
- 向上向下计数模式(双向计数方式):计数值从0开始递增,到设定的计数最大值后发生“向上计数溢出”,再递减,到0时发生“向下计数溢出”,两次溢出都产生更新事件
计时结束
-
计数溢出
-
当达到设定值时,会发生计数溢出
-
若此时计数资源控制寄存器TIMx_CR1中的UDIS位为0,则产生一个更新事件UEV,且状态寄存器TIMx_SR中的UIF位(更新事件中断标志)被硬件自动置1
-
若使能了更新中断功能(即中断使能寄存器TIMx_IER中的UIE位设置为1)则在产生更新事件的同时,产生一个更新事件中断UIF,进入中断服务程序
一般在中断程序中把UIF位清零,以此让计数器重新运作
-
-
更新事件UEV
-
由上文可知计数溢出时会发生更新事件UEV,但不一定有更新事件中断UIF
-
想要发生更新事件,必须保证控制寄存器1(TIM4_CR1)中的禁止更新位(UDIS)为0
-
更新事件的作用在于其发生时,将会自动完成如下事项:
- 自动重装载寄存器TIM4_ARR中的配置数据会被存放入其影子寄存器中
- 预分频寄存器TIM4_PSCR中的预分频值会被写入预分频器的缓冲器中
- 状态寄存器TIM4_SR中的UIE(更新事件中断标志) 位被置为1
也就是说:更新事件的意义就是刷新了相关的寄存器配置值
-
TIM4计时功能配置流程
-
TIM4的功能简单,基本只用来计时,本节用其来演示如何配置计时功能
要使用其他计时器资源TIM1/2/3等作计时功能时,配置方法相似
TIM4配置简述
-
预分配系数
配置TIM4_PSCR寄存器 PSC[2:0]位,设置预分频寄存器数值
-
确定计时值
配置自动重装载寄存器TIM4_ARR寄存器ARR的[7:0]位
-
开启TIM4更新事件中断
配置TIM4_IER寄存器的UIE位为1,让计时器达到计数值时产生一个中断
另外,同配置外部中断的流程,要构建计时器中断服务函数,还要使用asm("rim") 在主程序中开启总中断
-
使能计时器功能
配置TIM4_CR1寄存器的CEN位为1,让计时器开始运作
TIM4相关寄存器
-
这些寄存器不仅仅为TIM4独有,TIM1/2/3都有相同的寄存器(前缀数字不同以区别)用以配置各计数器资源,不过因为其功能更复杂,一些在TIM4置零的位在TIM1/2/3中可能有特殊的作用,详见各自的寄存器介绍
-
预分频寄存器TIM4_PSCR
配置分频系数以得到主时钟分频后的CK_CNT时钟,拥有“缓冲器”结构
其PSC位(0到2位)用于以二进制设定预分频值,其它位必须置0
分配系数可以是1-128直接的2的任意次幂,即2PCS[2;0],可见PCS取值范围为0-7,也就是最大为111(2),即7(10)
-
控制寄存器TIM4_CR1
配置与控制TIM4自动重装的载入功能;计数器的运行状态;更新事件与更新中断的功能配置;计数器的启用与关闭
具体配置例:配置该寄存器为5(0000 0101),即让自动重装载寄存器立刻更新,并且设置溢出时产生中断
TIM4_CR1 = 0x05; //0000 0101
//产生的效果等同下列代码:
TIM4_CR1_ARPE = 0; //TIM4_ARR的配置值会立刻写入影子寄存器,不必等待中断
TIM4_CR1_OPM = 0; //计数器在更新事件时不停止
TIM4_CR1_URS = 1; //更新中断使能时(TIM4_IER_UIE置1),只在计数器溢出时才发送中断请求
TIM4_CR1_UDIS = 0; //当TIM4_EGR_UG为1时立即产生一次中断
TIM4_CR1_CEN =1; //使能计数器
-
中断使能寄存器TIM4_IER
配置更新中断的启用与禁止
如果需要在更新事件产生的同时产生一个中断信号,则配置该寄存器UIE位(位0)为1
该寄存器的1至7位都必须保持为0
-
计数值寄存器TIM4_CNTR
TIM4向上计数器中的计数值
该计数器的0到7位用于以二进制储存计数器值
-
自动重装寄存器TIM4_ARR
装载用户设定的计数最大值,有对应的“影子寄存器”
该计数器的0到7位用于以二进制储存自动重装载值
-
状态寄存器TIM4_SR
反应是否发生了更新事件
在发生了更新事件后,该寄存器的UIF位(位0)将会被硬件置1,可以由软件清零
同中断使能寄存器,1至7位必须保持为0
-
事件产生寄存器TIM4_EGR
提供一种软件更新事件的配置位
更新事件可以由计数溢出后产生,也可以人为安排,这种做法即软件更新事件
对该寄存器UG位(位0)置1,哪怕没有计数溢出也会直接产生一个更新事件
同上,1至7位必须保持为0
注意TIM4_CR1控制寄存器1的UDIS位(位1)对更新事件的影响:当其为1(禁止产生更新事件)时,UG位置0不产生更新事件,但计数器与预分频器会被初始化
定时值的配置公式
-
配置TIM2/3/4定时值的公式
由上文的介绍,可知定时器的定时值(经过多久会产生更新事件)由三个参数决定:主时钟频率,计时器的分频系数,自动重装寄存器ARR的设定值,前两个参数决定数多快,后一个决定数多久
对于基本的计时器TIM2/3/4来说,这三者的关系公式如下:
TIMx_ARR=(fmaster×t/2PSCR[3:0])-1,其中主频的单位是MHz,设定时间t单位是us
-
比如说,主频是默认的2MHz,要求计时1ms,分频值是8,那么:
PSCR寄存器设定为0x03:分频23=8
ARR寄存器设定为(2×1000/8)-1=249,即0xF9
-
-
配置TIM1定时值的公式
TIM1是高级计时器,其有一个显著的特点便是对分频的操控更为精细,其有十六位的寄存器来存放分频设定值,其分频系数也不再局限于2PSCR(即不分频、进行2分频、4分频、8分频等),而是十六位能存放的任意值加上1,因此其公式如下:
TIM1_ARR=[(fmaster×t)/(PSCR[15:0]+1)]-1
另外TIM1可以配置向上、向下和双向计数模式,上面的公式只适用向上/向下模式,双向模式下需要再给分频值(PSCR[15:0]+1)×2,并且不必减1
代码实现
-
TIM4实现定时功能
此处延时用TIM4计时1s,让LED灯每隔1s变换一次亮灭为实验现象
初始化TIM4的函数,与主程序、中断服务程序的代码示例:
unsigned int num = 0; void TIM4_init()//计时器初始化 { //设定1ms的时间 TIM4_PSCR = 0x03; //对主时钟进行8分频,250kHz TIM4_ARR = 0xF9; //自动重装设置为249,1ms TIM4_IER = 0x01; //开启更新事件中断使能 //TIM4_IER_UIE = 1; 直接控制对应位达到同样的效果 TIM4_CNTR = 0xF9; //配置计数初值,这一步不必要 //可使其等于溢出值,使一开始计数产生一个更新事件,触发重装并初始化计数器 //TIM4_EGR = 1; 也可以通过直接给事件产生寄存器置1达成同样的效果 //前提是TIM4_CR1_UDIS=0 TIM4_CR1 = 0x01; //开启计时器 } void main()//主程序 { PB_DDR_DDR0 = 1; //0000 1111 PB0-3被配置为1输出 PB_CR1_C10 = 1; //控制寄存器CR1被配置为1推挽输出 TIM4_init(); asm("rim");//开总中断 while(1); } #pragma vector = 25//计数器溢出后的中断函数,TIM4中断号为23,偏移2得25 __interrupt void TIM4_UPD_OVF_IRQHandler(void)//中断服务函数 { num++; TIM4_SR_UIF = 0;//将中断后被置1的中断标志位重置 if(num >= 1000)//每秒进行一次操作(1000ms) { PB_ODR_ODR0 = ~PB_ODR_ODR0; num = 0; } }
-
TIM2/3实现计时功能
与8位的基本计时器TIM4相比,16位通用计数器最大特点在于:计数值可达16位
使用两个重装载寄存器来储存装载数值,每个8位,共可储存16位的数值
TIM2_ARRH储存装载数值的高8位,TIM2_ARRL储存低8位
例如想要设置触发值为62500(10),即F424(16),那么TIM2_ARRH储存0xF4,TIM2_ARRL储存0x24
void TIM2_init() { TIM2_PSCR = 5; //设置预分频系数为 2^5=32,即频率为2M/32=62500, TIM2_ARRH = 0xF4; //设置自动装载寄存器的值为 62499,那么定时中断周期刚好为1s TIM2_ARRL = 0x23; // 62500转换成十六进制为 0xF423 TIM2_CR1 = 0x04; //设置为更新时计数器不停止,且仅计数器溢出时中断有效 // TIM2_CR1_URS = 1; TIM2_EGR = 0x01; //立即产生一个更新事件,触发重装并初始化计数器 // TIM2_EGR_UG = 1; TIM2_IER = 0x01; //允许更新中断 // TIM2_IER_UIE = 1; TIM2_CR1 = 0x01; //使能定时器 // TIM2_CR1_CEN = 1; } void main() { TIM2_init(); asm("rim");//开总中断 …… } #pragma vector = TIM2_OVR_UIF_vector //或者使用向量号加偏移,为15 __interrupt void TIM2_Overflow_IRQHandler(void) { //省略具体语句 TIM2_SR1 &= 0xFE;//清除中断标志 }
-
TIM1实现计时功能
同TIM2/3的配置,注意TIM1对分频的控制更为精细,其他计时器的分频系数都是2的次方分频,TIM1却能直接设定分频系数(设定值为分频系数-1)
注意TIM1功能更多,相应的控制位更复杂,比如IER和CR1的其他位除了控制计时,还能控制很多功能,在之后将会单独介绍,因此不要直接使用=为IER和CR1寄存器赋值,使用|=或者直接配置相应的位
void TIM1_init(void) { TIM1_PSCRH = 0; TIM1_PSCRL = 15;//进行16分频 //因为TIM1有多个配置项需要分别配置高低位两个寄存器,为了设定方便,下面是使用一个16位的变量set_num配置两个寄存器的方法 TIM1_ARRH = (uint8_t)(set_num>>8);//配置高8位计数值 TIM1_ARRL = (uint8_t)set_num&0x00FF;//配置低8位计数值 /* TIM1_CNTRH =……; TIM1_CNTRL =……; 默认的计数器寄存器值为0,这一步并非必须 但如果需要计数器启动后立即中断一次,可以同ARR配置 */ TIM1_IER |= 0x01;//UIE位置1,开启溢出中断 } void main() { TIM1_init(); TIM1_CR1 |= 0x01;//可以把使能计数器这一步单独拿出来 asm("rim");//开总中断 …… } #pragma vector = 0x0D//TIM1中断向量号 __interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler() { //省略具体语句 TIM1_SR1 &= 0xFE;//清除溢出中断标志位UIF }
关于计时器的故障
- 我在自己编写实验程序时,曾遇到过定时器无法正确进入中断复位程序的情况,在网上查询时发现有很多人有类似经历,我在摸索许久后将排障的方法记录下来,希望对你的问题有所帮助
- 首先,确保定时器的故障不是硬件问题而是软件问题,比如说:给单片机烧录一段简单的测试代码,观察实验现象,就比如上文的LED定时闪烁,哪一个定时器资源出问题就用哪一个TIM来做,务必新开一个工作空间来做这个实验,而不要在已经有问题的程序上修改
- 确定单片机硬件没有问题(绝大多数情况都是软件问题)后回到有错的项目中检测TIM相关的代码,易错点包括:初始化时各项参数设定有问题,初始化后没有使能计数器,没有开总中断,在中断服务程序中没有清除中断标志位
- 如果对以上代码进行修改都没有解决问题,那么最大的可能是中断服务程序本身有问题,IAR编译器无法给一些现象异常而语法上没有错误的程序报错,因此把有问题的程序烧进了单片机,把放在中断服务程序中调用的函数注释掉,换成LED状态取反闪烁这样的测试代码,看实验现象,如果还是有问题说明TIM还没有配置好,返回第二步,如果正常了,就去修改调用的函数
本文来自博客园,作者:无术师,转载请注明原文链接:https://www.cnblogs.com/untit1ed/p/18565871
本文使用知识共享4.0协议许可 CC BY-NC-SA 4.0
请注意: 特别说明版权归属的文章以及不归属于本人的转载内容(如引用的文章与图片)除外