利用STM32CubeMX和Keil模拟器,3天入门FreeRTOS(4.0) —— 动态创建队列

前言

(1)FreeRTOS是我一天过完的,由此回忆并且记录一下。个人认为,如果只是入门,利用STM32CubeMX是一个非常好的选择。学习完本系列课程之后,再去学习网上的一些其他课程也许会简单很多。
(2)本系列课程是使用的keil软件仿真平台,所以对于没有开发板的同学也可也进行学习。
(3)叠甲,再次强调,本系列课程仅仅用于入门。学习完之后建议还要再去寻找其他课程加深理解。
(4)本系列博客对应代码仓库:gitee仓库

为什么需要队列

(1)在前文的 同步与互斥的缺陷中,我们讲解了当两个任务都需要调用printf()函数时候,会存在内容覆盖的情况。为了防止出现这类问题,FreeRTOS提出了一个解决办法,就是队列。
(2)我们举个例子,假如老板树懒有两个员工,一个兔子和一个乌龟。
<1>兔子和乌龟都需要在网络上提交自己的工作内容,而老板树懒工作速度慢,而且他们公司的网络系统只能接受一个工作内容。那么因为兔子的工作效率高,频繁的提交工作内容,这就会导致兔子提交的内容会覆盖掉乌龟的工作内容。因此,全公司似乎就只有兔子在干活,乌龟是一个可有可无的员工。
<2>为了解决这个问题,公司的网络系统升级了,可以接受多个工作内容提交。那么当乌龟提交完内容之后的瞬间,兔子再去提交内容,就会自动的放在乌龟提交的内容后面。可以理解为一个传送带,每次提交数据都是放在传送带上。最终传送带将内容运送给树懒。
(3)从上面的例子中,我们就可以明白为什么需要存在队列,以及队列的作用了。

前期准备

(1)将3.0章节的工程复制一份。

利用STM32CubeMX和Keil模拟器,3天入门FreeRTOS(4.0) —— 动态创建队列_第1张图片

(2)在freertos.c中包含头文件queue.h

利用STM32CubeMX和Keil模拟器,3天入门FreeRTOS(4.0) —— 动态创建队列_第2张图片

(3)本次实验将会使用到两个按键,因此将PA0PA1设置为下拉输入。

利用STM32CubeMX和Keil模拟器,3天入门FreeRTOS(4.0) —— 动态创建队列_第3张图片

实战

使用STM32CubeMX创建队列

(1)按照下图方式创建动态创建一个队列

利用STM32CubeMX和Keil模拟器,3天入门FreeRTOS(4.0) —— 动态创建队列_第4张图片

使用keil端创建队列

(1)按Ctrl+F搜索BEGIN Variables即可找到如下代码块,进行补充。

/* USER CODE BEGIN Variables */
QueueHandle_t KeilQueueHandle; //Keil端创建的队列句柄
/* USER CODE END Variables */

(2)按Ctrl+F搜索BEGIN RTOS_QUEUES即可找到如下代码块,进行补充。

/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
KeilQueueHandle   = xQueueCreate(10, sizeof(uint16_t));
/* USER CODE END RTOS_QUEUES */

具体任务代码补充

