对于使用uCOS II我们可以使用task来定时执行函数,如果是裸机,我们就可以使用SysTick来定时或延时。
我们首先想到的就是到数据手册中搜索,显示结果如下:
我们下载《STM32F10xxx Cortex-M3编程手册》搜索,结果如下:
SysTick 是一个24 位的倒计数定时器(它放在了NVIC中),当计到0 时,将从LOAD 寄存器中自动重装载定时初值(Sys 系统 ,tick 滴答声 ,系统滴答滴答很形象地表示了它是一个系统节拍器)。只要不把它在SysTick 控制及状态寄存器中的使能位清除(即CTRL寄存器中的ENABLE),就永不停息。
SysTick 是一个简单的系统时钟节拍计数器,它属于 ARM Cortex-M3 内核嵌套向量中断控制器 NVIC 里的一个功能单元,而非片内外设 SysTick常用于操作系统(如:μC/OS-II
FreeRTOS等)的系统节拍定时 由于 SysTick是属于 ARM Cortex-M3 内核里的一个功能单元,因此使用 SysTick 作为操作系统节拍定时,使得操作系统代码在不同厂家的 ARM Cortex-M3 内核芯片上都能够方便地进行移植 。当然,在不采用操作系统的场合下 SysTick 完全可以作为一般的定时/计数器来使用(我最初学习的目的就是在不实用系统的情况下精确延时)。
知道它是什么东西以后我们就开始学习了,由于我使用库函数来开发程序,因此接下来除了必要以外均是从网上找来的资料。
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。在以前,大多操作系统需要一个硬件定时器来产生操作系统需要的滴答中断,作为整个系统的时基。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。SysTick的最大使命,就是定期地产生异常请求,作为系统的时基。OS都需要这种“滴答”来推动任务和时间的管理。
也就是说:Systick就是一个定时器而已,只是它放在了NVIC中,主要的目的是为了给操作系统提供一个硬件上的中断(号称滴答中断)。滴答中断?这里来简单地解释一下。操作系统进行运转的时候,也会有“心跳”。它会根据“心跳”的节拍来工作,把整个时间段分成很多小小的时间片,每个任务每次只能运行一个“时间片”的时间长度就得退出给别的任务运行,这样可以确保任何一个任务都不会霸占整个系统不放。或者把每个定时器周期的某个时间范围赐予特定的任务等,还有操作系统提供的各种定时功能,都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。 只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息。【关于这一点我暂时没有遇到相关的例子,无法直观的发现,如果用到,我讲去掉这句话】
Cortex‐M3处理器内部包含了一个简单的定时器。因为所有的Cortex‐M3芯片都带有这个定时器,软件在不同 Cortex‐M3器件间的移植工作得以化简。该定时器的时钟源可以是内部时钟(FCLK,Cortex‐M3上的自由运行时钟),或者是外部时钟( Cortex‐M3处理器上的STCLK信号)。不过,STCLK的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能会大不相同,你需要检视芯片的器件手册来决定选择什么作为时钟源。SysTick定时器能产生中断,Cortex‐M3为它专门开出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其它系统软件在Cortex‐M3器件间的移植变得简单多了,因为在所有Cortex‐M3产品间对其处理都是相同的。
SysTick定时器还可以用作闹钟,作为启动一个特定任务的时间依据。它作为一个闹铃,用于测量时间。要注意的是,当处理器在调试期间被喊停(halt)时,则SysTick定时器亦将暂停运作(上面有说到)。我在测试项目中用到的就是这个功能。
我们在“7 互联型产品的复位和时钟控制(RCC)”中找到STM32的时钟树,我们可以可以看到:
SysTick的时钟源可以是HCLK的8分频或者就是HCLK,具体是哪种可以通过配置“控制和状态寄存器(CTRL)”来选择
从简单的51系列单片机到STM8内核的单片机,我们知道我们对单片机编程就是对寄存器进行操作(当然,很多人STM8也是使用库在开发),所以我们首先需要知道SysTick定时器的寄存器,SysTick有4个寄存器:
SysTick control and status register(STK_CTRL) 控制和状态寄存器在《Cortex_M3技术参考手册》的3-2面我们可以看到:
每一个寄存器系统都进行了一个地址映射当我们操控我们定义的寄存器时实际上已是通过那种映射关系操控了芯片内部的值(其实在STM32中对寄存器的操作都是通过这种方式进行的)。
STK_CTRL寄存器用于使能SysTick功能,各个位的分布如下:
功能如下:
其中:
中断标志位COUNTFLAG在每次读取之后会自动清零,不需要我们手动清零。
CLKSOURCE
TICKINT用于设置是否触发中断(下面有提到)。
ENABLE设置
在裸机开发中使用SysTick定时非常方便,我们可以实现非常精确的延时,下面是相关代码(1 S = 1 000 ms = 1 000 000 us),如果频率为72MHZ,也就是说执行72M个时钟周期为1S,我们知道72MHZ = 72 000 000HZ,也就是说执行72个时钟周期为1us。其中STK_LOAD寄存器的取值范围为0x00000001-0x00FFFFFF(即0~16 777 215)。
<pre name="code" class="cpp">/************************************************************** * Function: DelayBySysTick_us * Input: unsigned char x 若时钟72M,则x就是多少 unsigned int y 延时多少微秒,y就输入多少 * OUTPUT: void * Return: void * DESCRIPTION: 直接操作寄存器的方式使用SysTick延时1us * Others: void * Auth: Alan * Date: 2015-01-01 **************************************************************/ void DelayBySysTick_us(unsigned char x, unsigned int y) { SysTick->LOAD = x * y; //装载计数值 SysTick->CTRL = 0x00000005; //时钟来源设为为HCLK(72M),打开定时器 while(!(SysTick->CTRL & 0x00010000)); //等待计数到0 SysTick->CTRL = 0x00000004; //关闭定时器 }
在72MHZ的情况下,该函数函数中参数y的值为(0 ~ (16777215/72 = 233016.875)),否则会溢出,即最大延时0.23s,超过就会溢出。
<pre name="code" class="cpp">/************************************************************** * Function: DelayBySysTick_us * Input: unsigned char x 若时钟72M,则x就是多少 unsigned int y 延时多少毫秒,y就输入多少 * OUTPUT: void * Return: void * DESCRIPTION: 直接操作寄存器的方式使用SysTick延时1us * Others: void * Auth: Alan * Date: 2015-01-01 **************************************************************/ void DelayBySysTick_us(unsigned char x, unsigned int y) { SysTick->LOAD = 1000 * x * y; <span style="white-space:pre"> </span> //装载计数值 SysTick->CTRL = 0x00000005; //时钟来源设为为HCLK(72M),打开定时器 while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); //等待计数到0 SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk; //关闭定时器 }
/** @addtogroup CMSIS_CM3_SysTick CMSIS CM3 SysTick memory mapped structure for SysTick @{ */ typedef struct { __IO uint32_t CTRL; /*!< Offset: 0x00 SysTick Control and Status Register */ __IO uint32_t LOAD; /*!< Offset: 0x04 SysTick Reload Value Register */ __IO uint32_t VAL; /*!< Offset: 0x08 SysTick Current Value Register */ __I uint32_t CALIB; /*!< Offset: 0x0C SysTick Calibration Register */ } SysTick_Type;
在库函数中关于SysTick的函数只有3个,下面分别进行学习
第1个为在misc.c中SysTick_CLKSourceConfig函数,这个函数是官方库函数,不允许修改。它的功能是配置SysTick时钟源。其中参数SysTick_CLKSource:指定SysTick时钟源,该参数是以下其中一个值:
SysTick_CLKSource_HCLK_Div8: AHB时钟8分频作为SysTick时钟源(AHB就是我们设置的主时钟)
SysTick_CLKSource_HCLK: AHB时钟作为SysTick时钟源.
/** * @brief Configures the SysTick clock source. * @param SysTick_CLKSource: specifies the SysTick clock source. * This parameter can be one of the following values: * @arg SysTick_CLKSource_HCLK_Div8: AHB clock divided by 8 selected as SysTick clock source. * @arg SysTick_CLKSource_HCLK: AHB clock selected as SysTick clock source. * @retval None */ void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource) { /* Check the parameters */ assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource)); if (SysTick_CLKSource == SysTick_CLKSource_HCLK) { SysTick->CTRL |= SysTick_CLKSource_HCLK; } else { SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; } }第2个为在ore_cm3.h文件中的SysTick_Config函数,这个函数是官方库函数,不允许修改。它的功能是初始化并开启SysTick计数器及其中断,输入参数ticks是两次中断间的ticks数值。通过次函数可以初始化系统滴答定时器及其中断并开启系统滴答定时器在自由运行模式下以产生周期中断。
/** * @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) { if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */ NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */ SysTick->VAL = 0; /* Load the SysTick Counter Value */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */ return (0); /* Function successful */ }
第3个为在“stm32f10x_it.c”中的SysTick_Handler函数,这是一个中断函数,里面没有任何内容,用于用户自定义(实际上几乎所有的中断函数的名字都已经在“stm32f10x_it.c”中写好了,所以我们有的时候在程序中使用的一些中断,都可以在这个地方进行查找)。
/** * @brief This function handles SysTick Handler. * @param None * @retval None */ void SysTick_Handler(void) { }
1)配置SysTick
2)写延时函数
3)写中断函数(在stm32f10x_it.c文件中的中断函数:void SysTick_Handler(void))
SysTick_Config(SystemCoreClock/1000);
首先,我的系统时钟是72MHZ(可以通过代码修改,见《STM32学习笔记(3):时钟配置》)。通过这个函数,①打开了SysTick中断②设置了SysTick的重装载寄存器。如果系统时钟是72MHZ,也就是说,每Tick一次需要1 / 72 000 000 秒,如果是72 000 000,那么Tick应该是1S(因为72 000 000分频了)我之前写的是SysTick_Config(SystemCoreClock);,但是根据源代码查看,发现是错的,源代码如下:
/** * @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) { if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */ NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */ SysTick->VAL = 0; /* Load the SysTick Counter Value */ 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_LOAD_RELOAD_Msk为:
/* 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_Config中最大的参数为0xFFFFFFul,而72 000 000为0x44AA200,已经超过了,所以是直接用72 000 000是错误的,最常见的是72 000分频,也就是我上面的代码。如果我们要使用这个软件但是不使用中断函数,我们可以对其进行修改,代码如下:
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
我们需要用到中断,因此只有开始的那一行。
/************************************************************** * Function: DelayBySysTick_s * Input: unsigned int nSecond 延时多少微秒,y就输入多少 * OUTPUT: void * Return: void * DESCRIPTION: 一直等着TimingDelay 减到0。精度是1ms(一般都是ms 级别),可以实现大于1S的定时 * Others: void * Auth: Alan * Date: 2015-01-01 **************************************************************/ void DelayBySysTick_ms(unsigned int nSecond) { TimingDelay = nSecond; while(TimingDelay != 0); } /************************************************************** * Function: TimingDelay_Decrement * Input: void * OUTPUT: void * Return: void * DESCRIPTION: 供SysTick的中断响应函数调用。每次调用就将 TimingDelay减少1,直到0为止 * Others: void * Auth: Alan * Date: 2015-01-01 **************************************************************/ void TimingDelay_Decrement(void) { if (TimingDelay != 0x00) { TimingDelay--; } }
/** * @brief This function handles SysTick Handler. * @param None * @retval None */ void SysTick_Handler(void) { TimingDelay_Decrement(); }我们在中断中调用该函数,通过以上代码,就可以实现使用中断函数来定时了,精度为1ms,可以实现大于1s的延时(别忘了声明)。