STM32-HAL-usDelay

一、STM32单片机的延时

STM32单片机的延时,是指在程序中暂停一段时间,等待一定的时间后再继续执行下一条指令。常见的延时方式有循环延时定时器延时

毫秒延时的使用场景:

  • 等待外设完成某项操作:在使用外设时,有时需要等待外设完成某项操作才能进行下一步操作。例如,在使用SPI通信OneWire通信(DS18B20 DHT11等)时,需要等待数据传输完成才能读取接收到的数据。
  • 控制任务执行时间:在多任务系统中,任务的执行时间需要控制在一定范围内,以避免出现任务响应时间不稳定、任务饥饿等问题。
  • 实现延时操作:有时需要在任务中实现一些延时操作,例如等待一定时间后再执行某些操作。
  • 实现周期性任务:在一些周期性任务中,需要在每个周期内执行一定的操作。此时,可以使用毫秒延时来实现周期性的触发。

需要注意的是,使用毫秒延时时应注意精度和稳定性。
在需要更高精度的应用中,可以考虑使用定时器来实现延时。

毫秒延时的实现方法:

  • 使用SysTick定时器

  • 使用TIM定时器

  • 使用for循环进行延时

  • 使用DWT寄存器

二、测试准备

  • 基于STM32L431RCT6的小熊派开发板在这里插入图片描述
  • 安装windows系统并安装CubemxKeil MDK的电脑

三、初始化片上外设

3.1 设置板载的LED-PC13和PA11为推挽输出模式

在这里插入图片描述
同样设置PA11引脚为推挽输出模式,为的是在后面方便测试。

在这里插入图片描述

3.2 使用定时器进行毫秒延时

使用定时器进行精确延时的原理:
定时器在设置好初始值的时候,便会自增,在自增的过程中便会产生一个时间等待,使用定时器的精确计数便可以精确设置要等待的时间。

  • 打开定时器TIM2

在这里插入图片描述
设置定时器2的Clock Source Internal Clock

  • 时钟源设置为外部高速时钟(使用内部产生的时钟源也可)

在这里插入图片描述
【重要】查看开发板的板载晶振的频率(根据自己的开发板的晶振频率设置),因此设置输入的时钟的频率为8Hz,经过分频后最后设置频率为最大80MHz,查看经过设置后定时器所在的外设桥时钟频率亦为80MHz

在这里插入图片描述

  • 针对TIM2的一些参数进行配置

在这里插入图片描述

设置分频系数为84 - 1 ,则定时器的计数时钟频率为1MHz,即为计数一次需要消耗1s/1M = 1us的时间。

设置的计数周期是65535,本次实验不涉及中断,因此不需要开启中断。

  • 设置生成Keil-MDK代码文件

3.3 使用SysTick定时器进行毫秒延时

STM32的滴答定时器(SysTick)是一种基于硬件的定时器,可以提供系统级别的延时和定时功能。它使用一个24位计数器和一些相关寄存器来控制其行为。

滴答定时器的相关寄存器介绍:

[来自core_cm3.h]

/** @addtogroup CMSIS_CM3_SysTick CMSIS CM3 SysTick
  memory mapped structure for SysTick
  @{
 */
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;
CTRLSysTick控制和状态寄存器,用于控制SysTick计数器的启动、中断、时钟源以及清零等操作
LOADSysTick重装载值寄存器,用于设置SysTick计数器的重装载值
VALSysTick当前值寄存器,用于读取SysTick计数器当前的计数值
CALIBSysTick校准寄存器,用于获取SysTick计数器的时钟周期数和是否支持64位读取操作的信息

四、测试

4.1 编写定时器的延时代码

[在tim.c的代码添加处添加]
/* USER CODE BEGIN 1 */

/*设置的milliseconds需要小于65535阈值*/

void us_Delay(uint32_t milliseconds)                     // 延时函数,参数为需要延时的毫秒数
{
	HAL_TIM_Base_Start(&htim2);                          // 启动定时器 2

	__HAL_TIM_SET_COUNTER(&htim2, 0);                    // 将定时器 2 的计数器清零

	while(__HAL_TIM_GET_COUNTER(&htim2) < milliseconds); // 等待定时器 2 的计数器达到指定的毫秒数
	
	HAL_TIM_Base_Stop(&htim2);                           // 停止定时器 2
}
/* USER CODE END 1 */

