你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

查看: 6009|回复: 21

[STM32L496] 【NUCLEO-L496ZG评测】+ UCOSIII:内存管理和消息队列

[复制链接]

60

主题

2051

回帖

31

蝴蝶豆

版主

最后登录
2020-12-9
发表于 2017-4-15 10:06:34 | 显示全部楼层 |阅读模式
本帖最后由 creep 于 2017-4-16 14:03 编辑

           在上个贴了里面我们测试了信号量和软件定时器,信号量的作用类似一个标志位一样,能在中断和任务之间传输一种状态 ,但是并不能传递更多的信息内容和信息长度大小这些,下面我们要测试的消息队列就是用来传递具体的消息内容在中断和任务以及任务之间。        有些情况下任务或者ISR与另一个任务间进行通信,这种信息交换有2种方法:全局变量或者发送消息。果使用全局变量,任务或者ISR就必须确保它独占该变量。如果防止被ISR嵌套就只能用来关中断来进行保护。如果是任务间共享该变量,那么可以通过关中断、锁调度器、信号量、mutex来保护变量。任务与ISR通信只能通过全局变量,如果全局变量被ISR改变,任务将不会知道全局变量被改变,除非任务检测该变量或者ISR标记任务告知该变量被改变。发送消息可以很好的解决这个问题。


     消息中一般包含一个指向数据的指针和数据的大小等信息,这里的指针可以指向数据区或者一个函数。消息的发送方和接收方都应知道消息的具体含义 ,这样就可以在接收到消息时进行正常的处理。因为消息发送的是指针并不是直接拷贝的消息内容,所以接收消息方要在接收到消息后自己去访问具体的消息内容。
    一个比较重要的问题是因为消息发放的是地址给接收方,所以在消息发送后接收方访问之前该消息必须不能被修改,否则可能会导致消息的传递出错。简要概括如下:
1)在发送消息时会发送消息的地址和消息的地址长度。 初始化消息时会设置消息个数,如果发送的消息没有被取走的话达到了

消息存储的最大个数时会导致消息的丢失。
2)发送消息之后消息被取走之前,消息所在的内存不应该被修改,否则可能会导致取到消息出错,所以正确的做法是定一个内存空间用于保存每条发送的消息。

一个比较形象的消息传递图如下:


TIM截图20170415093222.png

1、内存管理


    UCOSIII提供的动态内存管理方案是将存储区分成区和块,一个存储区有几个固定大小的块组成。如下图:


401e2c3c-4e8e-41e8-b677-a13f3a3ffdfb.png
这个存储区可以直接定义或者使用malloc()申请。使用的时候从这个存储区中取出空闲的块,使用之后释放。
为了测试消息队列,我们使用UCOSIII的内存管理定一个长度为100字节,个数为12的内存空间用来存储消息。
  1. /*直接定义的方法*/

  2. //定义存储变量
  3. OS_MEM MyPartition;
  4. //直接定义存储区
  5. CPU_INT08U MyPartitionStorage[12][100];

  6. /*使用malloc的方式*/

  7. //定义存储变量
  8. OS_MEM *MyPartitionPtr;
  9. //使用malloc定义存储区
  10. MyPartitionPtr = (OS_MEM *)malloc(sizeof(OS_MEM));
复制代码
定义之后需要创建一个内存管理对象

  1.   //定义一个内存管理
  2.     OSMemCreate((OS_MEM *) &MyPartition,
  3.                 (CPU_CHAR *) "mymemory",
  4.                 (void *) &MyPartitionStorage[0][0],
  5.                 (OS_MEM_QTY) MY_MEM_NUM,
  6.                 (OS_MEM_SIZE) MY_MEMBLOCK_SIZE,
  7.                 (OS_ERR *) &err);
复制代码
使用的时候先申请后使用

  1. //定义要申请的内存变量
  2. CPU_INT08U *MyDataBlkPtr;
  3. //申请内存
  4.   MyDataBlkPtr = OSMemGet((OS_MEM *) &MyPartition, (OS_ERR *) &err);
复制代码
使用之后释放即可

  1. //释放内存p
  2.     OSMemPut((OS_MEM *) &MyPartition, (void *) p, (OS_ERR *) &err);
复制代码


从上面的操作过程可以看到UCOSIII的内存管理其实就是定义一个二维数组进行使用,只是使用UCOSIII的内存管理可以很好的判断内存是否有空闲以及申请和释放更加安全简单一些。


  2、消息队列
  
