FreeRTOS学习笔记(四)——延时函数,列表,软件定时器,低功耗模式,内存管理

FreeRTOS学习笔记(四)——延时函数,列表,软件定时器,低功耗模式,内存管理



FreeRTOS学习笔记系列

【FreeRTOS(MDK、STM32CUBEIDE)学习笔记(合集),超详细!!!】
【FreeRTOS学习笔记(一)—— 裸机和RTOS,Freertos移植(MDK),stm32cubeIDE使用Freertos】
【FreeRTOS学习笔记(二)—— 任务,挂起,临界区中断】
【FreeRTOS学习笔记(三)—— 消息队列,信号量,事件标志组,任务通知】
【FreeRTOS学习笔记(四)—— 延时函数,列表,软件定时器,低功耗模式,内存管理】



开胃小菜(空闲任务)

所有其它任务都阻塞或被挂起时运行


1、延时函数

相对延时:vTaskDelay()
绝对延时:xTaskDelayUntil()

注意
只由Freertos提供的这两个延时函数,才有任务调度的功能
其他自己写的延时就直接是干等着,什么都不干

相对延时
指每次延时都是从函数vTaskDelay()开始计数,直到延时指定的时间结束

绝对延时
指每次延时都是从任务开始时计数,直到延时指定的时间结束
将整个任务的运行周期看成一个整体,即整个任务运行周期就是这点时间
适用于需要按照一定频率运行的任务,比如:看门狗
假如,任务所需时间比绝对延时设置的时间要短,则任务会被拦腰斩断

相对延时 & 绝对延时 比较
同样的功能,黄线是相对延时,绿线用的是绝对延时
中间在插入1个字节写的10ms延时,最后都是延时500ms
在这里插入图片描述

微妙延时
微妙延时还是得用定时器来写,且没有调度功能
可以用 系统滴答定时器外设硬件定时器Freertos的软件定时器
详见往期文章【定时器TIM配置微妙延时函数】
详见往期文章【延时函数是怎么来的?频率和滴答计数之间的计算?(无ucos,小白向)】
参考代码

void delay_us(uint32_t nus)
{		
	uint32_t ticks;
	uint32_t told,tnow,tcnt=0;
	uint32_t reload=SysTick->LOAD;		//系统定时器的重载值	    	 
	ticks=nus*(SystemCoreClock/1000000);//需要的节拍数 
	told=SysTick->VAL;        			//刚进入时的计数器值
	/* 挂起调度器[可选,会导致高优先级任务无法抢占当前任务,但能够提高当前任务时间的精确性] */
	vTaskSuspendAll();	
	while(1)
	{
		tnow=SysTick->VAL;
		if(tnow!=told)
		{	 
			/* SYSTICK是一个递减的计数器 */
			if(tnow<told)
				tcnt+=told-tnow;		
			else 
				tcnt+=reload-tnow+told;	  
			told=tnow;
			/* 时间超过/等于要延迟的时间,则退出。*/
			if(tcnt>=ticks)
				break;			
		}  
	}
	/* 恢复调度器[可选] */
	xTaskResumeAll();
}  

void delay_ms(uint32_t nms)
{	
   	vTaskDelay(nms);	 						   	
}


2、列表(相当于链表)

列表: FreeRTOS 中的一个数据结构,用来跟踪 FreeRTOS中的任务
列表项:存放在列表中的项目


跟数组相比

数组的特点:数组成 地址是连续 的,数组在最初确定了 成员数量后期无法改变
列表的特点:列表项间的 地址非连续的 ,列表项的数目随时可以改变

在这里插入图片描述


列表结构体

在移植的文件 l i s t . c list.c list.c l i s t . h list.h list.h

