STM32使用内核System Tick定时器实现微秒延时

2023-05-16

同名微信公众号“固件工人”同步发布的文章,欢迎同时关注,及时获取最新文章。


在单片机的程序开发中,延时一般都会用到,在对延时精度要求不高的场合,一般使用软件延时实现,就是在一个循环中执行NOP空语句。如果要实现相对高精度的延时,可以考虑使用定时器。

这里以STM32F429I-DISCO为基础硬件,来讲解如何使用ARM单片机内核System Tick定时器实现相对高精度的微秒和毫秒延时。

STM32F429I-DISCO开发板的官方例程默认配置并开启System Tick定时器,默认的定时周期是1ms。STM32F429I-DISCO开发板使用8MHz的外部晶振,这里将开发板的单片机主频设置成168MHz,System Tick定时器的时钟源来自单片机主频,所以该定时器计数值每变化1,对应的时间为1/168微秒。这样定时器的计数值每变化168,对应时间即为1微秒。我们可以将要延时的微秒数t转成定时器的计数值n,当定时器的计数值变化n时,说明对应的延时时间已到。

这里需要注意的地方是定时器的定时周期是1ms,即1000us,到了1000us之后,定时器会自动重载并重新开始计数,所以为了尽量规避定时器溢出对延时精度可能造成的影响,这里以定时器周期的一半,即500us为一个延时周期p,来设计微秒延时函数vDelayUs()。

微秒延时函数的设计思路如下。

(1)根据要延时的微秒数t,计算500us延时周期的个数n,再得出剩余的微秒数r。

(2)先延时n个500us的延时周期,完成之后,再延时剩余的微秒数r。

程序代码实现如下。该延时函数使用时有以下一些注意事项。

  • 如果需要将代码移植到其他的单片机,需要根据单片机定时器的实际设置情况,对应修改最上面的相关宏定义即可。

  • 延时函数的一些初始化代码也会占用一定的执行时间,所以这里的延时也会有一定的误差,但是微秒延时的理论误差应该可以控制在1us以内。

  • 在实际使用时,进行延时时尽量只调用该函数一次,尽量不要使用循环的方式多次调用该函数实现一些长延时,因为这样会累加函数本身初始化代码的执行时间,造成延时误差变大。

  • 该函数也会被中断打断,所以中断的执行时间要尽量短,否则会加大这里的延时误差。

由于该函数只对定时器的VAL计数值寄存器进行读取,所以并不影响定时器本身的1ms中断定时功能,可以充分利用该定时器资源来实现较为精确的延时功能。

#include "stm32f4xx.h"             /* Cortex-M4 processor and core peripherals */
​
#define US_TICKS        168    // 每微秒的定时器计数值,需根据MCU工作主频修改,这里主频是168MHz
#define MAX_US_PER_PERIOD    (1000 / 2)  // 1个周期的最大微秒数,需根据System Tick周期修改,设置成System Tick周期的一半,防止微秒延时溢出造成不准确
#define MAX_TICKS_PER_PERIOD  (MAX_US_PER_PERIOD * US_TICKS)  // 1个周期的最大微秒数对应的定时器计数值
#define MAX_US_DELAY      4294967295    // uint32_t类型最大值
​
void vDelayUs(uint32_t p_dwUs)
{
  uint32_t l_dwReloadValue = SysTick->LOAD;
  uint32_t l_dwUsPeriodNum = p_dwUs / MAX_US_PER_PERIOD;
  uint32_t l_dwUsRemainTicks = (p_dwUs % MAX_US_PER_PERIOD) * US_TICKS;
  uint32_t l_dwDeltTicks = 0;
  uint32_t l_dwCurTicks, l_dwPreTicks, l_dwIntervalTicks, i;
​
  l_dwPreTicks = SysTick->VAL;
  for(i = 0; i < l_dwUsPeriodNum; i++)
  {
    while(1)
    {
      l_dwCurTicks = SysTick->VAL;
      // Cortex-M4的System Tick是向下计数的
      if(l_dwCurTicks <= l_dwPreTicks)
      {
        l_dwIntervalTicks = l_dwPreTicks - l_dwCurTicks + l_dwDeltTicks;
      }
      else
      {
        l_dwIntervalTicks = l_dwPreTicks + (l_dwReloadValue - l_dwCurTicks + 1) + l_dwDeltTicks;
      }
      if(MAX_TICKS_PER_PERIOD <= l_dwIntervalTicks)
      {
        l_dwPreTicks = SysTick->VAL;
        l_dwDeltTicks = l_dwIntervalTicks - MAX_TICKS_PER_PERIOD;
        break;
      }
    }
  }
  if(0 < l_dwUsRemainTicks)
  {
    while(1)
    {
      l_dwCurTicks = SysTick->VAL;
      if(l_dwCurTicks <= l_dwPreTicks)
      {
        l_dwIntervalTicks = l_dwPreTicks - l_dwCurTicks + l_dwDeltTicks;
      }
      else
      {
        l_dwIntervalTicks = l_dwPreTicks + (l_dwReloadValue - l_dwCurTicks + 1) + l_dwDeltTicks;
      }
      if(l_dwUsRemainTicks <= l_dwIntervalTicks)
      {
        break;
      }
    }
  }
}

