IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> FreeRTOS记录(六、FreeRTOS消息队列(Enocean模块串口通讯)、RAM空间不足问题分析) -> 正文阅读

[嵌入式]FreeRTOS记录(六、FreeRTOS消息队列(Enocean模块串口通讯)、RAM空间不足问题分析)

本篇文章记录FreeRTOS消息队列的使用,我不从理论开始介绍,直接用起来,然后从发现的问题分析记录解决。
..补充RAM空间不足问题内容,增加FreeRTOS任务占用的RAM空间说明							2021/11/19

1、创建消息队列

在CubemX中,操作如下:
在这里插入图片描述创建完毕生成代码,在代码中可以看到:

...
osThreadId enoecantaskHandle;
osMessageQId EnoceanQueueHandle;
...
void MX_FREERTOS_Init(void) {
  /* USER CODE BEGIN Init */
       
  /* USER CODE END Init */

  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
  /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* Create the queue(s) */
  /* definition and creation of EnoceanQueue */
  osMessageQDef(EnoceanQueue, 100, uint8_t);
  EnoceanQueueHandle = osMessageCreate(osMessageQ(EnoceanQueue), NULL);
  ...
  osThreadDef(enoecantask, StartenoecanTask, osPriorityIdle, 0, 192);
  enoecantaskHandle = osThreadCreate(osThread(enoecantask), NULL);
	/* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  __HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_RXNE);
  /* USER CODE END RTOS_THREADS */

在上面代码void MX_FREERTOS_Init(void)最后一部分我加入了__HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_RXNE);串口中断使能,开启串口接收。

为什么在MX_FREERTOS_Init最后加入串口中断使能,是为了防止先使能了串口中断,如果操作系统还没有开始调度之前有中断发送,在中断中有消息队列的入队处理,那么是有可能出问题的。

2、中断中发送消息

我们使用消息队列接收串口的数据,那么需要在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除了接收,发送可以直接用以前写好的函数,直接在需要的任务中调用:
在这里插入图片描述

5 、RAM空间不足问题

5.1问题的出现

这次测试使用的是STM32L051C8,8KB的RAM,64KB的Flash,Flash还是够用的,但是8KB的RAM使用起来就有点捉襟见肘,在中途编译的时候就已经发现:
在这里插入图片描述在这里插入图片描述
具体如何计算我有一篇博文单独介绍 内存问题:STM32的内存管理相关(内存架构,内存管理,map文件分析)

果然,在接下来RAM空间不够了:
在这里插入图片描述原因是由于我发现 FreeRTOS 内存可用字节数不够了:
在这里插入图片描述
于是我把 FreeRTOS 可用的内存空间修改大了:
在这里插入图片描述
我改大了 1KB, 开始我们编译已经看到用了大概7.7KB,所以这么一加上去,RAM空间不够用,提示溢出 616 bytes,

那么我试一下,如果我减少 200 bytes,那么他编译溢出是不是就只有 416 bytes了:
在这里插入图片描述
确实如此,但是注意!!!这个值需要是1024 的倍数,这是只是单纯分配大小测试 RAM的占用情况

5.2 FreeRTOS任务占用的RAM空间

通过上面我们也知道,FreeRTOS

5.3 问题的解决办法

那既然遇到这个问题,该如何解决呢?

首先我们得知道内存分配的相关知识,我们知道 系统的堆空间 Heap Size 默认0x200,而且在程序中如果基本不用malloc动态分配空间,那么这个 Heap Size 是可以不用的,但是不能粗暴的设置为0,部分C库函数还要用,一定要分配一定大小。
在这里插入图片描述
在这里插入图片描述

所以针对自己使用的芯片RAM大小,任务的大小和任务的分配需要好好考虑一下,可以在产品测试的时候先通过vTaskList查看每个任务需要使用的内存大小,再确定分配多少内存,如何查看,请参考另一篇博文:

FreeRTOS记录(四、FreeRTOS任务堆栈溢出问题和临界区)

学会合理的分配内存空间是很有必要的,FreeRTOS 消息队列 用作串口通讯的测试就到这里,后续有关系消息队列的知识再来补充。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-11-19 17:47:09  更:2021-11-19 17:48:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 8:44:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码