FreeRTOS记录(六、FreeRTOS消息队列—Enocean模块串口通讯、RAM空间不足问题分析)_freertos每个现成占用ram

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

我们使用消息队列接收串口的数据,那么需要在stm32l0xx_it.c文件中相关串口的中断处理函数进行消息队列的入队操作:
stm32l0xx_it.c

...
#include "cmsis\_os.h"
...
/\* USER CODE BEGIN EV \*/
extern osMessageQId EnoceanQueueHandle;
/\* USER CODE END EV \*/

...
/\*\*
 \* @brief This function handles LPUART1 global interrupt / LPUART1 wake-up interrupt through EXTI line 28.
 \*/
void LPUART1\_IRQHandler(void)
{
  u8 res;
  /\* USER CODE BEGIN LPUART1\_IRQn 0 \*/
  if(\_\_HAL\_UART\_GET\_FLAG(&hlpuart1,UART_FLAG_RXNE) == SET){
    res = hlpuart1.Instance->RDR;

    xQueueSendFromISR(EnoceanQueueHandle,&res,NULL);
  }
  /\* USER CODE END LPUART1\_IRQn 0 \*/
  HAL\_UART\_IRQHandler(&hlpuart1);
  /\* USER CODE BEGIN LPUART1\_IRQn 1 \*/

  /\* USER CODE END LPUART1\_IRQn 1 \*/
}

2.1 操作寄存器接收串口数据

上面代码中可以看到,使用的是M0 的内核,操作 ISR 和 RDR 寄存器:

在这里插入图片描述

如果是M3、M4 的内核,操作 SR 和 DR 寄存器:

在这里插入图片描述
在这里插入图片描述

2.1 中断中入队

在串口中断中,使用了xQueueSendFromISR(EnoceanQueueHandle,&res,NULL);向消息队列中发送数据;

在这里插入图片描述

为什么使用xQueueSendFromISR而不用osMessagePut

在CubeMX中,封装好的消息发送函数为osMessagePut,和其他一样,封装好的会自动判断是否在中断中发送,自动引用xQueueSendFromISR或者xQueueSend函数,源码如下:

/\*\*
\* @brief Put a Message to a Queue.
\* @param queue\_id message queue ID obtained with \ref osMessageCreate.
\* @param info message information.
\* @param millisec timeout value or 0 in case of no time-out.
\* @retval status code that indicates the execution status of the function.
\* @note MUST REMAIN UNCHANGED: \b osMessagePut shall be consistent in every CMSIS-RTOS.
\*/
osStatus osMessagePut (osMessageQId queue_id, uint32\_t info, uint32\_t millisec)
{
  portBASE_TYPE taskWoken = pdFALSE;
  TickType_t ticks;
  
  ticks = millisec / portTICK_PERIOD_MS;
  if (ticks == 0) {
    ticks = 1;
  }
  
  if (inHandlerMode()) {
    if (xQueueSendFromISR(queue_id, &info, &taskWoken) != pdTRUE) {
      return osErrorOS;
    }
    portEND\_SWITCHING\_ISR(taskWoken);
  }
  else {
    if (xQueueSend(queue_id, &info, ticks) != pdTRUE) {
      return osErrorOS;
    }
  }
  
  return osOK;
}


但是注意!!!osMessagePut 的第二个参数为uint32_t 类型,所以只有当定义的消息Item Sizeuint32_t 时候才能使用,否则消息会出错!

3、消息队列函数形参分析

3.1 关于 void *p

从源码可知,消息队列接收中定义了一个osEvent event;

osEvent osMessageGet (osMessageQId queue_id, uint32\_t millisec)
{
  portBASE_TYPE taskWoken;
  TickType_t ticks;
  osEvent event;

我们在上一篇文章

FreeRTOS记录(五、FreeRTOS任务通知)

中的二、任务通知使用 章节的 3、接收通知 小节 用到过osEvent类型,给出了结构体的定义。

其中结构体中关于 value 是一个联合体,比如如果是uint32_t 类型的数据,直接使用 value.v 读取,但如果是一个地址,而且可能是不同的数据类型,就得使用到 void *p :

在这里插入图片描述

同样的,我们在xQueueReceivexQueueGenericSend里面,第二个形参也使用了void *类型:

BaseType_t xQueueReceive( QueueHandle_t xQueue, void \* const pvBuffer, TickType_t xTicksToWait )
...
...
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void \* const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )

这里要说明的是,void *可以指向任何类型的数据!

来看看我们经常使用的memset原型,是不是能更好的理解:

void \*memset(void \*s, int ch, size\_t n);//将s中当前位置后面的n个字节 (typedef unsigned int size\_t )用 ch 替换并返回 s 

