感谢韦东山老师的直播教学,在今天的这次教学中学到了很多实用的知识。对逻辑开发和RTOS开发有了本质的理解。
博主也在这里向大家推荐一个嵌入式学习网站百问网官网。在这里有所有嵌入式学习的视频,更有韦东山老师的亲自授课为你解答疑惑。
所谓裸机开发,指的就是没有操作系统,就是单片机开发。程序的运行,完全取决于代码的逻辑设计,硬件设备的固定设定。不需要操作系统的参与和调度。
这里将韦老师上课举得例子拿来进行分析
讲的是一位宝妈,需要一编进行喂孩子吃饭,一边需要回复同事的消息。
那么我们首先想到的方式就是进行轮询
void main{}{
while(1){
eat();//喂孩子吃饭函数
message();//回复同事消息
}
}
这是一个非常经典的单片机程序,是不是就是你的跑马灯程序。那么我们来分析这个程序:
在执行喂孩子这个函数的时候,回复同事消息这个函数是无法执行的,在执行回复同事消息这个函数的时候,程序是无法执行喂孩子这个函数,那么对于同事而言,宝妈总是在一段时间消失,无法回复消息。对于孩子而言,妈妈总是在一段时间无法来喂我吃饭。
双方(同事和孩子)似乎都没有得到满足。显然这个程序是不太好的,那么我们如何来进行优化呢?
相信已经有朋友类比到了我们最初的单片机实验,我们可以使用中断呀!没错中断就是下面我们的优化方式,也叫做事件驱动方式
事件是一个宽泛的概念,什么是事件?可以是:按下了按键、串口接收到了数据、模块产生了中断、某个全局变量被设置了。
什么叫事件驱动?当某个事件发生时,才调用对应函数,这就叫事件驱动。
我们将上面的例子进行改进:
void crying_isr(){//检测孩子是否在哭的中断函数
eating();//哭了就执行喂孩子吃饭的函数
}
void message_isr(){//检测同事是否发消息函数.
message();//执行回消息函数
}
void main(){
while(1){
}
}
这种编程方式就使得这两个中断函数执行的都很快,不用像轮询一样再去等待上一个函数的执行完毕。
但是如果两个中断同时发生,就会相互影响:
对于上面的程序,我们出现的问题是,当两个中断同时产生的时候,同一时间只能处理一个,如果一个中断处理时间比较长,就会影响另一个中断的处理。
下面我们针对这些问题来进行改进我们的程序。
对于中断的处理,原则上是“尽快”。否则就会影响其他中断,导致其他中断的处理延迟,甚至丢失。
下面我们通过设置标志位来改进程序。
void crying_isr(){//检测孩子是否哭了
is_crying=1;//如果哭了就将标志位置1
}
void message_isr(){
is_message=1;//将有消息标志位置1。
}
void main(){
while(1){
if(is_crying==1)
eating();
if(is_message==1)
message();
}
}
设置了标志位以后,我们的中断处理函数就会很快执行,那么就不会影响到其他中断的处理,不会导致中断的延迟,丢失。
相信大家已经想到了,中断持续触发后的后续处理就退回轮询了。那岂不是我们这也没啥改进?别急,下面我们继续改进!
这里我先用韦老师的例子来给大家介绍这种方法,然后再来分析上面的例子的改进方法。
例子:宝妈喂饭这个例子只有两个任务,如果有多个任务,一些有经验的工程师会使用定时器来驱动
那么我们编写代码可以如下
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数组成员的remain都减1.
for(i=0;i<3;i++){
timers[i].remain--;
}
//当remain减到0,就表示要调用对应结构体中的函数了
for(i=0;i<3;i++){
if(timers[i].remain==0){
timers[i].function();//调用函数
timers[i].remain=timers[i].period;//重置remain.
}
}
}
经过这样设置以后,我们很好的解决了每个人数的处理时间。但是对于当某一个程序执行时间很长,就会出现下面的后果:
那么我们怎么改进呢?针对这个问题,这里我以上面第二个例子进行分析,对于宝妈问题同理
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){
for(int j=0;j<3;j++){
if(flag[i]){
timers[i].function();//调用函数
}
}
}
}
void timer_isr(){
for(i=0;i<3;i++){
timers[i].remain--;
}
for(i=0;i<3;i++){
if(timers[i].remain==0){
flag[i]=1;//设置标志位
timers[i].remain=timers[i].period;
}
}
}
通过上面设置标志位,来解决因为某个函数执行时间过长导致影响整个过程的时间基准。
问题,如果当任务处理函数执行时间都很长的时候,我们的裸机该怎么办呢?
这里我们可以使用状态机的思想来解决这个问题(其实思路就是操作系统的时间片)
void crying_isr(void)
{
static int state = 0;
switch (state)
{
case 0: /* 开始 */
{
/* 盛饭 */
state++;
return;
}
case 1: /* 盛菜 */
{
/* 盛菜 */
state++;
return;
}
case 2:
{
/* 拿勺子 */
state++;
return;
}
}
}
void mesage_isr(void)
{
static int state = 0;
switch (state)
{
case 0: /* 开始 */
{
/* 打开电脑 */
state++;
return;
}
case 1:
{
/* 观看信息 */
state++;
return;
}
case 2:
{
/* 打字 */
state++;
return;
}
}
}
void main()
{
while (1)
{
crying_isr();
message_isr();
//其实就是将这个执行时间很长的函数,拆分为短时间来处理。
}
}
显然这里使用状态机拆分程序:
总的来说,裸机程序难以解决的问题就是,控制每个任务的运行时间。难以消除任务与任务之间的相互影响。
假设要调用两个函数AB,AB执行的时间都很长,使用裸机程序时可以把AB函数改造为"状态机",还可以使用RTOS。这两种方法的核心都是"分时复用":
这里还是以宝妈的例子进行分析:
将宝妈比作CPU,喂孩子比作函数A,回消息比作函数B
宝妈一会儿喂孩子饭,一会儿回消息。当这个时间足够短的时候,从宏观上来看就是两个事件同时发生;从微观上来看,这依旧是两件事情。
// RTOS程序
喂饭()
{
while (1)
{
喂一口饭();
}
}
回信息()
{
while (1)
{
回一个信息();
}
}
void main()
{
create_task(喂饭);//创建一个任务
create_task(回信息);//创建一个任务
start_scheduler();//执行任务列表
while (1)
{
sleep();
}
}
关键在于RTOS让多个任务轮流运行,不再需要我们手工在任务函数去使用状态机拆分程序。
注意: RTOS其实现的原理就是链表的操作,通过优先级的高低,形成遍历链表顺序的先后。达到优先级高,先处理。通过判断链表是否为空,判断是否需要执行函数。同时同一链表,通过分时,在时间片内时间执行一个任务后,将该任务放置链表末尾,进而执行下一个任务。
关于休眠和唤醒,其实就是将要休眠(或者没有达到满足条件的任务)放置到休眠链表中,当条件满足时再唤醒该任务。
后面详细介绍该部分。
这里其实就是和我们平时在Linux上编程一样,要考虑临界资源的访问问题,解决办法依旧还是设置互斥锁。
当我们对某一个任务的执行设置了条件的时候,如果我们不将被设置条件的任务进行休眠,那么这个函数就会不停的进行条件判断,如下
void main(){
A(){
//当A快要执行完,执行此内容(假设,也可能是某个条件)
if(xxx){
flag=1;
}
};
if(flag){//如果不将B进行休眠,如果A执行100000次,那么这个if判断条件就会执行这么多次。所以这样就会造成浪费资源,没必要的开销。
B();
}
}
所以设置任务的休眠,将B进行休眠,就让A一直执行,当flag为1时,再唤醒B,这样就能避免这个浪费。
提示:这里对文章进行总结:
非常充实的一天,温故了以前的知识。同时理解到了RTOS和裸机开发的区别,以及RTOS的工作实质。
继续加油,不断努力!