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

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

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 10

TimerEvent 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秒后执行Task1

    RegisterTimerEvent(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秒后执行Task1

    RegisterTimerEvent(5000, Task2); // 5秒后执行Task2

    // 启动定时器

    Start_Timer();

    // 使能定时器中断

    Enable_Timer_Interrupt();

    // 主循环

    while (1) {

        // CPU可以执行其他任务

        Perform_Other_Tasks();

    }

}

// 定时器到时后的任务

void Task1(void) {

    // 执行任务1

}

void Task2(void) {

    // 执行任务2

}

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

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

2024.3.12 增加一个取消注册函数的操作。

// 取消注册一个事件
int UnregisterTimerEvent(TimerCallback callback) {
    TimerEventNode *current = head;
    TimerEventNode *prev = NULL;

    while (current != NULL) {
        if (current->callback == callback) {
            // 找到了匹配的事件
            if (prev == NULL) {
                // 要删除的节点是头节点
                head = current->next;
            } else {
                // 要删除的节点不是头节点
                prev->next = current->next;
            }
            free(current); // 释放内存
            return 0; // 取消注册成功
        }
        prev = current;
        current = current->next;
    }
    // 如果没有找到匹配的事件,返回错误代码
    return -1;
}

 在这个取消注册函数中,我们遍历链表来查找具有指定回调函数的事件节点。如果找到匹配的节点,我们就将其从链表中移除并释放内存。如果事件已经执行,它将从链表中移除,因此取消注册将返回-1表示失败。
请注意,这个取消注册函数是基于回调函数地址来匹配事件的。如果同一回调函数被注册了多次,这个函数将取消第一个匹配的事件。如果您需要基于其他条件来取消事件(例如,基于事件的数据或额外的标识符),您需要修改链表节点的结构以包含额外的信息,并在取消注册函数中使用这些信息来匹配正确的节点。
此外,由于定时器中断服务例程可能会并发访问链表,您需要在适当的时刻确保取消注册操作的安全性,例如,在禁用定时器中断的情况下执行取消注册操作。

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

作者:风一样的航哥

物联沃分享整理
物联沃-IOTWORD物联网 » 单片机定时器实现一次性回调函数执行

发表评论