返回列表 发新帖我要提问本帖赏金: 50.00元(功能说明)

[APM32F4] mbedos RTOS介绍与应用(下)

[复制链接]
4502|2
 楼主| 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 个线程使用数据,如下面的示意图:
image002.png

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

  3. Queue<uint32_t, 255> queue;
  4. void send_thread()
  5. {
  6.     uint32_t i = 0;
  7.     while (true)
  8.     {
  9.         i++;
  10.         queue.put(&i);
  11.         ThisThread::sleep_for(1s);
  12.     }
  13. }

  14. int main (void)
  15. {
  16.     Thread thread;
  17.     thread.start(&send_thread);

  18.     while (true)
  19.     {
  20.         osEvent evt = queue.get();
  21.         if (evt.status == osEventMessage)
  22.         {
  23.             uint32_t* v = (uint32_t *)evt.value.p;
  24.             printf("Value id %u. \n", *v);
  25.         }
  26.     }
  27. }

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


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


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


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


   示例代码如下:

  1. #include "mbed.h"
  2. #include "rtos.h"
  3. #include "UnbufferedSerial.h"

  4. typedef struct
  5. {
  6.     uint32_t length;
  7.     char str[255];
  8. } message_t;

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

  12. /* Send Thread */
  13. void send_thread ()
  14. {
  15.     int32_t i =-1;
  16.     char serialdata[255];
  17.     char key;
  18.     while (true)
  19.     {
  20.         pc.read(&key, 1);
  21.         serialdata[++i] = key;
  22.         if (serialdata[i]=='\n' || i==255)
  23.         {
  24.             message_t *message = mpool.alloc();
  25.             message->length = i;
  26.             memcpy(message->str,serialdata,i);
  27.             queue.put(message);
  28.             i=-1;
  29.         }
  30.         ThisThread::yield();
  31.     }
  32. }
  33. int main (void)
  34. {
  35.     Thread thread;
  36.     thread.start(&send_thread);

  37.     while (true)
  38.     {
  39.         osEvent evt = queue.get();
  40.         if (evt.status == osEventMessage)
  41.         {
  42.             message_t *message = (message_t*)evt.value.p;
  43.             pc.write("User input length is %d.\r\n",message->length);
  44.             for (uint8_t i=0;i<message->length;i++)
  45.             {
  46.                 pc.write(&message->str[i],1);
  47.             }
  48.             pc.write("\r\n", 2);
  49.             mpool.free(message);
  50.         }   
  51.         ThisThread::yield();
  52.     }
  53. }

    这段代码实现了一个基于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 的功能,工作示意图如下:

image003.png


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

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

  1. #include "mbed.h"
  2. #include "rtos.h"
  3. #include "UnbufferedSerial.h"

  4. typedef struct
  5. {
  6.     uint32_t length;
  7.     char str[255];
  8. } message_t;

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

  13. void serialhandler ()
  14. {
  15.     char key;
  16.     pc.read(&key,1);
  17.     serialdata[++bufindex]=key;
  18.     if (serialdata[bufindex]=='\n' || bufindex==255)
  19.     {
  20.         message_t *message = queue.alloc();
  21.         message->length = bufindex;
  22.         memcpy(message->str,serialdata,bufindex);
  23.         queue.put(message);
  24.         bufindex=-1;
  25.     }
  26. }

  27. int main (void)
  28. {
  29.     pc.attach(&serialhandler);
  30.     while (true)
  31.     {
  32.         osEvent evt = queue.get();
  33.         if (evt.status == osEventMail)
  34.         {
  35.             message_t *message = (message_t*)evt.value.p;
  36.             pc.write("User input length is %d.\r\n",message->length);
  37.             for (uint8_t i=0;i<message->length;i++)
  38.             {
  39.                 pc.write(&message->str[i],1);
  40.             }
  41.                
  42.             pc.write("\r\n",2);
  43.             queue.free(message);
  44.         }
  45.         ThisThread::yield();
  46.     }
  47. }

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

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

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

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

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


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

  4. typedef struct
  5. {
  6.     uint32_t length;
  7.     char str[255];
  8. } message_t;

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

  14. void serialhandler ()
  15. {
  16.     char key;
  17.     pc.read(&key, 1);
  18.     serialdata[++bufindex]=key;
  19.     if (serialdata[bufindex]=='\n' || bufindex==255)
  20.     {
  21.         message_t *message = queue.alloc();
  22.         message->length = bufindex;
  23.         memcpy(message->str,serialdata,bufindex);
  24.         queue.put(message);
  25.         bufindex=-1;
  26.     }
  27. }

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

  39.         pc.write("\r\n", 2);
  40.         queue.free(message);
  41.     }
  42. }
  43. int main (void)
  44. {
  45.     pc.attach(&serialhandler);
  46.     tick.attach(&serialout,1s);
  47.     while(true);
  48. }

    这段代码与之前的代码相比,主要区别在于引入了一个定时器对象`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
理由:恭喜通过原创审核!期待您更多的原创作品~

评论

衔接上文,介绍mbedos 线程间的通讯方式以及实现演示,并跟随了中断中的应用,文末对两篇文章进行总结梳理,结构完整。  发表于 2024-5-30 15:14
szt1993 发表于 2024-5-23 17:05 | 显示全部楼层
RTOS(实时操作系统)是不可或缺的一部分,尤其是在驱动控制层面
您需要登录后才可以回帖 登录 | 注册

本版积分规则

60

主题

108

帖子

17

粉丝
快速回复 在线客服 返回列表 返回顶部