我们在stm32f103c8t6单片机上验证RTOS队列的写入与读出,利用stm32cube进行RTOS的配置。在选择TIM2当做RTOS的时钟,裸机的时钟源默认是 SysTick,但是开启 FreeRTOS 后,FreeRTOS会占用 SysTick (用来生成1ms 定时,用于任务调度),所以需要需要为其他总线提供另外的时钟源。
验证的功能比较简单,选择V1 版本的内核完全够用。
一、验证的思路及需用函数及队列使用的说明
1.创建2个任务taskKEY1,taskKEY2。
任务要求如下:
taskKEY1:按下KEY1调用xQueueSend()函数,发送数据。发送成功,显示发送的数据;发送失败,显示写入队列失败。
taskKEY2:按下KEY2调用xQueueReceive()函数,读取数据。读取成功,显示读取的数据;读取失败,显示读取队列失败。
2.需要的函数
创建队列
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
uxQueueLength:队列可同时容纳的最大项目数 。
uxItemSize:存储队列中的每个数据项所需的大小(以字节为单位)。 返回值: 如果队列创建成功,则返回所创建队列的句柄 。 如果创建队列所需的内存无法分配 , 则返回 NULL。
写队列
BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait );
xQueue:队列的句柄,数据项将发送到此队列
pvItemToQueue:待写入数据
xTicksToWait:阻塞超时时间
如果成功写入数据,返回 pdTRUE,否则返回 errQUEUE_FULL。
该函数会往队列的尾部写入消息,当你写入的数据超过了队列长度或者在阻塞时间内没有写入数据就会写入失败
读队列
BaseType_t xQueueReceive( QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait )
xQueue:待读取的队列
pvItemToQueue:数据读取缓冲区
xTicksToWait:阻塞超时时间
返回值: 成功返回 pdTRUE,否则返回 pdFALSE。
该函数会从队列头部读取消息,并删除消息,当你读出的的数据超过了写入的数据或者在阻塞时间内没有读出数据就会读出失败
3. 队列使用的说明
队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息。
队列项目:队列中的每一个数据;(打个预防针,每一个的理解不是真的就一个,这篇文章的代码部分freertos.c那里我会说明。)
队列长度:队列能够存储队列项目的最大数量;
创建队列时,需要指定队列长度及队列项目大小。这个在我们使用stm32cube配置的时候会提到。
队列的特点
①通常采用先进先出(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取。 也可以配置为后进先出(LIFO)方式,但用得比较少。FIFO就是First Input First Output
②采用实际值传递,即将数据拷贝到队列中进行传递,也可以传递指针,在传递较大的数据的时候 采用指针传递。我们的读写队列函数里面的数据参数就是指针类型。
③队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息
④当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队。 阻塞时间如果设置为: 0:直接返回不会等待;
0~port_MAX_DELAY:等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不 再等待;
port_MAX_DELAY:死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;
二、stm32cube的配置
SYS
RCC
GPIO
PA0对应按键1,PA1对应按键2。
RTOS
在Tasks and Queues中配置我们的2个任务,一个队列
其实就是相当于stm32cube帮我们调用了并封装了生成任务xTaskCreate(),生成队列xQueueCreate函数。
两个任务和一个队列的名字分别是
TaskSend,TaskReceive, myQueue
两个任务的入口函数名字分别是
StartSend,StartReceive;
各参数的配置相同,如下图
任务1和2除了任务名字和入口函数不同,其余的都相同
三、代码部分
usart.c
#include "stdio.h"
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
同时打开“魔术棒”,勾选Use MicroLIB,点击OK。这样就可以进行串口打印了。
在freertos.c里我要分两种情况给大家说明创建队列的时候队列项目和队列长度
队列项目:队列中的每一个数据;
队列长度:队列能够存储队列项目的最大数量;
freertos.c
首先来验证一次性发送一个数据,读一个数据
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
//#include "queue.h"
//QueueHandle_t queue;
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
osThreadId TaskSendHandle;
osThreadId TaskReceiveHandle;
osMessageQId myQueueHandle;
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartSend(void const * argument);
void StartReceive(void const * argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/* GetIdleTaskMemory prototype (linked to static allocation support) */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize );
/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
*ppxIdleTaskStackBuffer = &xIdleStack[0];
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
/* place for user code */
}
/* USER CODE END GET_IDLE_TASK_MEMORY */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
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 myQueue */
osMessageQDef(myQueue, 16, uint16_t);
myQueueHandle = osMessageCreate(osMessageQ(myQueue), NULL);
// queue = xQueueCreate(5,12);
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* definition and creation of TaskSend */
osThreadDef(TaskSend, StartSend, osPriorityNormal, 0, 128);
TaskSendHandle = osThreadCreate(osThread(TaskSend), NULL);
/* definition and creation of TaskReceive */
osThreadDef(TaskReceive, StartReceive, osPriorityNormal, 0, 128);
TaskReceiveHandle = osThreadCreate(osThread(TaskReceive), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
}
/* USER CODE BEGIN Header_StartSend */
/**
* @brief Function implementing the TaskSend thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartSend */
void StartSend(void const * argument)
{
/* USER CODE BEGIN StartSend */
uint16_t buf = 100;
BaseType_t status;
/* Infinite loop */
for(;;)
{
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
status = xQueueSend(myQueueHandle, &buf, 0);
if (status == pdTRUE)
printf("写入队列成功,写入值为%d\r\n", buf);
else
printf("写入队列失败\r\n");
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
}
osDelay(10);
}
/* USER CODE END StartSend */
}
/* USER CODE BEGIN Header_StartReceive */
/**
* @brief Function implementing the TaskReceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartReceive */
void StartReceive(void const * argument)
{
/* USER CODE BEGIN StartReceive */
uint16_t buf;
BaseType_t status;
/* Infinite loop */
for(;;)
{
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
status = xQueueReceive(myQueueHandle, &buf, 0);
if (status == pdTRUE)
printf("队列数据读取成功,读出值为%d\r\n", buf);
else
printf("队列读取失败\r\n");
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
}
osDelay(10);
}
/* USER CODE END StartReceive */
}
需要说明的是
osMessageQDef(myQueue, 16, uint16_t);
myQueueHandle = osMessageCreate(osMessageQ(myQueue), NULL);
这两个函数是stm32cube封装过的队伍生成函数xQueueCreate(),第一个函数的参数是队伍的名字,队列的长度,一个数的的大小;第二个是返回队伍的句柄,也就是myQueueHandle,我们在xQueueSend和xQueueReceive都要用到该句柄。
我们可以看到当独处的数据超过写入的数据,读出就会失败
freertos.c
再来验证一次性发送多个数据,读多个数据
include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "queue.h"
QueueHandle_t queue;
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
osThreadId TaskSendHandle;
osThreadId TaskReceiveHandle;
osMessageQId myQueueHandle;
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartSend(void const * argument);
void StartReceive(void const * argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/* GetIdleTaskMemory prototype (linked to static allocation support) */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize );
/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
*ppxIdleTaskStackBuffer = &xIdleStack[0];
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
/* place for user code */
}
/* USER CODE END GET_IDLE_TASK_MEMORY */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
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 myQueue */
osMessageQDef(myQueue, 5, 12);
myQueueHandle = osMessageCreate(osMessageQ(myQueue), NULL);
queue = xQueueCreate(5,12);
/* USER CODE BEGIN RTOS_QUEES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* definition and creation of TaskSend */
osThreadDef(TaskSend, StartSend, osPriorityNormal, 0, 128);
TaskSendHandle = osThreadCreate(osThread(TaskSend), NULL);
/* definition and creation of TaskReceive */
osThreadDef(TaskReceive, StartReceive, osPriorityNormal, 0, 128);
TaskReceiveHandle = osThreadCreate(osThread(TaskReceive), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
}
/* USER CODE BEGIN Header_StartSend */
/**
* @brief Function implementing the TaskSend thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartSend */
void StartSend(void const * argument)
{
/* USER CODE BEGIN StartSend */
int buf[3] = {100,200,300};
BaseType_t status;
/* Infinite loop */
for(;;)
{
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
status = xQueueSend((QueueHandle_t)queue, buf,500);
if (status == pdTRUE)
printf("写入队列成功,写入值为%d,%d,%d\r\n",buf[0],buf[1],buf[2] );
else
printf("写入队列失败\r\n");
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
}
osDelay(10);
}
/* USER CODE END StartSend */
}
我们可以看到,一次性读写三个数据
一次性读写一个数字与一次性读写三个数字的区别
基于一次性读写一个数字的基础上,我们在开头加入了
#include "queue.h"
QueueHandle_t queue;
在MX_FREERTOS_Init()里面有额外加了queue = xQueueCreate(5,12);
这一段程序的作用就相当于把原来我们在stm32cube里面的对创建任务的参数,又重新配置了一遍。原来的参数是队列长度是16,大小是uint16_t,我们在StartSend和StartReceive传递的参数大小也是uint16_t,所以一次只能传输一个。现在我们把队列的长度改成了5,大小是12字节;我们在StartSend和StartReceive传递的参数大小也是一个int,4个字节,刚好一次性可以传输3个,程序里面细微的才别我就不说了,自己去看看,很容易找的。
所以队列中的每一个数据,一个数据不止只传输一个数字,而是传输一个和你规定队伍参数一样大小的数据。