/*
设置延时时间为1ms 但是由于就是代码在执行的过程中是也有执行时间的,因此就是需要手动调节延时参数。
*/
void 1ms_Delay(void)
{
    us_Delay(951);
}
/*
设置延时时间为1s
*/
void one_s_Delay(void)
{
	for(uint16_t i = 0;i<1000;i++)
	{
		one_ms_Delay();
	}	
}
[在主函数while循环中添加]
 /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		HAL_GPIO_WritePin(USER_GPIO_Port,USER_Pin,GPIO_PIN_SET);  //设置PA11为高电平
     	//HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);  //设置PC13为高电平
		one_ms_Delay();                                           //延时1ms
        //one_s_Delay();                                          //延时1s
		HAL_GPIO_WritePin(USER_GPIO_Port,USER_Pin,GPIO_PIN_RESET);//设置PA11为低电平
     	//HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);  //设置PC13为高电平
		one_ms_Delay();                                           //延时1ms
        //one_s_Delay();                                          //延时1s
  }

使用逻辑分析仪器进行电平的分析

设置延时时间为1ms

在这里插入图片描述
设置延时时间为1s 但是不是严丝合缝的1s

在这里插入图片描述

4.2 编写SysTick定时器的延时代码

使用滴答定时器配置相关寄存器,实现us、ms 和s级的延时

下面是适用于固件库中的一个us级延时代码

[用于固件库中使用,节选自一个项目中]
/**
*@brief		初始化延迟函数
*@param		SYSCLK:系统时钟
*@return	无
*/
void systick_init (u8 sysclk)
{
	SysTick->CTRL&=0xfffffffb;							/*bit2清空,选择外部时钟  HCLK/8*/
	fac_us=sysclk/8;		    
	fac_ms=(u16)fac_us*1000;
}	

/**
*@brief	    微秒延时函数
*@param		time_ms:要延时微秒时间数
*@return	无
*/
void delay_us(uint32 time_us)
{		
	u32 temp;	    	 
	SysTick->LOAD=time_us*fac_us; 		/* 将时间加载进SysTick的重载值寄存器 */ 		 
	SysTick->VAL=0x00;                  /*清空计数器*/
	SysTick->CTRL=0x01 ;      	        /* 开始倒数,使用内部时钟,开启SysTick计时器 */	
	do
	{
		temp=SysTick->CTRL;             /* 获取SysTick的CTRL寄存器值 */
	}
	while(temp&0x01&&!(temp&(1<<16)));	/*等待时间到达*/
	SysTick->CTRL=0x00;       			/*关闭SysTick计数器*/
	SysTick->VAL =0X00;       		    /*清空计数器*/
}


/**
*@brief	    毫秒延时函数
*@param		time_ms:要延时毫秒时间数
*@return	无
*/
void delay_ms(uint32 time_ms)
{	 		  	  
	u32 temp;		   
	SysTick->LOAD=(u32)time_ms*fac_ms;		/*时间加载(SysTick->LOAD为24bit)*/
	SysTick->VAL =0x00;           			/*清空计数器*/
	SysTick->CTRL=0x01 ;         			/*开始倒数*/ 
	do
	{
		temp=SysTick->CTRL;
	}
	while(temp&0x01&&!(temp&(1<<16)));		/*等待时间到达*/
	SysTick->CTRL=0x00;       				/*关闭计数器*/
	SysTick->VAL =0X00;      			    /*清空计数器*/	  	    
} 

/**
*@brief	    秒延时函数
*@param		time_s:要延时秒时间数
*@return	无
*/
void delay_s(uint32 time_s)
{
  for(;time_s>0;time_s--)
    delay_ms(1000);
}
[在主函数中添加]
systick_init(72);				      	/*初始化Systick工作时钟*/
while(1)
{
    delay_us(1000);
    printf("一毫秒延时打印测试");
}

下面是适用于HAL库中的一个us级延时代码

