找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3202

积分

0

好友

477

主题
发表于 2 小时前 | 查看: 3| 回复: 0

前面我们已经用链表和队列构建了异步事件的骨架。但在协作式微内核中,最大的禁忌就是阻塞死等。软件定时器的核心使命,就是彻底消灭 delay。它将所有的“延时”和“周期性动作”转化为非阻塞的“时间事件”,通过异步回调或发布事件的方式,实现多任务的周期性调度与超时控制。

很多人刚开始写单片机代码,最喜欢用 HAL_Delay(1000)

  • 后果:CPU 在这 1 秒内是在“发呆”,如果有串口数据来了,或者有紧急按键,系统完全没反应。这就是阻塞(Blocking)

这一章,我们要实现一个核心模块:软件定时器 (SoftTimer) —— 给系统装上心跳

我们要达到的目标是:在整个工程里,禁止出现任何 Delay 函数。

1. 核心设计思想:定时器即对象

回顾“句柄到对象”的思路。定时器不应该是一个全局数组里的索引(比如 Timer[0]),而应该是一个独立的对象。谁需要定时,谁就自己定义一个定时器对象。

1.1 定义定时器对象

我们在 framework_timer.h 中定义:

#include "utils_list.h"
#include <stdint.h>
#include <stdbool.h>

// 1. 定义回调函数原型
// context: 又是它!解决 C 语言没有 this 指针的关键
typedef void(*TimerCallback)(void* context);

// 2. 定时器对象
typedef struct {
    // 【继承】侵入式链表节点
    // 只要把这个结构体挂到链表上,它就“活”了
    ListNode node;

    // 属性
    uint32_t timeout_tick;  // 到期的时间点 (Absolute Time)
    uint32_t interval;      // 周期时间 (0 表示单次)
    bool is_running;        // 运行状态

    // 行为
    TimerCallback cb;       // 时间到了干什么?
    void* context;          // 传给回调的参数
} SoftTimer;

// 句柄定义
typedef SoftTimer* SoftTimerHandle;

2. 核心逻辑实现 (The Manager)

2.1 初始化与基准时间

framework_timer.c 中:

static List active_timers; // 存放所有正在运行的定时器

// 系统初始化时调用
void SoftTimer_Init() {
    List_Init(&active_timers);
}

// 获取当前系统滴答 (STM32中通常封装 HAL_GetTick)
static uint32_t Get_System_Tick() {
    return HAL_GetTick(); // 假设 1ms 一个 tick
}

2.2 启动与停止 (Start / Stop)

这就是面向对象的接口设计。用户只需要操作句柄。

// 启动定时器
void SoftTimer_Start(SoftTimerHandle h, uint32_t ms, bool repeat, TimerCallback cb, void* ctx){
    // 1. 如果已经在运行,先从链表移除,避免重复添加破坏链表结构
    SoftTimer_Stop(h);

    // 2. 填充属性
    h->interval = ms;
    h->timeout_tick = Get_System_Tick() + ms; // 计算绝对到期时间
    h->cb = cb;
    h->context = ctx;

    // 3. 标记重复模式(如果 interval > 0 也可以认为重复,看具体设计)
    // 这里为了逻辑清晰,如果 repeat 为 true,则 interval 必须 > 0
    if (repeat && h->interval == 0) h->interval = 1;
    h->interval = repeat ? h->interval : 0;

    h->is_running = true;

    // 4. 【关键】加入链表
    // 这里简单起见插到队尾。
    // 进阶优化:可以按 timeout_tick 排序插入,这样检查时只要看第一个,效率更高。
    List_AddTail(&active_timers, &h->node);
}

// 停止定时器
void SoftTimer_Stop(SoftTimerHandle h){
    if (h->is_running) {
        List_Del(&h->node); // 利用侵入式链表,O(1) 删除
        h->is_running = false;
    }
}

2.3 心跳驱动 (Update)

这是整个定时器系统的引擎。它需要在主循环中被频繁调用。

注意点:在遍历链表时删除节点(Callback 可能会调用 Stop)是危险的。所以我们要用安全的遍历方法。

