大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是嵌入式里通用微秒(microseconds)计时函数框架设计与实现

  在嵌入式软件开发里,计时可以说是非常基础的功能模块了,其应用也非常广泛,比如可以辅助计算信号脉冲宽度时间,也可以直接用于常规延时等。相信很多人初次领略 MCU 的神奇都是从计时功能相关小程序开始的。

  在 MCU 里要想实现精确计时,往往都是利用其内部硬件定时器。不同厂商的 MCU,其定时器设计与使用都不太一样。即使是同一 MCU 内,通常也会有好几种不同类型的定时器共存。基于此,痞子衡今天分享一种非常简单实用的通用计时函数框架,这个框架的目的是统一计时函数接口,并且在实现上将通用部分和硬件相关部分剥离开,这样你的嵌入式项目在使用这个框架时可以无缝快捷地切换底层定时器。

  注:本框架主要适合定时器时钟源不小于 1MHz 的 MCU,因为函数接口里延时最小单元是 1us。对于一些定时器时钟源低于 1MHz 的 MCU,可将本框架简单改成成毫秒(milliseconds)计时函数。

一、微秒(microseconds)计时函数库设计

1.1 函数接口定义

  首先是设计通用计时函数框架头文件:microseconds.h ,这个头文件里直接定义如下 7 个接口函数原型。涵盖必备的初始化流程init()、shutdown(),最核心的计时功能get_ticks()、convert_to_microseconds(),常用的延时功能delay()、set_delay()、is_timeout()。

//! @brief 初始化计时
void microseconds_init(void);
//! @brief 关闭计时
void microseconds_shutdown(void);
//! @brief 获取系统累计计数值
uint64_t microseconds_get_ticks(void);
//! @brief 将计数值转换为时间值(微秒)
uint32_t microseconds_convert_to_microseconds(uint64_t ticks);
//! @brief 阻塞型延时(微秒级)
void microseconds_delay(uint32_t us);
//! @brief 设置超时时间(用于非阻塞型延时)
void microseconds_set_delay(uint32_t us);
//! @brief 判断是否超时(用于非阻塞型延时)
bool microseconds_is_timeout(void);

1.2 通用函数实现

  然后是设计通用计时函数框架共用源文件:microseconds_common.c,这个文件里涉及三个静态全局变量定义,四个私有函数声明,以及除了 get_ticks() 之外的 6 个接口函数实现。

  其中 s_tickPerMicrosecond 变量存的是每微秒对应计数值,其实这个变量不是一定要定义的,可以在函数需要时实时计算,但为了小小提升框架性能,就在 init() 里将这个值先算出来了,方便其他函数直接使用。

  s_highCounter 变量存的是定时器中断次数,即高位计数器,因为框架 get_ticks() 接口返回的是 64bit 的计数值,对于有些宽度小于 32bit 的定时器,我们常常需要开启定时器中断,否则无法保证系统长时间运行线性计时的正确性(比如 100MHz 时钟源的 32bit 定时器,最长约 43 秒就会清零翻转一次,需要 s_highCounter 变量记录翻转次数)。当然如果 MCU 里能级连出 64bit 的定时器,就可以不用开启中断(清零翻转的时间特别长,可近似认为是永久),s_highCounter 此时就不需要了。

  关于延时函数接口,delay() 用于阻塞型延时,即调用这个函数后一定是死等指定时间后才退出,系统会被强制挂起;set_delay()/is_timeout()用于非阻塞型延时,系统可以继续干其他任务,在需要的时侯来查看一下超时时间是否到了即可。两种延时各有各的用途。

