Cortex-M架构SysTick系统定时器阻塞和非阻塞延时

Posted 网易独家音乐人Mike Zhou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cortex-M架构SysTick系统定时器阻塞和非阻塞延时相关的知识,希望对你有一定的参考价值。

Cortex-M架构SysTick系统定时器阻塞和非阻塞延时

首先是最常用的阻塞延时

void delay_ms(unsigned int ms)

	SysTick->LOAD = 50000000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
	while(ms--)
	
		while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
	
	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器

void delay_us(unsigned int us)

	SysTick->LOAD = 50000000/1000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
	while(us--)
	
		while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
	
	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器

50000000表示工作频率
分频后即可得到不同的延时时间
以此类推

那么 不用两个嵌套while循环 也可以写成:

void delay_ms(unsigned int ms)

	SysTick->LOAD = 50000000/1000*ms-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器

	while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待

	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器

void delay_us(unsigned int us)

	SysTick->LOAD = 50000000/1000/1000*us-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
	
	while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待

	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器

但是这种写法有个弊端
那就是输入ms后,最大定时不得超过计数值,也就是不能超过LOAD的最大值,否则溢出以后,则无法正常工作

而LOAD如果最大是32位 也就是4294967295

晶振为50M的话 50M的计数值为1s 4294967295计数值约为85s

固最大定时时间为85s

但用嵌套while的话 最大可以支持定时4294967295*85s

如果采用非阻塞的话 直接改写第二种方法就好了:

void delay_ms(unsigned int ms)

	SysTick->LOAD = 50000000/1000*ms-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器

	//while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待

	//SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器

void delay_us(unsigned int us)

	SysTick->LOAD = 50000000/1000/1000*us-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
	
	//while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待

	//SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器

将等待和关闭定时器语句去掉
在使用时加上判断即可变为阻塞:

delay_ms(500);
while ((SysTick->CTRL & 0x00010000)==0);
SysTick->CTRL = 0;

在非阻塞状态下 可以提交定时器后 去做别的事情 然后再来等待

不过这样又有一个弊端 那就是定时器会自动重载 可能做别的事情以后 定时器跑过了 然后就要等85s才能停下

故可以通过内部定时器来进行非阻塞延时函数的编写

基本上每个mcu的内部定时器都可以配置自动重载等功能 网上资料很多 这里就不再阐述了

STM32 系统定时器(SysTick)

SysTick定义

在这里插入图片描述
SysTick : 24位系统定时器,只能递减,存在于内核嵌套在NVIC中。所有的Cortex-M中都有这个系统定时器。

重装载值reload 递减,当递减到0会触发中断并且会有 置位countflag标志,VAL表示当前值。
然后reload继续从预设值开始递减,周而复始。

在这里插入图片描述
还有一个校准数值寄存器,不常用,想知道的可以看下其他文章会讲。

SysTick定时时间的计算

  1. T : 一个计数循环的时间,跟reload和 CLK有关
  2. CLK : 72M 或者9 M, 由CTRL寄存器配置
  3. reload : 24位,用户自己配置

T = reload * (1/ CLK)
CLK= 72M, 1us = (72 )* (1/ 72 000 000)
CLK= 72M, 1ms = (72 000 )* (1/ 72 000 000)
时间单位换算: 1s = 1000ms = 1000 000 us = 1000 000 000 ns

SysTick结构体

typedef struct
{
__IO uint32_t CTRL; //控制及状态寄存器
__IO uint32_t LOAD; //重装载寄存器
__IO uint32_t VAL; //重装载寄存器
__I uint32_t CALIB; //校准寄存器
} SysTick_Type;

SysTick固件库函数

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); //判断reload的值是否大于最大值 2^24
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; //配置reload寄存器的初值

NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); //配置中断优先级 1<<4 -1 =16-1=15

SysTick->VAL = 0; //配置count的值
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | //配置system时钟为 72M
SysTick_CTRL_TICKINT_Msk | //使能中断
SysTick_CTRL_ENABLE_Msk; //使能systick
return (0);
}
在这里插入图片描述
在这里插入图片描述

SysTick中断优先级

在这里插入图片描述
数字越小中断优先级越高

1.system tick 属于内核的外设,它的中断优先级 与片上外设的中断优先级相比谁高?
依据是什么?

答:systick 中断优先级配置的是scb->shprx寄存器
外设中断优先级配置的是nvic-> iprx寄存器
有优先级分组
在这里插入图片描述
3.STM32的外设(内核还是片上)都是使用4个二进制来表示中断优先级

