01初识FreeRTOS【前情回顾篇】

news2024/11/16 15:57:50

为什么要使用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,保存到栈里里面前去,也会划分自己的栈,以及一些必要寄存器以及局部变量也会保存到栈里

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

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

完整的状态转换图

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

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

优先级链表管理

任务调度

  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函数

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

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

/* 等同于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();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2116981.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通过域名无法访问不到网站,IP可正常访问(DNS污染)

一 DNS被污染 就在刚刚突然访问不到csdn,域名无法访问如下图: 确认DNS是否解析有问题 1 ping 域名 先ping一下域名,ping 域名后得到ip, ping通了如下图: 2 使用IP访问测试 通过ip再访问网站,ip可以正常访问如下图&…

nginx搭配gateway的集群配置

一、nginx在http里配置如下信息 upstream gateway-cluster {server 127.0.0.1:10001;server 127.0.0.1:10002;}server {listen 1000;server_name localhost;location ~/zzw_project/(.*) {proxy_pass http://gateway-cluster/$1;proxy_set_header Host $host; # 代理设…

延迟渲染路径

1. 延迟渲染路径处理光照的方式 延迟渲染路径对光照的数量没有任何限制,并且所有灯光都可以采用逐像素渲染。理论上来说,即 使场景中有成百上千个实时灯光,依然可以保持比较流畅的渲染帧率。它支持法线纹理、阴影等等效果的处理;…

【C++】STL容器详解【下】

目录 一、list容器 1.1 list基本概念 1.2 lsit构造函数 1.3 list数据元素插入和删除操作 1.4 list大小操作 1.5 list赋值操作 1.6 list数据的存取 1.7 list反转排序 二、set/multiset容器 2.1 set/multiset基本概念 2.2 set构造函数 2.3 set赋值操作 2.4 set大小操…

ChatGPT+Simple Mind Map生成思维导图:快速提升学习效率

一、告别杂乱笔记,一键生成清晰思维导图! 最近开始学习网络安全,一头扎进了各种协议、漏洞、防御机制的海洋中。信息量巨大,知识点零散,让我很快便陷入了“知识焦虑”——笔记越记越多,却越来越混乱&#…

Django+Vue3前后端分离学习(二)(重写User类)

一、重写User类: 1、首先导入User类: from django.contrib.auth.models import User 2、然后点在User上,按住ctrl 点进去,发现 User类继承AbstractUser Ctrl点进去AbstractUser,然后将此方法全部复制到自己APP的mo…

基于微信小程序+Java+SSM+Vue+MySQL的宿舍管理系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于微信小程序JavaSSMVueMySQL的宿舍管理系统【附源码文档…

UMI复现基础环境安装配置全流程(三)——UMI环境搭建

一、搭建UMI环境 (二)中安装了Anaconda3,在此基础上,创建umi环境,在universal_manipulation_interface-main文件夹中打开terminal并输入指令 conda env create -f conda_environment.yaml 此指令根据yaml文件创建环…

​数据编织+敏捷BI,打造企业智赢AI时代的双引擎丨直播预告

大数据产业创新服务媒体 ——聚焦数据 改变商业 随着数字化和智能化浪潮的不断推进,企业如何高效利用数据资源,成为了获取竞争优势的关键。在这样的背景下,我们诚邀您参加即将于2024年9月10日14:30开启的直播——“数据编织敏捷BI&#xff0…

实时通信利器:Web Broadcast Channel API 全面解读

一. 引言 在 Web 开发领域,实时通信一直是一个备受关注的话题。为了更好地实现实时消息传递和跨标签页通信,在 HTML5 规范中引入了 Web Broadcast Channel API。在本文中,我们将解析 Web Broadcast Channel API,探讨其用法以及相…

常见的正则化方法以及L1,L2正则化的简单描述

深度学习中的正则化是通过在模型训练过程中引入某些技术来防止模型过拟合的一种策略。过拟合是指模型在训练数据上表现非常好,但在新的、未见过的数据上表现不佳。正则化通过限制模型的复杂度或对模型参数施加约束,从而提高模型的泛化能力。 常见的正则…

【机器学习】高斯网络的基本概念和应用领域

引言 高斯网络(Gaussian Network)通常指的是一个概率图模型,其中所有的随机变量(或节点)都遵循高斯分布 文章目录 引言一、高斯网络(Gaussian Network)1.1 高斯过程(Gaussian Proces…

Notepad++ 修改 About

1. 用这个工具,看标题,修改 1700 里的 Caption, 保存为 xx.exe, 2.修改链接,先准备如上。 2.1 使用插件 Hex Editor,拖入刚保存的 Notepad.exe 到 Notepad.exe, 按 c..S..H 2.2 按 ctrlf 查找 68 00 74 00 74 00 70 00 73 00 3…

ggplot作图基础

目录 ggplot作图语法 散点图 折线图 group分组 face_wrap()图像切片摆放 facet_grid()交叉分组切片 条形图 2.1 单组变量条形图 2.2 多维展示变量 直方图有与密度估计 直方图 密度估计图 ..density..语法和stat“density” ggplot作图语法 ggplot作图是将数据按需要进…

锡林郭勒奶酪品牌呼和浩特市大召店盛大开业

礼献中秋,香飘乳都。为进一步拓展锡林郭勒奶酪区域公用品牌产品销售渠道,9月8日,锡林郭勒奶酪区域公用品牌大召店在呼和浩特市大召广场月明楼隆重开业,现场为第三批新授权的39家奶酪生产经营主体代表授牌。至此,锡林郭…

Debian 12如何关闭防火墙

在Debian 12中,默认的防火墙管理工具是ufw(Uncomplicated Firewall)。您可以使用以下命令来关闭防火墙: 关闭防火墙: sudo ufw disable查看防火墙状态: sudo ufw status如果需要重新开启防火墙:…

9.8javaweb项目总结

1.主界面用户信息显示 登录成功后,将用户信息存储在记录在 localStorage中,然后进入界面之前通过js来渲染主界面 存储用户信息 将用户信息渲染在主界面上,并且头像设置跳转,到个人资料界面 这里数据库中还没有设置相关信息 2.模糊…

数学建模笔记—— 主成分分析(PCA)

数学建模笔记—— 主成分分析 主成分分析1. 基本原理1.1 主成分分析方法1.2 数据降维1.3 主成分分析原理1.4 主成分分析思想 2. PCA的计算步骤3. 典型例题4. 主成分分析说明5. python代码实现 主成分分析 1. 基本原理 在实际问题研究中,多变量问题是经常会遇到的。变量太多,无…

通信工程学习:什么是PSK相移键控、2PSK/BPSK二进制相移键控

PSK相移键控、2PSK/BPSK二进制相移键控 PSK(相移键控)和2PSK/BPSK(二进制相移键控)是两种在通信系统中广泛使用的调制技术。以下是对它们的详细解释: 一、PSK:相移键控 1、PSK相移键控的定义:…

websocket client无法连接到websocket server 的问题

1. 问题描述 生产环境的websocket client和server无法通信 2. 日志现象 通过查看日志和问题复现,定位到是client连接到server失败,导致无法通信。 出现问题的代码 出现问题的日志 21:25:27.790 [main] INFO websocket.MyWebSocketClient - start to…