int main()
{
while(1)
{
do_something1();
do_something2();
}
}
适用于大部分情况,mian()中无长时间的阻塞任务缺点:函数互相之间有影响,在做do_something1()其他函数完全被阻塞,只能等待函数完成 2.事件驱动方式(前后台)
int main()
{
while(1)
{
}
}
do_sth1() //中断函数1
{
do_something1;
}
do_sth2()//中断函数2
{
do_something2;
}
仅适用于在中断中处理时间较短的函数,若时间较长且中断优先级高,则会影响其他函数同一时间(宏观)只能有一个函数被处理,其它中断可能丢失相对于轮询,对于优先级高的函数实时性提高 3.改进的事件驱动方式
int main()
{
while(1)
{
if(1 == flag)
do_sth1();
if(2 == flag)
do_sth2();
}
}
do_sth1() //中断函数1
{
flag = 1;
}
do_sth2()//中断函数2
{
flag = 2;
}
中断处理很快,保证中断不会丢失对于中断所对应的任务,统一放在main函数里轮询执行,像是轮询和前后台的一个折中 4.常用时间驱动方式:定时器***(划重点)***
最大运行时间:一个函数从开始执行到结束的最长时间实时响应指标:要求函数做出响应的最长时间周期
定时器方式在最大运行时间、实时响应指标都确定的情况下,人为的将他们安排在时间轴上,相对于前后台更为准确。在没有RTOS前,使用这种方式。
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;
for (i = 0; i < 3; i++)
timers[i].remain--;
for (i = 0; i < 3; i++)
{
if (timers[i].remain == 0)
{
timer[i].function();
timers[i].remain = timers[i].period;
}
}
}
可以看出,以上这种方式function()的执行会影响到计数器的计数,进而影响到之后的全部函数。
参考前后台方式,我们可以将任务函数放入到main函数中去,优缺点与前后台类似。
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)
{
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;
for (i = 0; i < 3; i++)
if (timers[i].remain)
timers[i].remain--;
}
5.状态机思想(RTOS铺垫)
以上四种方式在面对函数时间处理特别长的情况下都会造成不同程度的影响,为了减轻这种影响,我们可以人为的将一段函数拆分成若干段,每次执行其中的一段,执行结束后立即跳出,检测是否有其他更高优先级的任务去做,如果有优先执行,如果没有,从离开的地方继续执行被分段的函数
代码如下:
void A()
{
//函数A执行需要特别长的时间
}
int main()
{
while(1)
{
A();
}
}
void A()
{
static int state = 0;
switch(state)
{
case 0:
A1();
state++;
return;
case 1:
A2();
state++;
return;
...
}
}
int main()
{
while(1)
{
A();
B();
...
}
}
二、RTOS的引入及编程注意问题
1.RTOS引入
由裸机开发模式可知,在状态机思想的编程模式下,单片机系统可以获得一个更为优异的性能。
但是在实际编程过程中,每个函数的拆分需要实际的软硬件调试,需要经验且效率较低。
RTOS的功能之一是将任务进行时间片轮转,以实现与状态机相同的效果,且编程方式简单。
示例代码如下:
// RTOS程序
Task1()
{
while (1)
{
do_sth1();
}
}
Task2()
{
while (1)
{
do_sth2();
}
}
void main()
{
create_task(Task1);//创建任务
create_task(Task2);
start_scheduler();//任务调度
while (1)
{
sleep();
}
}
2.编程注意问题
2.1 临界区保护
在RTOS开发中,在不同的任务之间进行切换,就会出现两个任务同时对同一变量进行修改的情况。
如TaskA对a的值通过寄存器R0进行a++修改:
(A1)R0 = a; (A2)R0 = R0 +1; (A3)a = R0;
当进行到(A2)时突然有TaskB通过R1对a的值也进行a++修改:
(A1)R0 = a++; (A2)R0 = R0 +1; (B1)R1 = a; (B2)R1 = R1 + 1; (B3)a = R1; (A3)a = R0;
整个过程结束后实际上只对a进行了一次a++操作,而函数中需要执行两次,丢失了一次a++。
所以在RTOS中,在不同的任务中对同一变量进行修改需要进行临界去保护,即对变量修改的过程收临界区保护,不允许其他任务同时进行修改。
int main()
{
while(1)
{
A();//flag = 0; flag++; A函数将flag从0开始累加
B();//if(flag == 1000){do_sth;} B函数等到flag1000执行
}
}
在while每次执行中,都会进行flag累加和查询的步骤,执行了1000次 ×(flag++)和1000次 × if(flag == 1000),但绝大部分的if判断都是无效的。
RTOS可以在第一次接受到if判断的时候就把函数B记录下来,等到flag = 1000时主动去通知函数B。
从而在执行过程中减少了999次if判断,CPU可以将执行此操作的时间去执行其它任务,进而提高了CPU利用率。
无论裸机还是RTOS,都可以分为中断处理和用户程序处理两种任务处理方式,且中断的优先级都高于用户程序的优先级(即只要有中断,就执行中断,除非其他的中断优先级更高)。
裸机的函数执行时间:
中断:根据中断优先级高低,优先执行优先级高的函数。用户程序:根据main函数中放置任务函数的前后顺序,从前往后依次执行,处理完中断函数后,继续在上次离开位置向下执行。总结:裸机中,响应的实时性最好的时放在最高优先级的中断函数中。除中断函数外,其他用户程序响应的实时性根据在main函数中不同的执行时间来排,比较机械。 RTOS的函数执行时间:
中断:根据中断优先级高低,优先执行优先级高的函数。但在一些临界区保护的函数处理时,会屏蔽掉部分中断,导致这部分的中断响应实时性变差(实际就几个指令的时间,影响不大)。用户程序:根据在就绪列表中的优先级,优先执行优先级高的任务。总结:想要绝对的响应实时性就往最高优先级的中断函数中去写。 对比:对于不同中断或者用户程序,裸机开发与RTOS开发的响应实时性各有特点。普遍来说,RTOS对响应的先后控制更为灵活有效。 2.裸机编程中需要进行临界区保护吗? 答:需要如果在main()函数里对一变量a进行操作,突然有中断也对a进行操作,这时候就有可能出现错误之前裸机编程中都没出现错误,猜测是出现这种问题的概率太低了,即便出现,重启一次后问题消失,也难以找出原因



