在单片机中实现一个异步的一次性定时器,通常可以使用硬件定时器模块或者嵌入式操作系统中的定时器功能。以下是一个基于硬件定时器的实现方法,适用于没有操作系统的情况:
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,这个值是很多操作系统的参考值。
很多搞嵌入式的都很惧怕动态分配内存,总觉得万一分配失败,功能就不正常了。有关嵌入式的动态内存分配,以后再研究。