所以消息队列这里使用表示他可以发送和接收任意类型的数据,也就是消息类型,从简单的数据,到结构体,二维数组等消息都是可以传递的。

3.2 关于 void * const pvBuffer 和 const void * const pvItemToQueue

对于第一个void * const pvBuffer

const 后面紧跟的是 pvBuffer , pvBuffer const 类型的,不可变(这里是指的某个地址不可变)但是 *pvBuffer 可变(该地址的数据是可变的)
消息队列接收的时候使用这个定义的形参;

对应的举个例子,如果是const void *pvBuffer:*pvBuffer 是const ,const 后面紧跟的是void,所以 *pvBuffer 可能是任意类型,但是这个数据不可变。

对于第二个const void * const pvItemToQueue

pvItemToQueue 和 *pvItemToQueue 都是不可变的(这个地址不可变,这个地址上的数据不可变)
消息队列发送的时候使用这个定义实参;

4、数据接收处理

4.1 在任务中接收

在任务中接收消息,因为要保存到数组里面,使用了xQueueReceive函数:

...
uint8 USART_Enocean_BUF[100];
uint8 Enocean_Data = 0;       //数据长度记录 
...
/\* USER CODE END Header\_StartenoecanTask \*/
void StartenoecanTask(void const \* argument)
{
  /\* USER CODE BEGIN StartenoecanTask \*/
  /\* Infinite loop \*/
  for(;;)
  {
    if(xQueueReceive(EnoceanQueueHandle,&USART_Enocean_BUF[Enocean_Data++],portMAX_DELAY) == pdPASS){
      while(xQueueReceive(EnoceanQueueHandle,&USART_Enocean_BUF[Enocean_Data++],15));
      HAL\_UART\_Transmit(&huart1,USART_Enocean_BUF, Enocean_Data,0xFFFF); //将串口3接收到的数据通过串口1传出 
      memset(USART_Enocean_BUF,0,sizeof(USART_Enocean_BUF));
      Enocean_Data=0;
    }
    // osDelay(1);
  }

在CubeMX中,封装好的消息发送函数为osMessageGet ,和其他一样,封装好的会自动判断是否在中断中,自动引用xQueueReceiveFromISR或者xQueueReceive函数,源码如下:

/\*\*
\* @brief Get a Message or Wait for a Message from a Queue.
\* @param queue\_id message queue ID obtained with \ref osMessageCreate.
\* @param millisec timeout value or 0 in case of no time-out.
\* @retval event information that includes status code.
\* @note MUST REMAIN UNCHANGED: \b osMessageGet shall be consistent in every CMSIS-RTOS.
\*/
osEvent osMessageGet (osMessageQId queue_id, uint32\_t millisec)
{
  portBASE_TYPE taskWoken;
  TickType_t ticks;
  osEvent event;
  
  event.def.message_id = queue_id;
  event.value.v = 0;
  
  if (queue_id == NULL) {
    event.status = osErrorParameter;
    return event;
  }
  
  taskWoken = pdFALSE;
  
  ticks = 0;
  if (millisec == osWaitForever) {
    ticks = portMAX_DELAY;
  }
  else if (millisec != 0) {
    ticks = millisec / portTICK_PERIOD_MS;
    if (ticks == 0) {
      ticks = 1;
    }
  }
  
  if (inHandlerMode()) {
    if (xQueueReceiveFromISR(queue_id, &event.value.v, &taskWoken) == pdTRUE) {
      /\* We have mail \*/
      event.status = osEventMessage;
    }
    else {
      event.status = osOK;
    }
    portEND\_SWITCHING\_ISR(taskWoken);
  }
  else {
    if (xQueueReceive(queue_id, &event.value.v, ticks) == pdTRUE) {
      /\* We have mail \*/
      event.status = osEventMessage;
    }
    else {
      event.status = (ticks == 0) ? osOK : osEventTimeout;
    }
  }
  
  return event;
}

需要把数据保存至我们自己定义的数组中,所以使用了xQueueReceive函数。

然后等待15ms,确保收到的是一帧完整的数据。接收完一帧数据,通过串口1打印出来,然后清空数据。

4.2 数据解析

根据上面的代码,通过串口助手测试看看效果,发现有下面的问题,最后会多出来一位, 但是每次都是一帧数据正常发送(很简单的问题,仔细看一下代码就知道问题所在了):

在这里插入图片描述

问题的原因很简单,如下图:

在这里插入图片描述
然后数据解析函数直接用以前的驱动包,需要稍微修改一下函数:

在这里插入图片描述
测试效果,成功:

在这里插入图片描述

至此,使用消息队列 串口接收 不定长度的数据测试完成,结果也比较理想。

另外提一下,Enocean除了接收,发送可以直接用以前写好的函数,直接在需要的任务中调用:

在这里插入图片描述

4.3 缓存大小问题(未解决)

在测试中,本来为了解决RAM使用空间,想把USART_Enocean_BUF[100] 数组大小设置为50,因为最大的一帧数据也只有在读ID的时候44个字节,每次读取完成都会把数组清0。所以觉得数组 大小 50 足够使用了,但是实际上测试下来发现,50,甚至是80 90的数组大小都不够使用,接收任务会出问题,具体原因一下子还不明白。

这个缓存问题,按理来说,80、90字节 应该也够的,我试着把消息队列的大小也定义成缓存一样大大小,还是不行,这个问题后面如果发现再来补充!(未解决)

5 、RAM空间不足问题

内存空间不足 小结写在前面:

  • FreeRTOS定义的 TOTAL_HEAP_SIZE,直接在内存占用这么大的空间,编译过后的.bss段直接增加对应大小;
    在这里插入图片描述

  • 系统的Minimum Heap Size 和 Minimum Stack Size ,(也可以认为)直接需要占用这么大的空间;
    在这里插入图片描述

  • 如果 FreeRTOS 使能了定时器,定时器启动以后会有一个任务:
    在这里插入图片描述
    在这里插入图片描述
    虽然前面申请了 TOTAL_HEAP_SIZE 空间,图中这个任务Tmr Svc会占用TOTAL_HEAP_SIZE 空间内容

    注意!!除了任务所占用的申请过的空间不用计算内存,开启软件定时器之后还会额外占用.bss段
    在这里插入图片描述

  • Include definitions里面使能了需要的定义,需要占用一点.bss段
    在这里插入图片描述
    在这里插入图片描述

  • 消息队列肯定也是要占用额外的.bss段的(这里已经使用上了,我就不重新去改掉,后面遇到再来维护)

5.1问题的出现

这次测试使用的是STM32L051C8,8KB的RAM,64KB的Flash,Flash还是够用的,但是8KB的RAM使用起来就有点捉襟见肘,在中途编译的时候就已经发现:

在这里插入图片描述
在这里插入图片描述

具体如何计算我有一篇博文单独介绍 内存问题:STM32的内存管理相关(内存架构,内存管理,map文件分析),包括本文后面的一些分析内存部分,也需要参考上面这篇博文的内容。

果然,在接下来RAM空间不够了:

在这里插入图片描述
原因是由于我发现 FreeRTOS 内存可用字节数不够了:

在这里插入图片描述

于是我把 FreeRTOS 可用的内存空间TOTAL_HEAP_SIZE修改大了:

在这里插入图片描述

我改大了 1KB, 开始我们编译已经看到用了大概7.7KB,所以这么一加上去,RAM空间不够用,提示溢出 616 bytes,

那么我试一下,如果我减少 200 bytes,那么他编译溢出是不是就只有 416 bytes了:

在这里插入图片描述

确实如此,但是注意!!!这个值需要是1024 的倍数,这是只是单纯分配大小测试 RAM的占用情况

那么在小容量的MCU上运行 RTOS,我们就得非常注意这个内存空间的使用,应该合理的划分了,那么如何能够合理的划分内存空间,除了需要知道 STM32的内存管理相关 的内容,还需要对FreeRTOS任务如何占用内存空间需要一定的了解。

5.2 FreeRTOS任务占用的RAM空间

通过上面我们知道,FreeRTOS TOTAL_HEAP_SIZE 是直接在RAM里面划分空间的,那么这个TOTAL_HEAP_SIZE 占用的空间是在RAM的什么位置呢?

如果了解 FreeRTOS 任务创建原理相关的知识,这点不难回答,我们这里可以用一种简单的办法告诉答案,通过.map文件,还是在 STM32的内存管理相关(内存架构,内存管理,map文件分析)文章中最后一节,GCC下.map文件中有相关解释,这里再次说明一遍:

在这里插入图片描述

在Cubemx中设置的TOTAL_HEAP_SIZE大小,直接占用的是上图部分RAM空间的大小,结尾的地方是.bss.heap_end.4167 0x200010c0

FreeRTOS 每一个任务都在这个开辟 ,我们选中的是heap_4.c内存管理方案,在heap_4.c文件里面是有申请内存的操作:

在这里插入图片描述
在这里插入图片描述

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

adow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
[外链图片转存中…(img-IeMGPKzw-1715786444782)]
[外链图片转存中…(img-ZJXTnqMB-1715786444783)]

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值