项目简介

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;
}

至此,介绍完成通过链表实现软件定时器,并且可以实现多个软件定时器,完成了定时器代码的详解。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