STM32学习笔记(5)——系统定时器SysTick

单独拿出来讲的一个内核外设(所以不要期望在STM32中文参考手册找到它!即使找到也只会叫你看cm3内核编程手册),说明它真的很重要。

STM32学习笔记(5)——系统定时器SysTick

    • 一、系统定时器Systick
      • 1. SysTick简介
      • 2. SysTick相关寄存器
        • (1)SysTick control and status register (STK_CTRL)
        • (2)SysTick reload value register (STK_LOAD)
        • (3)SysTick current value register (STK_VAL)
        • (4)SysTick calibration value register (STK_CALIB)
      • 3. 详解SysTick_Config()函数
      • 4. SysTick相关的函数
    • 二、延时函数
    • 三、简单例程
      • 1. 不使用中断服务函数
      • 2. 使用中断服务函数

一、系统定时器Systick

1. SysTick简介

SysTick是一个24位的系统节拍定时器,具有自动重载和溢出中断功能,所有基于Cortex M3或Cortex M4处理器的微控制器都有这个定时器。

Systick定时器常用来做延时,或者用来做实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。

它一个24位的倒计数定时器(意味着有2^24个时间间隔),计到0时,将从RELOAD寄存器中自动重装载定时初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。

学过51单片机的同学应该对计数器比较熟悉了。

SysTick在NVIC的中断向量号为6(可对照STM32中文参考手册9.1.2节中断和异常向量中表格的灰色部分)。

2. SysTick相关寄存器

SysTick有4个寄存器:CTRL(控制和状态寄存器)、LOAD(自动重装载除值寄存器)、VAL(当前值寄存器)、CALIB(校准值寄存器)(如图所示)。

STM32学习笔记(5)——系统定时器SysTick_第1张图片

关于这4个寄存器的参考资料为 《 STM32F10xxx Cortex-M3编程手册》(STM32F10xxx/20xxx/21xxx/L1xxxx
Cortex ® -M3 programming manual)
(没找到中文版的,自己生啃一下英文吧),4.5节SysTick timer (STK)。4.5.6节还有SysTick寄存器地图。

由于篇幅够多,下面我们来一一介绍这4个寄存器(其实引用了上述手册的介绍):

(1)SysTick control and status register (STK_CTRL)

16位(COUNTFLAG): SysTick数到0后,该位为1;其他情况为0。如果读取该位,也会置0。说白了就是用来标志是不是数完了。

2位(CLKSOURCE): 0为外部时钟源(STCLK)(HCLK的1/8),1为内部时钟源(FCLK)(HCLK)。用来选择时钟源的。顺便提一句,HCLK用的是AHB时钟。

1位(TICKINT): 1为计数器数到零时产生SysTick中断请求,0为不产生请求。

0位(ENABLE): 用来使能的。

其余位保留。

(2)SysTick reload value register (STK_LOAD)

23位-0位(RELOAD): 存储的是一个值,当计数到0时,将重新装载该值,开始新一轮倒数。

其余位保留。

(3)SysTick current value register (STK_VAL)

23位-0位(CURRENT): 跟上面的有点像,也是存储一个值。但是,当你读取它时,寄存器会返回这个值(就是读取了这个值);当你写它时会自动清零,同时CTRL寄存器的第16位(COUNTFLAG)置0。

其余位保留。

(4)SysTick calibration value register (STK_CALIB)

这个寄存器似乎比较少关注到,对于用户来讲应该不常用?

31位(NOREF): 1为没有外部参考时钟(外部时钟源STCLK不可用),0为外部参考时钟可用。

30位(SKEW): 1为校准值不是准确的10ms,0为校准值是准确的10ms。

23位-0位(TENMS): 存储的是10ms的间隔倒计时的格数。芯片设计者应该提供这数值,若该值为0,则无法使用校准功能。

其余位保留。

在头文件core_cm3.h中有结构体定义:

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;

3. 详解SysTick_Config()函数

在使用SysTick之前,用户必须调用SysTick_Config()函数对其进行配置。这个函数是怎样进行配置的呢?我们可以看看头文件core_cm3.h 的1694行:

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 */
}

