FreeRTOS实时操作系统(九)时间延时函数及消息队列

系列文章

FreeRTOS实时操作系统(一)RTOS的基本概念

FreeRTOS实时操作系统(二)任务创建与任务删除(HAL库)

FreeRTOS实时操作系统(三)任务挂起与恢复

FreeRTOS实时操作系统(四)中断任务管理

FreeRTOS实时操作系统(五)进入临界区、任务调度器挂起与恢复

FreeRTOS实时操作系统(六)列表与列表项

FreeRTOS实时操作系统(七)时间片调度及RTOS的滴答定时器

FreeRTOS实时操作系统(八)任务状态查询及时间统计函数

FreeRTOS实时操作系统(九)时间延时函数及消息队列

FreeRTOS实时操作系统(十)信号量

FreeRTOS实时操作系统(十一)队列集

FreeRTOS实时操作系统(十二)事件标志组

FreeRTOS实时操作系统(十三)任务通知

FreeRTOS实时操作系统(十四)软件定时器

FreeRTOS实时操作系统(十五)Tickless低功耗模式

FreeRTOS实时操作系统(十六)内存管理



延时函数

1.vTaskDelay():相对延时,每次延时从该函数开始计算。
2.vTaskDelayUntil():绝对延时,将整个任务的运行周期看成一个整体,延时时间包括:任务主体运行时间+延时时间+其他抢占任务执行时间。所以适合用在需要按照一定频率运行的任务。但是即使是这样,当其他高优先级任务抢占且不能及时返回到我们这个任务时,就会出现一定的时间偏差。

实验测试:

void task1( void * pvParameters )
{
	TickType_t xLastWakeTime;
	xLastWakeTime=xTaskGetTickCount();
    while(1)
    {
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_4);
        vTaskDelayUntil(&xLastWakeTime,500);
    }
}

队列

基础介绍

队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)

一般情况下,全局变量的数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损。

当然,如果每次我们都采用临界区代码保护也可以,但是读写队列自带这样的操作,用起来简便

队列简介

在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度,在创建队列时,指定队列长度以及队列项目的大小。

队列特点