消息队列的测试是在按键扫描函数中Post消息给另外一个任务,消息中包含当前OS的TICKS,接收到消息的任务会取出消息输出到串口同时也会打印剩余的消息个数。注意测试中我们定义的消息个数最大为12个。


使用之前定义和创建消息队列
  1. /定义消息队列
  2. OS_Q  Key_Q;
  3. //创建消息队列
  4. OSQCreate((OS_Q *) &Key_Q,
  5.            (CPU_CHAR *) "my_q",
  6.            (OS_MSG_QTY) 12,
  7.            (OS_ERR * )&err);
复制代码
任务中pend消息队列,OSQPend的返回值就是消息队列传递的指针,返回的size就是这条消息的长度。
为了测试消息的传递和消息的个数,我们的post任务5秒执行一次。
  1. void task1_task(void *p_arg)
  2. {
  3.   OS_ERR err;
  4.   void *p;
  5.         OS_MSG_SIZE size;
  6.   uint8_t buff[20];
  7.   CPU_SR_ALLOC();

  8.   while (DEF_ON)
  9.   {
  10.     p = OSQPend((OS_Q *) &Key_Q,
  11.                (OS_TICK) 0,
  12.                (OS_OPT)OS_OPT_PEND_BLOCKING ,
  13.                (OS_MSG_SIZE *) &size,
  14.                (CPU_TS *) 0,
  15.                (OS_ERR *) &err);
  16.     OS_CRITICAL_ENTER();
  17.     sprintf((char*)buff,"msg num:%d\r\n",Key_Q.MsgQ.NbrEntries);
  18.     LPUart_SendData((uint8_t*)buff, strlen((const char*)buff));
  19.     LPUart_SendData((uint8_t*)p, size);
  20.     //释放内存
  21.     OSMemPut((OS_MEM *) &MyPartition, (void *) p, (OS_ERR *) &err);
  22.     OS_CRITICAL_EXIT();
  23.     //延时5秒
  24.     OSTimeDlyHMSM(0, 0, 5, 0, OS_OPT_TMR_PERIODIC, &err);
  25.   }
  26. }
复制代码


  按键的检测函数中Post消息,注意如果消息个数超过12个导致内存没有被及时释放会出现消息丢失,此时会打印信息到串口
  1. //按键定时器回调函数
  2. void KeyTmrCallback(void *p_tmr, void *p_arg)
  3. {
  4.     //定义USER键按下的时间计数,每次为10ms
  5.     static uint8_t KeyUser_PressCnt = 0;
  6.     static uint8_t KeyUser_Release  = ENABLE;
  7.     CPU_INT08U *MyDataBlkPtr;

  8.     OS_ERR err;

  9.     if(KEY_USER)
  10.     {
  11.         //按键按下
  12.         KeyUser_PressCnt++;

  13.         if((KeyUser_PressCnt > 10) && (KeyUser_Release == ENABLE))
  14.         {
  15.           //申请内存
  16.           MyDataBlkPtr = OSMemGet((OS_MEM *) &MyPartition, (OS_ERR *) &err);
  17.                                         if(MyDataBlkPtr != NULL)
  18.                                         {
  19.                                                 sprintf((char*)MyDataBlkPtr,"Current Ticks:%d",OSTickCtr);
  20.                                                 //psot消息队列
  21.                                                 OSQPost((OS_Q *)&Key_Q , (void *) MyDataBlkPtr, (OS_MSG_SIZE) strlen((const char*)MyDataBlkPtr), (OS_OPT) OS_OPT_POST_FIFO, (OS_ERR *) &err);
  22.           }
  23.                                         else
  24.                                         {
  25.                                                 //申请内存失败
  26.                                                 printf("OSMemGet Failed\r\n");
  27.                                         }
  28.                                         KeyUser_PressCnt = 0;
  29.           KeyUser_Release  = DISABLE;
  30.         }
  31.     }
  32.     else
  33.     {
  34.         //按键松开
  35.         KeyUser_PressCnt = 0;
  36.         KeyUser_Release  = ENABLE;
  37.     }
  38. }
复制代码
测试中我们按下按键可以看到输出如下,如果5秒内按键次数少于12个时消息的传输不会出错


TIM截图20170415095122.png



如果在5秒内快速按键超过12个,串口输出会提示申请内存出错,消息队列中只会保存12个有效的消息,多余的消息将会丢失。
要避免这种问题要么申请大的内存空间要么加快消息的处理(此处是为了演示效果故意5秒处理一次消息)

TIM截图20170415095624.png


消息队列的功能远不止上面测试的那些,除此之外还有很多比较强大的功能,但是基本的使用熟悉后再进阶就比较简单了,更多的内容可以参考之前帖子分享的官方文档内容。


