发新帖本帖赏金 50.00元(功能说明)我要提问
返回列表
打印
[APM32F4]

mbedos RTOS介绍与应用(下)

[复制链接]
3838|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
DKENNY|  楼主 | 2024-5-20 20:53 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 DKENNY 于 2024-5-20 20:51 编辑

#申请原创# @21小跑堂

## 前言

      上篇介绍了 mbed rtos 的一些基本知识,以及线程的同步机制,接下来我们继续讲讲 mbedos 的线程间的通讯以及在中断服务函数中的使用。

## 3、mbedos 线程间通讯

      mbed-rtos 提供了多种方式的线程间通讯,包括队列(Queue)、邮件(Mail)两种,另外还提供了一个辅助类内存池(MemoryPool,用于 Queue 中的数据存储),它们提供的主要方法如下:




Queue
template<typename Queue()
T,
uint32_t
queue_sz>
queue_sz T
osStatus put(T* data, uint32_t millisec=0)
T
osEvent get(uint32_t millisec=osWaitForever)
osEvent
MemoryPool
template<typename MemoryPool()
T,
uint32_t
pool_sz>
pool_sz T
T* alloc(void)
T NULL
T* calloc(void)
T 0 NULL
osStatus free(T *block)
block
Mail
template<typename Mail()
T,
uint32_t
queue_sz>
queue_sz T
T* alloc(uint32_t millisec=0)
Mail T NULL
T* calloc(uint32_t millisec=0)
T 0 NULL
osStatus put(T *mptr)
T
osEvent get(uint32_t millisec=osWaitForever)
osEvent
osStatus free(T *mptr)
mptr


    从以上的方法列表可以看出,Queue 和 Mail 本质上是一样的,不同的是 Queue 需要额外的内存池,而 Mail 可以从系统中分配内存,另外,从使用方式上来说,都是基于先进先 出的队列方式,其中 1 个线程产生数据,另外 1 个线程使用数据,如下面的示意图:


    下面是 Queue 方式单独使用的例子:
#include "mbed.h"
#include "rtos.h"

Queue<uint32_t, 255> queue;
void send_thread()
{
    uint32_t i = 0;
    while (true)
    {
        i++;
        queue.put(&i);
        ThisThread::sleep_for(1s);
    }
}

int main (void)
{
    Thread thread;
    thread.start(&send_thread);

    while (true)
    {
        osEvent evt = queue.get();
        if (evt.status == osEventMessage)
        {
            uint32_t* v = (uint32_t *)evt.value.p;
            printf("Value id %u. \n", *v);
        }
    }
}

      Queue 方式独立使用的缺点是只能传递简单类型的数据,对于结构类型的数据就无能为力了,我们必须借助 MemoryPool 的帮助,只在 Queue 队列中传递指针即可。    在Mbed OS中,MemoryPool和Queue都是用于实现任务间通信的机制,但它们的设计和实现方式略有不同,这导致了它们对数据类型的限制也不同。


    1. **MemoryPool:** MemoryPool是一个用于分配和管理内存块的机制,它可以用于动态地分配内存,然后在不同的任务之间传递这些内存块。因为MemoryPool本质上只是管理一块块内存,而不关心内存中存储的具体内容,所以它能够传递任何类型的数据,包括结构体。


    2. **Queue:** Queue是一个先进先出(FIFO)的数据结构,用于在任务之间传递数据。但是,Queue在实现时需要考虑数据的复制和管理,因此它对传递的数据类型有一定的限制。在Mbed OS中,Queue通常只能传递简单的数据类型,如整数、浮点数、指针等,因为它需要在队列中保存数据的拷贝,并且要确保数据的复制和释放过程是安全和高效的。传递复杂的数据类型,如结构体或者对象,可能会涉及到深拷贝和析构函数的调用,这会增加系统的复杂性和开销,因此通常不建议在Queue中传递复杂的数据类型。


    综上所述,MemoryPool和Queue在设计和实现上有所区别,导致了它们对传递数据类型的限制不同。MemoryPool能够传递任何类型的数据,而Queue通常只能传递简单的数据类型,以确保系统的高效和安全。


   示例代码如下:

#include "mbed.h"
#include "rtos.h"
#include "UnbufferedSerial.h"

typedef struct
{
    uint32_t length;
    char str[255];
} message_t;

MemoryPool<message_t, 16> mpool;
Queue<message_t, 16> queue;
UnbufferedSerial pc(USBTX,USBRX);