在上面的微秒延时函数的基础上,可以进一步设计毫秒延时函数。按照前面尽量减少微秒延时函数调用次数的原则,这里以最大微秒延时时间为基本单元,进行毫秒延时函数vDelayMs()的设计。设计思路如下。

(1)将要延时的毫秒数t转成微秒数,计算最大微秒延时时间的个数n,以及剩余的微秒延时数r。

(2)调用vDelayUs()函数先完成n个最大微秒延时,再调用vDelayUs()完成剩余的微秒延时数r。

同样,为了尽量提高延时精度,毫秒延时函数vDelayMs()在进行延时时也要尽量只进行一次调用。

毫秒延时函数vDelayMs()的程序代码实现如下。

void vDelayMs(uint32_t p_dwMs)
{
  uint64_t p_llUs = (uint64_t)p_dwMs * 1000;
  uint32_t p_dwMaxUsNum = p_llUs / MAX_US_DELAY;
  uint32_t p_dwRemainUs = p_llUs % MAX_US_DELAY;
  uint32_t i;
​
  for(i = 0; i < p_dwMaxUsNum; i++)
  {
    vDelayUs(MAX_US_DELAY);
  }
  if(0 < p_dwRemainUs)
  {
    vDelayUs(p_dwRemainUs);
  }
}

如果想进一步提高延时精度,可以实测延时函数开头的一些变量的初始化时间ti,然后在延时中对这些额外的执行时间进行补偿即可。也可以将C代码转成汇编代码来评估代码的实际执行时间。

从C代码转成汇编代码的方法可以参考以下链接。

arm-none-eabi交叉编译工具常用的一些指令:

https://blog.csdn.net/a13526758473/article/details/54982817

这里将延时函数放在了delay.c文件中,可以通过以下命令生成对应的汇编代码文件。

arm-none-eabi-objdump -d -S delay.o >delay1.txt

需要注意的是,要在汇编代码中嵌入C代码,需要在编译阶段使用-g参数来使汇编代码和C代码对应。即编译时使用以下命令。