uint32_t SysTick_Config(uint32_t ticks)

函数形参ticks为两个中断之间的脉冲数(number of ticks between two interrupts),即相隔ticks个时钟周期会引起一次中断。

if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);

判断ticks是否符合规则,这个是什么规则呢?就是判断ticks是否大于SysTick_LOAD_RELOAD_Msk,如果大于,则返回1(failed),表示不符合规则。

SysTick_LOAD_RELOAD_Msk是一个宏,在文件388行可以找到:

#define SysTick_LOAD_RELOAD_Pos             0 
#define SysTick_LOAD_RELOAD_Msk            (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos) 

0xFFFFFF在十进制中是2^24(=16777216),其中,ul的意思是无符号long型整数(
unsigned long int)
。然后我们就知道,ticks最大值不能超过2^24(因为是24位系统定时器),否则就配置失败。

SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */

这里就是将ticks写入load寄存器,ticks是计数最开始的值。减去一的原因是计数从0结束。

NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);

NVIC中断配置,SysTick_IRQn在头文件stm32f10x.h可以找到:

 SysTick_IRQn                = -1,     /*!< 15 Cortex-M3 System Tick Interrupt*/

宏定义__NVIC_PRIO_BITS可以在本文件core_cm3.h找到:

#define __NVIC_PRIO_BITS          4 /*!< STM32 uses 4 Bits for the Priority Levels*/

因此(1<<__NVIC_PRIO_BITS) - 1的意思是1左移4位(即16),表达式的值为15(注意二进制表示为1111),这里面的4是因为(寄存器NVIC->IPRx)使用4个位来配置中断优先级。

所以SysTick的优先级是最低的吗?由于没有指定优先级分组,所以这就要看怎么分了。

假如分组为1,则将1111分为两组,前面组为抢占优先级,只有一个1(说明抢占优先级为1),而后面组为响应优先级,为111(说明响应优先级为7),需要注意到这里1和0的个数表示了二进制的数,进而指明了优先级别的高低。

假如分组为2,则将1111分为两组,前面组为抢占优先级,有11(说明抢占优先级为3),而后面组为响应优先级,为11(说明响应优先级为3)。

假如分组为3,则将1111分为两组,前面组为抢占优先级,有111(说明抢占优先级为7),而后面组为响应优先级,为1(说明响应优先级为1)。以此类推,所以SysTick的优先级是不一定的,只要你去配置,你想怎样都行。

SysTick->VAL = 0;

将0载入到val寄存器中。

SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;

配置时钟源、中断请求和使能。

4. SysTick相关的函数

SysTick_CLKSourceConfig()    //Systick时钟源选择,在misc.c中

SysTick_Config(uint32_t ticks) //初始化systick,时钟为HCLK,并开启中断,在core_m3.h中

void SysTick_Handler(void);
//Systick中断服务函数,需要用户自行编写

二、延时函数

使用SysTick写出来的延时函数,其精度要比软件延时要高。不过这个函数要自己写。

下面是一个写好的微妙级别的延时函数,我们来分析一下:

void Systick_Delay_us(uint32_t us)
{
   uint32_t i;
   SysTick_Config(72);
   
   for( i = 0; i < us; i++)
   {
   	while( !((SysTick -> CTRL) & (1 << 16)) );
   }
   SysTick -> CTRL &= ~SysTick_CTRL_ENABLE_Msk;
   
}

SysTick_Config(72);

配置SysTick时钟。这里为什么是72呢?有个公式:t = reload * ( 1/clk ),clk为内核时钟频率,reload为LOAD寄存器的值。在这个程序中,Clk = 72MHz,reload = 72,因此 t = (72) *(1 / 72 MHz)= 1us。

while( !((SysTick -> CTRL) & (1 << 16)) );

这里有必要说明一下(SysTick -> CTRL) & (1 << 16)是什么意思。SysTick -> CTRL是CTRL寄存器的首地址,1<<16为左移16位,两者进行与运算,正好得到CTRL寄存器第16位地址,即COUNTFLAG,因此这条语句是用来判断计数是否到0。如果没到0,while继续循环。每跳出一次while循环,说明已经过去了1us的时间,而for循环则循环了指定次数的1us。