/* Send Thread */
void send_thread ()
{
    int32_t i =-1;
    char serialdata[255];
    char key;
    while (true)
    {
        pc.read(&key, 1);
        serialdata[++i] = key;
        if (serialdata[i]=='\n' || i==255)
        {
            message_t *message = mpool.alloc();
            message->length = i;
            memcpy(message->str,serialdata,i);
            queue.put(message);
            i=-1;
        }
        ThisThread::yield();
    }
}
int main (void)
{
    Thread thread;
    thread.start(&send_thread);

    while (true)
    {
        osEvent evt = queue.get();
        if (evt.status == osEventMessage)
        {
            message_t *message = (message_t*)evt.value.p;
            pc.write("User input length is %d.\r\n",message->length);
            for (uint8_t i=0;i<message->length;i++)
            {
                pc.write(&message->str[i],1);
            }
            pc.write("\r\n", 2);
            mpool.free(message);
        }   
        ThisThread::yield();
    }
}

    这段代码实现了一个基于Mbed OS的简单串口通讯应用,其中包括一个发送线程和一个接收线程。主要功能如下:

    1. **初始化**:定义了一个消息结构体`message_t`,内含消息长度和消息内容数组。创建了一个内存池`mpool`和一个消息队列`queue`,用于存储消息。初始化了一个串口对象`pc`,用于串口通讯。

    2. **发送线程** (`send_thread`):该线程负责从串口读取用户输入字符,将字符存入缓冲区`serialdata`中。当接收到换行符或达到最大长度时,将数据封装成消息并放入消息队列中。

    3. **主函数** (`main`):在主函数中,启动发送线程。主循环中不断检查消息队列是否有消息到达。如果有消息到达,就从队列中取出消息并通过串口发送消息内容,最后释放消息所占用的内存。

    总体来说,这段代码展示了如何利用Mbed OS的特性,如内存池和消息队列,实现了一个简单的串口通讯应用。发送线程负责接收用户输入并封装成消息,接收线程则负责从消息队列中获取消息并发送消息内容。通过这种方式实现了线程间的通讯和数据交换。


      这里有必要了解一下线程内的变量作用域,如果你把上面 send_thread 函数中的 i 和 serialdata 变量定义到 while(true)里面,你就会发现应用工作不正常了,输出的字符长度都是 0,这是因为线程重入后,变量在 while 循环中被重新初始化了。

     以上功能如果用 Mail 通讯机制就会更加方便,因为 Mail 整合了 Queue 和 MemoryPool 的功能,工作示意图如下:




## 4、mbed-rtos 在中断服务程序中的应用

      mbed-rtos 提供的同步机制和通讯机制同样也可以应用在中断服务程序中,但考虑到中断服务程序必须尽快返回,所以互斥锁机制不能用,而且所以涉及到需要等待的场合必须立刻返回,我们来看下面的示例代码,其效果和采用多线程方式实现是一样的:

#include "mbed.h"
#include "rtos.h"
#include "UnbufferedSerial.h"

typedef struct
{
    uint32_t length;
    char str[255];
} message_t;

Mail<message_t, 16> queue;
UnbufferedSerial pc(USBTX,USBRX);
int32_t bufindex =-1;
char serialdata[255];

void serialhandler ()
{
    char key;
    pc.read(&key,1);
    serialdata[++bufindex]=key;
    if (serialdata[bufindex]=='\n' || bufindex==255)
    {
        message_t *message = queue.alloc();
        message->length = bufindex;
        memcpy(message->str,serialdata,bufindex);
        queue.put(message);
        bufindex=-1;
    }
}

int main (void)
{
    pc.attach(&serialhandler);
    while (true)
    {
        osEvent evt = queue.get();
        if (evt.status == osEventMail)
        {
            message_t *message = (message_t*)evt.value.p;
            pc.write("User input length is %d.\r\n",message->length);
            for (uint8_t i=0;i<message->length;i++)
            {
                pc.write(&message->str[i],1);
            }
               
            pc.write("\r\n",2);
            queue.free(message);
        }
        ThisThread::yield();
    }
}

    这段代码实现了一个基于Mbed OS的串口通讯应用,包括了一个串口数据处理函数和一个主循环。主要功能如下:

    1. **初始化**:定义了一个消息结构体`message_t`,包含消息长度和消息内容数组。创建了一个邮箱`queue`,用于存储消息。初始化了一个串口对象`pc`,用于串口通讯。还定义了一个缓冲区`serialdata`和一个索引`bufindex`用于暂存串口接收的数据。

    2. **串口数据处理函数** (`serialhandler`):该函数通过串口中断处理接收到的字符,将字符存入缓冲区`serialdata`中。当接收到换行符或达到最大长度时,将数据封装成消息并放入邮箱中。

    3. **主函数** (`main`):在主函数中,通过`pc.attach(&serialhandler)`将串口数据处理函数与串口对象关联。主循环中不断检查邮箱是否有消息到达。如果有消息到达,就从邮箱中取出消息并通过串口发送消息内容,最后释放消息所占用的内存。

    总体来说,这段代码实现了一个简单的串口通讯应用,通过邮箱实现了消息的传递和处理。串口数据处理函数负责接收用户输入并封装成消息,主循环则负责处理接收到的消息并发送消息内容。通过这种方式实现了串口数据的异步处理和通讯。


    当然,不同的中断服务程序之间也可以用 Queue 或 Mail 实现通讯,但需要注意的是, 在使用这两者的 get 函数时一定要立即返回,示例代码如下:
