第十八章 低功耗Tickless模式
睡眠模式:__WFI 中断唤醒 __WFE 事件唤醒 CPU CLK关闭
停止模式:RAM保持 中断唤醒
当 STM32F103 处于休眠模式的时候 Cortex-M3 内核停止运行,但是其他外设运行正常,
比如 NVIC、SRAM 等。
休眠模式的功耗比其他两个高,但是休眠模式没有唤醒延时,应用程
序可以立即运行。
停止模式基于 Cortex-M3 的深度休眠模式与外设时钟门控,在此模式下 1.2V 域的所有时钟
都会停止,PLL、HSI 和 HSE RC 振荡器会被禁止,但是内部 SRAM 的数据会被保留。调压器
可以工作在正常模式,也可配置为低功耗模式
相比于前面两种低功耗模式,待机模式的功耗最低。待机模式是基于 Cortex-M3 的深度睡
眠模式的
其中调压器被禁止。1.2V 域断电,PLL、HSI 振荡器和 HSE 振荡器也被关闭。除了
备份区域和待机电路相关的寄存器外,SRAM 和其他寄存器的内容都将丢失
退出待机模式的话会导致 STM32F1 重启
处理器处理空闲任务的时候就进入低功耗模式
FreeRTOS 就是通过在处理器处
理空闲任务的时候将处理器设置为低功耗模式来降低能耗
一般会在空闲任务的钩子函数中执行低功耗相关处理,
比如设置处理器进入低功耗模式、关闭其他外设时钟、降低系统主频等等
FreeRTOS 的系统时钟是由滴答定时器中断来提供的
Tickless 模式,当处理器进入空闲任务周期
以后就关闭系统节拍中断(滴答定时器中断),
只有当其他中断发生或者其他任务需要处理的时候处理器才会被从低功耗模式中唤醒
关闭了系统节拍中断的话就会导致系统时钟停止运行,这是绝对不允许的
该如何解决这个问题呢?
我们可以记录下系统节拍中断的关闭时间,
当系统节拍中断再次开启运行的时候补上这段时间就行
如果处理器在进入低功耗模式之前能够获取到还有多长时间运行下一个任务那么问题就迎刃而解了
我们只需要开一个定时器,定时器的定时周期设置为这个时间值就行了
定时时间到了以后产生定时中断,处理器不就从低功耗模式唤醒了
#define configUSE_TICKLESS_IDLE (1)
portSUPPRESS_TICKS_AND_SLEEP()
空闲任务是唯一可运行的任务,因为其他所有的任务都处于阻塞态或者挂起态
系统处于低功耗模式的时间至少大于 configEXPECTED_IDLE_TIME_BEFORE_SLEEP
个时钟节拍,
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2
我们可以在 FreeRTOSConfig.h 中重新定义,此宏必须大于 2
portSUPPRESS_TICKS_AND_SLEEP()有个参数,此参数用来指定还有多长时间将有任务
进入就绪态,其实就是处理器进入低功耗模式的时长(单位为时钟节拍数)
滴答定时器的计数寄存器是 24 位
__disable_irq(); //关闭全部中断
__enable_irq();//开启全部中断
在真正的低功耗设计中不仅仅是将处理器设置到低功耗模式就行了,还需要做一些其他的
处理,比如:
● 将处理器降低到合适的频率,因为频率越低功耗越小,甚至可以在进入低功耗模式以后
关闭系统时钟。
● 修改时钟源,晶振的功耗肯定比处理器内部的时钟源高,进入低功耗模式以后可以切换
到内部时钟源,比如 STM32 的内部 RC 振荡器。
● 关闭其他外设时钟,比如 IO 口的时钟。
● 关闭板子上其他功能模块电源,这个需要在产品硬件设计的时候就要处理好,比如可以
通过 MOS 管来控制某个模块电源的开关,在处理器进入低功耗模式之前关闭这些模块的电源
宏 configPRE_SLEEP_PROCESSING ()和 configPOST_SLEEP_PROCESSING()
进入低功耗硬件操作。退出低功耗硬件操作
默认情况下 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 为 2 个时钟节拍,并且最小
不能小于 2 个时钟节拍
进入低功耗的时最小时钟节拍数,设置为10,当进入低功耗时间大于10个系统时钟才允许进入
#if ( configUSE_IDLE_HOOK == 1 )
{
extern void vApplicationIdleHook( void );
/* Call the user defined function from within the idle task. This
allows the application designer to add background functionality
without the overhead of a separate task.
NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
CALL A FUNCTION THAT MIGHT BLOCK. */
vApplicationIdleHook();
}
#endif /* configUSE_IDLE_HOOK */
/* This conditional compilation should use inequality to 0, not equality
to 1. This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
user defined low power mode implementations require
configUSE_TICKLESS_IDLE to be set to a value other than 1. */
#if ( configUSE_TICKLESS_IDLE != 0 )
{
TickType_t xExpectedIdleTime;
/* It is not desirable to suspend then resume the scheduler on
each iteration of the idle task. Therefore, a preliminary
test of the expected idle time is performed without the
scheduler suspended. The result here is not necessarily
valid. */
xExpectedIdleTime = prvGetExpectedIdleTime();
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
vTaskSuspendAll();
{
/* Now the scheduler is suspended, the expected idle
time can be sampled again, and this time its value can
be used. */
configASSERT( xNextTaskUnblockTime >= xTickCount );
xExpectedIdleTime = prvGetExpectedIdleTime();
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
traceLOW_POWER_IDLE_BEGIN();
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
traceLOW_POWER_IDLE_END();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
从上面可以看出tickless不在空闲钩子函数中
第十九章 空闲任务
当 FreeRTOS 的调度器启动以后就会自动的创建一个空闲任务,这样就可以确保至少有一任务可以运行。
但是这个空闲任务使用最低优先级,0
如果某个任务要调用函数 vTaskDelete()删除自身,那么这个任务的任务控制块 TCB 和任务堆栈等这些由 FreeRTOS 系统自动分配的内存需要在空闲任务中释放掉
如果删除的是别的任务那么相应的内存就会被直接释放掉,不需要在空闲任务中释放
如果使能了空闲任务钩子函数的话就执行这个钩子函数,空闲任务钩子函数的函数名为 vApplicationIdleHook(),这个函数需要用户自行编写!在编写这个钩子函数的时候一定不能调用任何可以阻塞空闲任务的 API 函数
因此绝对不能在空闲任务钩子函数中调用任何可以阻塞空闲任务的 API 函数,比如 vTaskDelay(),或者其他带有阻塞时间的信号量或队列操作函数
- 如果使用通用低功耗模式的话每个滴答定时器中断都会将处理器从低功耗模式中唤醒,反复的进入低功耗、退出低功耗
- FreeRTOS 自带的 Tickless 模式更加合理有效
第二十章 内存管理
函数 pvPortMalloc()、vPortFree()
与函数 malloc()、free()的函数原型类似
- 内存碎片是伴随着内存申请和释放而来的
- 多次申请释放以后产生内存碎片
1. heap_1
当宏 configAPPLICATION_ALLOCATED_HEAP 为 1 的时候需要用户自行定义内存堆
否则的话由编译器来决定,默认都是由编译器来决定的
如果自己定义的话就可以将内存堆定义到外部 SRAM 或者 SDRAM 中
heap_1 实现起来就是当需要 RAM 的时候就从一个大数组(内存堆)中分一小块出来
heap_1 特性如下:
1、适用于那些一旦创建好任务、信号量和队列就再也不会删除的应用,实际上大多数的FreeRTOS 应用都是这样的 。
2、具有可确定性(执行所花费的时间大多数都是一样的),而且不会导致内存碎片。
3、代码实现和内存分配过程都非常简单,内存是从一个静态数组中分配到的,也就是适合于那些不需要动态内存分配的应用。
2. heap_2
heap_2 的特性如下:
1、可以使用在那些可能会重复的删除任务、队列、信号量等的应用中,要注意有内存碎片
产生!
2、如果分配和释放的内存 n 大小是随机的,那么就要慎重使用了
3.heap_3
4. heap_4
heap_4 提供了一个最优的匹配算法
heap_4 会将内存碎片合并成一个大的可用内存块,它提供了内存块合并算法
heap_4 特性如下:
1、可以用在那些需要重复创建和删除任务、队列、信号量和互斥信号量等的应用中。
2、不会像 heap_2 那样产生严重的内存碎片,即使分配的内存大小是随机的。
3、具有不确定性,但是远比 C 标准库中的 malloc()和 free()效率高。
heap_4 非常适合于那些需要直接调用函数 pvPortMalloc()和 vPortFree()来申请和释放内存
的应用,注意,我们移植 FreeRTOS 的时候就选择的 heap_4!
heap_4 也使用链表结构来管理空闲内存块,链表结构体与 heap_2 一样。heap_4 也定义了两个局部静态变量 xStart 和 pxEnd 来表示链表头和尾,其中 pxEnd 是指向 BlockLink_t 的指针。
5.heap_5
heap_5 使用了和 heap_4 相同的合并算法,内存管理实现起来基本相同,但是 heap_5 允许内存堆跨越多个不连续的内存段
至此,FreeRTOS 官方提供的 5 种内存分配方法已经讲完了,
heap_1 最简单,但是只能申请内存,不能释放。
heap_2 提供了内存释放函数,用户代码也可以直接调用函数 pvPortMalloc()和
vPortFree()来申请和释放内存,但是 heap_2 会导致内存碎片的产生!
heap_3 是对标准 C 库中的函数 malloc()和 free()的简单封装,并且提供了线程保护。
heap_4 相对与 heap_2 提供了内存合并功能,可以降低内存碎片的产生,我们移植 FreeRTOS 的时候就选择了 heap_4。
heap_5 基本
上和 heap_4 一样,只是 heap_5 支持内存堆使用不连续的内存块。
- 内存泄露:
在使用内存管理的时候最常遇到的一个问题就是内存泄露,
内存泄露的原因是没有正确的释放内存!
内存泄露严重的话应用可能因为申请不到合适的内存而导致死机
第七章 列表和列表项
freeRTOS大量用到列表和列表项
列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似
列表
c
/*
* Definition of the type of queue used by the scheduler.
* 调度器使用的队列类型的定义。
*/
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE//默认不开启
configLIST_VOLATILE UBaseType_t uxNumberOfItems;//列表项的数量
ListItem_t * configLIST_VOLATILE pxIndex;//记录当前列表索引号
MiniListItem_t xListEnd;//表示列表结束
listSECOND_LIST_INTEGRITY_CHECK_VALUE//默认不开启
} List_t;
uxNumberOfItems
pxIndex
xListEnd
列表项
就是存放在列表中的项目,FreeRTOS 提供了两种列表项:列表项和迷你列表项。这两个都在文件 list.h 中有定义
c
/*
* Definition of the only type of object that a list can contain.
*/
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; //记录列表项归谁所有
void * configLIST_VOLATILE pvContainer; //记录列表项归哪个列表
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
};
typedef struct xLIST_ITEM ListItem_t; /* For some reason lint wants this as two separate definitions. */
定义
ListItem_t xStateListItem;
创建一个任务以后
xStateListItem.pvOwner = 任务控制块地址
当任务就绪以后
xStateListItem.pvContainer = 就绪列表
迷你列表项
c
struct xMINI_LIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_VOLATILE TickType_t xItemValue;//列表项值
struct xLIST_ITEM * configLIST_VOLATILE pxNext;//下一个列表
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;//上一个列表
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
列表初始化
c
void vListInitialise( List_t * const pxList )
{
/* The list structure contains a list item which is used to mark the
end of the list. To initialise the list the list end is inserted
as the only list entry. */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
/* The list end value is the highest possible value in the list to
ensure it remains at the end of the list. */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* The list end next and previous pointers point to itself so we know
when the list is empty. */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
/* Write known values into the list if
configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
临界段
临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段
FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断
c
taskENTER_CRITICAL() 、taskEXIT_CRITICAL() 、任务级的临界段代码保护
taskENTER_CRITICAL_FROM_ISR() 和taskEXIT_CRITICAL_FROM_ISR(),中断级的临界段代码保护
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
开关中断
portENABLE_INTERRUPTS ()
portDISABLE_INTERRUPTS()
任务
延时函数
消息队列
也称消息队列
信号量
二值信号量
计数信号量
互斥信号量
软件定时器
事件标志组
EventBits_t 类型的变量可以存储 24 个事件位
任务通知
效率更高:FreeRTOS 的每个任务都有一个 32 位的通知值
FreeRTOS 的任务通知只能有一个接收任务