/* 列表项结构体 */
struct xLIST_ITEM
{
   	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/* 用于检测列表项的数据完整性 */
   	configLIST_VOLATILE TickType_t xItemValue			/* 列表项的值,多用于按升序对列表中的列表项进行排序 */
    struct xLIST_ITEM * configLIST_VOLATILE pxNext		/* 下一个列表项 */
 	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious	/* 上一个列表项 */
   	void * pvOwner										/* 列表项的拥有者(通常是任务控制块) */
   	struct xLIST * configLIST_VOLATILE pxContainer; 	/* 列表项所在列表 */
  	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE			/* 用于检测列表项的数据完整性 */
};
typedef struct xLIST_ITEM ListItem_t; 	
/* 迷你列表项 */
/* 迷你列表项也是列表项,但迷你列表项仅用于标记列表的末尾和挂载其他插入列表中的列表项  */
struct xMINI_LIST_ITEM
{
   	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE 				/* 用于检测数据完整性 */
	configLIST_VOLATILE TickType_t xItemValue;				/* 列表项的值 */
   	struct xLIST_ITEM * configLIST_VOLATILE pxNext;			/* 上一个列表项 */
  	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; 	/* 下一个列表项 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
/* 列表结构体 */
typedef struct xLIST
{
  	listFIRST_LIST_INTEGRITY_CHECK_VALUE			/* 校验值 */
  	volatile UBaseType_t uxNumberOfItems;			/* 列表中的列表项数量(不包含 xListEnd) */
 	ListItem_t * configLIST_VOLATILE pxIndex		/* 指向列表中的某个列表项,用于遍历列表项的指针 */
  	MiniListItem_t xListEnd							/* 末尾列表项(迷你列表项) */
  	listSECOND_LIST_INTEGRITY_CHECK_VALUE			/* 校验值 */
} List_t;

/* 校验值: 是两个宏,这两个宏是确定的已知常量
 FreeRTOS通过检查这两个常量的值,来判断列表的数据在程序运行过程中,是否遭到破坏 
 该功能一般用于调试, 默认是不开启的 
 */

在这里插入图片描述


API函数

API函数描述
vListInitialise()初始化列表
vListInitialiseItem()初始化列表项
vListInsertEnd()列表末尾插入列表项
vListInsert()列表插入列表项
uxListRemove()列表移除列表项

具体原理,先去学学C语言的链表(双向循环),本文只作介绍,告知有这这样一个工具可使用



3、软件定时器

对应的宏:
使能:configUSE_TIMERS
优先级: configTIMER_TASK_PRIORITY = 31
命令队列长度:configTIMER_QUEUE_LENGTH = 5

简介

  • 定时器
    从指定时刻开始,经过指定时间,触发指定事件
  • 硬件定时器
    芯片自带的定时器模块,
    在定时时间到达之后就会自动触发中断,在中断服务函数中处理信息。
    硬件定时器的精度较高,硬件定时器数量有限
  • 软件定时器
    指具有定时功能的软件,可设置定时周期
    软件定时器精度相对较低,定时器理论上只要内存足够,就可以创建无数个
    软件定时器以系统时钟为基准,但系统时钟中断优先级是最低,容易被打断
    在时间到达后要调用回调函数(也称超时函数),在回调函数中处理信息

状态 和 命令队列

新创建的软件定时器处于休眠状态,需手动开启

  • 休眠态
    休眠态的软件定时器可以通过其句柄被引用
    但因为是休眠态,所以其定时超时回调函数不会被执行

  • 运行态
    运行态的定时器,当指定时间到达之后,它的超时回调函数会被调用

  • 软件定时器命令队列
    提供给 FreeRTOS 中的软件定时器使用的,用户是不能直接访问的。
    API 函数大多都是往定时器的队列中写入消息(发送命令)
    在这里插入图片描述


单次/周期定时器

  • 单次定时器

在这里插入图片描述
单次定时器的一旦定时超时,只会执行一次其软件定时器超时回调函数
不会自动重新开启定时,不过可以被手动重新开启。

  • 周期定时器

在这里插入图片描述
周期定时器的一旦启动,就会在执行完回调函数以后自动的重新启动
从而周期地执行其软件定时器回调函数。


注意事项

在调用函数 vTaskStartScheduler()开启任务调度器的时候
会创建一个用于管理软件定时器的【软件定时器服务任务】,叫prvTimerTask( )
【软件定时器的超时回调函数】是由 【软件定时器服务任务】调用的
【服务任务】不是专为某个定时器服务的,还要处理其他定时器
【超时回调函数】本身不是任务,要尽快实行,不能进入阻塞状态
不能调用那些会阻塞任务的 API 函数,如:vTaskDelay()
访问队列或者信号量的非零阻塞时间的 API 函数也不能调用。


结构体

typedef	struct
{
     const char * 						pcTimerName				/* 软件定时器名字(无大用) */
     ListItem_t 						xTimerListItem			/* 软件定时器列表项(软件定时器列表,溢出列表) */
     TickType_t 						xTimerPeriodInTicks;	/* 软件定时器的周期(超时时间) */     
     void * 							pvTimerID				/* 软件定时器的ID(同一个回调函数通过ID判断定时器) */
     TimerCallbackFunction_t	 		pxCallbackFunction; 	/* 软件定时器的回调函数 */
#if ( configUSE_TRACE_FACILITY == 1 )
     UBaseType_t 						uxTimerNumber			/*  软件定时器的编号,调试用  */
#endif
     uint8_t 							ucStatus;  				/*  软件定时器的状态(单次/周期)  */
} xTIMER;

API函数

函数描述
xTimerCreate()动态方式创建软件定时器
xTimerCreateStatic()静态方式创建软件定时器
xTimerStart()开启软件定时器定时
xTimerStop()停止软件定时器定时
xTimerReset()复位软件定时器定时
xTimerChangePeriod()更改软件定时器的定时超时时间
xTimerStartFromISR()在中断中开启软件定时器定时
xTimerStopFromISR()在中断中停止软件定时器定时
xTimerResetFromISR()在中断中复位软件定时器定时
xTimerChangePeriodFromISR()在中断中更改定时超时时间

4、低功耗模式

一般MCU都低功耗模式,裸机也可以使用MCU的低功耗模式。
FreeRTOS也提供了低功耗模式,叫Tickless

对应的宏:

  • configUSE_TICKLESS_IDLE
    使能低功耗Tickless模式
  • configEXPECTED_IDLE_TIME_BEFORE_SLEEP
    定义系统进入相应低功耗模式的最短时长
  • configPRE_SLEEP_PROCESSING(x)
    定义需要在系统进入低功耗模式前执行的事务,如:关闭外设时钟,以达到降低功耗的目的
  • configPOSR_SLEEP_PROCESSING(x)
    定义需要在系统退出低功耗模式后执行的事务,如:开启外设时钟,以使系统能够正常运行

原理

在空闲任务执行的期间,让MCU进入低功耗模式
当其他任务准备运行时,唤醒MCU退出低功耗模式
在这里插入图片描述
在整个系统的运行过程中,其实大部分时间是在执行空闲任务的
Tickless低功耗模式的本质:通过调用指令 WFI 实现睡眠模式


STM32低功耗模式类型

睡眠模式、停止模式、待机模式

模式名称进入唤醒对1.2V域时钟的影响对VDD域时钟的影响调压器
睡眠WFI任意中断CPU CLK关闭对其他时钟或模拟时钟源无影响开启
(立即休眠或退出是休眠)WFE唤醒事件CPU CLK关闭对其他时钟或模拟时钟源无影响开启
停止PDDS位+SLEEPDEEP位+WFI或WFE任意EXTI线(在EXTI寄存器中配置,内部线和外部线)所有1.2V域时钟都关闭HSI和HSE振荡器关闭开启或处于低功耗模式
待机PDDS位+SLEEPDEEP位+WFI或WFEWKUP引脚上升沿、RTC闹钟(闹钟A或闹钟B)RTC唤醒事件、RTC入侵事件、RTC时间戳事件、NRST引脚外部复位、IWDG复位所有1.2V域时钟都关闭HSI和HSE振荡器关闭关闭

Tickless使用的是睡眠模式