1.队列通常采用“先进先出”(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取,FreeRTOS中也可以配置为“后进先出”LIFO方式;
2.FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递, FreeRTOS采用拷贝数据传递,也可以传递指针,所以在传递较大的数据的时候采用指针传递
3.队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息
4.当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队:若:若阻塞时间为0,直接返回不会等待;若阻塞时间为0~port_MAX_DELAY,等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;若阻塞时间为port_MAX_DELAY,死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;

阻塞情况

在这里插入图片描述

当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务在等待同一 个队列的空间,当空间空出的时候,优先级最高的任务或优先级相同,等待时间最久的任务会进入就绪态。

运行情况

在这里插入图片描述

结构体及API函数

队列的结构体

在这里插入图片描述

typedef struct QueueDefinition 
{
    int8_t * pcHead					/* 存储区域的起始地址 */
    int8_t * pcWriteTo;        				/* 下一个写入的位置 */
.....
    List_t xTasksWaitingToSend; 			/* 等待发送列表 */
    List_t xTasksWaitingToReceive;			/* 等待接收列表 */
    volatile UBaseType_t uxMessagesWaiting; 	/* 非空闲队列项目的数量 */
    UBaseType_t uxLength;			/* 队列长度 */
    UBaseType_t uxItemSize;                 		/* 队列项目的大小 */
    volatile int8_t cRxLock; 				/* 读取上锁计数器 */
    volatile int8_t cTxLock;			/* 写入上锁计数器 */
.......

API函数

一般要用到创建队列、写队列、读队列这几种

创建队列

xQueueCreate() :动态方式创建队列
xQueueCreateStatic() :静态方式创建队列
区别:队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中分配,而静态创建需要用户自行分配内存。

动态创建队列:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif

参数:uxQueueLength:队列长度;uxItemSize :队列项目大小;
返回值:NULL:队列创建失败;其他:队列创建成功,返回任务句柄;

FreeRTOS 基于队列实现了多种功能,每一种功能对应一种队列类型:

/* For internal use only.  These definitions *must* match those in queue.c. */
#define queueQUEUE_TYPE_BASE				( ( uint8_t ) 0U )   //队列
#define queueQUEUE_TYPE_SET					( ( uint8_t ) 0U )   //队列集
#define queueQUEUE_TYPE_MUTEX 				( ( uint8_t ) 1U )   //互斥信号量
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE	( ( uint8_t ) 2U )   //计数型信号量
#define queueQUEUE_TYPE_BINARY_SEMAPHORE	( ( uint8_t ) 3U )   //二值信号量
#define queueQUEUE_TYPE_RECURSIVE_MUTEX		( ( uint8_t ) 4U )   //递归互斥信号量

在这里插入图片描述

写入队列
函数描述
xQueueSend()往队列的尾部写入消息
xQueueSendToBack()同 xQueueSend()
xQueueSendToFront()往队列的头部写入消息
xQueueOverwrite()覆写队列消息(只用于队列长度为 1 的情况)
xQueueSendFromISR()在中断中往队列的尾部写入消息
xQueueSendToBackFromISR()同 xQueueSendFromISR()
xQueueSendToFrontFromISR()在中断中往队列的头部写入消息
xQueueOverwriteFromISR()在中断中覆写队列消息(只用于队列长度为 1 的情况)

由下图可知,这几个函数只是传入的参数不同,都调用了xQueueGenericSend()这个函数。
在这里插入图片描述
注意覆写队列只有在队列长度为1的时候,才能使用

/* For internal use only. */
#define	queueSEND_TO_BACK		( ( BaseType_t ) 0 )
#define	queueSEND_TO_FRONT		( ( BaseType_t ) 1 )
#define queueOVERWRITE			( ( BaseType_t ) 2 )  

下面是内部函数xQueueGenericSend() 的实现:

BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )

形参:xQueue:待写入队列;pvItemToQueue:待写入消息;xTicksToWait:阻塞超时时间;xCopyPosition:写入位置;
返回值:pdTRUE:队列写入成功;errQUEUE_FULL :队列写入失败

读取队列消息
函数描述
xQueueReceive()从队列头部读取消息,并删除消息
xQueuePeek()从队列头部读取消息
xQueueReceiveFromISR()在中断中从队列头部读取消息,并删除消息
xQueuePeekFromISR()在中断中从队列头部读取消息
BaseType_t    xQueueReceive( QueueHandle_t   xQueue,  void *   const pvBuffer,  TickType_t   xTicksToWait )

此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。
形参:xQueue:待读取的队列;pvBuffer:信号读取缓冲区;xTicksToWait:阻塞超时时间
返回值:pdTRUE:读取成功;pdFALSE:读取失败;

BaseType_t   xQueuePeek( QueueHandle_t   xQueue,   void * const   pvBuffer,   TickType_t   xTicksToWait )

此函数用于在任务中,从队列中读取消息, 但与函数 xQueueReceive()不同,此函数在成功读取消息后,并不会移除已读取的消息!
参数:xQueue:待读取的队列;pvBuffer:信号读取缓冲区;xTicksToWait:阻塞超时时间
返回值:pdTRUE:读取成功;pdFALSE:读取失败;

实验测试

#include "queue.h"
QueueHandle_t key_queue;        /* 小数据句柄 */
QueueHandle_t big_date_queue;   /* 大数据句柄 */
char buff[100] = {"我是一个大数组,大大的数组 124214 uhsidhaksjhdklsadhsaklj"};
 



int fputc(int ch,FILE *f)
{
	HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xffff);
	return ch;
}

TaskHandle_t    task1_handler;

#define TASK1_PRIO         2
#define TASK1_STACK_SIZE   128
TaskHandle_t    task1_handler;
void task1( void * pvParameters );

