在上一节中,提到了关于延时操作的处理方案
static void LedOn()
{
LED = ON // 点亮LED灯
delay(3) //延迟3秒
LED = OFF //关闭LED灯
}
while(1){
event = Event(); //事件检测,可以检测到按键按下,完成了事件的出队列
if(event != -1){
func = findEventHandler(event); //事件匹配,可以匹配到LedOn函数
func(); //事件处理,调用LedOn函数,延时阻塞在这里
}
}
在事件处理中,我们不能直接调用delay,会严重影响事件循环
#define KEYDOWN 3 //按键按下的事件为3
#define TIMER 4 //定时器事件
static eventHandler[16] = {NULL,NULL,NULL,LedOn,LedOff ....};//LedOn的数组下标为3
static void LedOn()
{
LED = ON; // 点亮LED灯
TimerOn(3); //开启一个3s的定时器
}
void keyDownEvent() //在中断中实现
{
if(isKeyDown())
setBit(EVENT,KEYDONW); //设置事件
}
void TimerEvent() //时钟中断中实现
{
setBit(EVENT,TIMER);
}
为了避免影响事件循环,我们引入了定时器事件,但代码逻辑已经非常不清楚。
解决这种问题,大概有三种方案,OS的线程,协程,回调。三种方法各有利弊,我们先介绍回调方案,至于其他解决方案,留到后面的章节再去讨论
我们先实现一个普适的定时器
typdef void (*timerHandler)();
static uint Tick = 0; //当前系统时钟滴答
struct timerEvent{
uint tick; //时钟滴答
timerHandler handler; //定时器事件处理函数
};
struct timerEventNode{
struct timerEvent timer;
struct timerEventNode *next;
struct timerEventNode *prev;
};
struct timerEventHead{
struct timerEventNode *next;
struct timerEventNode *prev;
}
struct timerEventHead Head = {
.next = NULL;
.prev = NULL;
};
void setTimerEvent(int ticks,timerHandler handler) //tick为要延时的滴答数
{
struct timerEventNode *node = malloc(sizeof(*node));
node->timer.tick = Tick + ticks; //请思考一下回滚问题
node->timer.handler = handler;
if(Head.next == NULL && Head.prev == NULL){ //定时器链表操作
...
}else{
...
}
}
void execTimerEvent()
{
struct timerEventNode *node = NULL;
for(node = Head.next;node != NULL;node = node->next){
if(node.timer.tick == Tick){ //定时器到了
node.timer.handler() //调用定时处理函数
//TODO 从定时器链表中删掉自己
}
}
}
void timerEvent() //中断中实现,定时器中断若被设置为1ms间隔,则滴答计数器的精度为1ms
{
Tick++; //系统滴答计数加一,
execTimerEvent(); //寻找到时的计时器,并执行相应处理函数
}
void timerInit()
{
//TODO //设置定时器
}
根据上面伪代码的实现,我们可以了解到这个定时器,有以下特点
- 系统滴答间隔为1ms。也就是说,我们1ms遍历一次定时器链表。
- 定时器是链表实现的,也就是说,每次对定时器链表的遍历都是O(n)的复杂度
- 定时滴答数会回滚,即 当前滴答数为MAX_INT时,下一个滴答便会回滚到0
-
struct timerEvent
的tick
参数为系统Tick+间隔,这对回滚有什么益处?
你可以思考怎么优化上面提出的问题,这里我们不再讨论。
我们再回头看一下LED定时闪烁的问题
void LedFlash() //led 闪烁,相比来讲代码结果已经比较清晰
{
if(LED == ON)
LED = OFF;
else
LED = ON;
setTimerEvent(3000,LedFlash); //led 3000ms(tick) 闪烁一次
}
我们把这段代码结合我们前面提到的定时器放到我们的事件循环中
void fifoPush(int event) //先入先出队列,数据入队
int fifoPop() //出队,无数据返回 -1
int Event()
{
reutrn fifoPop() //没有事件时,返回 -1
}
typedef void (*eventHandler)(); //函数指针的声明
#define KEYDOWN 3 //按键按下的事件为3
#define TIMER 4 //定时器事件
static eventHandler[16] = {NULL,NULL,NULL,LedFlash,timerEvent ....};//LedFlash的数组下标为3
void keyDownEvent() //在中断中实现
{
if(isKeyDown())
fifoPush(KEYDONW); //设置事件
}
void LedFlash() //led 闪烁,相比来讲代码结果已经比较清晰
{
if(LED == ON)
LED = OFF;
else
LED = ON;
setTimerEvent(3000,LedFlash); //led 3000ms(tick) 闪烁一次
}
int main()
{
int event;
eventHandler func;
timerInit();
while(1){
event = Event(); //事件检测,可以检测到按键按下,完成了事件的出队列
if(event != -1){
func = findEventHandler(event); //事件匹配,可以匹配到LedFlash函数
func(); //事件处理,调用LedFlash函数
}
}
}
我们看到,对于LED闪烁函数,其代码结构已经变得比较清晰。
请思考一下上面代码中,timerEvent的执行过程,setTimerEvent的执行过程,以及事件是怎么挂载和卸载的。
这是一个免费,开源的教程,如果你喜欢可以转发,也可以打赏奖励。 欢迎关注微信公众号小站练兵
网友评论