STM32_实现精确延时

void delay(unsigned int i)
{
	while(i--);
} 

上面是我们最初写的延时函数,比较简单,就是循环,但是无法实现精确延时,或者说想要实现精确延时很复杂,需要设置断点,一次次去碰运气,而且还占用CPU,就是CPU必须执行完delay延时,才可以执行其他程序(按顺序执行),而今天使用的SysTick不占用CPU,使用的时候,CPU还可以干其他的事。

SysTick 属于单片机内部的外设,不需要额外的硬件电路。
在这里插入图片描述

1、先介绍下SysTick:系统滴答计时器,虽然存放在了NVIC中,但是NVIC无法控制SysTick。

系统定时器是一个24位的向下递减的计数器,也就是计数值最大可以达到2^24-1,存放在重装载数值寄存器中,当重装载数值寄存器的值递减到0 的时候,系统定时器就产生一次中断,执行中断服务函数中的程序,以此循环往复。
计数器每计数一次的时间为1/SYSCLK,我们使用的是STM32F103VET6芯片,所以系统时钟SYSCLK等于72M。

为什么最大计数值会减1?
因为最大计数值再计以为就溢出归0。

系统定时器工作原理:
在这里插入图片描述

系统定时器的作用:
在这里插入图片描述
由于小白杨,还未学到操作系统(ucos2 ucos3 freertos…),所有也不知道在操作系统中如何使用系统定时器,以后学到了,会再补充的,这里就先知道如何实现精确延时就够了。

系统定时器的构成:包括4个寄存器,存放在CMSIS系统文件中
在这里插入图片描述
系统定时器的校准数值寄存器在定时实验中用不到,只使用前3个寄存器就可以
在这里插入图片描述

在这里插入图片描述

系统定时器的配置,通过代码进行讲解:
实验:利用SysTick 产生1s 的延时中断,使LED灯以1s 的频率进行闪烁。

先介绍一下系统时钟源,这是延时的根本来源:

在这里插入图片描述

STM32时钟树:

在这里插入图片描述

这里看图理解会有一个误区,以为滴答定时器是系统时钟的1/8,其实不是,滴答定时器的时钟既可以是HCLK/8,也可以是HCLK,这个是通过CTRL寄存器进行设定的。

在这里插入图片描述

计数时间计算:
t=reload*(1/SystemCoreClock)
当reload=72,t=72*(1/72M)=1us;
当reloa=72000,t=72000*(1/72M)=1ms。

这里介绍一下
static __INLINE uint32_t SysTick_Config(uint32_t ticks)函数,定义于core_cm3.h文件中,具体内容我已在下方代码中写了注释

/**
 * @brief  Initialize and start the SysTick counter and its interrupt.
 *初始化并开启定时计数器和中断
 * @param   ticks   number of ticks between two interrupts
 * 参数:计数值   
 * @return  1 = failed, 0 = successful
 * 返回值 1=不符合规定  0=符合
 *
 * Initialise the system tick timer and its interrupt and start the
 * system tick timer / counter in free running mode to generate 
 * periodical interrupts.  触发定期中断
 */
 
 //总结:定义一个内联函数,检查计数值ticks是否小于最大值,防止出错,且这个函数只可以在这个文件中使用
 #if 0
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);    //如果不符合规则,返回1

 //符合规定,执行下面程序 
 //将计数值保存到LOAD中,   
 //ticks和SysTick_LOAD_RELOAD_Msk(值为0xFF FFFF)位与 ,主要是为了屏蔽ticks中24bit~31bit的值(理论上会是0),作为一种冗余的措施,所以最后的结果还是ticks的值,其实目的还是防止ticks超过最大值
 //-1是因为每次每数的时候都是从0开始的,比如:SysTick定时器的初值是0(“SysTick->VAL = 0"),假设不减1,ticks=3,计数过程为:0->3->2->1->0,总共用经历了4个时钟周期,不符合规则,所以要减1,可以等到3次计数,0->2->1->0
                                                        
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;  

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

//使寄存器VAL重新赋值为0
 SysTick->VAL = 0; 
 
//选择时钟源,72MHz
//开启Systick中断
// 使能Systick定时器                
SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                 SysTick_CTRL_TICKINT_Msk   | 
                 SysTick_CTRL_ENABLE_Msk;                     
  return (0);//返回0函数成功配置                                                  
}

#endif

在这里插入图片描述
这里再解释一下返回值返回到哪里,它返回到 #if 0 那里 ,所以如果计数值正常,则不会执行这段程序。

#if 0  //为1才执行
code  //屏蔽代码
#endif

如果需要使用HCLK/8,可以直接调用SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)函数:
在这里插入图片描述

最后我把这些代码都汇总到一起,方便阅读:
led.h

#ifndef __LED_H
#define __LED_H

#include "stm32f10x.h"

//定义引脚
#define LED1_GPIO_CLK	RCC_APB2Periph_GPIOB
#define LED1_GPIO_PORT	GPIOB
#define LED1_GPIO_PIN	GPIO_Pin_5