//!< 每微秒等效计数值
static uint32_t s_tickPerMicrosecond;
//!< 超时时间点对应系统计数值(用于非阻塞型延时)
static uint64_t s_timeoutTicks;
//!< 高位计数器,仅当使能定时器超时中断时有效,用于记录中断累计次数
volatile uint32_t s_highCounter; //! @brief 打开硬件定时器
extern void microseconds_timer_init(void);
//! @brief 关闭硬件定时器
extern void microseconds_timer_deinit(void);
//! @brief 获取定时器时钟源数值
extern uint32_t microseconds_get_clock(void);
//! @brief 将时间值(微秒)转换为计数值
static uint64_t microseconds_convert_to_ticks(uint32_t microseconds); void microseconds_init(void)
{
// 清零高位计数器
s_highCounter = 0;
// 打开硬件定时器
microseconds_timer_init();
// 计算每微秒的等效计数值
s_tickPerMicrosecond = microseconds_get_clock() / 1000000UL;
// 假设定时器时钟源不小于 1MHz
assert(s_tickPerMicrosecond);
} void microseconds_shutdown(void)
{
// 关闭硬件定时器
microseconds_timer_deinit();
} uint32_t microseconds_convert_to_microseconds(uint64_t ticks)
{
return (ticks / s_tickPerMicrosecond);
} uint64_t microseconds_convert_to_ticks(uint32_t microseconds)
{
return ((uint64_t)microseconds * s_tickPerMicrosecond);
} void microseconds_delay(uint32_t us)
{
// 获取系统当前计数值
uint64_t currentTicks = microseconds_get_ticks();
// 计算超时时间点系统计数值
uint64_t ticksNeeded = ((uint64_t)us * s_tickPerMicrosecond) + currentTicks;
// 等待系统计数值到达超时时间点系统计数值
while (microseconds_get_ticks() < ticksNeeded);
} void microseconds_set_delay(uint32_t us)
{
// 计算超时时间等效计数值
uint64_t ticks = microseconds_convert_to_ticks(us);
// 设置超时时间点系统计数值
s_timeoutTicks = microseconds_get_ticks() + ticks;
} bool microseconds_is_timeout(void)
{
// 获取系统当前计数值
uint64_t currentTicks = microseconds_get_ticks();
// 判断系统计数值是否大于超时时间点系统计数值
return (currentTicks < s_timeoutTicks) ? false : true;
}

二、微秒(microseconds)计时函数库实现

2.1 定时器相关实现(基于Cortex-M内核的SysTick)

  最后是设计 MCU 相关的通用计时函数框架源文件:microseconds_xxTimer.c,这里我们以 Cortex-M 系列 MCU 的内核定时器 SysTick 为例。

  SysTick 是 24bit 递减定时器,时钟源有两种配置:一是内核主频,二是外部时钟(看厂商实现),最常用的时钟源配置就是与内核同频。

  上一节说了用 SysTick 这类宽度小于 32bit 的定时器,是需要开启定时器中断的,所以 s_highCounter 会生效。get_ticks()是整个计时函数框架里最基础也最核心的功能接口,这里面的实现有一个需要特别注意的地方,就是取系统当前计数值可能会有数值回退的风险,需要使用代码中 do {} while();方式来确保正确性。

//!< 高位计数器,仅当使能定时器超时中断时有效,用于记录中断累计次数
extern volatile uint32_t s_highCounter; void microseconds_timer_init(void)
{
// 调用 core_cmx.h 头文件里的初始化函数
// SysTick时钟源为内核时钟,开启中断,重装值为 0xFFFFFF
SysTick_Config(SysTick_LOAD_RELOAD_Msk + 1);
} void microseconds_timer_deinit(void)
{
SysTick->CTRL &= ~(SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk);
SysTick->VAL = 0;
} uint32_t microseconds_get_clock(void)
{
return SystemCoreClock;
} uint64_t microseconds_get_ticks(void)
{
uint32_t high;
uint32_t low;
// 这里的实现要注意确保中断发生时获取系统累计计数值的正确性
do
{
// 先缓存高位计数器
high = s_highCounter;
// 再读定时器实际计数值
low = ~SysTick->VAL & SysTick_LOAD_RELOAD_Msk;
} while (high != s_highCounter); // 保证缓存高位值与读实际低位值间隙中没有发生中断 return ((uint64_t)high << 24) + low;
} void SysTick_Handler(void)
{
s_highCounter++;
}

  当然还有很多具体 MCU 平台的各种定时器实现,因此这个项目会不断更新,也欢迎大家来参与贡献。

  至此,嵌入式里通用微秒(microseconds)计时函数框架设计与实现痞子衡便介绍完毕了,掌声在哪里~~~

欢迎订阅

文章会同时发布到我的 博客园主页CSDN主页知乎主页微信公众号 平台上。

微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。

