010 - STM32学习笔记 - SysTick系统定时器

010 - STM32学习笔记 - SysTick系统定时器

1、SysTick简介

SysTick是属于Cortex-M内核的一个外设,嵌套在NVIC中,系统定时器是一个24位的递减计数器,每次计数事件位1/SYSCLK,在F429中之前配置的SYSCLK配置的是180MHz,当SysTick的重装载寄存器的值递减到0的时候,系统定时器就会产生一次中断。SysTick属于Cortex-M内核外设,因此只要是CM内核的,都具有这个外设,移植起来就很容易了。

2、SysTick寄存器介绍

SysTick系统定时器一共有4个寄存器,其中CTRL、LOAD、VAL这三个需要配置,CALIB校准计时器不需要进行配置

寄存器名称 寄存器描述 读写类型
CTRL SysTick控制及状态寄存器 R/W
LOAD SysTick重装载数值寄存器 R/W
VAL SysTick当前数值寄存器 R/W
CALIB SysTick校准数值寄存器 RO

SysTick以SYSCLK驱动下,递减计数器在每接收到一个时钟信号,就会自减1,当递减计数器数值减到0的时候,就会触发一个中断信号,同时重装载寄存器的值会重新载入递减计数器,以此往复循环。

a、CTRL控制及状态寄存器
位段 名称 类型 复位值 描述
16 COUNTFLAG R/W 0 如果在上次读取本寄存器后, SysTick 已经计到 了 0,则该位为 1。
2 CLKSOURCE R/W 0 时钟源选择位, 0=AHB/8, 1=处理器时钟 AHB
1 TICKINT R/W 0 1=SysTick 倒数计数到 0 时产生 SysTick 异常请 求 , 0= 数 到 0 时无动作 。也 可以通 过读 取 COUNTFLAG 标志位来确定计数器是否递减到 0
0 ENABLE R/W 0 SysTick 定时器的使能位

其中第2位可以选择计数器时基来源,可选择为处理器时钟SYSCLK(即我们之前配置的180MHz)或者处理器时钟8分频后的时钟。这里我在看了正点原子的教程,里面提到这里其实是选择分频系数,0为8分频,1为1分频。

b、LOAD重装载数值寄存器
位段 名称 类型 复位值 描述
23:0 RELOAD R/W 0 当倒数计数至零时,将被重装载的值

重装载数值寄存器是当计数器递减计数到0的时候,触发中断后会将这个寄存器的数值重新加载到计数器中,从而开始新一轮级数,这个寄存器一般不用改,使用默认值即可。

c、VAL当前数值寄存器
位段 名称 类型 复位值 描述
23:0 CURRENT R/W 0 读取时返回当前倒计数的值,写它则使之清零,同时还会清除在 SysTick 控制及状态寄存器中的 COUNTFLAG 标志

系统定时器为24位,如果要获取当前计数器的值,可以访问VAL当前数值寄存器。

d、CALIB数值校准计数器

这个寄存器翻看了一下官方数据手册,也没看明白,野火的教程中也没有详细讲解,实际使用中也很少使用到,等后期找到相关资料了在补充一下相关知识。

typedef struct
{
  __IO uint32_t CTRL;                    /*!< Offset: 0x000 (R/W)  CTRL控制及状态寄存器 */
  __IO uint32_t LOAD;                    /*!< Offset: 0x004 (R/W)  LOAD重装载数值寄存器 */
  __IO uint32_t VAL;                     /*!< Offset: 0x008 (R/W)  VAL当前数值寄存器    */
  __I  uint32_t CALIB;                   /*!< Offset: 0x00C (R/ )  CALIB数值校准计数器  */
} SysTick_Type;

