01初识FreeRTOS【前情回顾篇】

为什么要使用FreeRTOS?

裸机轮询无法避免两个函数相互影响的问题,例如我们使用单片机在进行裸机开发时,我们使用了Delay延时函数,这时我们无法再执行其他的功能代码,需要等延时时间结束再执行其他代码,而使用FreeRTOS可以很好的解决这种问题。

CPU、内存、FLASH的简单概述

CPU 

中央处理器(CPU,Central Processing Unit)是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心(Control Unit)。它的功能主要是解释计算机指令以及处理计算机软件中的数据。
CPU包括运算逻辑部件、寄存器部件和控制部件等,英文Logic components;运算逻辑部件,可以执行定点或浮点算术运算操作、移位操作以及逻辑操作,也可执行地址运算和转换。

内存 

单片机中的内存主要分为以下几类:

  1. 寄存器: CPU内部的小容量、高速存储器,用于存储当前正在执行指令的数据和地址。
  2. SRAM(静态随机存取存储器): 一种易失性存储器,在断电时会丢失数据。
  3. Flash(闪存): 用于存储编译好的程序代码。
  4. RAM(随机存取存储器): 用于存储数据,包括SRAM和Flash中的RAM部分。
  5. 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,保存到栈里里面前去,也会划分自己的栈,以及一些必要寄存器以及局部变量也会保存到栈里

不同的任务有不同的栈,不同的任务有不同的局部变量

 

4550165cf1c64146b54235d5a2f8c0a0.pngTickType_t

如果FreeRTOSConfig.h定义configUSE_BIT_TICKS时,TickType_t就是uint16_t,否则TickType_t就是uint32_t

6b7cccda4f194fbf9147ae47f4b37e9c.png

5a727eff5dcf4cffbae0c227a4abbce2.png

062fe5ce06fb4ca59aeb7d591bc1cc48.png

7e27bcbf7fe94f97abeb9de85703b739.png

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函数:静态分配

动态

816eeb2aed894b148930d5c37e70733a.png

a96ce9a6e5ca42d8a41a13a6d64f5200.png

静态

ae70aa3a119b4130a497b1c95cf8cb7d.png

0d51dd70370b4b8f98126bf1c6674101.png

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资源,从而提高系统的响应能力和效率。

 

完整的状态转换图

d00e4b636482475885c532d4fce14e8c.png

阻塞状态:它等待某些事件

暂停状态:它不等待某些事件

优先级链表管理

任务调度

  1. 相同优先级的任务轮流执行,最高优先级的任务先运行
  2. 高优先级的任务未执行完,低优先级的任务无法执行
  3. 高优先级的任务一旦就绪,马上执行
  4. 最高优先级的任务有多个,轮流运行

哈希表:存放链表的数组

 

任务切换、tick

Tick中断

{

  1. cnt++时钟基准
  2. 判断DelayTaskLists里的任务是否可恢复,若可恢复重新让他返回就绪态

c、发起调度:遍历一次ReadyLists,找到第一个非空的链表,把pxCurrentTCB指向下一个任务,并且启动它

}

调用延时vTaskDelay();

任务进入阻塞状态,让出了CPU资源,任务会从ReadyLists移到DelayTaskLists

两个Delay函数

vTaskDelay(n),进入,退出vTaskDelay的时间间隔至少是n个Tick中断

xTaskDelayUntil(&Pre,n)前后两次退出xTaskDelayntil的时间至少是n个Tick中断

PreTime=xTaskGetTickCount();

xSuspendTaskList

任务如何退出

  1. 自杀vTaskDelete(NULL);
  2. 他杀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函数

拆分原因:中断要尽快处理完。

84800f95b90647f482d598cd0f6cb3fe.png

在任务和中断调用写队列函数:

/* 等同于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必须足够大。

b9ff8438ef314dc4ad8efa27f98eda17.png

/*获取任务的运行信息,形式为可读的字符串*/
void vTaskGetRunTimeStats( signed char *pcWriteBuffer );

3929d2d5309f4c51858f99087e8bafde.png

找到并改进大量消耗CPU的任务

任务运行时间统计

通过Tick中断函数去统计当前任务的累计运行时间很不精确,以为有更高优先级的任务就绪时,当前任务还没运行一个完整的Tick就被抢占了。因此,我们需要比Tick更快的时钟,可以使用一个定时器,让它发生中断的周期是0.1ms甚至更短。

原理:

143213c44b1d45829babd8456b64d75a.png

/*获取任务运行总时间函数*/
ulTOtalRunTime = portGET_RUN_TIME_COUNTER_VALUE();

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值