单片机编程系列之事件驱动方式(前后台系统)
前言
本次学习了百问网的七天物联网直播课收获很多,写下笔记记录一下。
事件驱动方式
事件在我们程序中是一个抽象的概念,我认为只要是一件事情的告知就可以代表一个事件。比如按键按下,定时器完成定时,红灯亮起,数组满了,变量值改变等等。这也使得这种程序框架有很多种分类型。由于代码太长,我这只画概念图,不能理解的,可以先去写写几个中断处理程序。
事件驱动直接处理类
由上图所示,事件驱动直接处理类的程序框架比较简单,即事件到达后,直接在中断任务程序里面执行相应动作,简单易懂,方便快捷。优点是对于事件,反应迅速。但由于中断优先级运行机制的影响,过复杂的中断执行函数,会严重占用MCU,导致系统不能正常运行。适用场景:可以用与编码器计数和与之相似类的场景。也就是,来一个几行代码就处理了,不能使用复杂代码,更不能在中断任务里面加入延时。
事件驱动置位标志位类
由上图所示,这类方式是让中断任务里面只放信息标志,当信息标志发生改变后,主函数轮询获取到信息到来,然后执行任务处理函数,这样可以使中断处理迅速,不会导致其他的中断处理被延迟,或者处理不到的情况发生,但处理函数后面又变成了轮询方式。这种方式适用于需要快速响应后,复杂处理的情况。但尽量在短时间里处理一个任务,如果处理多个任务,可以使用状态机开发模式。
事件驱动定时器类
如果很多任务都要执行,这时候我们可以使用定时器来设置这些任务的执行周期,这就使得任务能有一种并行运行的感觉,执行周期要根据实际任务需求进行调整。
设置一个定时器,定义为1ms产生一次中断;
函数A,设置它的执行周期为1ms,即每1ms执行一次;
函数B,设置它的执行周期为2ms;
函数C,设置它的执行周期为3ms;
代码示例:
typedef struct softtimer {
int remain;
int period;
void (*function)(void);
}softtimer, *psofttimer;
static soft_timer timers[] = {
{1, 1, A},
{2, 2, B},
{3, 3, C}, };
void main() {
while (1) { }
}
void timer_isr()
{
int i; /* timers数组里每个成员的expire都减一 */
for (i = 0; i < 3; i++)
timers[i].remain--;
/* 如果timers数组里某个成员的expire等于0:
* 1. 调用它的函数
* 2. 恢复expire为period
*/
for (i = 0; i < 3; i++)
{
if (timers[i].remain == 0)
{
timer[i].function();
timers[i].remain = timers[i].period;
}
}
}
这个方式可以按实际情况调整ABC三个函数的运行周期,使它们达到很好的运行效果,但缺点是,如果其中的一个函数执行时间很长,超过定时器的定时周期会造成整个定时周期的时间基不准和影响其他函数的执行周期。
也可以改为:
typedef struct softtimer {
int remain;
int period;
void (*function)(void);
}softtimer, *psofttimer;
static soft_timer timers[] = {
{1, 1, A},
{2, 2, B},
{3, 3, C}, };
void main()
{
int i;
while (1)
{
/* 如果timers数组里某个成员的expire等于0: * 1. 调用它的函数 * 2. 恢复expire为period */
for (i = 0; i < 3; i++) {
if (timers[i].remain == 0) {
timer[i].function();
timers[i].remain = timers[i].period;
}
}
}
}
void timer_isr()
{
int i; /* timers数组里每个成员的expire都减一 */
for (i = 0; i < 3; i++)
if (timers[i].remain)
timers[i].remain--;
}
这种避免了时间基准不会延误,但是由于是轮询方式,所以,任务与任务之间会相互影响,这些任务可以写成状态机的模式,可以减小这种影响。
总结:裸机程序框架对于那些复杂的任务,开发难度较高,一个好的程序员,不会局限于某一类的程序框架,而是多种程序框架相互关联,相互补缺,这需要程序员在项目中多总结,优化永无止境。