打印
[经验分享]

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

[复制链接]
2210|20
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
八层楼|  楼主 | 2024-12-13 08:24 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
裸机开发模式
所谓裸机开发,指的就是没有操作系统,就是单片机开发。程序的运行,完全取决于代码的逻辑设计,硬件设备的固定设定。不需要操作系统的参与和调度。
这里将韦老师上课举得例子拿来进行分析
讲的是一位宝妈,需要一编进行喂孩子吃饭,一边需要回复同事的消息。



那么我们首先想到的方式就是进行轮询

轮询方式
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.remain--;
}
//当remain减到0,就表示要调用对应结构体中的函数了
        for(i=0;i<3;i++){
        if(timers.remain==0){
        timers.function();//调用函数
        timers.remain=timers.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){
                        timers.function();//调用函数
                }
        }
        }
}
void timer_isr(){
        for(i=0;i<3;i++){
        timers.remain--;
}
        for(i=0;i<3;i++){
        if(timers.remain==0){
        flag=1;//设置标志位
        timers.remain=timers.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程序   
喂饭()
{
    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,这样就能避免这个浪费。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/m0_56145255/article/details/122901150

使用特权

评论回复
沙发
deliahouse887| | 2025-1-4 08:19 | 只看该作者
程序员直接操作硬件寄存器,实现对CPU、内存、外设的精细控制。

使用特权

评论回复
板凳
deliahouse887| | 2025-1-5 11:16 | 只看该作者
在多任务环境中,裸机开发需要手动管理任务间的切换和同步,这会增加开发的复杂性。

使用特权

评论回复
地板
timfordlare| | 2025-1-5 12:40 | 只看该作者
而对于资源极其有限、实时性要求相对较低或对硬件控制有特殊需求的场景,裸机编程可能是更合适的选择。裸机编程允许程序员直接操作硬件寄存器,实现对硬件资源的精细控制,且无需额外的操作系统开销。

使用特权

评论回复
5
gygp| | 2025-1-5 13:41 | 只看该作者
RTOS 本身需要占用一定的 Flash 和 RAM 空间来存储代码和运行时数据。对于资源极其有限的单片机,可能会因为 RTOS 的资源占用而无法满足应用需求。例如,一个只有几 KB Flash 和几百字节 RAM 的单片机可能无法运行复杂的 RTOS。

使用特权

评论回复
6
wilhelmina2| | 2025-1-5 14:31 | 只看该作者
由于没有操作系统的开销,程序可以直接访问硬件,执行效率高。

使用特权

评论回复
7
51xlf| | 2025-1-5 17:49 | 只看该作者
对于简单的项目或个人独立开发,裸机编程可能更加合适;而对于复杂的业务场景和团队协作开发,RTOS则能提供更好的任务管理和协作机制。

使用特权

评论回复
8
51xlf| | 2025-1-5 19:50 | 只看该作者
裸机开发是指在没有操作系统的情况下直接在硬件上运行应用程序的开发方式。在这种模式下,程序的运行完全依赖于代码的逻辑设计和硬件设备的固定设定

使用特权

评论回复
9
pmp| | 2025-1-5 20:10 | 只看该作者
没有操作系统的额外开销,如任务调度、内存管理等模块占用单片机的 Flash 和 RAM 空间,对于资源有限的单片机(如 8051 系列),可以最大限度地利用硬件资源来实现功能。例如,一个简单的温度控制裸机程序,几乎所有的内存空间都可以用于温度传感器数据存储和控制算法。

使用特权

评论回复
10
jonas222| | 2025-1-7 13:21 | 只看该作者
对于功能复杂、实时性要求高的应用,RTOS开发可以简化开发过程,提高系统的稳定性和可靠性。

使用特权

评论回复
11
geraldbetty| | 2025-1-7 13:53 | 只看该作者
在处理多任务时,裸机开发难以保证实时性,因为任务之间会相互干扰。

使用特权

评论回复
12
rosemoore| | 2025-1-7 14:18 | 只看该作者
相比裸机开发,RTOS的学习难度较大,需要掌握更多的概念和技术。

使用特权

评论回复
13
pmp| | 2025-1-7 15:10 | 只看该作者
对于简单的任务,裸机开发更加直观和容易实现。

使用特权

评论回复
14
mnynt121| | 2025-1-7 15:42 | 只看该作者
在实时系统中,任务的完成时间必须是可预测的。RTOS通过提供确定性调度来满足实时性要求,而裸机开发需要开发者自行保证。

使用特权

评论回复
15
hudi008| | 2025-1-8 08:13 | 只看该作者
裸机编程是指直接在单片机上编写程序,不依赖于任何操作系统。程序员需要手动管理所有的硬件资源,包括内存、中断、定时器等。

使用特权

评论回复
16
adolphcocker| | 2025-1-9 20:49 | 只看该作者
RTOS 是一种专门用于实时应用的操作系统,它提供了任务调度、时间管理、内存管理、资源分配等功能。在单片机上使用 RTOS 开发,开发人员可以将应用程序分解为多个独立的任务,由 RTOS 负责这些任务的调度和管理。

使用特权

评论回复
17
jtracy3| | 2025-1-11 15:46 | 只看该作者
RTOS开发是指在实时操作系统的基础上进行应用程序开发的模式。RTOS提供了一套完整的任务管理、同步、通信和定时等机制,使得开发者可以更容易地开发出复杂的嵌入式系统。

使用特权

评论回复
18
hudi008| | 2025-1-11 16:55 | 只看该作者
RTOS本身会占用一定的系统资源,对于资源非常有限的系统可能不太适用。

使用特权

评论回复
19
bestwell| | 2025-1-11 20:29 | 只看该作者
RTOS的引入增加了系统的复杂度,需要开发者理解操作系统的工作原理和API。此外,操作系统本身的开销可能会影响实时性能。

使用特权

评论回复
20
cashrwood| | 2025-1-11 21:20 | 只看该作者
单片机裸机编程和RTOS开发模式各有优缺点。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

91

主题

4166

帖子

2

粉丝