用定时器在单片机中实现一次性回调函数执行

在单片机中实现一个异步的一次性定时器,通常可以使用硬件定时器模块或者嵌入式操作系统中的定时器功能。以下是一个基于硬件定时器的实现方法,适用于没有操作系统的情况:

1.硬件定时器配置:

选择单片机中的一个硬件定时器(如16位或32位定时器)。

配置定时器的时钟源,预分频器,计数模式等,以便产生所需的时间基准。

设置定时器的比较值或重装载值,以确定定时的时长。

2.中断服务例程(ISR)编写:

编写定时器的中断服务例程,当定时器计数达到设定值时,会产生中断。

在中断服务例程中,清除中断标志,停止定时器,以便将其复用为一次性定时器。

调用预先定义好的回调函数,执行定时器到时后的相关任务。

3.回调函数定义:

定义一个函数指针类型,用于指向定时器事件到时的回调函数。

实现具体的回调函数,执行定时器到时后的操作。

4.定时器初始化和启动:

在主程序中初始化定时器,设置定时时间,并将回调函数地址赋值给相应的函数指针。

启动定时器,开始计时。

5.异步处理:

由于定时器中断是异步发生的,因此不会影响CPU的其他工作。

当CPU处理其他任务时,定时器会在后台独立运行,当达到设定的时间后,触发中断,执行回调函数。

示例代码(伪代码):

// 定义回调函数类型typedef void (*TimerCallback)(void);// 定时器中断服务例程void Timer_ISR(void) {// 清除中断标志Clear_Timer_Flag();// 停止定时器Stop_Timer();// 调用回调函数if (callback != NULL) {callback();}}// 启动一次性定时器void Start_OneShot_Timer(uint32_t timeout, TimerCallback cb) {// 设置定时器比较值或重装载值Set_Timer_Value(timeout);// 设置回调函数callback = cb;// 启动定时器Start_Timer();// 使能定时器中断Enable_Timer_Interrupt();}// 主程序int main(void) {// 初始化硬件Initialize_Hardware();// 设置一次性定时器,10秒后执行Task函数Start_OneShot_Timer(10000, Task);// 主循环while (1) {// CPU可以执行其他任务Perform_Other_Tasks();}}// 定时器到时后的任务void Task(void) {// 执行定时任务}

以上例子实现一个常用的定时器功能,不过函数只能执行一次,并且只能绑定一个函数。如果要绑定多个函数,如何实现呢?

要实现多个定时器事件,每个事件在特定时间触发不同的回调函数,可以通过以下方法:

1定时器管理:

如果单片机支持多个定时器,可以使用多个定时器模块来独立管理每个事件。

如果只有单个定时器可用,可以将其配置为周期性触发,然后使用一个软件计数器来跟踪每个事件的时间。

2事件结构:

创建一个事件结构体,用于存储每个事件的参数,包括剩余时间、回调函数和事件状态等。

3事件列表:

维护一个事件列表,用于存储所有注册的事件。

4定时器中断服务例程:

定时器中断服务例程中,遍历事件列表,更新每个事件的剩余时间。

检查是否有事件的剩余时间减到零,如果有的话,调用相应的回调函数,并更新事件状态。

5事件注册函数:

实现一个事件注册函数,允许用户添加新的事件到事件列表中,设置时间、回调等。

6事件处理函数:

实现一个事件处理函数,用于在定时器中断中调用,以处理到时事件。

下面是一个简化的伪代码示例,展示如何实现这个功能:

