一、裸机开发
1.轮询方式
轮询,顾名思义就是事情一件件按顺序来,同一时间只能做一件事情。举个例子,一个妈妈要做两件事情,喂饭和回信息,只能先喂好饭,然后再回信息,两件事情不能同时做有先后顺序。
void main()
{
while (1)
{
喂一口饭();
回一个信息();
}
}
2.事件驱动
事件是一个宽泛的概念,什么叫事件?可以是:按下了按键、串口接收到了数据、模块产生了中断、某个全局变量被设置了。
什么叫事件驱动?当某个事件发生时,才调用对应函数,这就叫事件驱动。放到MCU来讲,我们可以理解为来了一个按键中断,这时就要调用中断处理函数处理中断信息。
再以上个例子为例,妈妈一直给小孩喂饭,就做这件事情,某个时间点同事发来一条信息,这时就需要妈妈去处理这个信息,处理完成后继续喂饭。
void main()
{
while (1)
{
喂饭();
}
}
void message_isr() /* 同事发来信息触发中断 */
{
回一个信息();
}
对于中断的处理,原则是"尽快"。否则会影响到其他中断,导致其他中断的处理被延迟、甚至丢失,所以中断处理的事情要尽量的精简。
3.定时器方式
上述例子中只有两个任务,如果有更多的任务,很多有经验的工程师会使用定时器来驱动:
-
设置一个定时器,比如每1ms产生一次中断,以此为时间基准
-
对于函数A,可以设置它的执行周期,比如每1ms执行一次
-
对于函数B,可以设置它的执行周期,比如每2ms执行一次
-
对于函数C,可以设置它的执行周期,比如每3ms执行一次
-
注意:1ms、2ms、3ms只是假设,你可根据实际情况调整。
typedef struct soft_timer {
int remain;
int period;
void (*function)(void);
}soft_timer, *p_soft_timer;
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;
}
}
}
但这时又出现个问题,如果某个函数处理的时间过长,这又会影响其他函数,或者时间基准。当然也可以把函数分得更精简,一件事情拆成多个步骤完成,这样可以解决这个问题。
也可以可以做以下改进,将任务放回主函数中,这样做就退回轮询方式。
typedef struct soft_timer {
int remain;
int period;
void (*function)(void);
}soft_timer, *p_soft_timer;
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--;
}
4.状态机方式
假设要调用两个函数AB,AB执行的时间都很长,又要AB任务都可以及时得到响应,裸机就很难处理这种场景,这时就需要使用状态机方式,将一个任务拆多个任务处理。
void feed_kid(void)
{
static int state = 0;
switch (state)
{
case 0: /* 开始 */
{
/* 盛饭 */
state++;
return;
}
case 1: /* 盛菜 */
{
/* 盛菜 */
state++;
return;
}
case 2:
{
/* 拿勺子 */
state++;
return;
}
}
}
void send_msg(void)
{
static int state = 0;
switch (state)
{
case 0: /* 开始 */
{
/* 打开电脑 */
state++;
return;
}
case 1:
{
/* 观看信息 */
state++;
return;
}
case 2:
{
/* 打字 */
state++;
return;
}
}
}
void main()
{
while (1)
{
feed_kid();
send_msg();
}
}
二、rtos开发
由以上裸机开发方式可见,在处理多任务方面,裸机开发或多或少都存在缺陷,这时就急需引入一种多任务并行运行的方式,实时处理信息。rtos的开发方式就由此而来。
有人可能会说,我使用状态机也可以实现同样的效果呀,为啥那么麻烦学习rtos。使用rtos的关键就是在于可以多任务并行,遇到执行时间长的任务,也不需要像状态机那样将任务分拆,而且每个任务之间的资源和栈都是独立的,程序的耦合性大大降低,可移植性和可读性大大增强。
举例如下:
// RTOS程序
喂饭()
{
while (1)
{
喂一口饭();
}
}
回信息()
{
while (1)
{
回一个信息();
}
}
void main()
{
create_task(喂饭);
create_task(回信息);
start_scheduler();
while (1)
{
sleep();
}
}