# ltx裸机调度器 **Repository Path**: TiX233/ltx ## Basic Information - **Project Name**: ltx裸机调度器 - **Description**: 轻量级事件驱动单片机裸机调度器,支持空闲休眠tickless,支持SMP同构多核自动均衡负载 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 9 - **Forks**: 4 - **Created**: 2025-12-23 - **Last Updated**: 2026-09-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ltx 裸机调度器 V4 目录: - [ltx 裸机调度器 V4](#ltx-裸机调度器-v4) - [一、项目简介](#一项目简介) - [二、基础组件](#二基础组件) - [1、闹钟/Alarm](#1闹钟alarm) - [2、话题/Topic](#2话题topic) - [三、拓展组件](#三拓展组件) - [1、app/应用](#1app应用) - [2、script/脚本](#2script脚本) - [3、debug/调试](#3debug调试) - [~~4、lock/锁~~](#4lock锁) - [~~5、event\_group/事件组~~](#5event_group事件组) - [四、移植](#四移植) - [五、空闲休眠与 tickless](#五空闲休眠与-tickless) - [六、时间戳调度](#六时间戳调度) ## 一、项目简介 这是一个轻量级 (RAM: 72 Byte, ROM: 约 0.6kB) 的高性能事件驱动裸机调度器,异步友好,支持空闲休眠与 tickless,支持多核 SMP 调度自动负载均衡,使用时间戳调度,即便使用高精度时间单位也不需要产生那么高频率的中断。 目前已在如下个人开源项目中使用,具体如下: ltx V1: * [我的世界红石_数字方案](https://oshwhub.com/realtix/minecraft_redstone_digital) * [反应力测试器](https://oshwhub.com/realtix/human_benchmark) * [我的世界米家拉杆](https://oshwhub.com/realtix/lever) * [歌曲封面显示唱片机](https://oshwhub.com/realtix/cover_display) ltx V2: * [裸机空闲休眠时钟(见 git 历史提交)](https://oshwhub.com/realtix/idle_sleep_clock) ltx V3: * [裸机空闲休眠时钟](https://oshwhub.com/realtix/idle_sleep_clock) * [力反馈方向盘手柄,带挡杆](https://oshwhub.com/realtix/project_pyptyrni) * [ctx(作为其配套调度器)](https://github.com/TiX233/ctx) ltx V4: * [ctx(作为其配套单/多核调度器)](https://github.com/TiX233/ctx) ltx V1 由软件多定时器发展而来,通过加入闹钟和话题组件来提供事件驱动能力,但是它与大多数裸机调度器一样,调度器需要在主循环不断遍历活跃组件的事件链表,时间复杂度为 O(n),不仅响应性会降低,还无法提供高效的空闲休眠能力。 ltx V2 对 V1 进行了重构。V2 发布事件改为将其推入就绪链表队列,主循环不断弹出就绪话题并调用其回调即可,效率优化至 O(1)。事件响应性更高且可以更快判断系统是否空闲。 V2 和 V3 可以将调度器跑在软中断内(如 pendsv),此时主循环相当于空闲任务,便于空闲休眠。 ltx V3 对 V2 的闹钟部分进行了重构,删除了软件定时器,内建 tickless 支持。在 V2 中,systick 遍历闹钟还是 O(n),耗时久且不利于 tickless,所以 V3 将闹钟改为顺序存储,每个闹钟存储自身与前一个闹钟的时间差,那么 systick 只需弹出首(几)个闹钟节点即可,且进行 tickless 操作时只需要获取首个节点的倒计时即可,效率更高。 ltx V4 优化了空闲机制,不再依赖软中断,将休眠时间值交与外部进行控制,不再由调度器直接操作硬件定时器实现,并且改为时间戳调度,不需要提供 systick 中断,提高拓展性且适合细粒度时间。支持同构多核 SMP 调度,自动均衡负载,确保事件不被多核同时弹出以及避免事件回调被多核重入。并且调度器可以跑在 RTOS 的一个或多个线程内。 **性能:** | 组件操作 | 时间复杂度 | 备注 | | - | - | - | | topic 发布 | O(1) | - | | topic 弹出 | O(1) | - | | alarm 添加 | O(n) | 闹钟为顺序存储,所以如果您的任务比较频繁,那么插入时只需要遍历头几个节点就能插入,接近 O(1),对于不频繁的任务,那么很久才一次 O(n) 完全不会增加多少负担 | | alarm 删除 | O(1) | 小概率会有闹钟刚弹出就立即删除的操作,此时闹钟 topic 已经推入事件队列,从事件队列删除这个 topic 需要 O(n) | | alarm 弹出 | O(1) | - | ## 二、基础组件 ### 1、闹钟/Alarm 用于设置一个在一定 tick 后发布事件调用回调的闹钟。 可用于超时检查比如按键消抖,或者配合无栈协程作为非阻塞延时。 ### 2、话题/Topic > 建议使用 [ctx](https://github.com/TiX233/ctx) 的 `wait_topic` 组件。 话题组件的发布订阅机制用于提供事件驱动能力。与其他裸机框架的事件不同,这里的话题是一个单独的组件而并不依附于任务存在。 可用于数据发布,例如中断处理完数据后发布话题,那么所有订阅了这个话题的订阅者回调都会被调用,通过订阅与取消订阅,可方便地对数据打印进行开关。 闹钟到时也是通过发布话题通知订阅者实现的,所以可以实现对某些定时任务进行 hook 操作 ## 三、拓展组件 ltx 提供的 `app`、`脚本`、`事件组` 等等额外拓展功能组件皆由上述两个基础组件组合而来。 > 目前拓展组件均未来得及适配 ltx V4,需要更好的开发体验请使用 ctx。 ### 1、app/应用 通过对软件定时器的封装,抽象出周期任务 task 以及分离各业务的 app。 app 与 task 都有四个运行状态变更接口,分别为 `初始化`、`暂停`、`继续` 以及 `销毁`。 例如可以将采集传感器的内容放在一个 app 内,创建多个 task 来定期采集数据,那么外部就可以较为分明地对其进行启停。在 `ltx_cmd.c` 中有一个可供用户调试 app 的 `ltx_app` 命令,通过这个命令,可以用串口来打印所有 app 及其 task,并对其进行启停操作,便于调试 ### 2、script/脚本 脚本组件原设计用于执行特定的序列如显示屏初始化这种需要多次发送数据的固定步骤场景, 现已丰富为类似于 无栈协程 的存在,配合 ltx 调度器,可实现步骤间非阻塞延时以及新步骤非阻塞等待话题发布并设置等待超时时间,异步友好 大部分场景可取代 app 组件提供的 task 这一定时轮询任务功能, 通过设置步骤间延时为 0 tick,可实现仅出让单次调度轮询,而非出让一整个时间片(1 ms) 建议使用 [ctx](https://github.com/TiX233/ctx),不必手搓状态机。 ### 3、debug/调试 内含两个部分,分别为 `ltx_cmd.c` 和 `ltx_param.c`,前者用于创建一些串口调试命令,后者用于创建一些可供串口命令修改的参数变量。 通过自定义命令,可控制单片机的运行状态,比如暂停某些 app 等,也可依赖发布订阅机制实现数据更新后的自动打印,在 `ltx_cmd.c` 中提供的 `/print` 命令有一个 `heart_beat` 样例,用来每秒打印心跳,您可参考该样例来设置自己的订阅数据打印; 如果您需要经常修改一些参数如尝试某些不同的 pid 参数,那么也无需重新烧录,在 `ltx_cmd.c` 中提供了一个 `/param` 命令,该命令可更改 `ltx_param.c` 中指向的自定义数据进行读写; 所有的自定义命令可在 `ltx_cmd.c` 中查看,也可开机后给单片机发送 `/help` 命令来列出所有命令,您也可以参考这些命令创建一些方便调试自定义命令。 ### ~~4、lock/锁~~ > 功能不完善,建议使用 [ctx](https://github.com/TiX233/ctx) 的 互斥锁 组件。 用于对某些资源进行上锁操作,例如 spi 正在 dma 刷屏,则可在刷屏前上锁,在发送完成回调中解锁。 锁有超时机制,当锁超时后,会调用用户自定义超时回调,用户可以在这里解锁资源与锁,例如强制停止 dma 发送。 锁在解锁时会发布一个话题。 目前常用于按键消抖,或者作为一个配置更简单的闹钟,因为 V2 及之后的版本的闹钟配起来很麻烦,而且锁有超时回调 ### ~~5、event_group/事件组~~ > 功能不完善,建议使用 [ctx](https://github.com/TiX233/ctx) 的 事件组 组件。 可设置等待超时时间以及 31 个等待的事件,设计用于等待所有硬件初始化完成,使用一个 uint32 的整数来存储所有事件,最高位用于判断事件组回调是否为超时所调用。目前仅支持事件与,也就是所有设定事件位都触发才会触发事件组回调,未来可能会增加事件或能力。 ## 四、移植 如果仅需要基础组件,那么只需要移植 `ltx.c` 和 `ltx.h` 即可。 因为是裸机调度器,所以移植几乎没有难度,最快仅需三步即可: **1、配置进出临界区宏** 在 `ltx_config.h` 中选择对应的硬件架构,也就是解除所用的架构的头文件注释: ```c // 选择一个对应架构的配置文件 #include "ltx_arch_arm_cortex_m.h" // #include "ltx_arch_xxx.h" ``` 如果使用的是暂时没有支持的架构,那么至少需要根据你的环境修改相应的进出临界区宏: ```c #define _LTX_CRITICAL_INTO() do{__disable_irq(); __DMB();}while(0) #define _LTX_CRITICAL_OUTO() do{__DMB(); __enable_irq();}while(0) ``` **2、系统时间戳** 在架构配置中提供获取时间戳宏: ```c // 系统时间戳获取 #define ltx_Sys_get_tick() (TickType_t)HAL_GetTick() ``` **3、在 main.c 最后调用调度器** ```c ltx_Sys_scheduler(0); ``` **例如:** ```c #include "main.h" #include "ltx.h" // #include "myAPP_system.h" // #include "myAPP_device_init.h" // ... int main(void){ // 初始化外设 // ... // 创建组件 // ... /* 如果想拥有更强的业务隔离与管理能力,那么还可以额外引入 ltx_app 组件 // 创建 app // 系统调试 app ltx_App_init(&app_system); // 初始化 app ltx_App_resume(&app_system); // 运行 app // 硬件初始化 app ltx_App_init(&app_device_init); // 初始化 app ltx_App_resume(&app_device_init); // 运行 app */ // 运行调度器 ltx_Sys_scheduler(0); // 调度器内部有一个无限循环,所以后续代码不会被运行 while(1); } ``` ## 五、空闲休眠与 tickless 如果使用的是目前已支持的架构,那么只需要在 `ltx_config.h` 中解除相关头文件的注释即可: ```c // 选择一个对应架构的配置文件 #include "ltx_arch_arm_cortex_m.h" // #include "ltx_arch_xxx.h" ``` 然后打开 `ltx_config.h` 中的空闲任务开关宏(取消其注释): ```c // 需要空闲任务则打开此宏 #define ltx_cfg_USE_IDLE_SLEEP ``` 如果是暂未支持的架构,那么可以根据现有配置文件修改,一般需要提供如下能力: ```c // 恢复调度器执行,默认为唤醒 cpu // 如果调度器跑在 rtos 的一个线程,那么可以设置为发送信号量 #define _LTX_SET_SCHEDULE_FLAG() __SEV() // 实际休眠时间可以比 ticks 小,因为醒来后调度器还会判断一次时间戳,然后继续传递新值要求休眠新 ticks // 如果实际休眠时间比 ticks 大,调度器也能正确处理需要弹出的 alarm,但是会影响任务实时性 // 如果调度器是跑在 rtos 的一个线程内,那么可以改成等待信号量,超时时间就用 sleep_ticks,并将 _LTX_SET_SCHEDULE_FLAG(); 设置为发送信号量 #define ltx_hook_idle_in(core_id, sleep_ticks) do{ \ /* printf("---(%d)idle in, slp: %d---(%d)\n", core_id, sleep_ticks, ltx_Sys_get_tick()); */ \ /* 根据 sleep_ticks 设置硬件定时器中断,比如 lptimer 或者 RTC */ \ /* xxxxx(); */ \ /* 等待唤醒事件 */ \ __WFE(); \ /* printf("---(%d)idle out, ret: %ld---(%d)\n", core_id, dwWaitResult, ltx_Sys_get_tick()); */ \ }while(0) ``` ## 六、时间戳调度 V4 改用时间戳调度,不再由调度器管理时间,不再需要在 systick 里面跑一个系统嘀嗒,并且不影响原有实时性。 用户根据自身平台提供 `ltx_Sys_get_tick` 即可,可以用更细粒度的时间单位(比如微秒)作为 1tick 而不用产生那么高频率的中断 一般提供 `uint32_t` 的毫秒时间戳即可,不必担心溢出,除非您打算延时超过 `0xFFFFFFFE` 毫秒(约 50 天) 在 32 位机器上,64 位数据的计算耗时是 32 位的 5 倍以上,所以这里特地规避了一般时间戳调度需要提供 64 位时间戳的情况,并且不用担心溢出 总之根据业务中最大的延时时间确定类型定即可,假如业务里最大的延时不超过 `254` 毫秒,那么您甚至可以使用 `uint8_t` 作为 `TickType_t` 这并不代表 ltx V4 是直接将延时时间存储在闹钟内,那样的话 tickless 醒来后要遍历所有闹钟删减他们的延时,时间复杂度会来到 O(n) V4 闹钟节点只会存储距离前个闹钟节点的触发时间间隔,所以 tickless 醒来后只需要弹出首(几)个闹钟即可,时间复杂度是优雅的 O(1) 为什么闹钟节点不存储被触发的时间戳?因为如果时间戳溢出会导致闹钟链表顺序错乱