arm-none-eabi-gcc -g ...

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32使用内核System Tick定时器实现微秒延时 的相关文章

  • 内存对齐算法

    字节对齐是在分配内存时需要考虑的问题 xff0c 两个小算法 xff1a 1 最容易想到的算法 unsigned int calc align unsigned int n unsigned align if n align align 6
  • vscode快捷键整理

    1 注释 xff08 1 xff09 方式 注释 取消注释 xff1a Ctrl 43 xff08 2 xff09 方式 注释 xff1a Ctrl 43 Shift 43 取消注释 xff1a Ctrl 43 Shift 43 2 代码排
  • Qt之实现移动的方块(蚂蚁线)

    一 简介 移动的小方块或者说是类似移动的蚂蚁线 xff0c 从一篇文章看到的 xff0c 挺有趣的就自己做了一个 xff0c 可以自由添加方块的个数 xff0c 起始位置 xff0c 方块的宽度 xff0c 方块移动速度等待参数 xff0c
  • Docker 突然挂掉 failed to create shim task: OCI runtime create failed: container_linux.go:345: ...

    目录 问题描述 xff1a 参考 解决方案 最佳方案 xff1a 问题描述 xff1a docker Error response from daemon failed to create shim task OCI runtime cre
  • Qt之事件过滤器(eventFilter)详解

    1 2 1 Qt中事件是如何进行传递 1 2 2 Qt中的事件过滤器 xff08 eventFilter xff09 1 2 3 如何自己模拟发送事件消息 一 Qt中事件过滤器详解 我们先看下另外两个相关的方法 xff0c 一个是给对象安装
  • Qt实现微信截图功能(一)

    简述 Qt 之 简单截图功能 xff08 一 xff09 实现鼠标选中区域截图Qt 之 简单截图功能 xff08 二 xff09 实现可移动选中区域Qt 之 简单截图功能 xff08 三 xff09 实现可拖拽选中区域 在之前的文章中有带大
  • Qt之QMenu菜单去除投影效果(阴影)

    一 简述 我们使用Qt中的菜单 xff0c 正常情况下样式是跟随当前系统菜单的样式 xff0c 我们可以使用样式表进行修饰 xff0c 改变原有风格 xff0c 但是window系统上菜单边框四周会附带阴影的效果 xff0c 样式是无法取消
  • Qt 之 设置窗口边框的圆角

    Qt技术学习班开始了 xff0c 更多精彩 好玩的内容等着你 xff0c 赶紧报名吧 群号 xff1a 655815739 Qt在设置窗口边框圆角时有两种方式 xff0c 一种是设置样式 xff0c 另一种是在paintEvent事件中绘制
  • Qt 之 HTTP 请求下载(支持断点续传)

    简述 最近在研究了一下用Qt 的方法来实现http下载 xff0c Qt 中的Http请求主要用到了QNetworkAccessManager QNetworkReply QNetworkRequest 这三块 本篇文章主要叙述如何用Qt
  • Qt之实现录音播放及raw(pcm)转wav格式

    简述 在上一篇 Qt 之 WAV文件解析 中详细地分析了wav格式文件的文件头信息 通过QAudioInput实现录音功能 xff0c 但是录音生成的文件并不能用播放器打开 xff0c 就算更改后缀名也无法识别 xff08 有时候下载的一些
  • C++中 Unicode 与 UTF-8 编码互转

    1 简述 最近在发送网络请求时遇到了中文字符乱码的问题 xff0c 在代码中调试字符正常 xff0c 用抓包工具抓的包中文字符显示正常 xff0c 就是发送到服务器就显示乱码了 xff0c 那就要将客户端和服务器设置统一的编码 xff08
  • Qt 之 自定义按钮 在鼠标 悬浮、按下、松开后的效果

    Qt技术学习班开始了 xff0c 更多精彩 好玩的内容等着你 xff0c 赶紧报名吧 群号 xff1a 655815739 一 简述 在上一篇 Qt 之 去除窗口部件被选中后的焦点虚线框 中 xff0c 我们为了去除焦点虚线框 xff0c
  • Qt 之 自定义窗口标题栏

    Qt训练营开始了 xff0c 更多精彩 好玩的内容等着你 xff0c 赶紧报名吧 群号 xff1a 861353824 一 简述 今天晚上就如何用Qt自定义窗口标题栏 xff0c 写了一个小例子 xff0c 比较基础 xff0c 实用 在此
  • Qt 之 模仿 QQ登陆界面——旋转窗口篇

    一 简述 今天是新的一年第一篇博客 xff0c 有大半个月没有更新博客了 我想是时候 xff0c 打开电脑 拿起键盘 开始在我的代码之路上披荆斩棘 xff0c 斩杀恶龙 今天就继续来分享QQ登录界面的那些事 QQ登录界面的标题栏有一个小三角
  • Ubuntu配置无线路由器笔记记录

    参考文章 xff1a linux 开启制作无线路由器 ubuntu 1404 linux zhu的博客 CSDN博客 hostapd实现WIFI 热点 xff08 AP xff09 自由枫 的博客 CSDN博客 hostapd 终端get一
  • C++STL的使用心得汇总(vector,string,map,list)

    文章目录 find 函数vector的findstring的findmap的find count 函数vector的countstring的countmap的count vectorstringmap的各种排序方法转换相关 待完善 find
  • Qt 之 样式表的使用——设置样式的方法

    一 简述 我们通常在使用Qt开发的过程中都会使用样式表来美化我们的界面 xff0c 关于如何使用样式表的资料也很多 xff0c 样式表的使用方法也千变万化 为了搭建一个漂亮的界面那么必须学会如何使用样式表 xff0c Qt帮助文档中提供了非
  • 如何使QGraphicsItem不随QGraphicsView放大缩小而改变大小

    一 简述 在使用QGraphicsView过程中 xff0c 有时候我们需要对view进行缩放 xff0c 但是对于一般正常的加入view中的item都会随着view的大小变化而变化 xff0c 但是如果我们想让某些item不随view的缩
  • 【linux系统如何查看内核版本、操作系统版本等信息】

    有时候需要查看linux系统的内核版本 xff0c 可以有多种方法 xff0c 方法如下 xff1a xff08 下面以优麒麟系统为例 xff09 方法1 xff1a 打开mate终端 xff0c 在命令行输入以下命令 xff1a unam
  • 【linux系统如何安装arm交叉编译工具链】

    文章目录 前言一 arm交叉编译器介绍命名规则具体编译器 二 Arm GNU Toolchain安装总结 前言 本文简要介绍arm交叉编译器及工具链的安装方法 一 arm交叉编译器介绍 命名规则 交叉编译工具链的命名规则为 xff1a ar

