基本计时器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配置简述

  1. 预分配系数

    配置TIM4_PSCR寄存器 PSC[2:0]位,设置预分频寄存器数值

  2. 确定计时值

    配置自动重装载寄存器TIM4_ARR寄存器ARR的[7:0]位

  3. 开启TIM4更新事件中断

    配置TIM4_IER寄存器的UIE位为1,让计时器达到计数值时产生一个中断

    另外,同配置外部中断的流程,要构建计时器中断服务函数,还要使用asm("rim") 在主程序中开启总中断

  4. 使能计时器功能

    配置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自动重装的载入功能;计数器的运行状态;更新事件与更新中断的功能配置;计数器的启用与关闭

image

具体配置例:配置该寄存器为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
    }
    
    关于计时器的故障
    • 我在自己编写实验程序时,曾遇到过定时器无法正确进入中断复位程序的情况,在网上查询时发现有很多人有类似经历,我在摸索许久后将排障的方法记录下来,希望对你的问题有所帮助
    1. 首先,确保定时器的故障不是硬件问题而是软件问题,比如说:给单片机烧录一段简单的测试代码,观察实验现象,就比如上文的LED定时闪烁,哪一个定时器资源出问题就用哪一个TIM来做,务必新开一个工作空间来做这个实验,而不要在已经有问题的程序上修改
    2. 确定单片机硬件没有问题(绝大多数情况都是软件问题)后回到有错的项目中检测TIM相关的代码,易错点包括:初始化时各项参数设定有问题,初始化后没有使能计数器,没有开总中断,在中断服务程序中没有清除中断标志位
    3. 如果对以上代码进行修改都没有解决问题,那么最大的可能是中断服务程序本身有问题,IAR编译器无法给一些现象异常而语法上没有错误的程序报错,因此把有问题的程序烧进了单片机,把放在中断服务程序中调用的函数注释掉,换成LED状态取反闪烁这样的测试代码,看实验现象,如果还是有问题说明TIM还没有配置好,返回第二步,如果正常了,就去修改调用的函数

posted on 2024-11-24 15:31  无术师  阅读(24)  评论(0编辑  收藏  举报