前言
(1)FreeRTOS是我一天过完的,由此回忆并且记录一下。个人认为,如果只是入门,利用STM32CubeMX是一个非常好的选择。学习完本系列课程之后,再去学习网上的一些其他课程也许会简单很多。
(2)本系列课程是使用的keil软件仿真平台,所以对于没有开发板的同学也可也进行学习。
(3)叠甲,再次强调,本系列课程仅仅用于入门。学习完之后建议还要再去寻找其他课程加深理解。
(4)本系列博客对应代码仓库:gitee仓库
前期准备
(1)将前一章信号量的示例代码复制一份
实战
使用STM32CubeMX创建队列集
(1)
STM32CubeMX
中似乎没有添加队列集的选项。可能是因为,队列集本身就是队列,只不过队列集存放的是句柄而非数据,所以STM32CubeMX
将队列集和队列当成同一个对待了。
(2)虽然STM32CubeMX
将队列集和队列当成同样的方法对待,但是我们尽量还是进行区分编程。
使用keil端创建队列集
(1)首先,我们需要进入
FreeRTOS.h
中将configUSE_QUEUE_SETS
宏定义置1。
(2)按
Ctrl+F
搜索Private variables
即可找到如下代码块,进行补充。
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
osThreadId_t keilTaskHandle; //任务句柄
static xSemaphoreHandle KeilBinarySemHandle; // 二进制信号量
static xSemaphoreHandle KeilCountingSemHandle; // 计数型信号量
static QueueHandle_t KeilQueueHandle; // 队列
static QueueSetHandle_t KeilQueuesetHandle; // 队列集
/* USER CODE END Variables */
(3)按
Ctrl+F
搜索BEGIN RTOS_QUEUES
即可找到如下代码块,进行补充。
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
// 创建一个队列
KeilQueueHandle = xQueueCreate(3, sizeof(uint16_t));
//创建一个队列集,可以存放2个队列或信号量
KeilQueuesetHandle = xQueueCreateSet( 2 );
if(KeilQueuesetHandle != NULL)
{
printf("xQueueCreateSet succeeded!\r\n");
}
//将队列存入队列集中
xQueueAddToSet(KeilQueueHandle,KeilQueuesetHandle);
//将信号量存入队列集中
xQueueAddToSet(KeilBinarySemHandle,KeilQueuesetHandle);
/* USER CODE END RTOS_QUEUES */
应用程序
(1)按下
K0
向队列中写入数据,按下K1
释放信号量。如果检测到队列集中数据有余量,那么就打印是队列还是信号量。
(2)需要注意一个点,StartKeilTask()
任务中的时候,存在printf()
线程安全问题,所以我加了一个延时5ms
,目的是为了StartKeilTask()
任务中的数据能够打印出来。
/* USER CODE BEGIN Header_StartCubemxTask */
/**
* @brief Function implementing the CubemxTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartCubemxTask */
#define Test_Binary 0 //如果为1测试二进制信号量,否则是测试计数型信号量
void StartCubemxTask(void *argument)
{
/* USER CODE BEGIN StartCubemxTask */
/* Infinite loop */
BaseType_t status;
uint16_t Buf = 0;
for(;;)
{
// 按下K0队列写数据
if(HAL_GPIO_ReadPin(Key_0_GPIO_Port,Key_0_Pin) == GPIO_PIN_SET)
{
Buf++;
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");
}
// 关于按键检测在讲解完FreeRTOS基础知识后会进行一次专题讲解
while(HAL_GPIO_ReadPin(Key_0_GPIO_Port,Key_0_Pin) == GPIO_PIN_SET);
}
if(HAL_GPIO_ReadPin(Key_1_GPIO_Port,Key_1_Pin) == GPIO_PIN_SET)
{
status = xSemaphoreGive(KeilBinarySemHandle);
if(status == pdTRUE)
{
printf("xSemaphoreGive succeed\r\n");
}
else
{
printf("xSemaphoreGive failed\r\n");
}
// 关于按键检测在讲解完FreeRTOS基础知识后会进行一次专题讲解
while(HAL_GPIO_ReadPin(Key_1_GPIO_Port,Key_1_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;
}
void StartKeilTask(void *argument)
{
char *KeilTaskPrintf = (char *)argument;
QueueSetMemberHandle_t QueueSetHandle;
uint16_t Buf = 0;
while(1)
{
printf(KeilTaskPrintf);
// 等待队列集中有数据
QueueSetHandle = xQueueSelectFromSet( KeilQueuesetHandle,portMAX_DELAY);
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
// 这个延时用于解决printf互斥问题,注意,这里只是因为任务简单才这么做。实际项目应考虑printf线程安全
vTaskDelay(5/portTICK_PERIOD_MS);
printf("xQueueSelectFromSet finish\r\n");
if(QueueSetHandle == KeilQueueHandle)
{
xQueueReceive( QueueSetHandle,&Buf,portMAX_DELAY);
printf("xQueueReceive was successfully :%d\r\n", Buf);
}
else if(QueueSetHandle == KeilBinarySemHandle)
{
xSemaphoreTake( QueueSetHandle, portMAX_DELAY );
printf("xSemaphoreTake was successful\r\n");
}
}
}
/* USER CODE END Application */
仿真结果
(1)
理论
(1)学习了前面的队列知识,我们会发现一个问题,就是队列只能存放同一种类型的数据,那如果我们需要在任务之间传输不同类型的消息,应该如何处理呢?例如我们的遥控车,可能是按键式的遥控器(传输的是整型数据),也可能会兼容遥感式的遥控器(传输的是浮点数据)。这两种遥控器都能够控制我们的小车,那么应当如何进行数据传输呢?此时,就需要利用到本讲的队列集了。
(2)队列集的本质也是队列,只不过里面存放的是队列句柄,而不是数据。
(3)如果感兴趣的话,我们可以看看队列集的函数实现,其实本质上就是调用的队列操作函数。因此,如果为了节约空间,可以把队列集的宏定义关闭,这样能够节约一点点code
段。之后如果需要使用队列集的时候,直接使用队列的操作函数。不过个人不建议,因为这样会导致队列和队列集搞混,开发大型项目的时候尽量区分清楚好一点。
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )
{
QueueSetHandle_t pxQueue;
pxQueue = xQueueGenericCreate( uxEventQueueLength, ( UBaseType_t ) sizeof( Queue_t * ), queueQUEUE_TYPE_SET );
return pxQueue;
}
API函数介绍
创建队列集
(1)创建队列集
/**
* @brief 创建队列集
*
* @param uxEventQueueLength 队列集长度,最多能存放多少个数据(队列或信号量句柄)
*
* @return 如果成功创建队列集,则返回所创建队列集的句柄,否则返回 NULL
*/
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );
添加队列或信号量到队列集
(1)往队列集中添加队列或信号量。
(2)注意:队列被添加到队列集之前,队列中不能有效的消息。
/**
* @brief 把队列或信号量加入队列集
*
* @param xQueueOrSemaphore 队列或信号量的句柄
* -xQueueSet 队列集句柄
*
* @return 如果队列或信号量成功添加到队列集,那么返回 pdPASS,否则 pdFAIL
*/
BaseType_t xQueueAddToSet(QueueSetMemberHandle_t xQueueOrSemaphore,QueueSetHandle_t xQueueSet);
从队列集中删除队列或信号量
(1)仅当队列或信号量为空时,才能从队列集中删除队列或信号量 。
/**
* @brief 从队列集中删除队列或信号量
*
* @param xQueueOrSemaphore 队列或信号量的句柄
* -xQueueSet 队列集句柄
*
* @return 如果队列或信号量已成功从队列集中删除, 则返回 pdPASS。 如果队列不在队列集中,或者 队列(或信号量)不为空,则返回 pdFAIL。
*/
BaseType_t xQueueRemoveFromSet(QueueSetMemberHandle_t xQueueOrSemaphore,QueueSetHandle_t xQueueSet);
获取队列集中有效信息的队列
(1)在任务中获取队列集中有效信息的队列。
/**
* @brief 在任务中获取队列集中有效信息的队列
*
* @param xQueueSet 队列集
* -xTicksToWait 阻塞超时时间,表示立即返回,portMAX_DELAY表示无限等待
*
* @return 返回队列集中包含数据的队列的句柄或队列集中可用信号量的句柄,如果在指定的阻塞时间到期之前不存在这样的队列或信号量,则返回NULL
*/
QueueSetMemberHandle_t xQueueSelectFromSet(QueueSetHandle_t xQueueSet,const TickType_t xTicksToWait);
获取队列集中有效信息的队列(中断)
(1)在中断中获取队列集中有效信息的队列。
/**
* @brief 在中断中获取队列集中有效信息的队列
*
* @param xQueueSet 正在查询的队列集
*
* @return 返回队列集中包含数据的队列的句柄或队列集中可用信号量的句柄,如果在指定的阻塞时间到期之前不存在这样的队列或信号量,则返回NULL
*/
QueueSetMemberHandle_t xQueueSelectFromSetFromISR(QueueSetHandle_t xQueueSet);
参考
(1)FreeRTOS官方文档:队列集
(2)B站:【正点原子】手把手教你学FreeRTOS实时系统 第47讲
(3)百问网:韦东山freeRTOS快速入门 08-3_队列集