  • 进入睡眠模式
    WFI 指令:__WFI
    WFE 指令:__WFE
  • 退出睡眠模式
    任何中断 / 事件都可以唤醒睡眠模式

应用场景

很多应用场合对于功耗的要求很严格,比如可穿戴低功耗产品、物联网低功耗产品等
减少耗电,增强续航,减少发热


5、内存管理

FreeRTOS 创建对象的方法

动态方法创建
自动地从 FreeRTOS 管理的内存堆中申请创建对象所需的内存
并且在对象删除后,可将这块内存释放回FreeRTOS管理的内存堆

静态方法创建
需用户提供各种内存空间,并且使用静态方式占用的内存空间一般固定下来了
即使任务、队列等被删除后,这些被占用的内存空间一般没有其他用途

动态方式管理内存相比与静态方式,更加灵活。


标准C库的内存管理的缺点

标准的 C 库也提供了函数 malloc()和函数 free()来实现动态地申请和释放内存 。
1、占用大量的代码空间,不适合用在资源紧缺的嵌入式系统中
2、没有线程安全的相关机制
3、运行有不确定性,每次调用这些函数时花费的时间可能都不相同
4、内存碎片化
…………(总之就是,毛病一大堆)


FreeRTOS内存管理算法

FreeRTOS提供了5种动态内存管理算法,分别为: heap_1、heap_2、heap_3、heap_4、heap_5 。
具体可见本文【Freertos移植(MDK)】板块

算法优点缺点
heap_1分配简单,时间确定只允许申请内存,不允许释放内存
heap_2允许申请和释放内存不能合并相邻的空闲内存块会产生碎片、花费时间不定
heap_3直接调用C库函数malloc()和 free(),简单速度慢、花费时间不定
heap_4相邻空闲内存可合并,减少内存碎片的产生花费时间不定
heap_5能够管理多个非连续内存区域的heap_4花费时间不定
heap_1

只实现了pvPortMalloc(申请内存),没有实现vPortFree(释放内存)
最为简单,管理的内存堆是一个数组
在申请内存的时候, heap_1 内存管理算法只是简单地从数组中分出合适大小的内存

/* 定义一个大数组作为 FreeRTOS 管理的内存堆 */
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
heap_2

heap_2 内存管理算法使用最适应算法,并且支持申请和释放内存
但不能将相邻的空闲内存块合并成一个大的空闲内存块
heap_2 内存管理算法不可避免地会产生内存碎片
在这里插入图片描述

heap_3

标准C库
简单地包装了 C 语言的 malloc 和 free 函数,确保线程安全。

heap_4

heap_4 内存管理算法使用了首次适应算法
支持内存的申请与释放,能够将空闲且相邻的内存进行合并,减少内存碎片的现象
在这里插入图片描述

heap_5

heap_5 内存管理算法是在 heap_4 内存管理算法的基础上实现的
heap_5 内存管理算法实现了 管理多个非连续内存区域的能力
heap_5 内存管理算法默认并 没有定义内存堆
需要用户手动指定内存区域的信息,对其进行初始化。

/* 内存块结构体 */
typedef struct HeapRegion
{
	uint8_t* 	pucStartAddress; 	/* 内存区域的起始地址 */
	size_t 		xSizeInBytes; 		/* 内存区域的大小,单位:字节 */
} HeapRegion_t;


/* 内存块数组 */
Const  HeapRegion_t  xHeapRegions[] =
{
	{ (uint8_t *)0x80000000, 0x10000 }, 	/* 内存区域 1 */
	{ (uint8_t *)0x90000000, 0xA0000 }, 	/* 内存区域 2 */
	{ NULL, 0 } 							/* 数组终止标志 */
};
vPortDefineHeapRegions(xHeapRegions); 		/* 初始化定义堆内存区域的大小和数量 */

PS:最适应算法 & 首次适应算法

假设有3块已释放的内存,地址由低到高排序:5字节、50字节、25字节
现在新创建一个任务需要申请20字节的内存

第一步:
最适应算法 :找出 最小的且满足 pvPortMalloc的内存:25字节(第3块)
首次适应算法 :找出 第一个能满足 pvPortMalloc的内存:50字节(第2块)

第二步:
将内存分割出20字节内存,并返回返回这20字节的地址
剩下的字节仍然是空闲状态,留给后续的pvPortMalloc使用


API函数

函数描述
void* pvPortMalloc(size_t xWantedSize);申请内存
void vPortFree(void* pv);释放内存
size_t xPortGetFreeHeapSize(void);获取当前空闲内存的大小
/**
  * @brief  	申请内存
  * @param  	xWantedSize:申请的内存大小,以字节为单位;
  * @retval 	成功返回一个指向已分配大小的内存的指针 。失败则返回 NULL。
  */
void * pvPortMalloc( size_t  xWantedSize );

/**
  * @brief  	释放内存
  * @param  	pv:指针指向一个要释放内存的内存块;
  * @retval 	无
  */
void vPortFree(void* pv); 

/**
  * @brief  	释放内存
  * @param		无
  * @retval		返回当前剩余的空闲内存大小
  */
size_t  xPortGetFreeHeapSize( void );



┈┈┈┈▕▔╲┈┈┈┈┈┈┈ ┈┈┈┈▕▔╲┈┈┈┈┈┈┈ ┈┈┈┈▕▔╲┈┈┈┈┈┈┈┈
┈┈┈┈┈▏▕┈┈┈┈┈┈┈ ┈┈┈┈┈▏▕┈┈┈┈┈┈┈ ┈┈┈┈┈▏▕┈┈┈┈┈┈┈ ┈
┈┈┈┈┈▏ ▕▂▂▂▂▂┈┈┈┈┈┈┈▏ ▕▂▂▂▂▂┈┈┈┈┈┈┈▏ ▕▂▂▂▂▂┈┈┈
▂▂▂▂╱┈┈▕▂▂▂▂▏┈ ▂▂▂▂╱┈┈▕▂▂▂▂▏┈ ▂▂▂▂╱┈┈▕▂▂▂▂▏┈┈
▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈
▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈
▔▔▔▔╲▂▂▕▂▂▂▂▏┈ ▔▔▔▔╲▂▂▕▂▂▂▂▏┈ ▔▔▔▔╲▂▂▕▂▂▂▂▏┈┈

如果对你有帮助,就点赞收藏吧!(。・ω・。)ノ♡

  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值