查看: 1659|回复: 0

零死角玩转stm32-初级篇之Sysstick(系统滴答定时器)

[复制链接]
  • TA的每日心情
    慵懒
    2015-5-29 12:01
  • 签到天数: 11 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2013-7-11 17:43:42 | 显示全部楼层 |阅读模式
    分享到:
        6、Sysstick(系统滴答定时器)

       6.1 SysTick——操作系统的心跳
    SysTick定时器被捆绑在NVIC中,用于产生SysTick异常(异常号:15)。在以前,操作系统和有所有使用了时基的系统,都必须要一个硬件定时器来产生需要的“滴答”中断,作为整个系统的时基。滴答中断对操作系统尤其重要。例如,操作系统可以为多个任务许以不同数目的时间片,确保没有一个任务能霸占系统;或者把每个定时器周期的某个时间范围赐予特定的任务等,还有操作系统提供的各种定时功能,都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。
    Cortex-M3在内核部分 包含了一个简单的定时器——SysTick timer。因为所有的CM3芯片都带有这个定时器,软件在不同芯片生产厂商的 CM3器件间的移植工作就得以化简。该定时器的时钟源可以是内部时钟(FCLK,CM3上的自由运行时钟),或者是外部时钟( CM3处理器上的STCLK信号)。不过,STCLK的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能会大不相同。因此,需要阅读芯片的使用手册来确定选择什么作为时钟源。在STM32中SysTick 以 HCLK(AHB时钟)或HCLK/8 作为运行时钟。见图61。

    图61 时钟树(部分)-SysTick timer 时钟来源

        SysTick定时器能产生中断,CM3为它专门开出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其它系统软件在CM3器件间的移植变得简单多了,因为在所有CM3产品间,SysTick的处理方式都是相同的。SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。
    Systick 定时器属于cortex内核部件,可以参考《CortexM3权威指南》或《STM32xxx-Cortex编程手册》来了解
     6.2 SysTick timer工作分析
    SysTick是一个24位的定时器,即一次最多可以计数224 个时钟脉冲,这个脉冲计数值被保存到 当前计数值寄存器STK_VAL (SysTick current value register) 中,只能向下计数,每接收到一个时钟脉冲STK_VAL的值就向下减1,直至0,当STK_VAL的值被减至0时,由硬件自动把重载寄存器STK_LOAD(SysTick reload value register)中保存的数据加载到STK_VAL,重新向下计数。当STK_VAL的值被计数至0时,触发异常,就可以在中断服务函数中处理定时事件了。
    当然,要使SysTick进行以上工作必须要进行SysTick进行配置。它的控制配置很简单,只有三个控制位和一个标志位,都位于寄存器STK_CTRL(SysTick control and status register )中,见图6。

    图62 Systick CTRL寄存器

    Bit0: ENABLE
    为SysTick timer 的使能位,此位为1的时候使能SysTick timer,此位为0的时候关闭SysTick timer。
    Bit1:TICKINT
    为异常触发使能位,此位为1的时候并且STK_VAL计数至0时会触发SysTick异常,此位被配置为0的时候不触发异常
    Bit2:CLKSOURCE
    为SysTick的时钟选择位,此位为1的时候SysTick的时钟为AHB时钟,此位为0的时候SysTick时钟为AHB/8(AHB的八分频)。
    Bit16:COUNTFLAG
    为计数为0标志位,若STK_VAL计数至0,此标志位会被置1。
    与SysTick控制相关的所有寄存器如图 02,其中上面没有介绍的STK_CALIB寄存器是用于校准的,不常用。

    图 02 SysTick寄存器映像

       6.3 SysTick精确延时实例精讲
    前面的的实验例程中,当有延时需要的时候,我们都是利用内核循环执行变量自减的代码来实现,延时的时间无法精确测量,有很大的局限性,当我们需要精确延时时,就可以利用SysTick timer实现,理论上它的最小计时单位为AHB的时钟周期,即1/72000000 秒,72分之一的微秒,足以满足大部分极端应用需求。本小节以实例讲解如何利用SysTick进行精确延时。
             6.3.1实验描述及工程文件清单

       6.3.2配置工程环境
    本SysTick timer精确延时实验中我们用到了GPIO、RCC外设,所以我们先要把以下库文件添加到工程stm32f10x_gpio.c、stm32f10x_rcc.c
    由于本实验中,SysTick的中断是在文件core_cm3.h的函数配置的,没有使用NVIC来配置中断,所以可不添加misc.c文件 。而core_cm3.h在包含stm32f10x.h头文件时已被添加进工程了。
    接下来添加旧工程中的外设用户文件led.c,新建SysTick.c及SysTick.h文件,并在 stm32f10x_conf.h 中把使用到的ST库的头文件注释去掉。
    /**
    *****************************************************
    * @file    Project/STM32F10x_StdPeriph_Template/stm32f10x_conf.h
    * @author  MCD Application Team
    * @version V3.5.0
    * @date    08-April-2011
    * @brief   Library configuration file.
    **************************************/
    #include "stm32f10x_gpio.h"
    #include "stm32f10x_rcc.h"
       6.3.3 main文件
    我们从看main函数看起:
    /*
    * 函数名:main
    * 描述  :主函数
    * 输入  :无
    * 输出  :无
    */
    int main(void)
    {
    /* LED 端口初始化 */
    LED_GPIO_Config();
    /* 配置SysTick 为10us中断一次 */
    SysTick_Init();
    for(;;)
    {
    LED1( 0 );
    Delay_us(50000);        // 50000 * 10us = 500ms
    LED1( 1 );
    LED2( 0 );
    Delay_us(50000);        // 50000 * 10us = 500ms
    LED2( 1 );
    LED3( 0 );
    Delay_us(50000);        // 50000 * 10us = 500ms
    LED3( 1 );
    }
    }
    在main函数中,我们只见到SysTick_Init()Delay_us() 这两个函数比较陌生,它们的功能分别是配置好SysTick 定时器和进行精确延时。
    整个main函数的流程就是先初始化好LED及SysTick定时器之后,就进入死循环,轮流点亮LED1、LED2、LED3,点亮的时间为精确的500ms。
      6.3.4配置并启动SysTick timer
    接下来我们看一下SysTick_Init() 这个函数,它是由用户在SysTick.c这个文件中实现的,其功能是启动系统滴答定时器SysTick,并将SysTick配置为 10 us 中断一次:
    /*
    * 函数名:SysTick_Init
    * 描述  :启动系统滴答定时器 SysTick
    * 输入  :无
    * 输出  :无
    * 调用  :外部调用
    */
    void SysTick_Init(void)
    {
    /* SystemFrequency / 1000    1ms中断一次
    * SystemFrequency / 100000  10us中断一次
    * SystemFrequency / 1000000 1us中断一次
    */
    //  if (SysTick_Config(SystemFrequency / 100000)) // ST3.0.0库版本
    if (SysTick_Config(SystemCoreClock / 100000)) // ST3.5.0库版本
    {
    /* Capture error */
    while (1);
    }
    // 关闭滴答定时器
    SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
    }
    本函数实际上只是调用了SysTick_Config()函数,它是属于内核层的Cortex-M3通用函数,位于core_cm3.h文件中,若调用SysTick_Config() 配置SysTick不成功,则进入死循环,初始化SysTick成功后,先关闭定时器,在需要的时候再开启。
    SysTick_Config() 函数无法在《STM32外设固件库帮助手册.chm》文件中找到其使用方法。所以我们在keil环境下直接跟踪这个函数到core_cm3.h文件,查看函数的定义:
    /**
    * @brief  Initialize and start the SysTick counter and its interrupt.
    *
    * @param   ticks   number of ticks between two interrupts
    * @return  1 = failed, 0 = successful
    *
    * Initialise the system tick timer and its interrupt and start the
    * system tick timer / counter in free running mode to generate
    * periodical interrupts.
    */
    static __INLINE uint32_t SysTick_Config(uint32_t ticks)
    {
    /* Reload value impossible */
    if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);
    /* set reload register */
    SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
    /* set Priority for Cortex-M0 System Interrupts */
    NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
    /* Load the SysTick Counter Value */
    SysTick->VAL   = 0;
    SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
    SysTick_CTRL_TICKINT_Msk   |
    SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
    return (0);                                                  /* Function successful */
    }
    在这个函数定义的前面,有关于它的注释,如果我们不想去研究它的具体实现,可以根据这段注释了解函数的功能:这个函数启动了SysTick timer;并把它配置为计数至0时引起中断;输入的参数ticks为两个中断之间的脉冲数,即相隔ticks个时钟周期会引起一次中断;配置SysTick成功时返回0,出错进返回1。
    但是,这段注释并没有告诉我们它把SysTick的时钟设置为AHB时钟还是AHB/8,这是一个十分关键的问题,于是,野火对这个函数的具体实现进行分析,与大家再分享一下如何分析底层库函数。
    分析底层库函数,要有0小节关于SysTick timer工作分析的知识准备。
    检查输入参数
    SysTick_Config()第1行代码是检查输入参数ticks,因为ticks是脉冲计数值,要被保存到重载寄存器STK_LOAD寄存器中,再由硬件把STK_LOAD值加载到 当前计数值寄存器STK_VAL使用的,STK_LOADSTK_VAL都是24位的,所以当输入参数ticks大于其可存储的最大值时,将由这行代码检查出错误返回。
    位指示宏及位屏蔽宏
    检查ticks参数没有错误后,就稍稍处理一下把ticks-1赋值给STK_LOAD寄存器,要注意的是减1,若STK_VAL从ticks-1向下计数至0,实际上就经过了ticks个脉冲。这句赋值代码中使用到了宏SysTick_LOAD_RELOAD_Msk,与其它库函数类似,这个宏是用来指示寄存器的特定位置 或进行位屏蔽用的。它及类似的宏定义如下:
    /* SysTick Control / Status Register Definitions */
    #define SysTick_CTRL_COUNTFLAG_Pos         16                                             /*!< SysTick CTRL: COUNTFLAG Position */
    #define SysTick_CTRL_COUNTFLAG_Msk         (1ul << SysTick_CTRL_COUNTFLAG_Pos)            /*!< SysTick CTRL: COUNTFLAG Mask */
    #define SysTick_CTRL_CLKSOURCE_Pos          2                                             /*!< SysTick CTRL: CLKSOURCE Position */
    #define SysTick_CTRL_CLKSOURCE_Msk         (1ul << SysTick_CTRL_CLKSOURCE_Pos)            /*!< SysTick CTRL: CLKSOURCE Mask */
    #define SysTick_CTRL_TICKINT_Pos            1                                             /*!< SysTick CTRL: TICKINT Position */
    #define SysTick_CTRL_TICKINT_Msk           (1ul << SysTick_CTRL_TICKINT_Pos)              /*!< SysTick CTRL: TICKINT Mask */
    #define SysTick_CTRL_ENABLE_Pos             0                                             /*!< SysTick CTRL: ENABLE Position */
    #define SysTick_CTRL_ENABLE_Msk            (1ul << SysTick_CTRL_ENABLE_Pos)               /*!< SysTick CTRL: ENABLE Mask */
    /* SysTick Reload Register Definitions */
    #define SysTick_LOAD_RELOAD_Pos             0                                             /*!< SysTick LOAD: RELOAD Position */
    #define SysTick_LOAD_RELOAD_Msk            (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos)        /*!< SysTick LOAD: RELOAD Mask */
    /* SysTick Current Register Definitions */
    #define SysTick_VAL_CURRENT_Pos             0                                             /*!< SysTick VAL: CURRENT Position */
    #define SysTick_VAL_CURRENT_Msk            (0xFFFFFFul << SysTick_VAL_CURRENT_Pos)        /*!< SysTick VAL: CURRENT Mask */
    /* SysTick Calibration Register Definitions */
    #define SysTick_CALIB_NOREF_Pos            31                                             /*!< SysTick CALIB: NOREF Position */
    #define SysTick_CALIB_NOREF_Msk            (1ul << SysTick_CALIB_NOREF_Pos)               /*!< SysTick CALIB: NOREF Mask */
    #define SysTick_CALIB_SKEW_Pos             30                                             /*!< SysTick CALIB: SKEW Position */
    #define SysTick_CALIB_SKEW_Msk             (1ul << SysTick_CALIB_SKEW_Pos)                /*!< SysTick CALIB: SKEW Mask */
    #define SysTick_CALIB_TENMS_Pos             0                                             /*!< SysTick CALIB: TENMS Position */
    #define SysTick_CALIB_TENMS_Msk            (0xFFFFFFul << SysTick_VAL_CURRENT_Pos)        /*!< SysTick CALIB: TENMS Mask */
    /*@}*/ /* end of group CMSIS_CM3_SysTick */
    其中的寄存器位指示宏:SysTick_xxx_Pos ,宏展开后即为xxx在相应寄存器中的位置,如控制SysTick时钟源的SysTick_CTRL_CLKSOURCE_Pos ,宏展开为2,这个寄存器位正是在寄存器STK_CTRL中的Bit2
    而寄存器位屏蔽宏:SysTick_xxx_Msk,宏展开是xxx的位全部置1后,左移SysTick_xxx_Pos位。如控制SysTick时钟源的SysTick_CTRL_CLKSOURCE_Msk,宏展开为 (1ul << SysTick_CTRL_CLKSOURCE_Pos) ,把无符号长整型数值(ul) 1左移2位,得到了一个只有Bit2:CLKSOURCE 位被置1,其它位为0的数值,这样的数值配合位操作 &(按位与)、| (按位或)可以很方便地修改寄存器的某些位。假如控制CLKSOURCE 需要四个寄存器位,这个宏就应该被改为(0xf ul << SysTick_CTRL_CLKSOURCE_Pos) ,这样就会得到一个关于CLKSOURCE的四位被置1的值,这些宏的参数就是这样被确定的。
    寄存器位指示宏和位屏蔽宏在操作寄存器的代码(大部分库函数)中用得十分广泛,在前面GPIO_Init()函数分析时也遇到很多,为了方便以后再使用,野火就给这两类宏取了这两个名字。
    配置中断向量及重置STK_VAL寄存器
    回到SysTick_Config()函数,接下来调用了NVIC_SetPriority ()函数配置了SysTick中断,这就是为什么我们在外部没有再使用NVIC配置SysTick中断的原因。配置好SysTick中断后把STK_VAL寄存器重新赋值为0(在使能SysTick时,硬件会把存储在STK_LOAD寄存器中的ticks值加载给它)。
    配置SysTick timer时钟为AHB
    在这段代码最后,向STK_CTRL寄存器写入了SysTick timer的控制参数,配置为使用AHB时钟,使能计数至0时引起中断,使能SysTick。执行了这行代码,SysTick就开始运行,进行脉冲计数了。
    若读者想要使用AHB/8作为时钟,可以调用库函数SysTick_CLKSourceConfig()进行修改,也可以直接对SysTick_Config()函数的代码进行修改。
    使能、关闭定时器
    由于调用SysTick_Config()函数之后,SysTick定时器就被开启了,但我们在初始化的时候并不希望这样,而是在需要的时候再开启。所以在SysTick_Init()函数中,调用完SysTick_Config() 配置好后先把定时器关闭了。 SysTick的开启和关闭由寄存器STK_CTRL的Bit0:ENABLE位 来控制,使用位屏蔽宏,以操作寄存器的方式实现:
    // 使能滴答定时器
    SysTick->CTRL |=  SysTick_CTRL_ENABLE_Msk;
    // 失能滴答定时器
    SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
    6.3.5定时时间的计算
    现在回到函数SysTick_Init(),在调用SysTick_Config()函数时,向它输入的参数为:SystemCoreClock / 100000SystemCoreClock 为定义了系统时钟(SYSCLK)频率的宏,即等于AHB的时钟频率,本书的所有例程中AHB都是被配置为72MHz的,也就是这个SystemCoreClock 宏展开为数值7200 0000。
    根据前面对SysTick_Config()函数的介绍,它的输入参数为SysTick将要计的脉冲数,经过ticks个脉冲(经过ticks个时钟周期)后将触发中断,触发中断后又重新开始计数。
    由此我们可以算出定时的时间,下面为计算公式:
    T=ticks*(1/f)
    T 为要定时的总时间。
    ticksSysTick_Config()的输入参数。
    1/ f 即为SysTick timer使用的时钟源的时钟周期,f为该时钟源的时钟频率,当时钟源确定后为常数。
    例如:本实验例子中,使用时钟源为AHB时钟,其频率被配置为72MHz。调用函数时,把ticks赋值为ticks=SystemFrequency / 10 000 =720,表示720个时钟周期中断一次;(1/f)是时钟周期的时间,此时(1/f =1/72 us ),所以最终定时总时间T=720*(1/72),为720个时钟周期,正好是10us。
    SysTick定时器的定时时间(配置为触发中断,即为中断周期),由ticks参数决定,最大定时周期不能超过224个。以下是几种常用的中断周期配置,就是根据上面的公式计算出来的。
    /* ticks 常取以下值 */
    SystemFrequency / 1000         // 1ms中断一次
    SystemFrequency / 100000       //  10us中断一次
    SystemFrequency / 1000000      //   1us中断一次
    6.3.6编写中断服务函数
    回到main函数,我们使LED工作在一个无限循环中,在LED的开与关之间调用了Delay_us()函数:
    while (1)
    {
    //SysTick->CTRL = 1 << SYSTICK_ENABLE;         // 使能滴答定时器
    LED1( 0 );
    Delay_us(50000);    // 50000 * 10us = 500ms
    LED1( 1 );
    LED2( 0 );
    Delay_us(50000);        // 50000 * 10us = 500ms
    LED2( 1 );
    LED3( 0 );
    Delay_us(50000);        // 50000 * 10us = 500ms
    LED3( 1 );
    //SysTick->CTRL = 0 << SYSTICK_ENABLE;         // 失能滴答定时器
    }
    一旦我们调用了Delay_us() 函数,SysTick定时器就被开启,按照设定好的定时周期递减计数,SysTick的计数寄存器里面的值减为0时,就进入中断函数,当中断函数执行完毕之后由重新计时,如此循环,除非它被关闭。
    Delay_us()函数实现如下:
    /*
    * 函数名:Delay_us
    * 描述  :us延时程序,10us为一个单位
    * 输入  :- nTime
    * 输出  :无
    * 调用  :Delay_us( 1 ) 则实现的延时为 1 * 10us = 10us
    *       :外部调用
    */
    void Delay_us(__IO u32 nTime)
    {
    TimingDelay = nTime;
    // 使能滴答定时器
    SysTick->CTRL |=  SysTick_CTRL_ENABLE_Msk;
    while(TimingDelay != 0);
    }
    使能了SysTick之后,就使用 while(TimingDelay != 0)语句等待TimingDelay变量变为0,这个变量是在中断服务函数中被修改的。
    因此,我们需要编写相应的中断服务程序,在本实验室中我们配置为 10us 中断一次,每次中断把TimingDelay减1。中断程序在stm32f10x_it.c中实现:
    /**
    * @brief  This function handles SysTick Handler.
    * @param  None
    * @retval : None
    */
    void SysTick_Handler(void)
    {
    TimingDelay_Decrement();
    }
    SysTick中断属于系统异常向量,在stm32f10x_it.c文件中已经默认有了它的中断服务函数SysTick_Handler(),但内容为空。我们在找到这个函数,在里面调用了用户函数TimingDelay_Decrement()
    TimingDelay_Decrement()是由用户编写的一个应用程序,在SysTick.c中实现:
    /*
    * 函数名:TimingDelay_Decrement
    * 描述  :获取节拍程序
    * 输入  :无
    * 输出  :无
    * 调用  :在 SysTick 中断函数 SysTick_Handler()调用
    */
    void TimingDelay_Decrement(void)
    {
    if (TimingDelay != 0x00)
    {
    TimingDelay--;
    }
    }
    每次进入SysTick中断就调用一次TimingDelay_Decrement() 函数,把全局变量TimingDelay自减一次。用户函数Delay_us () 在TimingDelay被减至 等于 0时,才退出延时循环,即我们对TimingDelay赋的值为要中断的次数。
    所以总的延时时间T延时= T中断周期 * TimingDelay 。
    至此,SysTick的精确延时功能讲解完毕。
    6.3.7使用SysTick的测量时间的功能
    稍微改变一下用法,我们就可以利用SysTick进行时间测量。
    当我们开启SysTick定时器后,定时器开始工作,我们可以定义一个变量a来对中断次数进行记录,在定时器进入中断时,这个变量就a ++,当我们关闭定时器后,将变量的数值乘与定时器的中断周期 就等于测量时间。这个功能野火一般用于测量程序的运行时间,特别是涉及到算法的程序,这对于优化算法是有非常大的帮助。假如你的算法的是us级别的,那么SysTick就应该设定为us级中断,如果是ms级别的,就将SysTick设定为ms级中断。
    6.3.8实验现象
    将野火STM32开发板供电(DC5V),插上JLINK,将编译好的程序下载到开发板,即可看到板载的3个LED以500ms的频率闪烁。
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-4-21 00:40 , Processed in 0.137595 second(s), 18 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.