STM32使用systick定时器定义硬件精准延时函数

前言

  1. 博文基于STM32F103ZET6和标准固件库V3.5.0在MDK5环境下开发;
  2. 本博文讨论的是芯片不运行操作系统的情况下完成1s的延时功能;
  3. 如有不足之处还请多多指教;

SysTick—系统滴答定时器是什么?

是一个24位的硬件倒计数定时器;

SysTick的功能是什么?(分两种情况)

  1. 芯片运行操作系统(UCOS)情况下做:为操作系统(例如UCOS)提供硬件上的定时中断(滴答中断),作为整个系统的时基,为多个任务分配不同的时间片,确保不会出现一个任务霸占系统的情况;或者把每个定时器周期的某个时间范围赐予特定的任务等;还有操作系统提供的各种定时功能;
  2. 不运行操作系统,单纯做定时器:提供精准的定时功能;

SysTick的特点

  1. 和以往的外设定时器不同,SysTick这个定时器以及相关寄存器在CM3内核里。《STM32使用手册》里对并没有涉及SysTick的内容,只能去《Cortex-M3权威指南》看(PDF的133页);寄存的可寻址,且寄存器被定义在程序包中的core_m3.c 中,涉及相关的编程时一定要注意;
  2. 可以工作在芯片的睡眠状态下;
  3. SysTick被捆绑在NVIC(中断向量控制器)中,可以产生异常(异常号:15),这是在操作系统下为系统提供时间基准的必要条件;
  4. 每个CM3内核都含有一个SysTick,这样方便程序移植;
  5. SysTick时钟源的选择可以来自于外部,也可以来自于内部;使用来自外部的时钟源的时候要根据具体芯片生产厂商的手册;(本博文采用外部时钟源)

SysTick的四个相关寄存器(图片摘取《Cortex-M3权威指南》)

CTRL控制及状态寄存器

STM32使用systick定时器定义硬件精准延时函数_第1张图片
描述中解释的都很清楚,这里强调两个重要的点:

  1. 位[16]:只能读,不能写,算是一个状态位;如果计数器达到0,则读入为1;当读取或清除当前计数器值时,将自动清除为0;
  2. 地址:0xE000E010 ,这是本寄存器地址,一会编程的时候要用到;
LOAD重装载数值寄存器

在这里插入图片描述

VAL当前值寄存器

STM32使用systick定时器定义硬件精准延时函数_第2张图片

CALIB校准数值寄存器(这个寄存器在本博客中用不到)

STM32使用systick定时器定义硬件精准延时函数_第3张图片

SysTick时钟源的选择(两个)

STM32使用systick定时器定义硬件精准延时函数_第4张图片
在红框处,其实我是有疑问的:红框处提供的有3个时钟源HCLK,HCLK/8,FCLK;从名字上来看,FCLK似乎就是上面提到的SysTick的CTRL寄存器中时钟源可选择的内核时钟;但是从标准库函数提供的函数名上来看,提供内核时钟的是HCLK时钟源,提供外部时钟的是HCLK/8时钟源;看如下代码:

/*此段为我自己写的,为初始化SysTick*/
void delay_Init(void)
{
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //设置SysTick时钟源为外部时钟源--HCLK/8;
	fac_us=SystemCoreClock/8000000;     //系统内核时钟,即CM3的时钟;
	fac_ms=(u16)fac_us*1000;					
}
/*此段代码为库文件 misc.c中关于配置SysTick时钟源的函数*/
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;   //0x00000004  选择时钟源为内部时钟源
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;  //0xFFFFFFFB  选择时钟源为外部时钟源
  }
}
/*此段代码是misc.h文件中的关于时钟源的选项*/
#define SysTick_CLKSource_HCLK_Div8    ((uint32_t)0xFFFFFFFB)
#define SysTick_CLKSource_HCLK         ((uint32_t)0x00000004)
#define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
                                       ((SOURCE) == SysTick_CLKSource_HCLK_Div8))

从以上三段代码来看: SysTick_CLKSource_HCLK_Div8SysTick_CLKSource_HCLK是配置CTRL时钟源选择的值,由于这两个符号里都包含HCLK,所以我认为,SysTick的时钟源就来自于上面时钟框图中的HCLK和HCLK/8(先这么理解着,即使理解的不对,并不影响这个程序的进行,因为HCLK和FCLK的时钟频率是一样的,只是名字不同而已)

好了,关系都捋清了,只差代码了;

SysTick的配置和1s延时

配置步骤:(这一个函数的执行从头到尾就是1s)

  1. 配置SysTick时钟源;
  2. 获取SysTick计数器递减周期(每个周期计数器减一),这个需要计算;
  3. 根据上面的递减周期配置重载寄存器LOAD;
  4. 清零当前值寄存器VAL;
  5. 使能定时器(使能CTRL寄存器使能位);
  6. 等待VAL递减到0,从而CTRL寄存器的计数标志位COUNTFLAG被置1;
  7. 延时完成,函数的最后关闭定时器(关闭CTRL寄存器使能位);
  8. 函数结束;

所以综合叙述一下就是,先配置时钟源,有了时钟源就能确定当前值寄存器VAL递减周期,然后根据递减周期与1s之间的算数关系配置重载寄存器,然后使能寄存器后只需检测CTRL寄存器的计数标志位即可,

1s延时代码:

#include 

u32 fac_us;
u32 fac_ms;

void delay_Init(void)
{	/*
	配置时钟源为外部HCLK/8,经标准库初始化后的HCLK = 72MHz,
	即SysTick计数器的时钟源频率为72MHz/8 = 9MHz,即每9M个方波就是1s(秒基准),
	每9000个脉冲1ms(毫秒基准),每9个秒冲就是1us(微妙基准);
	所以我们可以认为当前值寄存器VAL的递减周期就是:每(1/9)us就减1;即每经过1us,经过了9个方波;
	*/
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); 
	fac_us=SystemCoreClock/8000000;     //最终fac_us=9;
	fac_ms=fac_us*1000;							//fac_ms = 9000;			
}

void delay_us(u32 nus)  
{																						//注意这里的SysTick变量是结构体指针变量;
	SysTick->LOAD=nus*fac_us;   									//将要定时的时间(nus*fac_us)装载重载寄存器;
	SysTick->VAL =0x00;         											//清零当前值寄存器;
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; 	//使能控制寄存器(CTRL),开启定时器;
	while((SysTick->CTRL) >> 16 != 1);				          //等待被重新转载后的当前值寄存器递减到0,从而使得控制寄存器的计数标志位被置1;

	SysTick->CTRL |= SysTick_CTRL_ENABLE_Pos;  //关闭计数器(注意运算符是|=,这一点一定要特别注意,因为此处配置的是CTRL寄存器,小心把其他位也给配置了,下面的程序也是一样)
}

void delay_ms(u32 nms)   //含义和上面一样;  当nms=1000是即可获得1s延时;
{		
	SysTick->LOAD=nms*fac_ms;
	SysTick->VAL =0x00;
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;
	while((SysTick->CTRL) >> 16 != 1);

	SysTick->CTRL |= SysTick_CTRL_ENABLE_Pos;       	
} 

你可能感兴趣的:(stm32)