深入理解裸机与RTOS开发模式

文章目录

  • 前言
  • 裸机开发模式
    • 轮询方式
    • 事件驱动方式
    • 改进的事件驱动方式
    • 常用时间驱动方式:定时器
    • 使用状态机进行改进
    • 总结
  • RTOS的引入
  • RTOS编程要注意的问题
    • 临界资源的访问
    • 任务的休眠唤醒
  • 总结


前言

感谢韦东山老师的直播教学,在今天的这次教学中学到了很多实用的知识。对逻辑开发和RTOS开发有了本质的理解。
博主也在这里向大家推荐一个嵌入式学习网站百问网官网。在这里有所有嵌入式学习的视频,更有韦东山老师的亲自授课为你解答疑惑。


裸机开发模式

所谓裸机开发,指的就是没有操作系统,就是单片机开发。程序的运行,完全取决于代码的逻辑设计,硬件设备的固定设定。不需要操作系统的参与和调度。
这里将韦老师上课举得例子拿来进行分析
讲的是一位宝妈,需要一编进行喂孩子吃饭,一边需要回复同事的消息。
深入理解裸机与RTOS开发模式_第1张图片那么我们首先想到的方式就是进行轮询

轮询方式

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();
}
}

设置了标志位以后,我们的中断处理函数就会很快执行,那么就不会影响到其他中断的处理,不会导致中断的延迟,丢失。

相信大家已经想到了,中断持续触发后的后续处理就退回轮询了。那岂不是我们这也没啥改进?别急,下面我们继续改进!

常用时间驱动方式:定时器

这里我先用韦老师的例子来给大家介绍这种方法,然后再来分析上面的例子的改进方法。

例子:宝妈喂饭这个例子只有两个任务,如果有多个任务,一些有经验的工程师会使用定时器来驱动

  • 设置一个定时器,比如每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数组成员的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();
       //其实就是将这个执行时间很长的函数,拆分为短时间来处理。
    }
}

显然这里使用状态机拆分程序:

  • 比较麻烦
  • 有些复杂的程序无法拆分为状态机。

总结

总的来说,裸机程序难以解决的问题就是,控制每个任务的运行时间。难以消除任务与任务之间的相互影响。

RTOS的引入

假设要调用两个函数AB,AB执行的时间都很长,使用裸机程序时可以把AB函数改造为"状态机",还可以使用RTOS。这两种方法的核心都是"分时复用":

  • 分时:函数A运行一小段时间,函数B再运行一小段时间
  • 复用:复用谁?就是CPU

这里还是以宝妈的例子进行分析:
将宝妈比作CPU,喂孩子比作函数A,回消息比作函数B

宝妈一会儿喂孩子饭,一会儿回消息。当这个时间足够短的时候,从宏观上来看就是两个事件同时发生;从微观上来看,这依旧是两件事情。
深入理解裸机与RTOS开发模式_第2张图片

// RTOS程序    
喂饭()
{
    while (1)
    {
        喂一口饭();
    }
}

回信息()
{
    while (1)
    {
        回一个信息();
    }
}

void main()
{
    create_task(喂饭);//创建一个任务
    create_task(回信息);//创建一个任务
    start_scheduler();//执行任务列表
    while (1)
    {
        sleep();
    }
}

关键在于RTOS让多个任务轮流运行,不再需要我们手工在任务函数去使用状态机拆分程序。

注意: 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的工作实质。
继续加油,不断努力!

你可能感兴趣的:(智能家居项目,单片机,stm32,c语言,freertos)