MCU通用微秒计时函数框架设计

发布时间:2023-02-01  

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


在 MCU 里要想实现精确计时,往往都是利用其内部硬件定时器。不同厂商的 MCU,其定时器设计与使用都不太一样。即使是同一 MCU 内,通常也会有好几种不同类型的定时器共存。


基于此,今天分享一种非常简单实用的通用计时函数框架。这个框架的目的是统一计时函数接口,并且在实现上将通用部分和硬件相关部分剥离开。这样你的嵌入式项目在使用这个框架时,可以无缝快捷地切换底层定时器。


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


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

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);

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)计时函数库实现

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)计时函数框架设计与实现便介绍完毕了。


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    作为选择结构的选择端口的控制量,此时图形代码窗口应有四个,并在枚举型控件中分别添加4个值:正弦波、三角波、方波和锯齿波,同时在每个图形代码窗口中放入所有波形显示控件的属性节点,并设置相应的布尔常数。  框图......
    开服务器,将VI重新打包时,打包后的VI所有数据缓存区都是置零的原始状态,重新接收数据,Chart图表曲线恢复良好。 (5)或者利用Chart控件的属性节点,通过编程方法定时将Chart图表......
    生成的句柄做为图形窗口句柄handles的成员被存储,对于第N个被创建的Activex控件,其句柄为handles.activexN,以后借用该句柄就可以通过函数来调用对应的Activex控件控件的属性设置可以通过双击控件或者右键菜单打开属性......
    (textArea1Buffer, TEXTAREA1_SIZE,'%2.4f', AdcResult); 第3个要注意的地方,也是跟上面textarea输出显示有关的问题。 在它的属性那里有个Auto-size......
    之间的DataSocket通信。 在控件的DataSocket Connection属性的配置对话框中可以设置该控件的DataSocket URL地址,地址的格式如下:dstp......
    组件将串口添加进入项目。 6.点击鼠标右键,修改串口模块的属性,包括 波特率等,本实验采用默认。 7.双击串口模块,添加数据接收中断函数 8.添加按钮模块,控制STM32开发板LED灯的亮灭。 9.好的......
    用户需要的控制量或显示量将它们放在适当的位置上再加上一些简单的图形及文字修饰就构成了与实际仪器类似的面板。通过图3可以看出通过调用控件面板中的列表框控件、字符串输入与字符串显示控件以及布尔控件,对它们的属性......
    模板我们这里不做赘述,重点关注 TouchGFX Mixins 类模板。TouchGFX 的 Mixins 类模板用于拓展控件的功能,目前 TouchGFX v4.20.0 中有四个 Mixin 功能,分别......
    时曲线保存为图片;将实时曲线的数据保存为TXT文档以及实时温度超过报警值时报警。 本系统上位机串口通讯控件采用SPCOMM,该控件具有丰富的与串口通信密切相关的属性及事件,支持多线程;提供了对串口的各种操作。图形控件......
    实时曲线;将实时曲线保存为图片;将实时曲线的数据保存为 TXT 文档以及实时温度超过报警值时报警。本系统上位机串口通讯控件采用 SPCOMM,该控件具有丰富的与串口通信密切相关的属性及事件,支持......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>