[用于HAL库中,代码节选自正点原子]
//此段代码需要屏蔽,因为和Cubemx生成的代码冲突
//static uint32_t g_fac_us = 0;       /* us延时倍乘数 */
///**
// * @brief     初始化延迟函数
// * @param     sysclk: 系统时钟频率, 即CPU频率(rcc_c_ck), 168MHz
// * @retval    
// */
//void delay_init(uint16_t sysclk)
//{
//    g_fac_us = sysclk;    
//}


/**
 * @brief       延时nus
 * @param       nus: 要延时的us数.
 * @note        nus取值范围 : 0~190887435(最大值即 2^32 / fac_us @fac_us = 21)
 * @retval      无
 */
void delay_us(uint32_t nus)
{
    uint32_t ticks;
    uint32_t told, tnow, tcnt = 0;
    uint32_t reload = SysTick->LOAD;        /* LOAD的值 */
    ticks = nus * g_fac_us;                 /* 需要的节拍数 */
    told = SysTick->VAL;                    /* 刚进入时的计数器值 */
    while (1)
    {
        tnow = SysTick->VAL;
        if (tnow != told)
        {
            if (tnow < told)
            {
                tcnt += told - tnow;        /* 这里注意一下SYSTICK是一个递减的计数器就可以了 */
            }
            else 
            {
                tcnt += reload - tnow + told;
            }
            told = tnow;
            if (tcnt >= ticks)
            {
                break;                      /* 时间超过/等于要延迟的时间,则退出 */
            }
        }
    }
}
/**
 * @brief       延时nms
 * @param       nms: 要延时的ms数 (0< nms <= 65535)
 * @retval      无
 */
void delay_ms(uint16_t nms)
{
    uint32_t repeat = nms / 540;    /*  这里用540,是考虑到可能有超频应用, 比如248M的时候,delay_us最大只能延时541ms左右了 */
    uint32_t remain = nms % 540;

    while (repeat)
    {
        delay_us(540 * 1000);        /* 利用delay_us 实现 540ms 延时 */
        repeat--;
    }

    if (remain)
    {
        delay_us(remain * 1000);    /* 利用delay_us, 把尾数延时(remain ms)给做了 */
    }
}

/**
 * @brief       HAL库内部函数用到的延时
 * @note        HAL库的延时默认用Systick,如果我们没有开Systick的中断会导致调用这个延时后无法退出
 * @param       Delay : 要延时的毫秒数
 * @retval      None
 */
void HAL_Delay(uint32_t Delay)
{
     delay_ms(Delay);
}


[在主函数while中添加]

/* USER CODE BEGIN 2 */
#define g_fac_us 84   //需要添加此宏定义,然后就可以用正点原子的代码托管后续的延时函数
/* USER CODE END 2 */

/* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_GPIO_WritePin(USER_GPIO_Port,USER_Pin,GPIO_PIN_SET);  //设置PA11为高电平
    delay_us(952);                                           //延时1ms  
    HAL_GPIO_WritePin(USER_GPIO_Port,USER_Pin,GPIO_PIN_RESET);//设置PA11为低电平   
    delay_us(952);                                            //延时1ms    
  }

/* USER CODE END 3 */

在我的代码中设置为延时952us可以呈现1ms的电平变化效果,因此在使用的时候需要根据自己的代码自行调试。

后面的逻辑分析仪分析不再展示

4.3 编写for循环实现的延时代码

/* USER CODE BEGIN 4 */
void delay_us(uint32_t us)
{
    for(uint32_t i = 0; i < us * 16; i++)
    {
        __NOP();
    }
}
/* USER CODE END 4 */

[while循环中添加]

/* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_GPIO_WritePin(USER_GPIO_Port,USER_Pin,GPIO_PIN_SET);  //设置PA11为高电平
    delay_us(1000);                                           //延时1ms  
    HAL_GPIO_WritePin(USER_GPIO_Port,USER_Pin,GPIO_PIN_RESET);//设置PA11为低电平   
    delay_us(1000);                                            //延时1ms    
  }

/* USER CODE END 3 */

这个延时数量不需要修改,非常准确的延时

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值