随机推荐

  • 比较冒泡排序、选择排序和快速排序的时间(C语言实现)

    文章目录 前言代码设计代码实现运行结果结果分析稳定性测试 总结 前言 本文主要比较冒泡排序 快速排序 选择排序的时间 冒泡排序和快速排序的思想可以参考我转载的以下博文 xff1a https blog csdn net gogo0707 a
  • freertos应用程序常见错误排查

    freertos系统应用程序常见问题 对一些比较常见的问题 xff0c 下面简要的以 FAQ 问答 的形式给出可能的原因和解决方法 问题现象 xff1a 在一个 Demo 应用程序中增加了一个简单的任务 xff0c 导致应用程序崩溃 任务创
  • keil5编译工程常见问题汇总

    简介 我们在编译keil工程的时候总是遇到很多问题 xff0c 我把一些常见的问题和解决方案汇总下来 xff0c 仅供大家参考 问题汇总 问题1 问题描述 选择arm v6版本编译器 xff0c 编译keil5工程 xff0c 报错 xff
  • mdk arm debug配置

    简述 本文简要讲述启动调试之前如何配置debug 点击魔术棒 xff0c 进入debug选项界面 xff0c 如下图 xff1a 我们可以选择软件仿真 xff0c 也可以选择硬件仿真 xff08 软件仿真不需要接开发板和仿真器 xff09
  • stm32高级定时器实现pwm互补输出

    简介 stm32设备一般都有很多类型的定时器 xff0c 常见的有systick timer 基本定时器 通用定时器 高级定时器 看门狗定时器 RTC等等 xff0c 本文简单介绍高级定时器是如何实现pwm互补输出 详细 我这里使用的dev
  • shell脚本基础知识(入门)

    简介 本文会全面介绍shell脚本的基础知识 脚本格式 要把shell命令放到一个 脚本 当中 xff0c 有一个要求 xff1a 脚本的第一行必须写成类似这样的格式 xff1a bin bash bash是一个shell解释器 xff0c
  • 记ADB shell for循环踩坑

    abd 里面的shell的电脑Linux的shell有点不太一样 以下这些案例均不能执行 xff1a for i 61 1 i lt 61 100 i 43 43 do echo i done for i in 1 100 do echo
  • linux线程调度策略简述

    简述 linux系统调度执行的最小单位是线程 xff0c 线程的调度策略有以下三种 xff1a xff08 1 xff09 SCHED FIFO 其静态优先级必须设置为1 99 xff0c 这将意味着一旦线程处于就绪态 xff0c 他就能立
  • stm32串口发送接口

    简介 本文记录一下stm32标准库实现串口发送功能和接收功能的接口函数 轮询方式发送串口数据 1 标准库实现 span class token comment 61 61 61 61 61 61 61 61 61 61 61 61 61 6
  • linux系统线程池

    简述 一个进程中的线程就好比是一家公司里的员工 xff0c 员工的数目应该根据公司的业务多少来定 xff0c 太少了忙不过来 xff0c 但是太多了也浪费资源 最理想的情况是让进程有一些初始数目的线程 xff08 线程池 xff09 xff
  • STM32串口环形缓冲区

    目录 1 xff1a 概述 2 xff1a 代码 1 xff1a 概述 1 1 xff1a 本篇实现串口驱动 xff0c 实现printf函数的重定向 xff0c 实现串口的中断接受和发送 xff0c 效仿modbus协议中的3 5T超时机
  • 天地飞6通道遥控器解码

    在做四轴 xff0c 想到要改造一下遥控器 我用的是天地飞6通道2 4G版 改造的思路是这样的 xff1a 用M8单片机读取PPM信号 xff0c 用液晶显示出6个通道的解码 xff0c 当然这个解码还需要根据飞控板进行一下 校准 xff0
  • 如何计算网络协议校验和以及为什么这么计算

    一 校验和是如何计算的 xff1f 这个问题牵扯到计算机最底层最神秘的部分 二进制运算 说实话 xff0c 从学计组计统起 xff0c 我就比较讨厌思考二进制的相关运算 但毕竟是学这个的 xff0c 只好硬着头皮想了 首先发送方校验和的计算
  • JAVA又臭又长,是一门垃圾语言,早晚会被淘汰

    1 JAVA又臭又长 xff0c 是一门垃圾语言 xff0c 早晚会被淘汰 2 JAVA能做的 xff0c python 等上层解释类语言大部分都可以坐到 3 JAVA为了面向对象而面向对象 xff0c 导致程序冗长 xff0c 效率低下
  • 封装OkHttp

    懒汉 安全 加同步 私有的静态成员变量 只声明不创建 私有的构造方法 提供返回实例的静态方法 private static OkHttpClient okHttpClient 61 null private OkHttp3Util publ
  • WXP380 电容式MEMS高性能数字气压传感器 电容式MEMS压力传感器快速代DPS310 BMP380 SLP06 HP303B “电容式”噪声超低的高精度MEMS气压传感器;

    午芯高科WXP380气压传感器 稳定供应 填补国内空白 2 WXP380气压传感器的 电容式 MEMS芯片填补了国内技术空白 xff01 高性能 xff1a 电容式 噪声超低的高精度MEMS气压传感器 xff1b 高度差测量精确度小至2cm
  • 解决ros的rosdep update报错的问题。

    参见以下文章 xff1a https blog csdn net leida wt article details 115120940 如果 xff1a https raw githubusercontent com ros rosdist
  • Docker报错Error spawning command line “dbus-launch --autolaunch=xxx --binary-syntax --close-stderr”

    环境 xff1a ubuntu 18 报错描述 xff1a Error spawning command line dbus launch autolaunch 61 a3d9197b765643568af09eb2bd3e5ce7 bin
  • 一种测量STM32程序执行时间的方法

    同名微信公众号 固件工人 同步发布的文章 xff0c 欢迎同时关注 xff0c 及时获取最新文章 1 1 背景 在单片机的固件开发过程中 xff0c 有的时候需要评估固件代码的执行性能 xff0c 会对部分关键程序代码的执行时间进行测量 通
  • STM32使用内核System Tick定时器实现微秒延时

    同名微信公众号 固件工人 同步发布的文章 xff0c 欢迎同时关注 xff0c 及时获取最新文章 在单片机的程序开发中 xff0c 延时一般都会用到 xff0c 在对延时精度要求不高的场合 xff0c 一般使用软件延时实现 xff0c 就是