#include <stdbool.h>#include <stdint.h>// 定义回调函数类型typedef void (*TimerCallback)(void);// 事件结构体typedef struct {uint32_t timeout;           // 事件超时时间uint32_t remainingTime;     // 剩余时间TimerCallback callback;     // 回调函数bool active;                // 事件是否激活} TimerEvent;// 假设最多支持10个同时进行的事件#define MAX_TIMER_EVENTS 10TimerEvent timerEvents[MAX_TIMER_EVENTS];// 初始化事件列表void InitializeTimerEvents(void) {for (int i = 0; i < MAX_TIMER_EVENTS; i++) {timerEvents[i].active = false;}}// 注册一个新的事件bool RegisterTimerEvent(uint32_t timeout, TimerCallback callback) {for (int i = 0; i < MAX_TIMER_EVENTS; i++) {if (!timerEvents[i].active) {timerEvents[i].timeout = timeout;timerEvents[i].remainingTime = timeout;timerEvents[i].callback = callback;timerEvents[i].active = true;return true; // 注册成功}}return false; // 事件列表已满}// 定时器中断服务例程void Timer_ISR(void) {for (int i = 0; i < MAX_TIMER_EVENTS; i++) {if (timerEvents[i].active) {if (--timerEvents[i].remainingTime == 0) {// 时间到,执行回调函数timerEvents[i].callback();// 根据需要,可以在这里禁用事件或重新设置时间timerEvents[i].active = false;}}}// 清除中断标志Clear_Timer_Flag();}// 主程序int main(void) {// 初始化硬件Initialize_Hardware();InitializeTimerEvents();// 注册两个事件RegisterTimerEvent(1000, Task1); // 1秒后执行Task1RegisterTimerEvent(5000, Task2); // 5秒后执行Task2// 启动定时器Start_Timer();// 使能定时器中断Enable_Timer_Interrupt();// 主循环while (1) {// CPU可以执行其他任务Perform_Other_Tasks();}}// 定时器到时后的任务void Task1(void) {// 执行任务1}void Task2(void) {// 执行任务2}

在这个示例中,我们使用了一个软件事件列表来管理多个定时器事件。每次定时器中断发生时,我们都会更新所有活动事件的剩余时间,并在必要时调用回调函数。注意,这个示例假设定时器的分辨率足够高,可以用来实现这些短时间的延迟。如果定时器分辨率不够,可能需要在软件中进行更精细的时间管理。

上述实现,可以实现多个回调函数的注册,并且每个回调函数只运行一次,但是问题来了,示例代码中预先指定了10个事件,如果注册的事件数量大于10个怎么办?学过数据结构,链表可以尝试使用。

如果您不想为事件列表预分配固定大小的内存,并且希望动态地管理事件,可以使用链表来实现。链表允许您根据需要动态地创建和销毁事件,从而更加灵活地使用内存。

以下是使用链表实现定时器事件的伪代码示例:

#include <stdbool.h>#include <stdint.h>#include <stdlib.h>// 定义回调函数类型typedef void (*TimerCallback)(void);// 事件节点结构体typedef struct TimerEventNode {uint32_t timeout;           // 事件超时时间uint32_t remainingTime;     // 剩余时间TimerCallback callback;     // 回调函数struct TimerEventNode *next; // 指向下一个节点的指针} TimerEventNode;// 链表头节点TimerEventNode *head = NULL;// 注册一个新的事件bool RegisterTimerEvent(uint32_t timeout, TimerCallback callback) {TimerEventNode *newNode = (TimerEventNode *)malloc(sizeof(TimerEventNode));if (newNode == NULL) {return false; // 内存分配失败}newNode->timeout = timeout;newNode->remainingTime = timeout;newNode->callback = callback;newNode->next = NULL;// 将新节点添加到链表末尾if (head == NULL) {head = newNode;} else {TimerEventNode *current = head;while (current->next != NULL) {current = current->next;}current->next = newNode;}return true; // 注册成功}// 定时器中断服务例程void Timer_ISR(void) {TimerEventNode *current = head;TimerEventNode *prev = NULL;while (current != NULL) {if (--current->remainingTime == 0) {// 时间到,执行回调函数current->callback();// 从链表中移除当前节点if (prev == NULL) {head = current->next;} else {prev->next = current->next;}free(current); // 释放内存current = (prev == NULL) ? head : prev->next;} else {prev = current;current = current->next;}}// 清除中断标志Clear_Timer_Flag();}// 主程序int main(void) {// 初始化硬件Initialize_Hardware();// 注册两个事件RegisterTimerEvent(1000, Task1); // 1秒后执行Task1RegisterTimerEvent(5000, Task2); // 5秒后执行Task2// 启动定时器Start_Timer();// 使能定时器中断Enable_Timer_Interrupt();// 主循环while (1) {// CPU可以执行其他任务Perform_Other_Tasks();}}// 定时器到时后的任务void Task1(void) {// 执行任务1}void Task2(void) {// 执行任务2}