(1)因为STM32CubeMXKeil所创建的队列最终都是只需要操作一个队列的句柄,所以我这里就只以Keil所创建的队列句柄举例子了。(注意,如果像以STM32CubeMX所创建的队列举例,只需要将KeilQueueHandle修改为CubemxQueueHandle
(2)按Ctrl+F搜索StartCubemxTask即可找到如下代码块,进行补充。
(3)如下代码需要做的是,介绍:
<1>当K0按下,插入数据。当K1按下,读取数据。
<2>我们通过修改宏,设置要进行的实验操作

/* USER CODE END Header_StartCubemxTask */
#define Test_xQueueSendToFront   1
#define Test_xQueueSendToBack    0
#define Test_xQueueOverwrite     0
#define Test_xQueueReset         0
void StartCubemxTask(void *argument)
{
  /* USER CODE BEGIN StartCubemxTask */
	char *CubemxTaskPrintf = (char *)argument;
	uint16_t Buf = 0;
	BaseType_t status;
  /* Infinite loop */
  for(;;)
  {
		if (HAL_GPIO_ReadPin(Key_0_GPIO_Port, Key_0_Pin) == GPIO_PIN_SET)
		{
			Buf++;
#if Test_xQueueSendToFront
			// 写实验1:测试头插xQueueSendToFront()函数
			status = xQueueSendToFront(KeilQueueHandle, &Buf, 0);
			if (status == pdTRUE)
			{
				printf("xQueueSendToFront writes data successfully : %d\r\n", Buf);
			}
			else
			{
				printf("xQueueSendToFront failed to write data\r\n");
			}
#elif Test_xQueueSendToBack
			// 写实验2:测试尾插xQueueSendToBack()函数
			status = xQueueSendToBack(KeilQueueHandle, &Buf, 0);
			if (status == pdTRUE)
			{
				printf("xQueueSendToBack writes data successfully : %d\r\n", Buf);
			}
			else
			{
				printf("xQueueSendToBack failed to write data\r\n");
			}
#elif Test_xQueueOverwrite
			// 写实验3:测试xQueueOverwrite()函数,这个只能用于队列大小为1的情况。不为1的队列使用这个,程序会崩溃
			xQueueOverwrite(KeilQueueHandle, &Buf);
			printf("xQueueOverwrite writes data successfully : %d\r\n", Buf);
#elif Test_xQueueReset
			// 写实验4:测试尾插xQueueReset()函数
			status = xQueueSendToBack(KeilQueueHandle, &Buf, 0);
			if (status == pdTRUE)
			{
				printf("xQueueSendToBack writes data successfully : %d\r\n", Buf);
			}
			else
			{
				printf("xQueueSendToBack failed to write data\r\n");
			}
			xQueueReset(KeilQueueHandle);
#endif
			//查询队列中存储的消息数
			status = uxQueueMessagesWaiting(KeilQueueHandle);
			printf("The number of data stored in the queue : %d\r\n",status);
			//注意,在RTOS中,还使用阻塞的方式判断事件,无疑是非常愚蠢的。但是因为这个涉及后面内容,因此暂时使用这个做例子
			while (HAL_GPIO_ReadPin(Key_0_GPIO_Port, Key_0_Pin) == GPIO_PIN_SET ); 
		}
  }
  /* USER CODE END StartCubemxTask */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
int fputc(int ch, FILE *f)
{
	unsigned char temp[1]={ch};
	HAL_UART_Transmit(&huart1,temp,1,0xffff);
	return ch;
}
#define Test_xQueueReceive       1
#define Test_xQueueSendToBack    0
void StartKeilTask(void *argument)
{
	uint16_t Buf = 0;
	BaseType_t status;
	while(1)
	{
#if Test_xQueueReceive
		// 按下K1读取数据
		if (HAL_GPIO_ReadPin(Key_1_GPIO_Port, Key_1_Pin) == GPIO_PIN_SET )
		{
			// 读实验1:从队列头部读取消息,并删除消息
			status = xQueueReceive(KeilQueueHandle, &Buf, 0);
			if (status == pdTRUE)
			{
				printf("KeilQueueHandle data read successfully :%d\r\n", Buf);
			}
			else
			{
				printf("KeilQueueHandle data read failed\r\n");
			}
#elif Test_xQueuePeek
			// 读实验2:从队列头部读取消息,但是不删除消息
			status = xQueuePeek(KeilQueueHandle, &Buf, 0);
			if (status == pdTRUE)
			{
				printf("xQueuePeek data read successfully :%d\r\n", Buf);
			}
			else
			{
				printf("xQueuePeek data read failed\r\n");
			}
#endif
			//查询队列中的可用空间数
			status = uxQueueSpacesAvailable(KeilQueueHandle);
			printf("There is space left in the queue : %d\r\n",status);
			//注意,在RTOS中,还使用阻塞的方式判断事件,无疑是非常愚蠢的。但是因为这个涉及后面内容,因此暂时使用这个做例子
			while (HAL_GPIO_ReadPin(Key_1_GPIO_Port, Key_1_Pin) == GPIO_PIN_SET ); 
		}
	}
}
/* USER CODE END Application */

测试结果

(1)因为这个读函数就有2个,写函数就有4个,一共8种组合。全写出来太费劲了。对具体任务感兴趣的,可以直接拿我写好的工程代码实测。
(2)如下为仿真器相关配置
<1>打开微库,因为我们需要使用printf()函数。

利用STM32CubeMX和Keil模拟器,3天入门FreeRTOS(4.0) —— 动态创建队列_第5张图片

<2>配置为软件仿真
DARMSTM.DLL
pSTM32F103C8

利用STM32CubeMX和Keil模拟器,3天入门FreeRTOS(4.0) —— 动态创建队列_第6张图片

(3)按照如下方法进行模拟按键按下和松开。然后你根据你想测试的实验,打开相关注释即可。

利用STM32CubeMX和Keil模拟器,3天入门FreeRTOS(4.0) —— 动态创建队列_第7张图片

函数介绍

队列创建

xQueueCreate()函数介绍

(1)动态创建队列。
(2)参数介绍:
<1>队列的长度。通俗来说,就是这条队伍最大你想要排多少人。
<2>队列的数据所占字节。通俗点说,就是这个队伍中,每个人之间的间距是多少。
(3)从上面的解释我们就可以得出,队列最终的所占字节为uxQueueLength * uxItemSize
(4)uxItemSize参数注意事项:
<1>这里需要注意一点的是,uxItemSize是数据的所占字节数,因此当你传入sizof(float)sizof(int)本质上是一个效果。(假设是32位系统)至于最终队列解析为float还是int,不由队列决定。
<2>这就理解为,这个队列中有两个体形硕大的人,队列中的uxItemSize只需要让这两个人能够站进来,至于这两个人,是男是女,长多高,年龄多大,都和队列无关。只是和最终处理这个队列数据的人有关系。

/**
 * @brief  动态创建队列
 *
 * @param  uxQueueLength    队列的长度
 *        -uxItemSize       队列中每个数据的所占字节
 *
 * @return  队列创建成功,返回所创建队列的句柄。创建队列创建失败,返回 NULL
 */
QueueHandle_t xQueueCreate( uxQueueLength, uxItemSize );

队列删除

vQueueDelete()函数介绍

(1) 释放分配用于存储放置在队列中的项目的所有内存

/**
 * @brief   释放分配用于存储放置在队列中的项目的所有内存
 *
 * @param   xQueue   要删除的队列的句柄
 *
 * @return  无
 */
void vQueueDelete( QueueHandle_t xQueue );

向队列写数据

xQueueSend()和xQueueSendToBack()函数介绍

(1)xQueueSend()xQueueSendToBack()是同一个东西,至于为什么要弄两个一模一样的函数,可能是一开始只有尾插xQueueSend()。然后后来又推出了头插xQueueSendToFront()函数,为了做对比就再弄了一个尾插xQueueSendToBack()
(2)这两个函数,就是在队列中末尾加入数据。这个可以理解为一个遵守纪律的人,自觉排到队尾。
(3)传入参数解释:
<1>xQueue:队列的句柄。
<2>pvItemToQueue:指向待入队数据项的指针。

  • 这里需要注意,传入的数据项内容大小,不应当超出创建队列时候指定的数据所占字节大小。否则会出现数据截断情况。
  • 如果你创建队列时候,使用的是sizof(float),而你这里传入一个int型数据。对于xQueueSend()xQueueSendToBack()函数而言,是可行的,因为他们都是4字节。
  • 但是对于整个工程项目来说,是危险的。因为最终读取数据的时候,int型数据和float型数据混搭在队列中,我们很难知道这个取出来的到底是int型数据还是float型数据。
  • 如果不理解,举个医院看病的例子。我们排队的时候,人与人之间的间隔距离(队列的数据所占字节)只和人的体形大小(传入队列的数据所占字节)有关。至于这个人是男是女(int还是float),和队列是没有关系的。那么,这样就会出现一个问题,一个男性胖子,排队的时候排到了妇产科。坐在里面的医生是不知道的,因此就会出问题。

<3>xTicksToWait: 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。

  • 如果设置为 0,调用将立即返回。如果队列已满,将直接返回errQUEUE_FULL,并不会将任务进如阻塞态。
  • 时间以滴答周期为单位定义,因此一般用常量 portTICK_PERIOD_MS 转换为实时。例如,队列已满,我们等待100ms,那么就传入100/portTICK_PERIOD_MS
  • 如果想要一直阻塞,直到队列有空位置才结束,那么传入portMAX_DELAY。这里需要注意,需要在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend 设置为 “1”
/**
 * @brief  队列数据尾插
 *
 * @param  xQueue         队列的句柄
 *        -pvItemToQueue  指向待入队数据项的指针,每次只能插入一个数据
 *        -xTicksToWait   如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。
 *
 * @return  如果数据尾插成功,返回 pdTRUE,否则返回 errQUEUE_FULL。
 */
BaseType_t xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) ;

xQueueSendToFront()函数介绍

(1)这个函数与xQueueSendToBack()相反的,xQueueSendToBack()是当数据需要进入队列的时候,自觉的排到最后面。而xQueueSendToFront()却是不知羞耻的插队到最前面。
(2)传入的参数解释同上,返回值同上。

/**
 * @brief  队列数据头插
 *
 * @param  xQueue         队列的句柄
 *        -pvItemToQueue  指向待入队数据项的指针,每次只能插入一个数据
 *        -xTicksToWait   如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。
 *
 * @return  如果数据头插成功,返回 pdTRUE,否则返回 errQUEUE_FULL。
 */
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait );

xQueueOverwrite()函数介绍

(1)这个函数旨在用于长度为 1 的队列, 这意味着队列要么为空,要么为满。这个长度为1的队列还有一种专业术语,叫做邮箱
(2)如果队列长度大于1,程序就会卡死。因为我现在手上没有开发板,所以无法上机实测具体是卡死在哪里了。软件仿真的结果只是知道程序崩了。

/**
 * @brief  以覆盖的方式将数据传入队列,旨在用于长度为 1 的队列, 这意味着队列要么为空,要么为满
 *
 * @param  xQueue         队列的句柄
 *        -pvItemToQueue  指向待入队数据项的指针
 *
 * @return  如果数据头插成功,返回 pdTRUE,否则返回 errQUEUE_FULL
 */
 BaseType_t xQueueOverwrite(QueueHandle_t xQueue,const void * pvItemToQueue);

xQueueReset()函数介绍

(1)将队列重置为其原始的空状态,队列中的所有数据都将会被清除。

/**
 * @brief  将队列重置为其原始的空状态
 *
 * @param  xQueue         队列的句柄
 *
 * @return  总是返回 pdPASS
 */
BaseType_t xQueueReset( QueueHandle_t xQueue );

向队列读数据

xQueueReceive()函数介绍

(1)从队列头部读取消息,并删除消息。
(2)需要注意的是,pvItemToQueue的数据类型要和队列中的数据类型可以不一致,只要数据大小一致即可,否则可能会出现数据截断情况。但是不建议这么用,因为xQueueReceive()就是上面xQueueSendToBack()函数介绍中举例的医生。你一个妇产科医生,想整花活,诊断男科。主打一个猝不及防。