#include "mbed.h"
#include "rtos.h"
#include "UnbufferedSerial.h"

typedef struct
{
    uint32_t length;
    char str[255];
} message_t;

Mail<message_t, 16> queue;
UnbufferedSerial pc(USBTX,USBRX);
int32_t bufindex =-1;
Ticker tick;
char serialdata[255];

void serialhandler ()
{
    char key;
    pc.read(&key, 1);
    serialdata[++bufindex]=key;
    if (serialdata[bufindex]=='\n' || bufindex==255)
    {
        message_t *message = queue.alloc();
        message->length = bufindex;
        memcpy(message->str,serialdata,bufindex);
        queue.put(message);
        bufindex=-1;
    }
}

void serialout()
{
    osEvent evt = queue.get(0);
    if (evt.status == osEventMail)
    {
        message_t *message = (message_t*)evt.value.p;
        pc.write("User input length is %d.\n",message->length);
        for (uint8_t i=0;i<message->length;i++)
        {
            pc.write(&message->str[i], 1);
        }

        pc.write("\r\n", 2);
        queue.free(message);
    }
}
int main (void)
{
    pc.attach(&serialhandler);
    tick.attach(&serialout,1s);
    while(true);
}

    这段代码与之前的代码相比,主要区别在于引入了一个定时器对象`tick`,并定义了一个定时器中断处理函数`serialout`。主要的实现功能包括:

    1. **定时器中断处理函数** (`serialout`):该函数定时从邮箱中获取消息,并将消息内容通过串口发送。与之前的代码相比,这里使用了定时器来周期性地检查邮箱中是否有消息到达,而不是在主循环中不断地轮询。

    2. **主函数** (`main`):在主函数中,通过`pc.attach(&serialhandler)`将串口数据处理函数与串口对象关联。使用`tick.attach(&serialout, 1s)`将定时器中断处理函数与定时器对象关联,设定定时器的周期为1秒。然后进入一个无限循环,等待程序运行。

    这段代码的功能与之前的代码相似,都是实现了一个串口通讯应用,通过邮箱来异步处理串口接收到的消息。不同之处在于使用了定时器来周期性地处理消息发送,而不是在主循环中轮询邮箱状态。这样可以更有效地利用系统资源,避免了主循环中的忙等待,提高了系统的响应速度。

## 结语

    在Mbed OS的世界里,RTOS(实时操作系统)是不可或缺的一部分。它提供了一个强大的基础,使得开发者能够轻松构建复杂的嵌入式系统,并在物联网(IoT)领域发挥作用。通过Mbed RTOS,开发者可以利用其轻量级、高效性以及丰富的功能特性,实现多任务处理、同步通信、定时调度等功能,从而构建出稳定、可靠的物联网设备和嵌入式系统。

    Mbed RTOS的应用范围非常广泛。它可以用于各种物联网设备的开发,如传感器节点、智能家居、工业控制设备等。同时,它也适用于嵌入式系统领域,能够帮助开发者管理系统中的多个任务,实现任务间的协作和通信,提高系统的稳定性和可靠性。另外,Mbed RTOS还可以应用于实时控制系统、嵌入式网络设备等领域,满足实时性要求较高的应用场景。

    在Mbed OS的生态系统中,RTOS是连接硬件和应用程序的桥梁,为开发者提供了一个高效、稳定的开发平台。通过Mbed RTOS,开发者可以专注于应用程序的开发,而无需过多关注底层的系统细节。这种简单易用的开发体验,使得Mbed OS成为了物联网设备和嵌入式系统开发的首选平台之一。

    总的来说,Mbed RTOS在Mbed OS的生态系统中扮演着至关重要的角色。它为开发者提供了一个强大的基础,使得他们能够轻松构建、部署和管理物联网设备和嵌入式系统。


    本次分享就到这里了,本人也是一个初入操作系统的小白,如有问题,欢迎各位讨论交流。





使用特权

评论回复

打赏榜单

21小跑堂 打赏了 50.00 元 2024-05-30
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2024-5-30 15:14 回复TA
衔接上文,介绍mbedos 线程间的通讯方式以及实现演示,并跟随了中断中的应用,文末对两篇文章进行总结梳理,结构完整。 
沙发
szt1993| | 2024-5-23 17:05 | 只看该作者
RTOS(实时操作系统)是不可或缺的一部分,尤其是在驱动控制层面

使用特权

评论回复
板凳
丙丁先生| | 2024-6-11 07:08 | 只看该作者
有例程代码吗?什么开发板呢?

使用特权

评论回复
发新帖 本帖赏金 50.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

34

主题

59

帖子

6

粉丝