为什么要使用FreeRTOS?
裸机轮询无法避免两个函数相互影响的问题,例如我们使用单片机在进行裸机开发时,我们使用了Delay延时函数,这时我们无法再执行其他的功能代码,需要等延时时间结束再执行其他代码,而使用FreeRTOS可以很好的解决这种问题。
CPU、内存、FLASH的简单概述
CPU
中央处理器(CPU,Central Processing Unit)是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心(Control Unit)。它的功能主要是解释计算机指令以及处理计算机软件中的数据。
CPU包括运算逻辑部件、寄存器部件和控制部件等,英文Logic components;运算逻辑部件,可以执行定点或浮点算术运算操作、移位操作以及逻辑操作,也可执行地址运算和转换。
内存
单片机中的内存主要分为以下几类:
- 寄存器: CPU内部的小容量、高速存储器,用于存储当前正在执行指令的数据和地址。
- SRAM(静态随机存取存储器): 一种易失性存储器,在断电时会丢失数据。
- Flash(闪存): 用于存储编译好的程序代码。
- RAM(随机存取存储器): 用于存储数据,包括SRAM和Flash中的RAM部分。
- ROM(只读存储器): 存储只读数据,如程序代码
FLASH(相当于电脑的硬盘)
主要功能是存储程序,在单片机中,"FLASH" 通常指的是闪存(Flash Memory),它是一种非易失性存储器,在断电后仍能保持数据的存储状态。Flash用来存储编译好的程序文件,而SRAM用来存储运行程序时所创建的临时数据。
Keil生成反汇编码
魔术棒--User--After Build--Run#1加上fromelf --text -a -c --output=test.dis xxx.axf
认识堆和栈
堆
所谓堆,就是一段空闲的内存,可以取出一段内存进行使用,用完再进行释放内存的分配和释放,链表是管理这块内存未使用的空闲的内存的手段。
小知识:volatile是一个类型修饰符,主要告诉编译器该变量可能在外部被意外地改变,因此编译器不要优化它。例如在多线程环境中,一个变量可能被多个线程同时访问或修改,如果没有用volatile修饰,编译器可能会对其进行优化,导致读取到地变量值不是最新的,从而引发错误。
栈
也是一块内存空间,CPU的SP寄存器指向它,它可以用于调用函数、局部变量、多任务系统里保存现场(保存LR和必要寄存器)LR是链接寄存器,是ARM处理器中一个有特殊用途的寄存器,当调用函数时,返回地址即PC的值被保存到LR中
C语言调用子函数的本质是使用BL指令
BL指令有两个功能,包括LR=返回地址,也就是下一条指令,PC=下一条要执行的语句地址,往PC寄存器写过某个值,PC就会自动跳到这个函数进行执行
局部变量是在栈里面分配的
每个RTOS的任务都有自己独享的栈空间
在每个C函数入口都会保存LR,保存到栈里里面前去,也会划分自己的栈,以及一些必要寄存器以及局部变量也会保存到栈里
不同的任务有不同的栈,不同的任务有不同的局部变量
TickType_t
如果FreeRTOSConfig.h定义configUSE_BIT_TICKS时,TickType_t就是uint16_t,否则TickType_t就是uint32_t
heap1.h只分配不回收
heap2.h有严重的碎片问题时间不定
heap3.h速度慢时间不定
heap4.h相邻空闲内存可合并,可解决碎片问题时间不定
heap5.h 在heap4基础上支持分隔的内存块,可解决碎片问题,时间不定
函数执行完之后,栈会被回收,入栈出栈
多任务系统
链表里存的是任务控制块
TCB(Task Control Block)用于管理和控制特定的任务或进程
pxCurrentTCB
它代表“当前任务控制块”的指针,是RTOS中常见的全局结构体指针变量,每个任务都有与之关联的任务控制块(TCB),TCB通常包含了任务的状态信息,堆栈指针、优先级等关键数据,RTOS内核使用这些TCB来管理任务的调度、切换和同步
pxCurrentTCB的主要作用是跟踪当前正在运行的任务的TCB,当RTOS进行任务切换时,它会更新pxCurrentTCB以指向新任务的TCB。
在创建最后一个任务的时候,pxCurrentTCB会指向它,所以任务的执行会从最后一个pxCurrentTCB指向的开始执行
创建任务
xTaskCreate函数:动态分配栈,动态分配TCB结构体
xTaskCreateStatic函数:静态分配
动态
静态
ARM中特殊的三个寄存器
在ARM体系中,一般分为四种寄存器:通用目的寄存器、堆栈指针(SP)、连接寄存器(LR) 以及 程序计数器(PC), 其中需要着重理解后面三种寄存器。
堆栈指针R13(SP)
每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈,也就是说五种异常模式、非异常模式(用户模式和系统模式),都有各自独立的堆栈,用不同的堆栈指针来索引。
当ARM进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性。
连接寄存器R14(LR)
保存子程序返回地址。使用BL或BLX时,跳转指令自动把返回地址放入r14中;
子程序通过把r14复制到PC来实现返回,通常用下列指令:MOV PC, LR;BX LR;
当异常发生时,异常模式的R14用来保存异常返回地址,将R14如栈可以处理嵌套中断。
程序计数器R15(PC)
PC是有读写限制的;
没有超过读取限制的时候,读取的值是指令的地址加上8个字节,由于ARM指令总是以字对齐的,故bit[1:0]总是00;
在CM3内部使用了指令流水线,读PC时返回的值是当前指令的地址+4.
向PC中写数据,就会引起一次程序的分支(但是不更新LR寄存器),CM3中的指令至少是半字对齐的,所以PC的LSB总是读回0。
在分支时,无论是直接写 PC 的值还是使用分支指令,都必须保证加载到 PC 的数值是奇数(即 LSB=1),用以表明这是在Thumb 状态下执行。
倘若写了 0,则视为企图转入 ARM 模式,CM3 将产生一个 fault 异常。
被调用者寄存器(R4-R11共8个)再加上LR寄存器共九个
创建任务
xCreatTask(函数名,任务名,参数,优先级,句柄);
创建多个任务可以只用一个函数,利用结构体指针,给函数设定一个指针参数,创建共函数的多任务就是为任务设定不同的任务名,传递不同的参数,我们可以设定一个结构体,然后定义一个结构体指针,通过传参的形式去引用结构体里面的内容,在函数的while语句里加上delay的作用是防止任务还没执行完被切换,状态未能及时跳转
删除任务
void vTaskDelete(TaskHandle_t xTaskDelete);给这个函数传递任务的句柄,就可以删除相对应的任务。
启动调度器
osKernelStart();启动调度器函数
任务状态:
Running(执行)状态
Ready(就绪)状态
Blocked(阻塞)状态
Suspend(暂停)状态
vTaskSupend(); //使任务变为停止状态
vTaskResume();//使任务变为running状态
自己调用肯定是处于running状态
别的任务调用,别人肯定处于running状态,自己的任务处于阻塞态或者ready状态
在FreeRTOS中,阻塞状态指的是一个任务当前正在等待某个外部事件的状态。例如,当一个任务调用了函数vTaskDelay()时,它会进入阻塞态,直到延时周期完成。同样,任务在等待队列、信号量、事件组、通知或互斥信号量时也会进入阻塞态。在阻塞状态下,任务会放弃CPU的使用权,这使得CPU可以去执行其他任务(如果其他任务也在等待或处于空闲状态,CPU则会执行空闲任务)。当任务等待的外部事件发生或延时时间到达时,任务会重新获取CPU的使用权并继续执行。
需要注意的是,任务进入阻塞态会有一个超时时间,如果在这个时间内外部事件没有发生,任务就会退出阻塞态。这有助于防止任务无限期地等待某个事件,从而导致系统资源的浪费或死锁情况的发生。
总的来说,FreeRTOS中的阻塞状态是任务管理中的一个重要概念,它允许任务在等待外部事件时释放CPU资源,从而提高系统的响应能力和效率。
完整的状态转换图
阻塞状态:它等待某些事件
暂停状态:它不等待某些事件
优先级链表管理
任务调度
- 相同优先级的任务轮流执行,最高优先级的任务先运行
- 高优先级的任务未执行完,低优先级的任务无法执行
- 高优先级的任务一旦就绪,马上执行
- 最高优先级的任务有多个,轮流运行
哈希表:存放链表的数组
任务切换、tick
Tick中断
{
- cnt++时钟基准
- 判断DelayTaskLists里的任务是否可恢复,若可恢复重新让他返回就绪态
c、发起调度:遍历一次ReadyLists,找到第一个非空的链表,把pxCurrentTCB指向下一个任务,并且启动它
}
调用延时vTaskDelay();
任务进入阻塞状态,让出了CPU资源,任务会从ReadyLists移到DelayTaskLists
两个Delay函数
vTaskDelay(n),进入,退出vTaskDelay的时间间隔至少是n个Tick中断
xTaskDelayUntil(&Pre,n)前后两次退出xTaskDelayntil的时间至少是n个Tick中断
PreTime=xTaskGetTickCount();
xSuspendTaskList
任务如何退出
- 自杀vTaskDelete(NULL);
- 他杀vTaskDelete(句柄);
空闲任务(Idel任务)在启动调度器里的一个prvIdleTask函数
其它任务进入阻塞状态,会让出CPU资源,运行空闲任务,空闲任务的作用之一,释放被删除的任务的内存(收尸)
良好的编程习惯
第一:事件驱动
第二:延时函数,不要使用使用死循环
同步与互斥
关中断函数disable_irq();
队列的本质
队列中,数据的读写本质就是环形缓冲区,在这个基础上增加了互斥措施,阻塞-唤醒机制。
如果这个队列不传输数据,只调整“数据个数”,它就是信号量
如果信号量中,限定“数据个数”最大值为1,它就是互斥量
队列的好处:
解耦和模块化:队列允许任务之间的通信和数据交换,从而实现任务之间的解耦,通过队列,任务可以发送和接收消息,而无需直接相互调用或了解彼此的内部状态。
优先级管理:FreeRTOS支持具有优先级的队列。这意味着高优先级的任务可以优先从队列中获取消息或发送消息,从而确保重要任务得到及时处理。
防止资源冲突:队列作为一种同步机制,可以有效防止任务同时访问和修改共享资源,从而避免资源冲突和数据不一致的问题,通过队列,任务可以有序地访问共享资源
简化任务间通信:通过任务,任务可以将数据或事件封装位消息并发送到队列中,其它任务可以从队列中读取这些消息并根据需要进行处理
创建
xQueueCreate()动态分配内存
xQueueCreateStatic()静态分配内存
队列:1.环形Buffer
2.两个链表:sender List、receiver List
receiver List里存的是想去读数据但读不到的任务
Queue.receiver List
DelayedList
写队列
普通场景:xQueueSend()
中断服务函数ISR:xQueueSendToBackFromISR(句柄,环形缓冲区);
读队列
xQueueReceive()
创建队列集
函数xQueueCreatSet(const UBaseType_t uxEnventQueueLength)
把队列加入队列集
函数原型如下:
xQueueAddToSet(队列句柄,队列集句柄)
读队列集
函数原型如下:
xQueueSelectFromSet(队列集,等待时间)
总结:创建任务,创建队列,创建队列集,创建输入任务,任务把数据写入队列,程序再把队列加到队列集里,即把队列的句柄加到队列集里,队列集不断地读取队列句柄,可以设置一个超市时间,读队列集,得到有数据的句柄,跳到数据处理函数,读队列,得到数据,处理数据。不要把任务创建写到创建队列集前面,这样会使队列写满,任务不再执行,队列集读不到队列的句柄,无法进行相应的任务操作。
信号量
创建信号量函数
信号量句柄=xSemaphoreCreateCounting();
获得信号量的函数:
xSemaphoreTake(信号量句柄,阻塞时间)
释放信号量函数
xSemaphoreGive(信号量句柄);
优先级反转
任务1,任务2,任务3依次优先级增加,任务1先获得信号量后先运行一段时间,任务2获得信号量之后运行,优先级比任务1高,任务1停止运行,任务1无法释放信号量,任务3无法获得信号量,优先级再高也没用
本质是队列,队列存储的是数据,信号量存储的是状态
互斥量
优先级继承,优先级恢复
创建互斥量函数
xSemaphoreCreateMutex
事件组
动态创建 xEventGroupCreate
静态创建 xEventGroupCreateStatic
删除事件组
vEventGroupDelete
等待事件
xEventGroupWaitBits(事件组句柄,位,pdTRUE/pdFALSE,等待时间);
设置事件组
xEventGroupSetBits 中断外
xEventGroupSetBitsFromISR 中断外
任务通知
发出通知xTaskNotifyGive/xTaskNotifyGiveFromISR
取出通知ulTaskNotifyTake
taskWAITING_NOTIFIVSTION
因为调用ulTaskNotifyTake而处于taskWAITING_NOTIFIVSTION
没用调用ulTaskNotifyTake而处于taskNOTIFICATION_RECEIVED
软件定时器
操作定时器:创建、启动、复位、修改周期
创建
动态分配内存和静态分配内存,函数原型为:
/* 使用动态分配内存的方法创建定时器
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以Tick为单位
* uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* 返回值: 成功则返回TimerHandle_t, 否则返回NULL
*/
TimerHandle_t xTimerCreate( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
/* 使用静态分配内存的方法创建定时器
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以Tick为单位
* uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* pxTimerBuffer: 传入一个StaticTimer_t结构体, 将在上面构造定时器
* 返回值: 成功则返回TimerHandle_t, 否则返回NULL
*/
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction,
StaticTimer_t *pxTimerBuffer );
启动/停止
启动定时器就是设置它的状态为运行态(Running、Active)。
停止定时器就是设置它的状态为冬眠(Dormant)。
函数原型:
/* 启动定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"启动命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait);
/* 启动定时器(ISR版本)
* xTimer: 哪个定时器
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL表示"启动命令"无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
/* 停止定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"停止命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );
/* 停止定时器(ISR版本)
* xTimer: 哪个定时器
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL表示"停止命令"无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
修改周期
使用下面的函数不仅可以使定时器周期修改,还可以让定时器从睡眠态转为运行态。
/* 修改定时器的周期
* xTimer: 哪个定时器
* xNewPeriod: 新周期
* xTicksToWait: 超时时间, 命令写入队列的超时时间
* 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
TickType_t xNewPeriod,
TickType_t xTicksToWait );
/* 修改定时器的周期
* xTimer: 哪个定时器
* xNewPeriod: 新周期
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,
TickType_t xNewPeriod,
BaseType_t *pxHigherPriorityTaskWoken );
任务和中断的两套API函数
两套API函数
拆分原因:中断要尽快处理完。
在任务和中断调用写队列函数:
/* 等同于xQueueSendToBack
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSend(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSendToBack(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToBackFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
/*
* 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSendToFront(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToFrontFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
任务里面可以进行指定超时时间阻塞等待,中断函数里面调用函数不可进行阻塞等待;
如何写队列时把更高优先级的任务唤醒了,可能会导致这个函数迟迟无法返回;
任务中调用
写队列
写失败,阻塞
写成功,wake up
中断中调用
唤醒:DelaydList->ReadyList
切换:保存现场、恢复新任务的现场(涉及寄存器的读写)
在中断中取消任务切换,取而代之的是记录:是否有更高优先级的任务被唤醒,退出中断服务函数之后再进行任务切换
xHigherPriorityTaskWoken的含义是:是否有更高优先级的任务被唤醒了。如果为pdTRUE,则意味着后面要进行任务切换。
任务切换
使用两个宏进行任务切换:
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
或
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
portEND_SWITHING_ISR使用汇编实现,portYIELD_FROM_ISR使用C语言实现,新版本都统一使用portYIELD_FROM_ISR。
资源管理
使用全局变量来互斥管理是不可靠的。
队列:环形buf
信号量/互斥量:cnt
事件组:int
任务通知:val和state
如何实现互斥操作
中断跟我抢,我就屏蔽中断;
其它任务跟我抢,我就禁止调度器,不运行任务切换;
屏蔽中断
在任务中屏蔽中断
任务中使用:taskENTER_CRITICA()/taskEXIT_CRITICAL()
示例:
/* 在任务中,当前时刻中断是使能的
* 执行这句代码后,屏蔽中断
*/
taskENTER_CRITICAL();
/* 访问临界资源 */
/* 重新使能中断 */
taskEXIT_CRITICAL();
使用taskENTER_CRITICA()/taskEXIT_CRITICAL()来访问临界资源是很粗鲁的方法:
中断无法正常进行
任务调度无法进行
所以之间的代码要尽可能快速地执行
在ISR中屏蔽中断
ISR中使用:taskENTER_CRITICAL_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR()
示例:
void vAnInterruptServiceRoutine( void )
{
/* 用来记录当前中断是否使能 */
UBaseType_t uxSavedInterruptStatus;
/* 在ISR中,当前时刻中断可能是使能的,也可能是禁止的
* 所以要记录当前状态, 后面要恢复为原先的状态
* 执行这句代码后,屏蔽中断
*/
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
/* 访问临界资源 */
/* 恢复中断状态 */
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
/* 现在,当前ISR可以被更高优先级的中断打断了 */
}
低优先级的中断被屏蔽了:优先级低于、等于configMAX_SYSCALL_INTERRUPT_PRIORITY
高优先级的中断可以产生:优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY
但是,这些中断函数ISR里,不允许使用Freertos的API函数
任务调度依赖中断、依赖API函数,所以:这段代码之间,不会有任务调度产生
暂停调度器
/* 暂停调度器 */
void vTaskSuspendAll( void );
/* 恢复调度器
* 返回值: pdTRUE表示在暂定期间有更高优先级的任务就绪了
* 可以不理会这个返回值
*/
BaseType_t xTaskResumeAll( void );
队列、信号量、互斥量、事件组、任务通知都是通过屏蔽中断或者暂停调度器来实现互斥操作;
在中断里面写事件组:他是去写timer队列,然后再在TimerTask里面写事件值
sprintf的用法
int sprintf(char *str, const char *format, ...);
str是一个字符串数组(或字符指针),用于存储格式化的字符串;
format是一个格式字符串,它指定了如何格式化后续的参数;
.....表示可变数量的参数,这些参数按照format字符串中的格式说明符进行格式化。
优化系统
精细调整栈大小
栈使用情况
在创建任务时分配了栈,可以填入固定的数值比如0xa5,以后可以使用以下函数查看“栈的高水位”,也就是还有多少空余的栈空间:
UBaseType_t uxTaskGetStackHighwaterMark(TaskHandle_t xTask);
原理:从栈底往栈顶逐个字节进行判断,它们的值持续是0xa5就表示它是空闲的。
参数xTask表示是哪个任务句柄,返回值:任务运行时、任务被切换时,都会用到栈。栈里原来值(0xa5)就会被覆盖。
逐个函数从栈的尾部判断栈的值连续为0xa5的个数,它就是任务运行过程中空闲内存容量的最小值。
注意:假设从栈尾开始连续为0xa5的栈空间是N字节,返回值是N/4。
通过这个函数以及printf去查看当前运行任务所剩栈空间,适当去调整创建任务时所分配的栈空间大小,节省内存。
任务中常用的函数列表
函数 | 描述 |
---|---|
uxTaskPriorityGet() | 查询某个任务的优先级 |
vTaskPrioritySet() | 改变某个任务的任务优先级 |
uxTaskGetSystemState() | 获取系统中任务状态 |
vTaskGetInfo() | 获取某个任务信息 |
xTaskGetApplicationTaskTag() | 获取某个任务的标签(Tag)值 |
xTaskGetCurrentTaskHandle() | 获取当前正在运行的任务的任务句柄 |
xTaskGetHandle() | 根据任务名字查找某个任务的句柄 |
xTaskGetIdleTaskHandle() | 获取空闲任务的任务句柄 |
uxTaskGetStackHighWaterMark() | 获取任务的堆栈的历史剩余最小值 |
eTaskGetState() | 获取某个任务的壮态 |
pcTaskGetName() | 获取某个任务的任务名字 |
xTaskGetTickCount() | 获取系统时间计数器值 |
xTaskGetTickCountFromISR() | 在中断服务函数中获取时间计数器值 |
xTaskGetSchedulerState() | 获取任务调度器的壮态,开启或未开启 |
uxTaskGetNumberOfTasks() | 获取当前系统中存在的任务数量 |
vTaskList() | 以一种表格的形式输出当前系统中所有任务的详细信息 |
vTaskGetRunTimeStats() | 获取每个任务的运行时间 |
vTaskSetApplicationTaskTag() | 设置任务标签(Tag)值 |
SetThreadLocalStoragePointer() | 设置线程本地存储指针 |
打印所有任务的栈信息
/*获取任务统计信息存放在buffer里面*/
void vTaskList(signed char *pcWriteBuffer);
获取任务的统计信息,形式为可读的字符串,注意,pcWriteBuffer必须足够大。
/*获取任务的运行信息,形式为可读的字符串*/
void vTaskGetRunTimeStats( signed char *pcWriteBuffer );
找到并改进大量消耗CPU的任务
任务运行时间统计
通过Tick中断函数去统计当前任务的累计运行时间很不精确,以为有更高优先级的任务就绪时,当前任务还没运行一个完整的Tick就被抢占了。因此,我们需要比Tick更快的时钟,可以使用一个定时器,让它发生中断的周期是0.1ms甚至更短。
原理:
/*获取任务运行总时间函数*/
ulTOtalRunTime = portGET_RUN_TIME_COUNTER_VALUE();