嵌入式开源项目精选专栏

本专栏由Mculover666创建,主要内容为寻找嵌入式领域内的优质开源项目,一是帮助开发者使用开源项目实现更多的功能,二是通过这些开源项目,学习大佬的代码及背后的实现思想,提升自己的代码水平,和其它专栏相比,本专栏的优势在于:

不会单纯的介绍分享项目,还会包含作者亲自实践的过程分享,甚至还会有对它背后的设计思想解读

目前本专栏包含的开源项目有:

  • cJSON | 一个轻量级C语言JSON解析器
  • paho | 支持10种语言编写mqtt客户端,总有一款适合你!
  • MultiButton | 一个小巧简单易用的事件驱动型按键驱动模块
  • letter-shell | 一个功能强大的嵌入式shell
  • EasyLogger | 一款轻量级且高性能的日志库
  • SFUD | 一款串行 Flash 通用驱动库
  • EasyFlash | 让 Flash 成为小型 KV 数据库

如果您自己编写或者发现的开源项目不错,欢迎留言或者私信投稿到本专栏,分享获得双倍的快乐!

1. MultiTimer

本期给大家带来的开源项目是 MultiTimer,一款可无限扩展的软件定时器,作者0x1abin,目前收获 95 个 star,遵循 MIT 开源许可协议。

MultiTimer 是一个软件定时器扩展模块,可无限扩展你所需的定时器任务,取代传统的标志位判断方式, 更优雅更便捷地管理程序的时间触发时序。

项目地址:https://github.com/0x1abin/MultiTimer

2. 移植MultiTimer

2.1. 移植思路

开源项目在移植过程中主要参考项目的readme文档,一般只需两步:

  • ① 添加源码到裸机工程中;
  • ② 实现需要的接口;

2.2. 准备裸机工程

本文中我使用的是小熊派IoT开发套件,主控芯片为STM32L431RCT6:

移植之前需要准备一份裸机工程,我使用STM32CubeMX生成,需要初始化以下配置:

  • 配置一个串口用于打印信息
  • printf重定向

2.3. 添加MultiTimer到工程中

① 复制MultiTimer源码到工程中:

② 在keil中添加 MultiTimer的源码文件:

③ 将MultiTimer头文件路径添加到keil中:

3. 使用MultiTimer

使用时包含头文件:

#include "multi_timer.h"

如果遇到multi_timer.c文件中NULL宏定义报错,则在multi_timer.h中添加<stddef.h>头文件即可。

3.1. 创建Timer对象

/* USER CODE BEGIN PV */
struct Timer timer1;
struct Timer timer2;/* USER CODE END PV */

3.2. Timer回调函数

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void timer1_callback()
{printf("timer1 timeout!\r\n");
}void timer2_callback()
{printf("timer2 timeout!\r\n");
}
/* USER CODE END 0 */

3.3. 初始化并启动Timer

始化定时器对象,注册定时器回调处理函数,设置定时时间(ms),循环定时触发时间:

/* USER CODE BEGIN 2 */
printf("multi timer test...\r\n");//重复计时,周期为1000次,即1000ms=1s
timer_init(&timer1, timer1_callback, 1000, 1000);
timer_start(&timer1);//单次计时,周期为50次,即50ms
timer_init(&timer2, timer2_callback, 50, 0);
timer_start(&timer2);/* USER CODE END 2 */

3.4. Timer对象处理

在循环中调用Timer对象处理函数,处理函数会判断链表上的每个定时器是否超时,如果超过,则拉起注册的回调函数:

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{/* USER CODE END WHILE *//* USER CODE BEGIN 3 */timer_loop();
}/* USER CODE END 3 */

3.5. 提供Timer时基信号

MultiTimer中所有的定时器都是通过一个32位的计数值_timer_ticks来判断的,所以需要一个硬件定时器提供时基信号,递增该值。

本文中使用的是STM32HAL库,所以通过Systick来提供,无需设置额外的定时器。

main.c文件的最后编写Systick回调函数:

/* USER CODE BEGIN 4 */
void HAL_SYSTICK_Callback(void)
{//给multi timer提供时基信号timer_ticks(); //1ms ticks
}/* USER CODE END 4 */

然后在stm32l4xx_it.c中调用该回调函数:

/*** @brief This function handles System tick timer.*/
void SysTick_Handler(void)
{/* USER CODE BEGIN SysTick_IRQn 0 */HAL_SYSTICK_IRQHandler();/* USER CODE END SysTick_IRQn 0 */HAL_IncTick();/* USER CODE BEGIN SysTick_IRQn 1 *//* USER CODE END SysTick_IRQn 1 */
}

接下来编译下载,看在串口助手中看到打印的日志:

4. MultiTimer设计思想解读

4.1. 软件定时器设计思想

MultiTimer的设计比较简洁。

设置一个计数值_timer_ticks不断递增,由定时器提供的中断驱动,只计次数,不计时间,有了很大的自由度,一般时基信号设置为1ms一次:

/*** @brief  background ticks, timer repeat invoking interval 1ms.* @param  None.* @retval None.*/
void timer_ticks()
{_timer_ticks++;
}

在程序运行时循环比较定时器设置的超时值是否大于当前_timer_ticks的计数值,如果是则再次判断是否重复计数值是否为0,是则停止定时器,完成单次计时效果,否则修改计数值,最后拉起注册到该定时器的回调函数执行:

/*** @brief  main loop.* @param  None.* @retval None*/
void timer_loop()
{struct Timer* target;for(target=head_handle; target; target=target->next) {if(_timer_ticks >= target->timeout) {if(target->repeat == 0) {timer_stop(target);} else {target->timeout = _timer_ticks + target->repeat;}target->timeout_cb();}}
}

4.2. 单链表操作

MultiTimer的代码少,非常适合拿来学习单链表的操作,学习数据结构的过程是乏味的,不如直接来个实例看看是如何操作的。

① 链表的节点设计为一个软件定时器,所以理论上支持的定时器数量只受内存限制。

typedef struct Timer {uint32_t timeout;uint32_t repeat;void (*timeout_cb)(void);struct Timer* next;
}Timer;

定时器初始化函数timer_init就是初始化一个链表节点:

void timer_init(struct Timer* handle, void(*timeout_cb)(), uint32_t timeout, uint32_t repeat)
{// memset(handle, sizeof(struct Timer), 0);handle->timeout_cb = timeout_cb;handle->timeout = _timer_ticks + timeout;handle->repeat = repeat;
}

② 设置链表头指针,只需知道头指针就能完成对整个单链表的操作:

//timer handle list head.
static struct Timer* head_handle = NULL;

③ 向单链表增加一个节点

向单链表增加一个节点有三种方式:

  • 在单链表尾部增加一个节点
  • 在单链表头部增加一个节点
  • 在单链表中间增加一个节点

MultiTimer中所有的结点都是定时器,每个定时器之间相互独立,不存在先后次序关系,所以无论加到中间,还是加到尾部,还是加到头部,最后的功能都是一样的,但是在插入算法上有优劣性能之分。

先来看看再单链表尾部增加一个节点的算法:

int timer_start(struct Timer* handle)
{/** * 算法1 —— 向单链表尾部添加节点* 时间复杂度O(n)* Mculover666*/struct Timer* target = head_handle;if(head_handle == NULL){/* 链表为空 */head_handle = handle;handle->next = NULL;}else{/* 链表中存在节点,遍历找最后一个节点 */while(target->next != NULL){if(target == handle)return -1;target = target->next;}target->next = handle;handle->next = NULL;}return 0;
}

这种算法理解简单,实现简单,但是算法时间复杂度秒变为O(n),当n很大时,插入一个节点的时间就会非常久。

再来看看在链表头部插入一个新节点的情况:

int timer_start(struct Timer* handle)
{/** * 算法2 —— 向单链表头部添加节点* 时间复杂度O(n),如果去掉判断重复,则时间复杂度O(1)* 0x1abin*/struct Timer *target = head_handle;//判断是否有重复的定时器while(target){if(target == handle){return -1;}target = target->next;}handle->next = head_handle;head_handle = handle;return 0;
}

这里第二种头部插入节点的算法时间复杂度依然是O(n),emmm?

其实,这里因为单链表节点是定时器,在插入的时候需要对整个链表进行判断,避免重复添加同样的定时器节点,所以无论任何一种算法,都需要对单链表进行遍历。

如果在不需要判断重复的情况下,尾部插入算法仍然需要遍历,但是头部插入算法只需要插入就可以,时间复杂度为O(1),算法更优

④ 单链表删除其中一个节点

删除单链表的节点时,因为节点自身只保存有下一个节点的指针,并没有指向上一个节点的指针,所以不能直接入手删除节点,那么如何删除单链表的节点呢?

方法是:设置二级指针(指向Timer类型指针的指针),通过遍历链表的方式来寻找节点中next指针指向删除节点的那个节点,代码如下。