SysTick -> CTRL &= ~SysTick_CTRL_ENABLE_Msk;

每一次使用完SysTick后要记得关闭。

三、简单例程

1. 不使用中断服务函数

//在systick.c中
void Systick_Delay_us(uint32_t us)
{
	uint32_t i;
	SysTick_Config(72);
	
	for( i = 0; i < us; i++)
	{
		while( !((SysTick -> CTRL) & (1 << 16)) );
	}
	SysTick -> CTRL &= ~SysTick_CTRL_ENABLE_Msk;
	
}

void Systick_Delay_ms(uint32_t ms)
{
	uint32_t i;
	SysTick_Config(72000);
	
	for( i = 0; i < ms; i++)
	{
		while( !((SysTick -> CTRL) & (1 << 16)) );
	}
	SysTick -> CTRL &= ~SysTick_CTRL_ENABLE_Msk;
	
}


//在main.c中
//实现功能:每隔500ms,LED0和LED1交错亮灭一次
 while(1)
{
	  GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	  GPIO_SetBits(GPIOE,GPIO_Pin_5);
	  Systick_Delay_ms(500);
	  GPIO_SetBits(GPIOB,GPIO_Pin_5);
	  GPIO_ResetBits(GPIOE,GPIO_Pin_5);
	  Systick_Delay_ms(500);
}

2. 使用中断服务函数

//systick.h
#ifndef __SYSTICK_H
#define __SYSTICK_H

#include "stm32f10x.h"
#include "core_cm3.h"

void Systick_Delay_ms(uint32_t ms);
void Systick_Delay_us(uint32_t us);

void SysTick_Init(void);
void Delay_ms(uint32_t ms);

#endif


//systick.c
#include "systick.h"

/* use interrupt */
__IO uint32_t TimingDelay;

void SysTick_Init(void)
{
	if(SysTick_Config(72000) == 1)
    {
       while(1);
    }
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}

void Delay_ms(uint32_t ms)
{
	TimingDelay = ms;
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
	while(TimingDelay != 0);
}


//stm32f10x_it.h
#include "stm32f10x_it.h" 
extern uint32_t TimingDelay; //看见这玩意我就恶心

void SysTick_Handler(void)
{
	if(TimingDelay != 0x00)
	{
		TimingDelay--;
	}
}


//main.c
SysTick_Init();	 
while(1)
{
	  GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	  GPIO_SetBits(GPIOE,GPIO_Pin_5);
	  Delay_ms(500);
	  GPIO_SetBits(GPIOB,GPIO_Pin_5);
	  GPIO_ResetBits(GPIOE,GPIO_Pin_5);
	  Delay_ms(500);
}

上面这种写法用了extern全局变量,个人感觉很别扭,但是中断服务函数是没有形参的,所以还有一种折中的写法(自己写一个函数,然后中断服务函数去调用):

//systick.h
#ifndef __SYSTICK_H
#define __SYSTICK_H

#include "stm32f10x.h"
#include "core_cm3.h"

void Systick_Delay_ms(uint32_t ms);
void Systick_Delay_us(uint32_t us);

void SysTick_Init(void);
void Delay_ms(uint32_t ms);
void timeDec(void); //自己写的函数,待会服务函数去调用

static __IO uint32_t TimingDelay;

#endif


//systick.c
#include "systick.h"

/* use interrupt */
void SysTick_Init(void)
{
	if(SysTick_Config(72000) == 1)
    {
       while(1);
    }
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}

void Delay_ms(uint32_t ms)
{
	TimingDelay = ms;
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
	while(TimingDelay != 0);
}


void timeDec(void)
{
	if(TimingDelay != 0x00)
	{
		TimingDelay--;
	}
}


//stm32f10x_it.h
#include "stm32f10x_it.h" 

void SysTick_Handler(void)
{
	timeDec(); //调用自己写的函数
}

再次,我个人习惯是,extern只有到万不得已才使用,因为这玩意太破坏文件之间的耦合度了,而且被多个文件使用,不太好追踪源头。

你可能感兴趣的:(#,STM32/STM8,学习笔记,stm32,嵌入式,单片机)