在这个示例中,我们使用了一个单向链表来管理定时器事件。每个事件都是一个节点,包含指向下一个节点的指针。在定时器中断服务例程中,我们遍历链表,更新每个事件的剩余时间,并在事件超时执行回调函数。事件执行后,我们从链表中移除并释放该节点的内存。这种方法允许您根据需要动态地添加和删除事件,而不会浪费内存。

还有一个点要注意,中断回调函数执行事件不应该超过定时器的最小粒度,要求我们最小粒度定义不能太小,比如最小粒度定义为10ms,这个值是很多操作系统的参考值。

很多搞嵌入式的都很惧怕动态分配内存,总觉得万一分配失败,功能就不正常了。有关嵌入式的动态内存分配,以后再研究。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/738124.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络 路由器 链路层 MTU 最大传送单元 网络层

一个链路层数据帧能够承载的最大数据量&#xff0c;被称为最大传送单元&#xff08;MTU&#xff09;。 因为IP数据报&#xff0c;被封装在链路层的帧中&#xff0c;因此链路层的MTU&#xff0c;严格地限制了IP数据报的长度&#xff0c;并且在IP数据报的源与目的路径上的各段链…

全面的 DevSecOps 指南:有效保护 CI/CD 管道的关键注意事项

数字化转型时代带来了对更快、更高效、更安全的软件开发流程的需求。DevSecOps&#xff1a;一种将安全实践集成到 DevOps 流程中的理念&#xff0c;旨在将安全性嵌入到开发生命周期的每个阶段 - 从代码编写到生产中的应用程序部署。DevSecOps 的结合可以带来许多好处&#xff0…

交叉编译x264 zlib ffmpeg以及OpenCV等 以及解决交叉编译OpenCV时ffmpeg始终为NO的问题

文章目录 环境编译流程nasm编译x264编译zlib编译libJPEG编译libPNG编译libtiff编译 FFmpeg编译OpenCV编译问题1解决方案 问题2解决方案 总结 环境 系统&#xff1a;Ubutu 18.04交叉编译链&#xff1a;gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu 我的路径/opt/toolch…

NLP:bert下载与使用

没办法&#xff0c;模型精度还是不够&#xff0c;只能暂时弃用text2vec。然后我在github上发现了中文文本处理的老大哥&#xff1a;bert python使用bert可以参考这篇博客&#xff1a;博客 但是篇博客又出现了上一节的问题&#xff1a; We couldnt connect to https://hugging…

数据库相关理论知识(有目录便于直接锁定相关知识点+期末复习)

一&#xff0c;数据模型&#xff0c;关系型数据模型&#xff0c;网状模型&#xff0c;层次模型 1.数据库模型是用来描述和表示现实世界中的事物、概念以及它们之间的关系的工具&#xff0c;但是并不是越专业越好&#xff0c;还要平衡它的模型的复杂性、通用性和成本效益等因素…

【Vue】vue文件外通过ref获取元素

问题描述 我在abc.js写HomeView.vue的逻辑代码我要在abc.js里&#xff0c;获取HomeView.vue组件里ref为test的元素 码 abc.js 导出test import { ref } from vue const test ref() export {test } HomeView.vue 引入test注意&#xff1a; ref不能加冒号&#xff0c;也就…

Java零基础入门-Comparable vs Comparator(上)

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流学习&#xff0c;互相学习&#xff0c;才能成长的更快&#xff0c;对吧。 我是一名java开发&#xff0c;所以日常接触到最多…

System是什么?为什么不能直接输出null?

在看学习下面的知识前&#xff0c;得先对java核心类库有个大致的了解&#xff0c;详情可参考链接 java基本概念-扩展点-CSDN博客 1、System 1.1 System是什么&#xff1f; System是一个类&#xff0c;它包含了一些有用的属性和方法。 1.2 System实现的功能 &#xff08;1&…

linux下如何hook第三方播放器的视频数据?

背景 作为显卡生产商,当用户使用我们的显卡硬解码播放视频时,如果出现比如花屏等问题,为了快速确定问题原因,我们需要一个工具来帮助判断出问题是出在原始视频端,亦或者是应用程序端,亦或者是显卡端。因此我们需要一种方法,来对目标播放器程序进行监控,并捕获到视频源的…

【深度学习】换脸新科技,InstantID: Zero-shot Identity-Preserving Generation in Seconds

