【开源!】mx_timlib:一个超轻量级嵌入式定时器组件,低成本搞定多任务定时管理
目录
5、完整示例:LED 闪烁 + 传感器采样 + 单次任务 + 动态修改
一、前言
做嵌入式开发的时候,你是否遇到过这样的困扰?
- 项目里需要同时管理 LED 闪烁、传感器定时采样、按键消抖、UART 心跳包……四五个定时任务
- 不想引入 RTOS,觉得太重
- 又不想自己手写一堆
HAL_TIM_Base_Start和HAL_TIM_PeriodElapsedCallback - 于是代码里充斥着
if (HAL_GetTick() - last_led_tick > 500)这样的裸写判断
如果你有同感,那么 mx_timlib 就是为你设计的。
mx_timlib 是一款专为嵌入式场景打造的轻量级定时器轮片(timer wheel)组件。核心实现 300+ 行代码,零外部依赖,支持裸机和 RTOS 环境。支持两种使用模式:池化模式(Classic API)和用户自有模式(Extended API),可按需选择。
1、mx_timlib是什么?
mx_timlib 是一个时间驱动组件,v3.1 版本采用了双队列 + 有序链表架构,实现了 O(1) 的到期检查:
┌─────────────────────────────────────────────┐
│ 1ms 定时器中断 (mx_timer_irq_handler) │
│ │
│ tick++ │
│ 检查 active_list 头部(只检查一个!) │
│ 到期 → 移入 expired_queue (头插法) │
│ PERIODIC: 预计算下次 expiry │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 主循环 (mx_timer_task_handler) │
│ │
│ 从 expired_queue 弹出(头删法) │
│ 执行 callback(临界区外,允许耗时) │
│ PERIODIC: 回调执行完后重新插入 active_list │
└─────────────────────────────────────────────┘
关键设计:
- ISR 只检查
active_head一个节点是否为最新到期,不是遍历全表 - ISR 和主循环操作不同的队列,通过
expired_queue解耦 - 临界区宏只在主循环中使用,ISR 由硬件保证原子性
- PERIODIC 定时器的 re-arm 在回调执行之后完成,消除了 zombie 定时器问题
2、特性一览
|
特性 |
说明 |
|
O(1) 到期检查 |
有序链表,ISR 只检查表头,不遍历全表 |
|
双队列设计 |
active_list(计时)+ expired_queue(待执行),ISR 与主循环完全解耦 |
|
两种模式 |
周期模式(自动重载)和 单次模式(触发后自动停止) |
|
分布式定时器 |
用户可静态定义 ,不占用内部池,数量无限制 |
|
丰富的查询 API |
查剩余时间、暂停/恢复、动态修改周期、低功耗唤醒间隔 |
|
中断安全 |
active_list 和 expired_queue 分别由 ISR 和主循环操作,flag 同步 |
|
无外部依赖 |
纯 C 代码,编译后仅数 KB |
|
MIT 协议 |
可商用,可修改,开源无忧 |
3、支持的场景
- 裸机项目 — STM32、GD32、NXP 等 MCU,无需操作系统
- RTOS 环境 — FreeRTOS、RT-Thread 等,timer 作为系统心跳或任务内延时
- 低功耗场景 —
mx_timer_next_expiry()返回距下次唤醒的 tick 数,配合深度睡眠使用 - 任何有定时器外设的平台 — 只需提供 1ms 周期中断
4、项目地址:
觉得项目还可以的请帮博主点点⭐,后续有bug可以直接反馈给我
https://github.com/jin-le-le/mx_timlib.git
二、快速开始
1、移植临界区宏
在包含 mx_timlib.h之前定义两个宏,这是唯一的移植工作。
推荐方案:只屏蔽目标定时器中断(不影响其他外设)
// STM32,只屏蔽 TIM2,不影响 UART、SPI 等其他外设中断
#define MX_ENTER_CRITICAL() NVIC_DisableIRQ(TIM2_IRQn)
#define MX_EXIT_CRITICAL() NVIC_EnableIRQ(TIM2_IRQn)
#include "mx_timlib.h"
备选方案:全局关中断(简单兼容)
// STM32 HAL
#define MX_ENTER_CRITICAL() __disable_irq()
#define MX_EXIT_CRITICAL() __enable_irq()
// 裸机 ARM GCC
#define MX_ENTER_CRITICAL() __asm__("cpsid i")
#define MX_EXIT_CRITICAL() __asm__("cpsie i")
临界区宏只在主循环中使用,ISR 不需要临界区保护(由硬件保证原子性)。
2、初始化
#include "mx_timlib.h"
int main(void)
{
mx_timer_init(); // 只需调用一次
// ... 创建和启动定时器 ...
}
3、创建和启动定时器
方式一:Classic API(池化模式) :
固定数量定时器池,mx_timer_create() 返回 ID,简单直接:
// 周期定时器:LED 每秒翻转一次
mx_timer_id_t id = mx_timer_create(1000, MX_TIMER_MODE_PERIODIC, led_toggle, NULL);
mx_timer_start(id);
// 单次定时器:500ms 后触发一次,然后自动停止
mx_timer_id_t id2 = mx_timer_create(500, MX_TIMER_MODE_ONCE, uart_send, NULL);
mx_timer_start(id2);
方式二:Extended API(用户自有模式)
用户自己在 .c 文件中定义 mx_timer_t,不占用内部池:
static mx_timer_t my_sensor; // 用户自有
mx_timer_init_ex(&my_sensor, 200, MX_TIMER_MODE_PERIODIC, sensor_sample, NULL);
mx_timer_attach(&my_sensor);
mx_timer_start_ex(&my_sensor);
4、驱动定时器
在 1ms 定时器中断中调用:
void TIM2_IRQHandler(void)
{
mx_timer_irq_handler();
// 清除中断标志(根据具体芯片)
}
在主循环中调用:
while (1)
{
mx_timer_task_handler();
// 其他业务逻辑可以放在这里
}
5、完整示例:LED 闪烁 + 传感器采样 + 单次任务 + 动态修改
#include "mx_timlib.h"
// ===== 移植临界区宏(STM32)=====
#define MX_ENTER_CRITICAL() NVIC_DisableIRQ(TIM2_IRQn)
#define MX_EXIT_CRITICAL() NVIC_EnableIRQ(TIM2_IRQn)
// ===== 回调函数 =====
/* LED 每秒翻转一次(PERIODIC)*/
void led_toggle(void *param)
{
(void)param;
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
}
/* 传感器每 100ms 采样一次(PERIODIC)*/
void sensor_sample(void *param)
{
(void)param;
uint32_t value = read_adc();
process_data(value);
}
/* 系统启动后 2 秒打印一次(ONCE)*/
void startup_task(void *param)
{
(void)param;
printf("System started!\n");
}
// ===== 主函数 =====
int main(void)
{
mx_timer_init();
/* Classic API */
mx_timer_id_t id_led = mx_timer_create(1000, MX_TIMER_MODE_PERIODIC, led_toggle, NULL);
mx_timer_id_t id_once = mx_timer_create(2000, MX_TIMER_MODE_ONCE, startup_task, NULL);
mx_timer_start(id_led);
mx_timer_start(id_once);
/* Extended API(用户自有传感器定时器)*/
static mx_timer_t sensor_timer;
mx_timer_init_ex(&sensor_timer, 100, MX_TIMER_MODE_PERIODIC, sensor_sample, NULL);
mx_timer_attach(&sensor_timer);
mx_timer_start_ex(&sensor_timer);
/* 动态修改 LED 周期(1秒改为2秒)*/
mx_timer_set_period(id_led, 2000);
/* 暂停/恢复演示 */
mx_timer_pause(id_led); // 暂停
mx_timer_resume(id_led); // 恢复
while (1)
{
mx_timer_task_handler();
}
}
三、API一览
1、初始化
|
函数 |
说明 |
|
|
初始化组件(池化模式) |
|
|
初始化用户自有定时器 |
2、生命周期
|
函数 |
说明 |
|
|
创建池化定时器,返回 ID |
|
/ |
启动 |
|
/ |
停止 |
|
|
删除(池化) |
|
|
脱离(用户自有) |
|
|
接入系统(用户自有) |
3、查询
|
函数 |
说明 |
|
|
剩余 tick 数(池化) |
|
|
剩余 tick 数(用户) |
|
|
是否运行中 |
|
|
是否运行中 |
|
|
距下次到期的 tick 数(低功耗用) |
4、修改
|
函数 |
说明 |
|
|
修改周期(池化) |
|
|
修改周期(用户) |
|
|
暂停 |
|
|
暂停 |
|
|
恢复 |
|
|
恢复 |
|
|
重置为完整周期 |
5、驱动
|
函数 |
说明 |
|
|
在 1ms ISR 中调用(O(1) 检查) |
|
|
在主循环中调用(执行回调) |
6、配置宏
|
宏 |
默认值 |
说明 |
|
|
8 |
池化模式最大定时器数量 |
|
|
未定义 |
临界区进入(必须定义) |
|
|
未定义 |
临界区退出(必须定义) |
7、回调函数
签名:void callback(void *param)
回调在 mx_timer_task_handler() 中被调用,执行在主循环上下文,不在 ISR 中,允许阻塞操作。回调函数指针在调用前被拷贝到局部变量,因此可以在回调内部安全地停止或删除当前定时器。
zombie guard 机制:如果用户在回调中调用了 detach,定时器不会被重新插入 active_list,因为 re-arm 前会检查 callback != NULL。
四、开源协议
本项目采用 MIT License 开源。你可以:
- 商用,无需付费
- 修改代码,适配你的项目
- 分发,包括闭源产品
- 唯一要求:保留原作者版权声明
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)