/* TASK2 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK2_PRIO         2
#define TASK2_STACK_SIZE   128
TaskHandle_t    task2_handler;
void task2( void * pvParameters );

void vTaskCode( void * pvParameters )
 {	 
    taskENTER_CRITICAL();               /* 进入临界区 */
    xTaskCreate((TaskFunction_t         )   task1,
                (char *                 )   "task1",
                (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK1_PRIO,
                (TaskHandle_t *         )   &task1_handler );
                
    xTaskCreate((TaskFunction_t         )   task2,
                (char *                 )   "task2",
                (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                (void *                 )   NULL,
                (UBaseType_t            )   TASK2_PRIO,
                (TaskHandle_t *         )   &task2_handler );
    vTaskDelete(NULL);
    taskEXIT_CRITICAL();                /* 退出临界区 */
 }
 

 // Function that creates a task.
 void vOtherFunction( void )
 {
	 
	     /* 队列的创建 */
    key_queue = xQueueCreate( 2, sizeof(uint8_t) );
    if(key_queue != NULL)
    {
        printf("key_queue队列创建成功!!\r\n");
    }else printf("key_queue队列创建失败!!\r\n");
    
    big_date_queue = xQueueCreate( 1, sizeof(char *) );
    if(big_date_queue != NULL)
    {
        printf("big_date_queue队列创建成功!!\r\n");
    }else printf("big_date_queue队列创建失败!!\r\n");
	
	
	xTaskCreate( vTaskCode, "tak1", 128, NULL, 1, &task1_handler );
	vTaskStartScheduler();
 }
 


void task1( void * pvParameters )
{
	BaseType_t   err = 0;
	uint8_t key=1;
	 char * buf;
    buf = &buff[0];
	
	err = xQueueSend( key_queue, &key, portMAX_DELAY );
	 if(err != pdTRUE)
	{
		printf("key_queue队列发送失败\r\n");
	}
	
	 err = xQueueSend( big_date_queue, &buf, portMAX_DELAY );
	if(err != pdTRUE)
	{
		printf("key_queue队列发送失败\r\n");
	}

    while(1)
    {
		vTaskDelay(100);
    }
}

void task2( void * pvParameters )
{
	BaseType_t err = 0;
	uint8_t key = 0;
	char * buf;
    while(1)
    { 
		 err = xQueueReceive( key_queue,&key,portMAX_DELAY);
        if(err != pdTRUE)
        {
            printf("key_queue队列读取失败\r\n");
        }else 
        {
            printf("key_queue读取队列成功,数据:%d\r\n",key);
        }
		
		 err = xQueueReceive( big_date_queue,&buf,portMAX_DELAY);
        if(err != pdTRUE)
        {
            printf("big_date_queue队列读取失败\r\n");
        }else 
        {
            printf("数据:%s\r\n",buf);
        }
       vTaskDelay(100);
    }
}

在这里插入图片描述

这里回顾一下C语言的知识,学的太差了:
声明了一个指向char的指针buf,指向的是buff的首地址,在入队操作时,我们入的是这个指针的地址,不是这个指针。

	char * buf;
    buf = &buff[0];
	err = xQueueSend( big_date_queue, &buf, portMAX_DELAY );

类似下面:

    const char * str = "hello";
    printf("%c\n",*str);//输出首字符
    printf("%s\n",str);//输出整串字符
    printf("%p\n",str);//输出字符串首字符地址
    printf("%p\n",&str);//输出指针str的地址

所以str指向是字符串的首地址,解引用的时候也就是首字符

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FreeRTOS中,滴答定时器(Tick Timer)是用于实现系统时基的关键组件。它提供了一个固定的时间间隔,用于驱动任务调度和其他时间相关的功能。 FreeRTOS的滴答定时器可以通过不同的方式进行实现,具体取决于所使用的硬件平台和配置。以下是一般情况下兼容滴答定时器的步骤: 1. 确定滴答定时器的时钟源:滴答定时器需要一个稳定的时钟源来提供计时功能。通常可以使用系统时钟、外部晶体振荡器或其他可靠的时钟源作为滴答定时器的时钟源。 2. 配置滴答定时器的计数周期:滴答定时器需要配置一个计数周期,用于确定每个滴答的时间间隔。这个时间间隔应该与系统需求和任务调度的精度相匹配。常见的配置方式是将滴答定时器的计数周期设置为系统时钟频率除以所需的滴答频率。 3. 实现滴答定时器中断服务程序(ISR):滴答定时器计数完成后,会触发一个中断。需要编写一个中断服务程序来处理滴答定时器中断。在中断服务程序中,需要调用FreeRTOS提供的函数xPortSysTickHandler(),它将处理任务调度和其他与滴答定时器相关的操作。 4. 启动滴答定时器:在系统初始化阶段,需要启动滴答定时器,使其开始计时并触发中断。具体的启动方式和配置方法取决于所使用的硬件平台和配置。 需要注意的是,以上步骤是一般情况下兼容滴答定时器的基本步骤。在实际应用中,可能会根据具体需求和硬件平台的不同而有所变化。因此,在使用FreeRTOS时,建议参考相关的硬件文档和FreeRTOS的文档,以确保正确配置和使用滴答定时器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值