一、背景
在嵌入式软件中,我们经常需要使用定时功能,比如每1s执行某个功能,比如触发了某个条件之后持续1s。如果每次遇到定时的功能,我们都自己去计数,这会让我们的代码很混乱,因此我们有必要准备几个好用的定时器调度器模块。
二、MultiTimer
简介
MultiTimer 是一个软件定时器扩展模块,可无限扩展你所需的定时器任务,取代传统的标志位判断方式, 更优雅更便捷地管理程序的时间触发时序。
项目地址:https://github.com/0x1abin/MultiTimer/blob/development/MultiTimer.c
使用方法
- 配置系统时间基准接口,安装定时器驱动;
uint64_t PlatformTicksGetFunc(void)
{
}
MultiTimerInstall(PlatformTicksGetFunc);
- 实例化一个定时器对象;
MultiTimer timer1;
- 设置定时时间,超时回调处理函数, 用户上下指针,启动定时器;
int MultiTimerStart(&timer1, uint64_t timing, MultiTimerCallback_t callback, void* userData);
- 在主循环调用定时器后台处理函数
int main(int argc, char *argv[])
{
...
while (1) {
...
MultiTimerYield();
}
}
功能限制
1.定时器的时钟频率直接影响定时器的精确度,尽可能采用1ms/5ms/10ms这几个精度较高的tick;
2.定时器的回调函数内不应执行耗时操作,否则可能因占用过长的时间,导致其他定时器无法正常超时;
3.由于定时器的回调函数是在 MultiTimerYield 内执行的,需要注意栈空间的使用不能过大,否则可能会导致栈溢出。
Examples
见example目录下的测试代码,main.c为普通平台测试demo,test_linux.c为linux平台的测试demo。
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include "MultiTimer.h"
MultiTimer timer1;
MultiTimer timer2;
MultiTimer timer3;
uint64_t PlatformTicksGetFunc(void)
{
struct timespec current_time;
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
return (uint64_t)((current_time.tv_sec * 1000) + (current_time.tv_nsec / 1000000));
}
void exampleTimer1Callback(MultiTimer* timer, void *userData)
{
printf("exampleTimer1Callback-> %s.\r\n", (char*)userData);
MultiTimerStart(timer, 1000, exampleTimer1Callback, userData);
}
void exampleTimer2Callback(MultiTimer* timer, void *userData)
{
printf("exampleTimer2Callback-> %s.\r\n", (char*)userData);
}
void exampleTimer3Callback(MultiTimer* timer, void *userData)
{
printf("exampleTimer3Callback-> %s.\r\n", (char*)userData);
MultiTimerStart(timer, 4567, exampleTimer3Callback, userData);
}
int main(int argc, char *argv[])
{
MultiTimerInstall(PlatformTicksGetFunc);
MultiTimerStart(&timer1, 1000, exampleTimer1Callback, "1000ms CYCLE timer");
MultiTimerStart(&timer2, 5000, exampleTimer2Callback, "5000ms ONCE timer");
MultiTimerStart(&timer3, 3456, exampleTimer3Callback, "3456ms delay start, 4567ms CYCLE timer");
while (1) {
MultiTimerYield();
}
}
源码解析
先看源码:
MultiTimer.h
#ifndef _MULTI_TIMER_H_
#define _MULTI_TIMER_H_
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef uint64_t (*PlatformTicksFunction_t)(void);
typedef struct MultiTimerHandle MultiTimer;
typedef void (*MultiTimerCallback_t)(MultiTimer* timer, void* userData);
struct MultiTimerHandle {
MultiTimer* next;
uint64_t deadline;
MultiTimerCallback_t callback;
void* userData;
};
int MultiTimerInstall(PlatformTicksFunction_t ticksFunc);
int MultiTimerStart(MultiTimer* timer, uint64_t timing, MultiTimerCallback_t callback, void* userData);
int MultiTimerStop(MultiTimer* timer);
int MultiTimerYield(void);
#ifdef __cplusplus
}
#endif
#endif
MultiTimer.c
#include "MultiTimer.h"
#include <stdio.h>
static MultiTimer* timerList = NULL;
static PlatformTicksFunction_t platformTicksFunction = NULL;
int MultiTimerInstall(PlatformTicksFunction_t ticksFunc)
{
platformTicksFunction = ticksFunc;
return 0;
}
int MultiTimerStart(MultiTimer* timer, uint64_t timing, MultiTimerCallback_t callback, void* userData)
{
if (!timer || !callback ) {
return -1;
}
MultiTimer** nextTimer = &timerList;
for (; *nextTimer; nextTimer = &(*nextTimer)->next) {
if (timer == *nextTimer) {
*nextTimer = timer->next;
break;
}
}
timer->deadline = platformTicksFunction() + timing;
timer->callback = callback;
timer->userData = userData;
for (nextTimer = &timerList;; nextTimer = &(*nextTimer)->next) {
if (!*nextTimer) {
timer->next = NULL;
*nextTimer = timer;
break;
}
if (timer->deadline < (*nextTimer)->deadline) {
timer->next = *nextTimer;
*nextTimer = timer;
break;
}
}
return 0;
}
int MultiTimerStop(MultiTimer* timer)
{
MultiTimer** nextTimer = &timerList;
for (; *nextTimer; nextTimer = &(*nextTimer)->next) {
MultiTimer* entry = *nextTimer;
if (entry == timer) {
*nextTimer = timer->next;
break;
}
}
return 0;
}
int MultiTimerYield(void)
{
MultiTimer* entry = timerList;
for (; entry; entry = entry->next) {
if (platformTicksFunction() < entry->deadline) {
return (int)(entry->deadline - platformTicksFunction());
}
timerList = entry->next;
if (entry->callback) {
entry->callback(entry, entry->userData);
}
}
return 0;
}
三、SmartTimer
##1.SmartTimer能干什么?##
简单说来,SmartTimer是一个轻量级的基于STM32的定时器调度器,在单片机”裸跑”的情况下,可以很方便的实现异步编程。
项目地址:https://github.com/lmooml/SmartTimer
它可以应用在对实时性要求没那么高的场合,比如说一个空气检测装置,每200ms收集一次甲醛数据,这个任务显然对实时性要求没那么高,如果时间上相差几毫秒,甚至几十毫秒也没关系,那么使用SmartTimer非常适合;而如果开发一个四轴飞行器,无论是对陀螺仪数据的采集、计算,以及对4个电机的控制,在时间的控制上都需要非常精确。那么这种场合下SmartTimer无法胜任,你需要一个带有抢占优先级机制的实时系统。
不同的场景,选择不同的工具和架构才是最合理的,SmartTimer只能做它力所能及的事情。
虽然SmartTimer是基于STM32开发的,但是它可以很方便的移植到其他的单片机上。
##2. SmartTimer的一般用法##
###2.1 Runlater。###
在单片机编程中,想实现在”xxx毫秒后调用xxx函数”的功能,一般有3种方法:
- 用阻塞的,非精确的方式,就是用for(i=0;i<0xffff;i++);这种循环等待的方式,来非精确的延迟一段时间,然后再顺序执行下面的程序;
- 利用硬件定时器实现异步的精确延时,把XXX函数在定时器中断里执行;
- 同样是利用硬件定时器,但是只在定时器中断里设置标志位,在系统的主While循环中检测这个标志位,当检测到标志置位后,去运行XXX函数。
从理论上来说,以上3种方式中,第3种采用定时器设定标志位的方法最好。因为首先主程序不用阻塞,在等待的时间里,MCU完全可以去做其他的事情,其次在定时器中断里不用占用太多的时间,节约中断资源。但这种方式有个缺点,就是实现起来相对麻烦一些。因为如果你要有N个runlater的需求,那么就得设置N个标志位,还要考虑定时器的分配、设定。在程序主While循环里也会遍布N个查询标志位的if语句。如果N足够多,其实大于5个,就会比较头疼。这样会使主While循环看起来很乱。这样的实现不够简洁、优雅。
SmartTimer首先解决的就是这个问题,它可以优雅地延迟调用某函数。
###2.2 Runloop###
在定时器编程方面还有另一个典型需求,就是“每隔xxx毫秒运行一次XXX函数,一共运行XXX次”。这个实现起来和runlater差不多,就是加一个运行次数的技术标志。我就不再赘述了。还是那句话:
SmartTimer可以优雅的实现Runloop功能。
###2.3 Delay### 并不是说非阻塞就一定比阻塞好,因为在某些场景下,必须得用到阻塞,使单片机停下来等待某个事件。那么SmartTimer也可以提供这个功能。
##3. SmartTimer的高级用法## 所谓的高级用法,并不是说SmartTimer有隐藏模式,能开启黑科技。而是说,如果你能转变思路,举一反三地话,可以利用SmartTimer提供的简单功能实现更加优化、合理的系统结构。
传统的单片机裸跑一般采用状态机模式,就是在主While循环里设定一些标志位或是设定好程序进行的步骤,根据事件的进程来跳转程序。简单的说来,这是一种顺序执行的程序结构。其灵活性和实时性并不高,尤其是当需要处理的业务越来越多,越来越复杂时,状态机会臃肿不堪,一不留神(其实是一定以及肯定)就会深埋bug于其中,调试解决BUG时也会异常痛苦。
如果你能转换一下思路,不再把业务逻辑中各个模块的关系看成基于因果(顺序),而是基于时间,模块间如果需要确定次序可以采用标志位进行同步。那么恭喜你,你已经有了采用实时系统的思想,可以尝试使用RT-thread等操作系统来完成你的项目了。但是,使用操作系统有几个问题,第一是当单片机资源有限的时候,使用操作系统恐怕不太合适;第二是学习操作系统本身有一定的难度,至少你需要花费一定的时间;第三如果你的项目复杂度没有那么高,使用操作系统有点大材小用。
那么,请允许我没羞没臊的说一句,其实利用SmartTimer中的Runloop功能可以简单的实现基于时间的主程序框架。
##4.关于Demo## 与源码一起提供的,还有一个Demo程序。这个Demo比较简单,主要是为了测试SmartTimer的功能。Demo程序基本可以体现Runlater,Runloop,Delay功能。同时也能基本体现基于时间的编程思想(单片机裸跑程序框架)。
##5.SmartTimer的使用## SmartTimer.h中声明的公开函数并不多,总共有8个:
void stim_init ( void );
void stim_tick (void);
void stim_mainloop ( void );
int8_t stim_loop ( uint16_t delayms, void (*callback)(void), uint16_t times);
int8_t stim_runlater ( uint16_t delayms, void (*callback)(void));
void stim_delay ( uint16_t delayms);
void stim_kill_event(int8_t id);
void stim_remove_event(int8_t id);
下面我将逐一介绍 ###5.1 必要的前提### SmartTimer能够工作的必要条件是:
- A. 设置Systick的定时中断(也可以是其他的硬件定时器TIMx,我选择的是比较简单的Systick),我默认设置为1ms中断一次,使用者可以根据自己的情况来更改。Systick时钟的设置在stim_init函数中,该函数必须在主程序初始化阶段调用一次。
- B. 在定时器中断函数中调用stim_tick();可以说,这个函数是SmartTimer的引擎,如A步骤所述,默认情况下,每1ms,定时器中断会调用一次stim_tick();
- C. 在主While循环中执行stim_mainloop(),这个函数主要有两个作用,一是执行定时结束后的回调函数;二是回收使用完毕的timer事件的资源。
###5.2 开始使用SmartTimer### 做好以上的搭建工作后,就可以开始使用SmartTimer了。
int8_t stim_runlater ( uint16_t delayms, void (*callback)(void));
该函数接受两个参数,返回定时事件的id。参数delayms传入延迟多长时间,注意这里的单位是根据之前A步骤里,你设置的时间滴答来确定的(默认单位是1ms);第二个参数是回调函数的函数指针,目前只支持没有参数,且无返回值的回调函数,未来会考虑加入带参数和返回值的回调。 举例:
timer_runlater(100,ledflash);
如果在stim_init()中,设置的时钟滴答为10ms执行一次,那么传入同样的参数,意义就会改变:
timer_runlater(100,ledflash);
int8_t stim_loop ( uint16_t delayms, void (*callback)(void), uint16_t times);
这个函数的参数意义同runlater差不多,我就不详细说明了。 该函数接收3个参数,delayms为延迟时间,callback为回调函数指针,times是循环次数。 举例(以1ms滴答为例):
timer_runloop(50,ledflash,5);
void timer_delay ( uint16_t delayms); //延迟xx ms
这个函数会阻塞主程序,并延迟一段时间。
void stim_kill_event(int8_t id);
void stim_remove_event(int8_t id);
这两个函数,可以将之前设定的定时事件取消。比如之前用stim_loop无限循环了一个事件,当获取某个指令后,需要取消这个任务,则可以用这两个函数取消事件调度。这两个函数的区别是:
void stim_kill_event(int8_t id);
###5.3 注意事项### SmartTimer可接受的Timer event数量是有上限的,这个上限由smarttimer.h中的宏定义
#define TIMEREVENT_MAX_SIZE 20
来决定的。默认为20个,你可以根据实际情况增加或减少。但不可多于128个
源码分析
smarttimer.h
#ifndef __SMARTTIMER_H__
#define __SMARTTIMER_H__
#include "stm32f10x.h"
#ifndef NULL
#define NULL ((void *)0)
#endif
#define CLOSE_INTERRUPT() __ASM("CPSID I")
#define OPEN_INTERRUPT() __ASM("CPSIE I")
#define STIM_EVENT_MAX_SIZE 20
#define STIM_LOOP_FOREVER (uint16_t)0xffff
#define STIM_INVALID 0xff
#define STIM_EVENT_IDLE 0x00
#define STIM_EVENT_ACTIVE 0x01
#define STIM_EVENT_RECYCLE 0x02
void stim_init ( void );
void stim_tick (void);
void stim_mainloop ( void );
int8_t stim_loop ( uint16_t delayms, void (*callback)(void), uint16_t times);
int8_t stim_runlater ( uint16_t delayms, void (*callback)(void));
void stim_delay ( uint16_t delayms);
void stim_kill_event(int8_t id);
void stim_remove_event(int8_t id);
#ifdef STIM_DEBUG
uint8_t stim_get_eventnum(void);
void stim_print_status(void);
#endif
#endif
smarttimer.c
#include "smarttimer.h"
#ifdef STIM_DEBUG
#include <stdio.h>
#endif
struct stim_event{
uint32_t tick_punch;
uint32_t interval;
uint32_t looptimes;
uint8_t id;
uint8_t stat;
struct stim_event *next;
struct stim_event *prev;
};
struct stim_event_list{
struct stim_event *head;
struct stim_event *tail;
uint8_t count;
};
struct stim_event_list_manager{
struct stim_event_list list[2];
uint8_t cur_index;
};
static struct stim_event event_pool[STIM_EVENT_MAX_SIZE];
static struct stim_event_list_manager list_manager;
static struct stim_event_list recycle_list;
static void (*callback_list[STIM_EVENT_MAX_SIZE])(void);
static uint8_t mark_list[STIM_EVENT_MAX_SIZE];
static uint32_t current_tick;
static void init_linked ( struct stim_event_list *list )
{
list->head = list->tail = NULL;
list->count = 0;
}
static void remove_node ( struct stim_event *event, struct stim_event_list *list )
{
if(list->head == event){
list->head = event->next;
if(list->head == NULL){
list->tail = NULL;
}else{
event->next->prev = NULL;
}
}else{
event->prev->next = event->next;
if(event->next == NULL){
list->tail = event->prev;
}else{
event->next->prev = event->prev;
}
}
list->count--;
}
static void insert_node_prev ( struct stim_event *new_node,struct stim_event *node,struct stim_event_list *list )
{
new_node->next = node;
if(node->prev == NULL){
list->head = new_node;
new_node->prev = NULL;
}else{
new_node->prev = node->prev;
node->prev->next = new_node;
}
node->prev = new_node;
list->count++;
}
static void insert_to_tail ( struct stim_event *new_node ,struct stim_event_list *list)
{
struct stim_event *node = list->tail;
if(list->count == 0){
list->head = new_node;
list->tail = new_node;
new_node->next = NULL;
new_node->prev = NULL;
}else{
node->next = new_node;
new_node->prev = node;
new_node->next = NULL;
list->tail = new_node;
}
list->count++;
}
static struct stim_event* malloc_event (void)
{
uint8_t i;
for(i = 0; i < STIM_EVENT_MAX_SIZE; i++){
if(event_pool[i].stat == STIM_EVENT_IDLE){
event_pool[i].stat = STIM_EVENT_ACTIVE;
return &event_pool[i];
}
}
return NULL;
}
static void free_event (struct stim_event *event)
{
callback_list[event->id] = NULL;
mark_list[event->id] = STIM_INVALID;
event->stat = STIM_EVENT_IDLE;
event->interval = 0;
event->looptimes = 0;
event->tick_punch = 0;
event->prev = NULL;
event->next = NULL;
}
static void insert_event ( struct stim_event *event,struct stim_event_list *list )
{
uint8_t i;
struct stim_event *node;
if(list->count == 0){
insert_to_tail(event,list);
}else{
node = list->head;
for(i = 0; i < list->count; i++){
if(event->tick_punch > node->tick_punch){
node = node->next;
}else{
break;
}
}
if(node == NULL){
insert_to_tail(event,list);
}else{
insert_node_prev(event,node,list);
}
}
}
static struct stim_event* find_event ( int8_t id, struct stim_event_list *list )
{
uint8_t i;
struct stim_event *event;
event = list->head;
for(i = 0; i < list->count; i++){
if(event->id == id){
break;
}else{
event = event->next;
}
}
return event;
}
static void recyle_event ( struct stim_event *event )
{
struct stim_event_list *list = &list_manager.list[list_manager.cur_index];
remove_node(event,list);
insert_to_tail(event,&recycle_list);
}
static struct stim_event* push_event ( uint32_t delayms, void (*callback)(void),uint16_t times )
{
struct stim_event *event;
event = malloc_event();
event->interval = delayms;
event->looptimes = times;
event->next = NULL;
event->tick_punch = current_tick + delayms;
mark_list[event->id] = 0;
if(callback != NULL){
callback_list[event->id] = callback;
}
if(event->tick_punch < current_tick){
insert_event(event,&list_manager.list[list_manager.cur_index ^ 0x01]);
}else{
insert_event(event,&list_manager.list[list_manager.cur_index]);
}
return event;
}
void stim_delay ( uint16_t delayms)
{
struct stim_event *event;
CLOSE_INTERRUPT();
event = push_event(delayms,NULL,1);
OPEN_INTERRUPT();
while(mark_list[event->id] == 0);
}
int8_t stim_runlater ( uint16_t delayms, void (*callback)(void))
{
struct stim_event *event;
CLOSE_INTERRUPT();
event = push_event(delayms,callback,1);
OPEN_INTERRUPT();
return event->id;
}
int8_t stim_loop ( uint16_t delayms, void (*callback)(void), uint16_t times)
{
struct stim_event *event;
CLOSE_INTERRUPT();
event = push_event(delayms,callback,times);
OPEN_INTERRUPT();
return event->id;
}
void stim_kill_event(int8_t id)
{
uint8_t i;
struct stim_event_list *list;
struct stim_event *event;
CLOSE_INTERRUPT();
for(i = 0; i < 2; i++){
list = &list_manager.list[i];
event = find_event(id,list);
if(event != NULL){
remove_node(event,list);
free_event(event);
break;
}
}
OPEN_INTERRUPT();
}
void stim_remove_event(int8_t id)
{
uint8_t i;
struct stim_event_list *list;
struct stim_event *event;
CLOSE_INTERRUPT();
for(i = 0; i < 2; i++){
list = &list_manager.list[i];
event = find_event(id,list);
if(event != NULL){
event->stat = STIM_EVENT_RECYCLE;
recyle_event(event);
break;
}
}
OPEN_INTERRUPT();
}
void stim_tick (void)
{
struct stim_event *event;
struct stim_event_list *list;
if(((current_tick + 1) & 0xffffffff) < current_tick){
list_manager.cur_index ^= 0x01;
}
current_tick++;
list = &list_manager.list[list_manager.cur_index];
event = list->head;
while(event != NULL && event->tick_punch <= current_tick){
mark_list[event->id] += 1;
if((event->looptimes != STIM_LOOP_FOREVER) &&
(--event->looptimes == 0)){
event->stat = STIM_EVENT_RECYCLE;
recyle_event(event);
}else{
event->tick_punch = current_tick + event->interval;
remove_node(event,list);
if(event->tick_punch < current_tick){
list = &list_manager.list[list_manager.cur_index ^ 0x01];
}
insert_event(event,list);
}
event = event->next;
}
}
void stim_mainloop ( void )
{
uint8_t i;
struct stim_event *event;
for(i = 0; i < STIM_EVENT_MAX_SIZE; i++){
if((mark_list[i] != STIM_INVALID) && (mark_list[i] > 0)){
if(callback_list[i] != NULL){
callback_list[i]();
}
mark_list[i] -= 1;
}
}
if(recycle_list.count > 0){
event = recycle_list.head;
while(event != NULL){
if(mark_list[event->id] == 0){
remove_node(event,&recycle_list);
free_event(event);
break;
}else{
event = event->next;
}
}
}
}
void stim_init ( void )
{
uint8_t i;
struct stim_event *event;
init_linked(&recycle_list);
init_linked(&list_manager.list[0]);
init_linked(&list_manager.list[1]);
list_manager.cur_index = 0;
current_tick = 0;
for(i = 0; i < STIM_EVENT_MAX_SIZE; i++){
event = &event_pool[i];
event->stat = STIM_EVENT_IDLE;
event->interval = 0;
event->tick_punch = 0;
event->looptimes = 0;
event->next = NULL;
event->prev = NULL;
event->id = i;
callback_list[i] = NULL;
mark_list[i] = STIM_INVALID;
}
SysTick_Config(SystemCoreClock / 1000);
}
#ifdef STIM_DEBUG
uint8_t stim_get_eventnum(void){
return list_manager.list[0].count + list_manager.list[1].count;
}
void stim_print_status(void)
{
uint8_t i;
struct stim_event *node;
struct stim_event_list *list;
printf("=============================\r\n");
printf("current_tick = %X\r\n",current_tick);
printf("cur_index = %d\r\n",list_manager.cur_index);
list = &list_manager.list[list_manager.cur_index];
if(list->count == 0){
printf("list is empty!\r\n");
}else{
node = list->head;
for(i = 0; i <list->count; i++){
printf("event tick_punch = %X\r\n",node->tick_punch);
printf("event id = %d\r\n",node->id);
node = node->next;
}
}
printf("========another list===========\r\n");
list = &list_manager.list[list_manager.cur_index ^ 0x01];
if(list->count == 0){
printf("list is empty!\r\n");
}else{
node = list->head;
for(i = 0; i <list->count; i++){
printf("event tick_punch = %X\r\n",node->tick_punch);
printf("event id = %d\r\n",node->id);
node = node->next;
}
}
printf("========recycle list===========\r\n");
list = &recycle_list;
if(list->count == 0){
printf("list is empty!\r\n");
}else{
node = list->head;
for(i = 0; i <list->count; i++){
printf("mark_list[%d] = %d\r\n",node->id,mark_list[node->id]);
node = node->next;
}
}
printf("=============================\r\n");
}
#endif
四、定时器矩阵
这个来自于:https://blog.csdn.net/qq_41854911/article/details/123027606?spm=1001.2014.3001.5501
五、总结
定时器都需要有一个时间基准,有了基准之后,我们就可以方便设置不同的定时器了。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)