STM32 SYSTick高精度延时功能代码实现

2024-02-13 15:20

本文主要是介绍STM32 SYSTick高精度延时功能代码实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 一、SYSTick定时器介绍
  • 二、SYSTick定时器和其他定时器的区别
  • 三、SYSTick定时器框图讲解
  • 四、HAL库中SYSTick配置代码讲解
  • 五、SYSTick实现高精度延时
  • 总结


前言

本篇文章将给大家讲解一下SYSTICK滴答定时器,以及讲解使用滴答定时器来实现高精度延时功能的代码。

一、SYSTick定时器介绍

SysTick定时器是嵌入式系统中常见的一个系统定时器,在ARM Cortex-M微控制器中广泛使用。下面是关于SysTick定时器的一些介绍:

用途: SysTick定时器通常被用作操作系统的时钟节拍(Tick)或者作为基本的定时器来执行周期性的任务。它可以提供一个精确的时间基准,用于定时器中断、延时函数的实现以及系统的时间管理。

定时器类型: SysTick定时器是一个24位向下计数器。它可以在一个范围内计数从最大值向0的时钟周期,然后在达到0时重新装载计数值,并触发一个中断(如果已使能)。

配置: 在STM32微控制器中,SysTick定时器可以通过设置相关的寄存器来配置。这些寄存器包括:

STK_CTRL:控制寄存器,用于使能或禁用SysTick定时器,选择时钟源和设置中断使能。
STK_LOAD:装载寄存器,用于设置初始的计数值。
STK_VAL:当前值寄存器,用于读取当前的计数值。
STK_CALIB:校准寄存器,用于存储SysTick定时器的校准值。

时钟源: SysTick定时器的时钟源可以选择为外部时钟或者系统时钟的一个分频。通常情况下,它与系统时钟同步,但也可以使用外部时钟来提供更灵活的配置选项。

中断: SysTick定时器可以在计数器溢出时触发中断。这种中断通常被用来实现操作系统的时钟节拍,或者用于周期性任务的执行。

应用: SysTick定时器广泛应用于嵌入式系统中,特别是在实时操作系统(RTOS)中用作系统时钟。它可以用来实现延时函数、精确的定时器中断、周期性任务的执行以及系统的时间管理。

二、SYSTick定时器和其他定时器的区别

1.SYSTick定时器是CPU内部的定时器,其他定时器是作为STM32的外部定时器。

之所以在处理器内增加一个定时器,是为了提高软件的可移植性。由于所有的 Cortex-M处理器都具有相同的SysTick定时器,为一种Cortex-M3/M4微控制器实现的OS也能适用于其他的Cortex-M3/M4微控制器
在这里插入图片描述
2.功能和用途:

SYSTick定时器通常用于实现系统的时间管理、时钟节拍以及简单的定时功能,如延时函数的实现等。
其他定时器(如TIM定时器)通常用于更复杂的定时和计时任务,例如PWM输出、捕获/比较模式、定时触发ADC转换等。

3.精度:

SYSTick定时器的精度通常受限于系统时钟频率,因此在一般情况下可能比较低。
其他定时器通常具有更高的精度,并且可以通过外部时钟源进行精确校准,因此适用于需要更精确时间控制的应用。

4.中断处理:

SYSTick定时器通常只能产生一个中断,用于系统的时钟节拍或简单的定时任务。
其他定时器可以配置多个中断触发条件,允许更灵活的中断处理和定时任务的执行。

5.寄存器和配置:

SYSTick定时器只有几个寄存器,配置相对简单。
其他定时器通常具有更多的寄存器和更复杂的配置选项,以支持各种不同的定时和计时功能。

6.外设依赖性:

SYSTick定时器不依赖于外部器件,因为它是CPU内部的一个特殊功能模块。
其他定时器通常依赖于外部时钟源和其他外部器件(例如计数输入、PWM输出引脚等)。

7.优先级:

由于SYSTick定时器是CPU内部的定时器,因此其中断处理通常具有较高的优先级。
其他定时器的中断处理优先级可能需要根据具体应用情况进行配置,并且可能不如SYSTick定时器的中断处理优先级高。

三、SYSTick定时器框图讲解

在这里插入图片描述
1.首先SYSTick根据处理器时钟或者参考时钟来减小计数

2.配置CTRL寄存器的第0位使能计数器,当前值寄存器在每个处理器时钟周期或参考时钟的上升沿都会减小。若计数减至0,它会从重加载寄存器中加载数值并继续
运行。

3.另外一个寄存器为 SysTick 校准值寄存器。它为软件提供了校准信息。由于 CMSISCore 提供了一个名为 SystemCoreClock 的软件变量(CMSIS 1.2及之后版本可用,CMSIS 1.1或之前版本则使用变量 SystemFrequency),因此它就未使用SysTick 校准值寄存器。系统初始化函数 SystemInit()函数设置了该变量,而且每次系统时钟配置改变时都要对其进行更新这种软件手段比利用SysTick 校准值寄存器的硬件方式更灵活。

