STM32学习笔记(4):SysTick
5. 学习笔记:使用MindNode或Evernote做笔记 #生活技巧# #工作学习技巧#
对于使用uCOS II我们可以使用task来定时执行函数,如果是裸机,我们就可以使用SysTick来定时或延时。
1 什么是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 完全可以作为一般的定时/计数器来使用(我最初学习的目的就是在不实用系统的情况下精确延时)。
知道它是什么东西以后我们就开始学习了,由于我使用库函数来开发程序,因此接下来除了必要以外均是从网上找来的资料。
2 SysTick存在意义
2.1 产生操作系统的时钟节拍
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。在以前,大多操作系统需要一个硬件定时器来产生操作系统需要的滴答中断,作为整个系统的时基。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。SysTick的最大使命,就是定期地产生异常请求,作为系统的时基。OS都需要这种“滴答”来推动任务和时间的管理。
也就是说:Systick就是一个定时器而已,只是它放在了NVIC中,主要的目的是为了给操作系统提供一个硬件上的中断(号称滴答中断)。滴答中断?这里来简单地解释一下。操作系统进行运转的时候,也会有“心跳”。它会根据“心跳”的节拍来工作,把整个时间段分成很多小小的时间片,每个任务每次只能运行一个“时间片”的时间长度就得退出给别的任务运行,这样可以确保任何一个任务都不会霸占整个系统不放。或者把每个定时器周期的某个时间范围赐予特定的任务等,还有操作系统提供的各种定时功能,都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。 只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息。【关于这一点我暂时没有遇到相关的例子,无法直观的发现,如果用到,我讲去掉这句话】
2.2 便于不同处理器之间程序移植
Cortex‐M3处理器内部包含了一个简单的定时器。因为所有的Cortex‐M3芯片都带有这个定时器,软件在不同 Cortex‐M3器件间的移植工作得以化简。该定时器的时钟源可以是内部时钟(FCLK,Cortex‐M3上的自由运行时钟),或者是外部时钟( Cortex‐M3处理器上的STCLK信号)。不过,STCLK的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能会大不相同,你需要检视芯片的器件手册来决定选择什么作为时钟源。SysTick定时器能产生中断,Cortex‐M3为它专门开出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其它系统软件在Cortex‐M3器件间的移植变得简单多了,因为在所有Cortex‐M3产品间对其处理都是相同的。
2.3 作为一个闹铃测量时间
SysTick定时器还可以用作闹钟,作为启动一个特定任务的时间依据。它作为一个闹铃,用于测量时间。要注意的是,当处理器在调试期间被喊停(halt)时,则SysTick定时器亦将暂停运作(上面有说到)。我在测试项目中用到的就是这个功能。
3 SysTick的时钟来源
我们在“7 互联型产品的复位和时钟控制(RCC)”中找到STM32的时钟树,我们可以可以看到:
SysTick的时钟源可以是HCLK的8分频或者就是HCLK,具体是哪种可以通过配置“控制和状态寄存器(CTRL)”来选择
4 SysTick寄存器
从简单的51系列单片机到STM8内核的单片机,我们知道我们对单片机编程就是对寄存器进行操作(当然,很多人STM8也是使用库在开发),所以我们首先需要知道SysTick定时器的寄存器,SysTick有4个寄存器:
SysTick control and status register(STK_CTRL) 控制和状态寄存器
SysTick reload value register (STK_LOAD) 重装载值寄存器
SysTick current value register (STK_VAL) 当前值寄存器
SysTick calibration value register (STK_CALIB) 校准值寄存器
在《Cortex_M3技术参考手册》的3-2面我们可以看到:
每一个寄存器系统都进行了一个地址映射当我们操控我们定义的寄存器时实际上已是通过那种映射关系操控了芯片内部的值(其实在STM32中对寄存器的操作都是通过这种方式进行的)。
4.1 STK_CTRL寄存器
STK_CTRL寄存器用于使能SysTick功能,各个位的分布如下:
功能如下:
其中:
中断标志位COUNTFLAG在每次读取之后会自动清零,不需要我们手动清零。
CLKSOURCE
TICKINT用于设置是否触发中断(下面有提到)。
ENABLE设置
5 直接操作寄存器开发
在裸机开发中使用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)。
5.1 精确延时1us
<pre name="code" class="cpp">
void DelayBySysTick_us(unsigned char x, unsigned int y)
{
SysTick->LOAD = x * y;
SysTick->CTRL = 0x00000005;
while(!(SysTick->CTRL & 0x00010000));
SysTick->CTRL = 0x00000004;
}
在72MHZ的情况下,该函数函数中参数y的值为(0 ~ (16777215/72 = 233016.875)),否则会溢出,即最大延时0.23s,超过就会溢出。
5.2 精确延时1ms
<pre name="code" class="cpp">
void DelayBySysTick_us(unsigned char x, unsigned int y)
{
SysTick->LOAD = 1000 * x * y; <span style="white-space:pre"></span>
SysTick->CTRL = 0x00000005;
while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk;
}
同样的在72MHZ的情况下,该函数函数中参数y的值为(0 ~ (16777215/72 = 233 016.875)),否则会溢出,即最大延时0.23s,超过就会溢出。
5.3 精确延时1S
直接操作寄存器的方式暂时没有找到很精确的方式,暂时还没有弄出来,等之后补全。
6 使用固件库开发
6.1 SysTick的库函数
寄存器的定义在core_cm3.h:
typedef struct
{
__IO uint32_t CTRL;
__IO uint32_t LOAD;
__IO uint32_t VAL;
__I uint32_t CALIB;
} 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时钟源.
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
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数值。通过次函数可以初始化系统滴答定时器及其中断并开启系统滴答定时器在自由运行模式下以产生周期中断。
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0);
}
第3个为在“stm32f10x_it.c”中的SysTick_Handler函数,这是一个中断函数,里面没有任何内容,用于用户自定义(实际上几乎所有的中断函数的名字都已经在“stm32f10x_it.c”中写好了,所以我们有的时候在程序中使用的一些中断,都可以在这个地方进行查找)。
void SysTick_Handler(void)
{
}
6.2 SysTick库开发步骤
使用固件库我们只需要做2件事:
1)配置SysTick
2)写延时函数
3)写中断函数(在stm32f10x_it.c文件中的中断函数:void SysTick_Handler(void))
6.2.1 配置SysTickSysTick_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);,但是根据源代码查看,发现是错的,源代码如下:
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0);
}
上面代码中SysTick_LOAD_RELOAD_Msk为:
#define SysTick_LOAD_RELOAD_Pos 0
#define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos)
可以看到在函数SysTick_Config中最大的参数为0xFFFFFFul,而72 000 000为0x44AA200,已经超过了,所以是直接用72 000 000是错误的,最常见的是72 000分频,也就是我上面的代码。如果我们要使用这个软件但是不使用中断函数,我们可以对其进行修改,代码如下:
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
我们需要用到中断,因此只有开始的那一行。
6.2.2 写延时函数void DelayBySysTick_ms(unsigned int nSecond)
{
TimingDelay = nSecond;
while(TimingDelay != 0);
}
void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
6.2.2 写中断函数
void SysTick_Handler(void)
{
TimingDelay_Decrement();
}
我们在中断中调用该函数,通过以上代码,就可以实现使用中断函数来定时了,精度为1ms,可以实现大于1s的延时(别忘了声明)。
7 应用
7.1 延时函数
延时函数比较好写,上面也提到了很多,因此不做表述。
7.2 定时调用函数
【END/2015-06-03】
网址:STM32学习笔记(4):SysTick https://www.yuejiaxmz.com/news/view/360189
相关内容
STM32中使用systick时钟进行延时的中断与非中断两种方法提高学习效率——5R笔记法
吴恩达深度学习笔记
做笔记=抄书?你还是不会学习!这样做笔记才能高效
Google Earth Engine学习笔记(一)
DeepLearning(深度学习)学习笔记整理.pdf资源
《强化学习》学习笔记3——策略学习
学习笔记
精选10款学习笔记必备神器,找到属于你的学习捷径
强化学习笔记二