/**
 * @brief  从队列头部读取消息,并删除消息
 *
 * @param  xQueue         队列的句柄
 *        -pvItemToQueue  指向缓冲区的指针
 *        -xTicksToWait   如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)
 *
 * @return  如果从队列成功接收到项目,返回 pdTRUE,否则返回 pdFALSE
 */
 BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);

xQueuePeek()函数介绍

(1)从队列头部读取消息,但是不删除消息。这个函数和xQueueReceive()唯一区别在于,xQueueReceive()读取数据的同时,还会把数据从队列中删除。而xQueuePeek()仅仅只会读数据,数据并不会被删除。

/**
 * @brief  从队列头部读取消息,但是不删除消息
 *
 * @param  xQueue         队列的句柄
 *        -pvItemToQueue  指向缓冲区的指针
 *        -xTicksToWait   如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)
 *
 * @return  如果从队列成功接收到项目,返回 pdTRUE,否则返回 pdFALSE
 */
 BaseType_t xQueuePeek(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);

获取队列数据数量信息

uxQueueSpacesAvailable()函数介绍

(1)查询队列中的可用空间数。

/**
 * @brief  查询队列中的可用空间数
 *
 * @param  xQueue         队列的句柄
 *
 * @return  返回队列中的可用空间数
 */
UBaseType_t uxQueueSpacesAvailable( QueueHandle_t xQueue );

uxQueueMessagesWaiting()函数介绍

(1)查询队列中存储的消息数

/**
 * @brief  查询队列中存储的消息数
 *
 * @param  xQueue         队列的句柄
 *
 * @return  返回队列中存储的消息数
 */
UBaseType_t uxQueueSpacesAvailable( QueueHandle_t xQueue );

参考

(1)FreeRTOS官方文档:队列管理

你可能感兴趣的:(FreeRTOS,stm32,嵌入式硬件,单片机)