void timer_stop(struct Timer* handle)
{struct Timer** curr;for(curr = &head_handle; *curr; ) {struct Timer* entry = *curr;if (entry == handle) {*curr = entry->next;
//          free(entry);} elsecurr = &entry->next;}
}

5. 项目工程源码获取和问题交流

目前我将MultiTimer源码、我移植到小熊派STM32L431RCT6开发板的工程源码上传到了QQ群里(包含好几份HAL库,QQ相对速度快点),可以在QQ群里下载,有问题也可以在群里交流,当然也欢迎大家分享出来自己移植的工程到QQ群里

放上QQ群二维码:

接收更多精彩文章及资源推送,欢迎订阅我的微信公众号:『mculover666』。

MultiTimer | 一款可无限扩展的软件定时器相关推荐

  1. 基于S32K144平台实现两种软件定时器

    文章目录 1.网红软件定时器MultiTimer 1.1 MultiTimer 简介 1.2 准备工作 1.3 MultiTimer 使用 1.3.1 新建工程 1.3.2 修改主函数 1.3.3 增 ...

  2. 关于软件定时器的一些讨论

    1.简介 这里先介绍下软件定时器和硬件定时器的区别 硬件定时器: CPU内部自带的定时器模块,通过初始化.配置可以实现定时,定时时间到以后就会执行相应的定时器中断处理函数.硬件定时器一般都带有其它功能 ...

  3. 一直在构建工作空间_智能工作空间让Dropbox拥有无限扩展潜力

    智能工作空间让Dropbox拥有无限扩展潜力 Dropbox一直以"让工作变得更好"的使命.在竞争激烈的市场中,Dropbox有着卓越的历史,就连苹果创始人史蒂夫·乔布斯曾经提出来 ...

  4. 没有一款趁手的数据监控软件?试一下NetData不,用了你就绝对离不开他!

    没有一款趁手的数据监控软件?试一下NetData不,用了你就绝对离不开他! Netdata是分布式的,用于系统和应用程序的实时性能和运行状况监控.它是您安装在所有系统和容器上高度优化的监视代理. Ne ...

  5. 推荐8款堪称神器的良心软件

    工欲善其事必先利其器,推荐8款堪称神器的良心软件,每一款都是精品,可以大幅提高电脑的使用体验,解决超多难题.喜欢的话记得点赞和关注哦~ 1.uTools uTools是一款快速搜索工具 ,跨平台支持W ...

  6. 推荐7款超良心的windows软件,每一个都是精品!

    安利7款使用已久的windows软件,每一个都是精品,可以解决很多问题,让电脑更好用. 1.格式工厂 格式工厂是一款办公利器,可以转换几乎所有类型多媒体格式,还有文件压缩.图片处理.视频文件修复.文件 ...

  7. 重构智能合约(中):平行宇宙与无限扩展

    1.前言 本文是小蚁的两位创始人过去两年中在设计小蚁智能合约时所做的深度思考和技术探索的结果.<重构智能合约>系列文章将分为上.中.下三篇,分别从确定性和资源控制.扩展性和耦合度.通用性和 ...

  8. zabbix 监控 db2_二十多款开源的服务器监控软件,你用过几款? – 阿汤博客

    1.Conky Conky能在多个的平台上运行,像Windows,Linux,Mac OS,大多数BSD都可以.它有简单的文本.总线进度条,图形窗口不同字体和颜色来显示信息,用户界面简单,300多个内 ...

  9. 盘点5款超棒的电脑软件

    1.Advanced Renamer Advanced Renamer是一款修改文件名称的软件,主要的功能就是帮助用户修改文件名称,并且可以对文件的名称以及扩展名进行修改操作,很多时候我们在使用电脑时 ...

最新文章

  1. js把base64串解析成中文_回文问题终极篇:最小代价构造回文串
  2. HTML5文档结构主体结构 语义结构,html5组织文档结构.pdf
  3. 生信服务器 | 更改 CentOS/RHEL 6/7 中的时区
  4. 组建元宇宙军团!「谷歌实验室」重生,超700人神秘团队都有谁?
  5. js 显示当前时间(年月日时分秒)——getYear()与getFullYear()
  6. 人工智能时代,Python机器学习及分析工具
  7. spring boot 菜鸟教程学习:spring是一个超级大工厂能够管理java对象(bean)和他们之间的关系(依赖注入)
  8. 网络编程之网络架构及其演变过程、互联网与互联网的组成、OSI七层协议、socket抽象层...
  9. Servlet配置初始化参数/配置参数
  10. 75 jsp基础语法汇总
  11. 史上最全AI论文集结:近千篇论文分门别类整理好
  12. javascript的万能查询器根据网上一些朋友的代码个性后得到.
  13. 苹果秋季新品发布会终于官宣:然而并不会发布新iPhone?
  14. 题目4:EXCEL排序
  15. JS 正则(RegExp)
  16. Java 原生 Base64 编解码、Md5、SHA-1、SHA-256 加密摘要算法
  17. 统计Java源代码中关键字的数目(每个关键字的数目)
  18. 一条sql语句查出男生前5名和女生前五名
  19. 应用程序如何使用驱动程序
  20. SEO批量文章繁简转换,同义词替换

热门文章

  1. java基础 面试题
  2. 预警神器来了,天翼大喇叭发出河道防汛强音
  3. 华为交换机关闭网口_定时关闭华为交换机的端口
  4. Java 实现回文数
  5. 2019年山东省第十届ACM程序设计竞赛 比赛总结
  6. python扫雷游戏设计_Python 扫雷游戏 完整源代码+图片素材
  7. 带emoji表情的react输入框组件
  8. 导入导出数据库或数据库表
  9. 游戏玩家的程序猿之路
  10. C专家编程读书笔记一:C语言晦涩难懂的声明