链表在嵌入式中的应用—基于GitHub开源项目MultiTimer
链表基础以及链表在嵌入式领域中的应用,描述的单向带头结点的链表实现,包括创建,删除,插入,头插法和尾插法
项目简介
MultiTimer是一块可以无限扩展的软件定时器,项目源地址链接:项目地址
在软件定时器的使用过程中用到的单向带头节点的链表。
链表基础知识可以回顾一下之前的blog:链表基础
这篇博客主要介绍内容是链表在嵌入式的应用。
代码移植
使用的开发板是正点原子的探索者F407,首先使用STM32CubMx配置完成工程,实现基本的串口打印即可。
CubMx基本配置:
紧接着把下载下来的源文件:
移植到生成的工程文件中:
之后打开工程,在代码块添加如下代码。
/* USER CODE BEGIN PTD */
#include <stdio.h>
#include "multi_timer.h"
struct Timer timer1;
struct Timer timer2;
void timer1_callback()
{
printf("T1 timeout \r\n");
HAL_GPIO_TogglePin(L1_GPIO_Port,L1_Pin);
}
void timer2_callback()
{
printf("T2 timeout \r\n");
HAL_GPIO_TogglePin(L0_GPIO_Port,L0_Pin);
}
/* USER CODE END PTD */
/* USER CODE BEGIN 2 */
printf("multi timer test...\r\n");
//重复计时 周期为500次,即500ms=0.5s
timer_init(&timer1,timer1_callback,500,500,0);
timer_start(&timer1);
//单次计时 周期为50次,即50ms
timer_init(&timer2,timer2_callback,50,0,0);
timer_start(&timer2);
/* USER CODE END 2 */
添加systick的回调函数:
/* USER CODE BEGIN 4 */
void HAL_SYSTICK_Callback()
{
timer_ticks();//1ms ticks;
}
/* USER CODE END 4 */
中断处理函数stm32f4xx_it.c中添加如下内容:
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 */
}
while(1)中启动定时器
while (1)
{
timer_loop();
}
实验现象
定时器1 0.5S动作一次,定时器2 0.05s动作一次!!!
链表定时器代码详细分析
定时器结构体初始化—链表
typedef struct Timer {
uint32_t cur_ticks; /* 当前滴答时间 */
uint32_t cur_expired_time; /* 记录当前所经历的时间 */
uint32_t timeout; /* 溢出时间 */
uint32_t repeat; /* 下次延时所添加的时间 */
void * arg; /* 回调函数参数 */
void (*timeout_cb)(void *arg); /* 定时器溢出时间回调函数 */
struct Timer* next; /* 链表的next指针 */
} Timer;
定时器初始化函数:对结构体成员初始化,传入定时器结构体句柄、回调函数、溢出时间、再装载溢出时间、回调函数参数。
void timer_init(struct Timer* handle, void (*timeout_cb)(void *arg), \
uint32_t timeout, uint32_t repeat, void *arg)
{
handle->timeout_cb = timeout_cb;
handle->timeout = timeout;
handle->repeat = repeat;
handle->cur_ticks = _timer_ticks;
handle->cur_expired_time = handle->timeout;
handle->arg = arg;
}
启动定时器:添加定时器结构体到链表中,采用的是头部插入法。
int timer_start(struct Timer* handle)
{
struct Timer* target = head_handle;
while(target) {
if(target == handle) {
return -1; //already exist.
}
target = target->next;
}
handle->next = head_handle;
head_handle = handle;
return 0;
}
停止定时器,将定时器从链表中删除。运用了二级指针删除链表中的元素。
int timer_stop(struct Timer* handle)
{
struct Timer** curr;
for(curr = &head_handle; *curr;) {
struct Timer* entry = *curr;
if(entry == handle) {
*curr = entry->next;
return 0; // found specified timer
} else {
curr = &entry->next;
}
}
return 0;
}
定时器作用函数
void timer_loop(void)
{
/*初始化定时器结构体*/
struct Timer* target;
/*target 赋值为头结点;头结点不为空;顺次向下*/
for(target = head_handle; target; target = target->next) {
/*如果滴答时间-当前定时器启动后的滴答时间>=定时器溢出时间*/
if(_timer_ticks - target->cur_ticks >= target->cur_expired_time) {
printf("cur_ticks: %u, cur_expired_time: %u, _timer_ticks: %u\r\n",
target->cur_ticks, target->cur_expired_time, _timer_ticks);
/*配置单次触发或者多次触发定时器*/
if(target->repeat == 0) {
timer_stop(target);
} else {
/*定时器当前的滴答时间=系统sys定时器的计数时间*/
target->cur_ticks = _timer_ticks;
/*定时器下次再定时的溢出时间*/
target->cur_expired_time = target->repeat;
}
/*调用回调函数*/
target->timeout_cb(target->arg);
}
}
}
定时器每次增加的tick数,每次增加1ms;
void timer_ticks(void)
{
_timer_ticks += CFG_TIMER_1_TICK_N_MS;
}
链表插入方式
尾部插入法
单链表中添加一个节点的方式:先前使用的是从头部添加一个链表元素,头部插入算法只要从头部插入,时间复杂度为O(1),算法优秀。
另外介绍尾部插入法:
尾部插入法的思想是找到next指针最后指向NULL的那个节点,然后对next指向做替换,替换成需要插入的节点。
int timer_start(struct Timer* handle)
{
/**
* 算法2 —— 向单链表尾部添加节点
* 时间复杂度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;
}
至此,介绍完成通过链表实现软件定时器,并且可以实现多个软件定时器,完成了定时器代码的详解。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)