测试代码:
UCOSIII-Q.rar (1.49 MB, 下载次数: 161)

评分

参与人数 1ST金币 +10 收起 理由
MrJiu + 10 支持这种原理讲解的帖子!!!.

查看全部评分

回复

使用道具 举报

47

主题

3404

回帖

30

蝴蝶豆

版主

最后登录
2020-12-7
发表于 2017-4-15 12:49:17 | 显示全部楼层
学习了。。。
回复 支持 反对

使用道具 举报

3

主题

999

回帖

363

蝴蝶豆

版主

最后登录
2020-11-26
发表于 2017-4-15 13:40:12 | 显示全部楼层
强烈支持原理帖子的讲解!!!!
回复 支持 反对

使用道具 举报

64

主题

744

回帖

23

蝴蝶豆

实习版主

最后登录
2020-12-8
发表于 2017-4-15 13:47:35 | 显示全部楼层
前来顶贴
回复 支持 反对

使用道具 举报

29

主题

1256

回帖

59

蝴蝶豆

论坛元老

最后登录
2020-11-16
发表于 2017-4-15 14:03:04 | 显示全部楼层
mark,又一篇原创精品帖
回复 支持 反对

使用道具 举报

76

主题

683

回帖

17

蝴蝶豆

论坛元老

最后登录
2020-11-19
发表于 2017-4-15 14:34:16 | 显示全部楼层
本帖最后由 any012 于 2017-4-15 14:50 编辑

好帖子,感谢分享,会出成系列贴吗?


请教个问题:
UCOS的教程里,大都是先新建个任务,然后再任务里再创建其它任务,创建完后删除最开始的任务。为什么是这样的呢?
最近在看freertos,CUBEMX直接生成的工程,是在main函数里,有个初始化函数MX_FREERTOS_Init(),在这个函数里创建各个任务及信号量等。
再回看UCOS里的初始化的这个任务,建立再删除,是否有些多余,不用任务,而是用函数来完成子任务的创建,行不行?


回复 支持 反对

使用道具 举报

60

主题

2051

回帖

31

蝴蝶豆

版主

最后登录
2020-12-9
 楼主| 发表于 2017-4-15 15:01:55 | 显示全部楼层
哈哈哈,因为参考的官方网站移植代码里面就是这样写的。像你说的freeRTOS那样肯定是可以的。
至于UCOSIII的演示例子代码里面为什么要先建立一个任务然后再在这个任务里面建立其他任务我也感觉这样有啥特别的作用。或许是为了mian看起来更加简洁,或者这样可以把其他的任务分成模块写到其他的.c文件中里面去,或许真的其他的原因。我也是一边看文档一边学习然后写测试代码,理解可能比较有限。
回复 支持 反对

使用道具 举报

60

主题

2051

回帖

31

蝴蝶豆

版主

最后登录
2020-12-9
 楼主| 发表于 2017-4-15 15:04:16 | 显示全部楼层
any012 发表于 2017-4-15 14:34
好帖子,感谢分享,会出成系列贴吗?

忘记直接点回复了,希望你能看到我的回帖,大家可以一起讨论交流学习。
回复 支持 反对

使用道具 举报

76

主题

683

回帖

17

蝴蝶豆

论坛元老

最后登录
2020-11-19
发表于 2017-4-15 15:15:15 | 显示全部楼层
本帖最后由 any012 于 2017-4-15 15:18 编辑
creep 发表于 2017-4-15 15:04
忘记直接点回复了,希望你能看到我的回帖,大家可以一起讨论交流学习。 ...

看到了,感谢。
刚才又下载了野火的ucos教程,也是先建个任务,任务里再创建其他任务后删除第一个任务。
原来是官方例程就这样写的啊。
回复 支持 反对

使用道具 举报

60

主题

2051

回帖

31

蝴蝶豆

版主

最后登录
2020-12-9
 楼主| 发表于 2017-4-15 15:26:50 | 显示全部楼层
any012 发表于 2017-4-15 15:15
看到了,感谢。
刚才又下载了野火的ucos教程,也是先建个任务,任务里再创建其他任务后删除第一个任务。
...

官方网站有各个型号和系列的开发板(nucleo、discovery 等)移植,也有一定参考学习价值。
回复 支持 反对

使用道具 举报

关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
13245底部标题123相同标题
12底部标题123相同标题
33333底部标题123相同序号
3435底部标题-无链接
关注我们
st-img 微信公众号
st-img 手机版