// 放在主循环里调用
void SoftTimer_Update() {
    uint32_t current_tick = Get_System_Tick();
    ListNodeHandle pos, next;
    SoftTimerHandle timer;

    // 使用 safe 模式遍历:在处理当前节点前,先保存 next 指针
    // 这样即使当前节点被删除了,循环也能继续
    for (pos = active_timers.head.next, next = pos->next;
         pos != &active_timers.head;
         pos = next, next = pos->next) {

        // 还原对象指针
        timer = container_of(pos, SoftTimer, node);

        // 检查是否到期 (这里忽略了 49 天溢出翻转问题,实际工程需处理)
        if (current_tick >= timer->timeout_tick) {

            // 1. 执行回调
            if (timer->cb) {
                timer->cb(timer->context);
            }

            // 2. 处理周期性
            if (timer->interval > 0) {
                // 如果是周期的,更新下一次到期时间
                timer->timeout_tick += timer->interval;
            } else {
                // 如果是单次的,停止它(从链表移除)
                SoftTimer_Stop(timer);
            }
        }
    }
}

3. 定时器 + 事件系统

现在,我们把定时器和前面章节实现的事件系统结合起来。

最佳实践:不要在 SoftTimer 的回调函数里做繁重的业务逻辑,也不要直接操作硬件(除非是极其简单的 LED 翻转)。推荐模式:定时器回调只负责 Event_Post

实战演示:每 500ms 打印一次日志

// 1. 定义定时器对象 (放在全局或静态区,保证生命周期)
static SoftTimer log_timer;

// 2. 定时器回调
// 注意:这里我们演示“回调产生事件”的模式
void OnLogTimer(void* ctx){
    // 上下文 ctx 可以传任何东西,这里演示传一个 ID
    uint32_t timer_id = (uint32_t)ctx;

    // 发送一个超时事件到队列
    Event_Post(SIG_TIMEOUT, timer_id);
}

// 3. 之前的事件分发器
void Dispatch_Event(Event* e){
    switch (e->sig) {
        case SIG_TIMEOUT:
            // 真正的业务逻辑在这里处理
            printf("Timer %d Timeout! System Running...\n", e->param);
            break;

        // ... 其他 case
    }
}

int main(){
    HAL_Init();
    SoftTimer_Init(); // 初始化定时器链表

    // 4. 启动定时器:500ms,重复,回调是 OnLogTimer,参数是 1
    SoftTimer_Start(&log_timer, 500, true, OnLogTimer, (void*)1);

    // 进入主循环
    while (1) {
        // 驱动定时器 (检查有没有到期的)
        SoftTimer_Update();

        // 驱动事件 (检查有没有新事件)
        Event e;
        if (Event_Get(&e)) {
            Dispatch_Event(&e);
        }
    }
}

4. 进阶:为什么要这么麻烦?

你可能会问:“我直接在 TimerCallback 里 printf 不行吗?为什么要 Post Event 绕一圈?”

主要原因在于

  1. 栈深度与并发安全:虽然 SoftTimer_Update 在 Main 里跑,但如果你的业务逻辑很复杂(比如写 Flash、复杂的计算),直接在 Callback 里做会卡住 SoftTimer_Update 的循环,导致后续的定时器全部延迟。
  2. 统一入口与解耦:通过 Event_Post,所有的业务逻辑都汇聚到了 Dispatch_Event。哪怕以后你把 SoftTimer_Update 移到了一个高优先级的定时器中断里去跑(为了更高精度),你的业务逻辑代码(printf)一行都不用改,因为它依然是在主循环处理事件。这就是解耦!

5. 本章总结

现在,我们的微内核框架已经具备了雏形:

  1. 数据层ListNode, RingBuffer链表作为基础数据结构是理解定时器管理的基石。
  2. 通信层Event_Post, Event_Get事件系统让定时器与业务逻辑实现完美解耦。
  3. 时间层SoftTimer, SoftTimer_Update

你的主循环现在看起来非常干净:

while(1) {
    SoftTimer_Update(); // 心跳
    if(Event_Get(&e)) Dispatch(&e); // 大脑
}

没有 HAL_Delay,没有乱七八糟的标志位判断。

这种基于微内核事件驱动的设计思路,为构建更复杂、响应更及时的嵌入式系统打下了坚实基础。如果你想了解更多关于操作系统内核设计的底层逻辑,或者探讨如何将其应用于具体项目,欢迎在云栈社区的嵌入式版块交流讨论。




上一篇:Nvidia M10 CCL揭秘:下一代AI服务器正交背板材料与2027年量产预期
下一篇:OpenClaw 2026.3.12 版本发布:控制台重构、安全补丁与K8s部署支持
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-15 15:29 , Processed in 0.447472 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表