痞子衡嵌入式:嵌入式里通用微秒(microseconds)计时函数框架设计与实现的更多相关文章

  1. 嵌入式 C 语言的可变参数表函数的设计

    首先在介绍可变参数表函数的设计之前,我们先来介绍一下最经典的可变参数表printf函数的实现原理.一.printf函数的实现原理在C/C++中,对函数参数的扫描是从后向前的.C/C++的函数参数是通过 ...

  2. 痞子衡嵌入式:嵌入式里串口(UART)自动波特率识别程序设计与实现

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是嵌入式里串口(UART)自动波特率识别程序设计与实现. 串口(UART)是嵌入式里最基础最常用也最简单的一种通讯(数据传输)方式,可以说 ...

  3. 痞子衡嵌入式:借助Serial Plot软件测量i.MXRT系列FlexSPI驱动Flash页编程执行时间

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT系列FlexSPI驱动Flash页编程执行时间. 痞子衡之前写过一篇文章 <串行NOR Flash的页编程模式对于量产 ...

  4. 痞子衡嵌入式:嵌入式MCU中通用的三重中断控制设计

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是嵌入式MCU中通用的三重中断控制设计. 我们知道在 MCU 裸机中程序代码之所以能完成多任务并行实时处理功能,其实主要是靠中断来调度的, ...

  5. 痞子衡嵌入式:改动i.MXRT1xxx里IOMUXC_GPR寄存器保留位可能会造成系统异常

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是改动i.MXRT1xxx里IOMUXC_GPR寄存器保留位可能会造成系统异常. 痞子衡的嵌入式技术交流群里有一位非常活跃的朋友(网名:文 ...

  6. 痞子衡嵌入式:在串口波特率识别实例里逐步展示i.MXRT上提升代码执行性能的十八般武艺

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在串口波特率识别实例里逐步展示i.MXRT上提升代码执行性能的十八般武艺. 恩智浦 MCU SE 团队近期一直在加班加点赶 SBL 项目 ...

  7. 痞子衡嵌入式:深扒IAR启动函数流程及其__low_level_init设计对函数重定向的影响

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IAR启动函数流程及其__low_level_init设计对函数重定向的影响. 上一篇文章 <IAR下RT-Thread工程自定义 ...

  8. 痞子衡嵌入式:大话双核i.MXRT1170之在线联合调试双核工程的三种方法(IAR篇)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是i.MXRT1170下在线联合调试双核工程的方法(基于IAR). 前段时间痞子衡写过一篇<双核i.MXRT1170之单独在线调试从 ...

  9. 痞子衡嵌入式:超级好用的可视化PyQt GUI构建工具(Qt Designer)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是PyQt GUI构建工具Qt Designer. 痞子衡开博客至今已有好几年,一直以嵌入式开发相关主题的文章为主线,偶尔穿插一些其他技术 ...

随机推荐

  1. 《IDA Pro 代码破解解密》笔记一

    博客地址:http://blog.csdn.net/qq1084283172/article/details/53158970 return 0;  C语言返回0语句的汇编形式 Intel-32汇编: ...

  2. C#-窗体鼠标穿透

    #region 窗体鼠标穿透 private const uint WS_EX_LAYERED = 0x80000; private const int WS_EX_TRANSPARENT = 0x2 ...

  3. Portswigger web security academy:Stored XSS

    Portswigger web security academy:Stored XSS 目录 Portswigger web security academy:Stored XSS Stored XS ...

  4. Redis数据结构—链表与字典的结构

    目录 Redis数据结构-链表与字典的结构 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 Redis字典的使用 Re ...

  5. ThinkPHP5查询-select与find理解

    出现问题 在tp5框架中判断select查询结果是否为空时,无论查询条件是否满足,判断查询结果都不为空 解析问题 select查询的是多条数据,若查询数据为空,则返回一个空的二维数组 array(ar ...

  6. 多线程-4.wait() notify() notifyAll() 生产者消费者模型

    1.wait()方法 该方法继承于Object类.在调用obj.wait()方法后,当前线程会失去obj的锁.待其他线程调用obj.notify()或notifyAll()方法后进入锁等待池,争抢到锁 ...

  7. Spring MVC工作原理及源码解析(四) ViewResolver实现原理及源码解析

    0.ViewResolver原理介绍 根据视图的名称将其解析为 View 类型的视图,如通过 ModelAndView 中的视图名称将其解析成 View,View 是用来渲染页面的,也就是将 Mode ...

  8. Java_常用类API之一

    Math类 Math类中包含一些对数据进行数学运算的方法,而该类中的方法全都是静态的.像这样的类称之为工具类. 1 public static int abs(int a) 2 对一个数据求绝对值 3 ...

  9. Jenkins 基础篇 - 基础设置

    站点设置 刚搭建好 Jenkins 环境,你还需要做一些简单设置,让我们的 Jenkins 看起来是这么一回事,特别是你要用于生产环境的时候.首先就是域名配置,如果你为 Jenkins 服务分配了一个 ...

  10. Introduction to x265 Rate Control Algorithm

    The rate control in x265 is the same as x264's implementation, which is mostly empirical. It include ...