论文&#xff1a;https://arxiv.org/abs/2401.07519 代码:https://github.com/InstantID/InstantID demo&#xff1a;https://huggingface.co/spaces/InstantX/InstantID 文章目录 1 引言2 相关工作2.1 文本到图像扩散模型2.2 主题驱动的图像生成2.3 保持ID的图像生成 3 方法3.…

简易线程池的实现

Worker的实现 总体来说我们首先需要实现一个Worker线程类&#xff0c;让他的线程实例拿取任务然后执行任务。 //worker主要是从jobs中拿工作&#xff0c;拿不到等&#xff0c;拿到了执行。class Worker implements Runnable{private volatile boolean running true;Overrid…

工程经济学一

工程项目&#xff1a; 工程项目是指为完成某一独特的产品、服务或任务所做的一次性努力。又称建设项目、基本建设项目、投资建设项目或建设工程项目。 工程项目前评价&#xff1a; 是项目建议书和可行性研究阶段进行的&#xff0c;是项目开工前对 拟建项目的必要性和可能性进…

删除、创建、验证Kafka安装自带的__consumer_offsets topic

删除Kafka自带Topic 一般情况下&#xff0c;你删除Kafka自带的__consumer_offsets topic&#xff0c;会报错提示不能删除。 倔强的你直接找到zookeeper删掉了它&#xff0c;list查看确实没有这个topic了&#xff0c;但是这会导致消费者和偏移量无法记录。 创建Kafka自带的Topi…

zabbix5监控tomcat

zabbix tomcat客户端配置 1、配置tomcat catalina.sh文件 CATALINA_OPTS"$CATALINA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port12345 -Dcom.sun.management.jmxremote.authenticatefalse -Dcom.sun.management.jmxremote.sslfalse -Djav…

smart-doc 社区 Committer 晋升公告

我们非常荣幸地宣布&#xff0c;经过 PMC 委员会的提名和讨论&#xff0c;社区成员李星志&#xff08;GitHub ID: netdied&#xff09;、陈琪&#xff08;GitHub ID: chenqi146&#xff09;和李兵&#xff08;GitHub ID: abing22333&#xff09;正式晋升为同程旅行 smart-doc 开…

Vue3全家桶 - Vue3 - 【3】模板语法(指令+修饰符 + v-model语法糖)

一、模板语法 主要还是记录一些指令的使用和vue2的区别&#xff1b;vue3指令导航&#xff1b; 1.1 v-text 和 v-html 指令的区别&#xff1a; v-text&#xff1a; 更新元素的文本内容&#xff1b;v-text 通过设置元素的 textContent 属性来工作&#xff0c;因此它将覆盖元素…

数据结构:静态链表(编程技巧)

链表的元素用数组存储&#xff0c; 用数组的下标模拟指针。 一、理解 如果有些程序设计语言没有指针类型&#xff0c;如何实现链表&#xff1f; 在使用指针类型实现链表时&#xff0c;我们很容易就可以直接在内存中新建一块地址用于创建下一个结点&#xff0c;在逻辑上&#x…

3、设计模式之工厂模式

工厂模式是什么&#xff1f;     工厂模式是一种创建者模式&#xff0c;用于封装和管理对象的创建&#xff0c;屏蔽了大量的创建细节&#xff0c;根据抽象程度不同&#xff0c;主要分为简单工厂模式、工厂方法模式以及抽象工厂模式。 简单工厂模式 看一个具体的需求 看一个…

重拾C++之菜鸟刷算法第11篇---回溯算法(上)

今天是个好日子&#xff0c;二月二龙抬头&#xff0c;龙年龙日龙抬头&#xff0c;顺风顺水好兆头&#xff0c;万事万物开好头&#xff0c;金银珠宝往家里走&#xff01;offer往家走&#xff01; 回溯算法 回溯法可以解决的问题 组合问题&#xff1a;N个数里面按照一定规则找…

华为交换机创建端口组

文章目录 创建永久端口组解散永久端口组创建临时端口组总结 创建永久端口组 [SW3]port-group ?STRING<1-32> Port-group name \\表示用包含1到32个字符的字符串&#xff0c;给端口组其个名字&#xff0c;这种是创建永久组的group-member Add port to current port-g…