四、HAL库中SYSTick配置代码讲解

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
滴答定时器配置代码:

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk){return (1UL);                                                   /* Reload value impossible */}SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */SysTick->VAL   = 0UL;                                             /* Load the SysTick Counter Value */SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |SysTick_CTRL_TICKINT_Msk   |SysTick_CTRL_ENABLE_Msk;                         /* Enable SysTick IRQ and SysTick Timer */return (0UL);                                                     /* Function successful */
}

下面是对这段代码的配置流程的讲解:

1.参数检查:

if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{return (1UL);  /* Reload value impossible */
}

这一部分首先检查传入的 ticks 参数是否超过了SysTick定时器的重装载寄存器(LOAD寄存器)的最大值。如果超过了,说明无法设置这么大的重载值,函数返回1表示配置失败。否则,继续执行后续的配置步骤。

2.设置重载寄存器:

SysTick->LOAD  = (uint32_t)(ticks - 1UL);  /* set reload register */

这一行代码设置SysTick定时器的重载寄存器,确定计数器计数到多少时触发一次SysTick中断。ticks - 1 是因为计数器是从零开始计数的。

3.设置中断优先级:

NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);  /* set Priority for Systick Interrupt */

这里设置了SysTick定时器的中断优先级。NVIC_SetPriority 是一个用于设置中断优先级的CMSIS(Cortex Microcontroller Software Interface Standard)函数。__NVIC_PRIO_BITS 表示中断优先级位的数量,通常由硬件定义。这里将SysTick中断的优先级设置为最低,即最高数值。

4.清零计数器寄存器:

SysTick->VAL   = 0UL;  /* Load the SysTick Counter Value */

这一行代码清零SysTick定时器的计数器寄存器,确保计数器从零开始计数。

5.配置并启用SysTick定时器:

SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |SysTick_CTRL_TICKINT_Msk   |SysTick_CTRL_ENABLE_Msk;  /* Enable SysTick IRQ and SysTick Timer */

这一行代码配置并启用SysTick定时器。具体配置包括:

SysTick_CTRL_CLKSOURCE_Msk:选择SysTick定时器的时钟源,通常选择处理器时钟。
SysTick_CTRL_TICKINT_Msk:使能SysTick定时器中断。
SysTick_CTRL_ENABLE_Msk:启用SysTick定时器。

返回配置结果:

   return (0UL);  /* Function successful */

如果上述配置步骤都成功执行,函数返回0,表示配置成功。

五、SYSTick实现高精度延时

创建systick.c和systick.h来管理高精度延时的代码。

systick.c

#include "systick.h"//us级延时
void udelay(int us)
{uint32_t told = SysTick->VAL;uint32_t tnow;uint32_t load = SysTick->LOAD;/* LOAD+1个时钟对应1ms* n us对应 n*(load+1)/1000个时钟*/uint32_t ticks = us*(load+1)/1000;uint32_t cnt = 0;while (1){tnow = SysTick->VAL;if (told >= tnow)cnt += told - tnow;elsecnt += told + load + 1 - tnow;told = tnow;if (cnt >= ticks)break;}	
}//ms级延时
void mdelay(int ms)
{for (int i = 0; i < ms; i++)udelay(1000);	
}//获取系统上电到现在经过了多少ns
uint64_t system_get_ns(void)
{uint64_t ns = HAL_GetTick(); /* ms */ns = ns*1000000;uint32_t tnow = SysTick->VAL;	uint32_t load = SysTick->LOAD;uint64_t cnt;cnt = load+1-tnow; /* 没有考虑tnow等于0的情况 */ns += cnt * 1000000 / (load+1) ;return ns;	
}

systick.h

#ifndef __SYSTICK_H__
#define __SYSTICK_H__#include "main.h"void udelay(int us);void mdelay(int ms);uint64_t system_get_ns(void);#endif
  1. udelay(int us)
    这个函数实现了微秒级延时。其原理是利用SysTick定时器进行精确计时。首先,获取当前SysTick计数器的值 told,然后计算出需要延时的时钟周期数 ticks,接着在一个循环中不断获取当前计数器的值 tnow,并计算经过的时钟周期数 cnt。当经过的时钟周期数达到了延时所需的时钟周期数 ticks 时,跳出循环,延时结束。

  2. mdelay(int ms)
    这个函数实现了毫秒级延时,它通过多次调用微秒级延时函数来实现。每次调用 udelay(1000) 即相当于延时了1毫秒。

  3. system_get_ns(void)
    这个函数用于获取系统上电到当前时刻经过的纳秒数。首先,获取系统运行时间的毫秒数 ns,然后通过当前SysTick计数器的值 tnow 和 SysTick加载值 load 计算出当前经过的时钟周期数 cnt,进而将其转换为纳秒数并加到系统运行时间上,最终返回总的纳秒数。

总结

本篇文章主要讲解了SYSTick的概念,寄存器配置和使用,并且使用SYSTick实现了高精度延时,大家可以自己写代码实验验证一下正确性。

这篇关于STM32 SYSTick高精度延时功能代码实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/705889

相关文章

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核