4.中断优先级的分组对内核和外设同样适用,只需要将中断优先级的四个位按外设优先级来分组即可,人为的进行分出抢占优先级和子优先级。例举:

1<<4 -1=16 -1= 15 (1 1 1 1 )
前面两位表示抢占优先级 后面两位表示子优先级
3 3 (抢占优先级3 ,子优先级3)

1<<4 = 1 0000 = 16

编写ms,us延时函数

systick和delay区别

delay一直占用cpu,而systick一般都采用中断方式,(不会有人在程序里一直查询标志位的),不占用cpu。除非任务很简单。否则实际项目不会使用循环的延时。不过因为stm32上面有更强大的外设定时器的。定时的范围也比systick要大。还可以级联,所以一般不用systick,只把systick用于操作系统的时钟节拍。

案例

SysTick.h

#include "stm32f10x.h"
void ms_delay(uint32_t ms);
void us_delay(uint32_t us);

SysTick.c

#include "stm32f10x.h"
#include "SysTick.h"
/*
		ϵͳ¶¨Ê±Æ÷Ìص㣺
		Ö»Äܵݼõ£¬Èç¹û¶¨ÒåµÄÊÇ72000
		ÄǾʹÓ72000¿ªÊ¼µÝ¼õµ½0£¬ÎªÒ»¸öÖÜÆÚ
		ËùÒÔÖ»ÐèÒªÅжÏÊÇ·ñ¼õµ½1
		
		ÈçºÎÅжϣ¿
		¿´COUNTFLAG¼Ä´æÆ÷£¬Ò²¾ÍÊÇSysTick->CTRL
		
*/
void ms_delay(uint32_t ms)
{
		uint32_t i;
		SysTick_Config(72000);//Ñ­»·Ò»´Î1ms 
	
		for(i=0;i<ms;i++)//Ҫѭ»·¶àÉÙms
		{
				while( !((SysTick->CTRL)&(1<<16)) );//ÅжÏSysTickÊÇ·ñµ½0£¬µ½0¸ÃλΪ1   
																					//!(1&1)Ìø³öÑ­»·
																					//Ϊʲô×óÒÆ16λÄØ£¬ÒòΪCTRLÊÇ16λµÄ?
																				  //¿´¿ØÖƼ°×´Ì¬¼Ä´æÆ÷µÄͼдÁË¡£
		}
				SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;
		
}

void us_delay(uint32_t us)
{
		uint32_t i;
		SysTick_Config(72);//Ñ­»·Ò»´Î1us
	
		for(i=0;i<us;i++)//Ҫѭ»·¶àÉÙus
		{
				while( !((SysTick->CTRL)&(1<<16)) );
		}
				SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;
		
}

led.h

#include "stm32f10x.h"
void Led_init(void);

led.c

#include "stm32f10x.h"
#include "led.h"

void Led_init(void)
{
	GPIO_InitTypeDef Led_init;
	
	//1.ʹÄÜAPB2µÄʱÖÓGPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	
	//2.½á¹¹ÌåÅäÖÃ
	Led_init.GPIO_Mode 	= GPIO_Mode_Out_PP;
	Led_init.GPIO_Pin	  = GPIO_Pin_13;
	Led_init.GPIO_Speed = GPIO_Speed_10MHz;
	
	GPIO_Init(GPIOC, &Led_init);
}

main.c

#include "stm32f10x.h"
#include "led.h"
#include "relay.h"
#include "shake.h"
#include "exti.h"
#include "usart.h"
#include "stdio.h"
#include "tim.h"
#include "motor.h"
#include "SysTick.h"

void delay(uint16_t time)
{
	uint16_t i =0;
	while(time--){
		i=12000;
		while(i--);
	}
}

int main()
{
	Led_init();
	GPIO_SetBits(GPIOA, GPIO_Pin_3);

//ʹÓÃϵͳ¶¨Ê±Æ÷SysTick´úÌædelay;
		while(1)
		{
				GPIO_ResetBits(GPIOC, GPIO_Pin_13);
				ms_delay(1000);
				GPIO_SetBits(GPIOC, GPIO_Pin_13);
				ms_delay(1000);
		}

}

以上是关于Cortex-M架构SysTick系统定时器阻塞和非阻塞延时的主要内容,如果未能解决你的问题,请参考以下文章

STM32 系统定时器(SysTick)

使用系统定时器SysTick实现精确延时微秒和毫秒函数

系统定时器SysTick

Cortex-M3 SysTick 双中断

FreeRTO之Cortex-M中断管理

SysTick—系统定时器