#define LED2_GPIO_CLK	RCC_APB2Periph_GPIOB
#define LED2_GPIO_PORT	GPIOB
#define LED2_GPIO_PIN	GPIO_Pin_0

#define LED3_GPIO_CLK	RCC_APB2Periph_GPIOB
#define LED3_GPIO_PORT	GPIOB
#define LED3_GPIO_PIN	GPIO_Pin_1

#define ON 	0
#define OFF    1

//库函数操作I/O口,带参宏,可以像内联函数一样使用
#define LED1(A) if (A)	\
				GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);\
				else	\
				GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
				
#define LED2(A) if (A)	\
				GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);\
				else	\
					GPIO_ResetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
				
#define LED3(A)  if (A)   \
				GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);\
				else	\
				GPIO_ResetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);
				
//声明LED点亮函数,在主代码中直接用led.h文件就可以
void LED_GPIO_Config(void);

#endif

\:这是续行符,用来表示下一行与上一行是同一行,这样比较有观赏性,否则,所有代码都放在一行,就不太好看,“\”后面不能有任何字符(包括注释、空格),只能直接回车。

led.c

#include "led.h"

void LED_GPIO_Config(void)
{	
	//引入结构体,声明一个名字叫做GPIO_InitStructure结构体,原型是GPIO_InitTypeDef(管方库),作用类似于赋值重取名
	GPIO_InitTypeDef GPIO_InitStructure;
	
	//2.打开时钟
	RCC_APB2PeriphClockCmd(LED1_GPIO_CLK|LED2_GPIO_CLK|LED3_GPIO_CLK,ENABLE);
	
	//3.选择引脚
	GPIO_InitStructure.GPIO_Pin=LED1_GPIO_PIN|LED2_GPIO_PIN|LED3_GPIO_PIN;
	
	//4.引脚模式
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	
	//5.引脚速度
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	
	//6.调用库函数,初始化GPIOB,作用就是将GPIO_InitTypeDef地址给GPIOB
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	//7.关闭所有LED,从最初状态开始
	GPIO_SetBits(GPIOB,GPIO_Pin_All);
		
}

delay.h

#ifndef __DELAY_H
#define __DELAY_H

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

void delay_us(uint32_t us);//微秒级别
void delay_ms(uint32_t ms);//毫秒级别

#endif

注意:如果下方两个头文件的顺序相反,会出现很多errors,因为我们引用了中断,但是中断现在stm32f10x.h这个文件中先定义的,使用时必须放在前面,类似于先声明。
在这里插入图片描述

delay.c

#include "delay.h"

#if 0
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
	//  判断 tick 的值是否大于 2^24,如果大于,则不符合规则
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);

  // 初始化reload寄存器的值	
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
	
  // 配置中断优先级,配置为15,默认为最低的优先级
	NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); 

  // 初始化counter的值为0	
  SysTick->VAL   = 0; 
  
  // 配置 systick 的时钟为 72M
  // 使能中断
  // 使能systick
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    
  return (0);                                                 
}
#endif

void delay_us(uint32_t us)
{
	uint32_t i; //定义一个变量
	SysTick_Config(72);//直接写ticks,这样比较好理解 72*(1/72M)=1us
	
	for(i=0; i<us; i++) //循环,当us=1000时,则执行1000次循环
	{
		while( !((SysTick->CTRL) & (1<<16)) );  //位与,当递减到0,置1,则!1=0
	}
	
	SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;//关闭定时器
}

void 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;
}

stm32f10x_it.h

#ifndef __STM32F10x_IT_H//Define to prevent recursive inclusion 
#define __STM32F10x_IT_H

#endif	//暂时用不到

stm32f10x_it.c

#include "stm32f10x_it.h"

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
void SysTick_Handler(void)
{
}  //中断服务函数,可以为空,但必须使用

main.c

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

int main(void)
{	
	//LED初始化
	LED_GPIO_Config();
	
	while(1)
	{
		LED1(ON);
		delay_ms(1000);//延时1s	
		LED1(OFF);
		delay_ms(1000);
		
		LED2(ON);
		delay_ms(1000);
		LED2(OFF);
		delay_ms(1000);
				
		LED3(ON);
		delay_ms(1000);
		LED3(OFF);
		delay_ms(1000);
		
	}
}

最后再补充一点C语言知识:
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
static:用于声明内部函数
在这里插入图片描述
inline 关键字可以把函数指定为内联函数,但是关键字inline必须与函数定义放在一起才能使函数成为内联函数,仅仅将inline放在函数声明前是不起任何作用的。
在这里插入图片描述

其实这篇文章中的精确延时方法还有很多需要优化的地方,先暂时不纠结深入了,以后懂了再去修改。

作者能力水平有限,文章难免存在错误和纰漏,请大佬不吝赐教,非常欢迎大家与小白杨进行技术交流,希望在此能遇到志同道合的朋友,一起学习技术。

  • 11
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值