这里看一下关于SysTick配置函数,函数原型在core_cm4.h中

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
    if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); }    /* 确定传入的值不超过重装载寄存器的上限 */

    SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* 将传入值写入重装载寄存器 */
    NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* 设置中断优先级 */
    SysTick->VAL   = 0UL;                                             /* 将当前数值寄存器置零 */
    SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |					/* 设置定时器时钟源,我选择的是SYSCLK不分频 */
        			SysTick_CTRL_TICKINT_Msk   |				   /* 使能计数器 */
        			SysTick_CTRL_ENABLE_Msk;                        /* 使能定时器 */  
    return (0UL);                                                     
}

参数:

ticks:用来设定重装载计数器的值,前面看到重装载计数器为24位寄存器,因此该值不能超过2^24。

这里需要注意,在SysTick_Config函数中,调用了NVIC_SetPriority函数用来设置SysTick定时器的优先级,看一下NVIC_SetPriority

__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
    if((int32_t)IRQn < 0) 				//判断中断是否位内核中断
    {
        SCB->SHP[(((uint32_t)(int32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
    }
    else 							//外设中断
    {
        NVIC->IP[((uint32_t)(int32_t)IRQn)] = (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
    }
}

函数体内判断了中断优先级是否为负数,设置内核优先级和设置外设优先级操作的寄存器是不同的,内核的优先级设置的是SCB的SHP寄存器,而外设优先级则是在NVIC的IP寄存器,其中SCB寄存器中就包含SysTick的优先级设置。(注:并不是所有的中断都可以配置寄存器,详细看驱动代码那节

在SysTick中,优先级配置为1UL << __NVIC_PRIO_BITS) - 1UL,其中__NVIC_PRIO_BITS为宏定义,值为4,1左移4位再减1,则为15,那么SysTick的优先级配置为最低(内核中)。

学到这里,就可以开始编程实操了。实操内容为:用SysTick产生1s的定时,让LED以1s的频率进行闪烁。

创建bsp_systick.c和bsp_systick.h文件,并将其所在路径加入编译环境中。

/***********************bsp_systick.c*************************/
#include "bsp_systick.h"

static __IO u32 TimingDelay;

void SysTick_Init(void)
{
    /* SystemCoreClock = 180000000,即180M,那么1秒就是一共数了180000000次,那么一次就是1/180000000 s
	*1s = 1000000 us,那么1us = 1/1000000 s = (1/180000000)* (1/1000000) = 1/180,也就是1us定时器计数180次,因此可以得出如下
	* SystemCoreClock/1000000: 1us 中断一次
	* SystemCoreClock/100000: 10us 中断一次
	* SystemCoreClock/1000: 1ms 中断一次
    */
    if(SysTick_Config(SystemCoreClock/100000))
    {
        while(1);
    }
}

/*

180000000/100000 * 1/180MHz = 10us,机选下来每产生一个中断耗时为1us
因此如果要产生1s的中断,则是在此基础上*100000,所以在调用Delay_us中传参为100000即可延时1s
*/
//延时程序,10us为一个单位
void Delay_us(__IO u32 nTime)				//这里我们在调用的时候以1S作为延时,将会传入100000
{
    TimingDelay = nTime;					//100000将写入TimingDelay中,TimingDelay会在SysTick_Handler中不停的去递减
    while(TimingDelay != 0);
}

//获取节拍程序
void TimingDelay_Decrement()
{
    /*每产生一次中断,TimingDelay就递减1次,上面配置的SysTick会在10us触
    * 发一次中断,因此TimingDelay就需要根据Delay_us传入的值进行递减
    */
    if(TimingDelay != 0x00)					
    {
        TimingDelay--;
    }
}


/***********************bsp_systick.h*************************/
#ifndef __BSP_SYSTICK_H__
#define __BSP_SYSTICK_H__

#include "stm32f4xx.h"

void SysTick_Init(void);
void Delay_us(__IO u32 nTime);
extern void TimingDelay_Decrement(void);	//使用extern修饰,用作外部文件访问

#endif	/* __BSP_SYSTICK_H__ */

/***********************stm32f4xx_it.h中对SysTick中断服务程序进行编写*************************/
void SysTick_Handler(void)
{
      TimingDelay_Decrement();
}

/***********************mian.c*************************/
#include "stm32f4xx.h"
#include "bsp_led.h"
#include "bsp_exti_key.h"
#include "bsp_systick.h"

int main(void)
{
    LED_Config();					//调用LED的GPIO配置函数
    SysTick_Init();					//调用SysTick初始化函数
    while(1)
    {
        LED_RED;					//红灯亮,宏定义,查看LED操作那节内容
        Delay_us(100000);			//延时1s
        
        LED_GREEN;
        Delay_us(100000);
        
        LED_BLUE;
        Delay_us(100000);
    }
}

这里我想了一下,针对于SysTick定时器有做了一些优化,将原子哥和火哥的结合了一下,做了如下改动:

/* 对于SysTick_Init函数不做修改,默认初始化时,仍然以10us为计时单位 */
void SysTick_Init(void)
{
    if(SysTick_Config(SystemCoreClock/100000))
    {
        while(1);
    }
}
/* 对于Delay_us函数进行修改,调用时,将SysTick的LOAD重载计数器设置为10us */
void Delay_us(__IO u32 uTime)
{
    SysTick->LOAD = SystemCoreClock/100000; 			//重新设置重装计时器	  		 
    SysTick->VAL = 0x00;        					   //清空计数器
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ;			//开始倒数
    TimingDelay = uTime;					   		   //将传入时间写入TimingDelay中,TimingDelay会在SysTick_Handler中不停的去递减
    while(TimingDelay != 0);
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;			//关闭计数器
	SysTick->VAL = 0X00;       						  //清空计数器
}
/* 增加ms延时,调用时,将SysTick的LOAD重载计数器设置为1ms */
void Delay_ms(__IO u32 mTime)
{
    SysTick->LOAD = SystemCoreClock/1000;				//重新设置重装计时器	  		 
    SysTick->VAL = 0x00;        					   //清空计数器
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ;			//开始倒数
    TimingDelay = mTime;							  //将传入时间写入TimingDelay中,TimingDelay会在SysTick_Handler中不停的去递减
    while(TimingDelay != 0);
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;			//关闭计数器
    SysTick->VAL = 0X00;							  //清空计数器
}

//获取节拍程序,不做更改
void TimingDelay_Decrement()
{
    if(TimingDelay != 0x00)					
    {
        TimingDelay--;
    }
}

/******************************  main.c  *****************************************/
#include "stm32f4xx.h"
#include "bsp_led.h"
#include "bsp_exti_key.h"
#include "bsp_systick.h"

int main(void)
{
    LED_Config();
    EXTI_Key_Config();
    SysTick_Init();
    while(1)
    {
        /* 这里交替调用Delay_ms和Delay_us验证效果 */
        LED_RED;
        Delay_ms(1000);
        LED_GREEN;
        Delay_us(100000);
        LED_BLUE;
        Delay_ms(1000);
    }
}

编程要点

1、初始化LED所在的GPIO;

2、配置SysTick;

3、编写中断服务函数SysTick_Handler

4、最终呈现效果。

注:这里需要注意的是,在重新设置SysTick的LOAD寄存器时,需要保证设置数值不超过2^24,所以我在上面做测试的时候,想按照毫秒的方式去实现1s的延时,当时认为SystemCoreClock/1000是作为1s装入SysTick重装载寄存器,所以就想1s=1000ms,那么直接将SystemCoreClock写入SysTick重装载寄存器不就可以实现了嘛,结果后来测试发现跟预期的不同,在反过来看的时候,才想起来,SysTick的LOAD最大到 2^24 = 16,777,216,,而SystemCoreClock = 180000000,写入后已经溢出了,所以这里各位一定要加判定的哈。

你可能感兴趣的:(stm32,stm32,单片机,学习)