韦东山FreeRTOS笔记

news2024/9/28 0:10:30

介绍

这篇文章是我学习FreeRTOS的笔记

学的是哔哩哔哩韦东山老师的课程

在学习FreeRTOS之前已经学习过江协的标准库和一丢丢的超子说物联网的HAL了。他们讲的都很不错

正在更新,

大家可以在我的Gitee仓库中下载笔记源文件、项目资料等

笔记源文件可以在Notion中导入

01.创建项目模板

一、项目基础配置

  1. 选用stm32F103C8T6芯片

  2. 设置Rcc 高速时钟为 陶瓷晶振

  3. 设置SYS Debug为 Serial Wire、基准时钟为TIM4

  4. 设置HCLK为72M

  5. 找到Middleware and Software Packs(中间件和软件包)、选择FreeRTOS、选择CMSIS V2、配置参数保持默认

  6. 设置工程名、配置IDE为MDK-ARM

  7. 在Code Generator中设置每个外设的初始代码的.c .h 分开(Generate peripheral initialization as a pair of'c/.h' files per periphera) 、其他默认

  8. 固件包版本设置为1.85 (为了防止 在编译时出现这个错误 #include CMSIS_device_header )

  9. 生成代码

  10. 选择下载器

  11. 设置复位后运行

  12. 关闭调试说明

二、添加基础程序

  1. 配置pc13 板载led测试程序

    • 在CubeMX设置PC13为推挽输出。

    • 添加led测试程序.c .h文件之后 在freertos.c中的void StartDefaultTask(void *argument) 函数中添加测试程序

  2. 配置屏幕

    • 在Connectivity中设置I2C1、使能为I2C、参数默认

    • 添加lcd、oled的.c .h文件 添加ascii_font.c字库文件

02.创建第一个多任务程序

cmsis.os2.c是一个统一的接口。

因为有很多的操作系统。他们的函数又不同。

但有一个统一的接口,就会根据底层的操作系统的不同调用不同的函数来执行操作。

而作为用户的我们,只需要知道cmsis.os2.c中的函数就可以了。

并且我们写出的函数就能即运行在这个操作系统中,又可以运行在别的操作系统中

xTaskCreate是 FreeRTOS 中用于动态创建任务的函数

各参数的含义如下:

  1. pvTaskCode:指向任务入口函数的指针。

  2. pcName:任务的描述性名称。

  3. uxStackDepth:任务堆栈的大小(单位是字,而不是字节)。

  4. pvParameters:传递给创建任务的参数。

  5. uxPriority:创建的任务将运行的优先级。数字越大,优先级越高。

  6. pxCreatedTask:用于返回已创建任务的句柄。

在指定的地方写好函数原型之后,把函数原型丢到xTaskCreate 中,填写好相应参数即可,比如

xTaskCreate(My_Task, "MyFirstTask", 128, NULL, osPriorityNormal, NULL);

这里两个NULL为空指针,暂时不做了解

03.硬件架构和汇编指令

一、硬件架构

ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),

它所用的指令比较简单,有如下特点: ① 对内存只有读、写指令 ② 对于数据的运算是在CPU内部实现 ③ 使用RISC指令的CPU复杂度小一点,易于设计

CPU内部有寄存器 R0-R15。以及计算单元.

后三个为程序状态寄存器:

  • R13:别名SP(Stack Pointer),栈指针。用来保存栈的地址 R14:别名LR(Link Register),用来保存返回地址。(A函数到B函数,B函数完成后返回A) R15:别名PC(Program Counter),程序计数器,表示当前指令地址,写入新值即可跳转

二、一些简单的汇编指令

  1. 读内存:Load

    LDR R0, [R1, #4] ; 读地址 ”R1+4 “ ,得到的四个字节数据存入R0

  2. 写内存:Stroe

    STR R0, [R1, #4] ; 把R0的四个字节写入到”R1+4“中去

可以在LDR或STR后加H 、 B 来表示读写 半字(Half) 字节(Byte)

  1. ADD R0, R1, R2 ; R0 = R1 + R2

    ADD R0, R1, #1 ; R0 = R1 + 1

  2. SUB R0, R1, R2 ; R0 = R1 - R2

    SUM R0, R1, #1 ; R0 = R1 - 1

  3. 比较

    CMP R0, R1 ;比较R0和R1,比较的结果保存在程序状态寄存器中 PSR

  4. 跳转

    • B :main ; Branch, 直接跳转

      执行这条指令,会导致程序状态寄存器R15(PC)寄存器中被已写入一个数值。数值为main函数的地址。CPU会从这个地址开始执行程序

    • BL:main ; Branch and Link, 在函数之间的跳转时,先把返回值保存在R14(LR)寄存器里再跳转

04. 堆与栈

一、堆

堆是一块动态分配的内存空间。

  • 特点

    • 分配和释放由程序员手动控制,相对灵活。可以在运行时根据实际需求从中分配出不同大小的内存块,例如可以使用编程语言提供的内存分配函数(如 C 语言中的 malloc、C++ 中的 new 等)来分配堆内存。当使用完这块内存后,需要显式地调用相应的释放函数(如 C 语言中的 free、C++ 中的 delete)把它放回去,否则会导致内存泄漏。

    • 内存分配和释放的时间开销相对较大,因为堆的管理通常涉及复杂的算法来寻找合适大小的空闲内存块。

    • 堆中的内存空间大小通常只受限于系统的可用物理内存和虚拟内存大小。

  • 用途

    • 适用于需要动态分配较大内存块且生命周期不确定的情况。比如存储大量数据结构(如链表、树等)、动态创建对象等。

二、栈

栈是一块由系统自动管理的内存空间。

  • 特点

    • 先进后出(FILO)的数据结构。CPU 的栈指针寄存器(SP)始终指向栈顶。当函数被调用时,函数的参数、局部变量和返回地址等信息被压入栈中;当函数返回时,这些信息被弹出栈。

    • 内存的分配和释放由系统自动完成,速度快。在函数调用结束后,栈上的局部变量会自动被释放,无需程序员手动管理。

    • 栈的大小通常是有限的,不同的操作系统和编译器可能会设置不同的栈大小限制。如果栈空间被耗尽,可能会导致栈溢出错误。

  • 用途

    • 主要用于函数调用和局部变量的存储。在多任务系统中,栈还可以用于保存任务的现场,例如当一个任务被中断时,当前的寄存器值、程序计数器等信息会被压入栈中,以便在任务恢复执行时能够恢复到中断前的状态。

三、问题

  1. 在函数调用时,LR被覆盖了怎么办?

    • 会 在C入口保存LR进栈。

  2. 局部变量在栈中是如何分配的?

    • 使用volatile 修饰的局部变量会保存在栈中。默认使用CPU寄存器来进行保存,如果寄存器不够用了才用栈

  3. 为什么每个ROTS任务都有自己的栈

    因为每个人物都有自己的调用关系和局部变量、所以在每个任务在切换出的时候要保存自己的现场

    • 保存现场

      • 在FreeRTOS运行时,内核的定时器中断函数会自动切换任务。

      • 并为需要为每个任务 保存现场,为恢复现场奠定基础

      • 保存现场当然要保存在RAM中的 这个任务分配的栈中。

      • 保存现场是保存所有的寄存器

      • 在任务A中的结构体里记录SP

    • 恢复现场

      • 找到A的结构体,得到A的栈。

      • 把栈中的值恢复到CPU中(把所有的寄存器的值恢复到硬件)

R13:别名SP(Stack Pointer),栈指针。用来保存栈的地址 R14:别名LR(Link Register),用来保存返回地址。(A函数到B函数,B函数完成后返回A) R15:别名PC(Program Counter),程序计数器,表示当前指令地址,写入新值即可跳转

05.FreeRTOS源码描述

7.1 FreeRTOS目录结构

使用STM32CubeMX创建的FreeRTOS工程中,FreeRTOS相关的源码如下:

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-7/image1.png

主要涉及2个目录:

  • Core

    • Inc目录下的FreeRTOSConfig.h是配置文件

    • Src目录下的freertos.c是STM32CubeMX创建的默认任务

  • Middlewares\Third_Party\FreeRTOS\Source

    • 根目录下是核心文件,这些文件是通用的

    • portable目录下是移植时需要实现的文件

      • 目录名为:[compiler]/[architecture]

      • 比如:RVDS/ARM_CM3,这表示cortexM3架构在RVDS工具上的移植文件

7.2核心文件 FreeRTOS的最核心文件只有2个:

  • FreeRTOS/Source/tasks.c

  • FreeRTOS/Source/list.c

    其他文件的作用也一起列表如下:

    http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-7/image2.jpg

**#7.3 移植时涉及的文件**

移植FreeRTOS时涉及的文件放在 FreeRTOS/Source/portable/[compiler]/[architecture] 目录下,比如:RVDS/ARM_CM3,这表示cortexM3架构在RVDS或Keil工具上的移植文件。 里面有2个文件:

  • port.c

  • portmacro.h

**#7.4 头文件相关**

**#7.4.1 头文件目录**

FreeRTOS需要3个头文件目录:

  • FreeRTOS本身的头文件:

Middlewares\Third_Party\FreeRTOS\Source\include

  • 移植时用到的头文件:

Middlewares\Third_Party\FreeRTOS\Source\portablecompiler

  • 含有配置文件FreeRTOSConfig.h的目录:Core\Inc

**#7.4.2 头文件**

列表如下:

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-7/image3.jpg

**#7.5 内存管理**

文件在Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang下,它也是放在“portable”目录下,表示你可以提供自己的函数。

源码中默认提供了5个文件,对应内存管理的5种方法。

后续章节会详细讲解。

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-7/image4.jpg

**#7.6 入口函数**

在Core\Src\main.c的main函数里,初始化了FreeRTOS环境、创建了任务,然后启动调度器。源码如下:

/* Init scheduler */
  osKernelInitialize();  /* 初始化FreeRTOS运行环境 */
  MX_FREERTOS_Init();    /* 创建任务 */
​
  /* Start scheduler */
  osKernelStart();       /* 启动调度器 */
​

**#7.7 数据类型和编程规范**

**#7.7.1 数据类型**

每个移植的版本都含有自己的portmacro.h头文件,里面定义了2个数据类型:

  • TickType_t:

    • FreeRTOS配置了一个周期性的时钟中断:Tick Interrupt

    • 每发生一次中断,中断次数累加,这被称为tick count

    • tick count这个变量的类型就是TickType_t

    • TickType_t可以是16位的,也可以是32位的

    • FreeRTOSConfig.h中定义configUSE_16_BIT_TICKS时,TickType_t就是uint16_t

    • 否则TickType_t就是uint32_t

    • 对于32位架构,建议把TickType_t配置为uint32_t

  • BaseType_t:

    • 这是该架构最高效的数据类型

    • 32位架构中,它就是uint32_t

    • 16位架构中,它就是uint16_t

    • 8位架构中,它就是uint8_t

    • BaseType_t通常用作简单的返回值的类型,还有逻辑值,比如pdTRUE/pdFALSE

**#7.7.2 变量名**

变量名有前缀:

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-7/image5.jpg

**#7.7.3 函数名**

函数名的前缀有2部分:返回值类型、在哪个文件定义。

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-7/image6.jpg

**#7.7.4 宏的名**

宏的名字是大小,可以添加小写的前缀。前缀是用来表示:宏在哪个文件中定义。

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-7/image7.jpg

通用的宏定义如下:

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-7/image8.jpg

06.内存管理

8.1 为什么要自己实现内存管理

后续的章节涉及这些内核对象:task、queue、semaphores和event group等。为了让FreeRTOS更容易使用,这些内核对象一般都是动态分配:用到时分配,不使用时释放。使用内存的动态管理功能,简化了程序设计:不再需要小心翼翼地提前规划各类对象,简化API函数的涉及,甚至可以减少内存的使用。

内存的动态管理是C程序的知识范畴,并不属于FreeRTOS的知识范畴,但是它跟FreeRTOS关系是如此紧密,所以我们先讲解它。

在C语言的库函数中,有mallc、free等函数,但是在FreeRTOS中,它们不适用:

  • 不适合用在资源紧缺的嵌入式系统中

  • 这些函数的实现过于复杂、占据的代码空间太大

  • 并非线程安全的(thread- safe)

  • 运行有不确定性:每次调用这些函数时花费的时间可能都不相同

  • 内存碎片化

  • 使用不同的编译器时,需要进行复杂的配置

  • 有时候难以调试

注意:我们经常"堆栈"混合着说,其实它们不是同一个东西:

  • 堆,heap,就是一块空闲的内存,需要提供管理函数

    • malloc:从堆里划出一块空间给程序使用

    • free:用完后,再把它标记为"空闲"的,可以再次使用

  • 栈,stack,函数调用时局部变量保存在栈中,当前程序的环境也是保存在栈中

    • 可以从堆中分配一块空间用作栈

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-8/image1.png

8.2 FreeRTOS的5中内存管理方法

FreeRTOS中内存管理的接口函数为:pvPortMalloc 、vPortFree,对应于C库的malloc、free。 文件在FreeRTOS/Source/portable/MemMang下,它也是放在portable目录下,表示你可以提供自己的函数。

源码中默认提供了5个文件,对应内存管理的5种方法。

参考文章:FreeRTOS说明书吐血整理【适合新手+入门】在新窗口打开

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-8/image2.jpg

8.2.1 Heap_1

它只实现了pvPortMalloc,没有实现vPortFree。

如果你的程序不需要删除内核对象,那么可以使用heap_1:

  • 实现最简单

  • 没有碎片问题

  • 一些要求非常严格的系统里,不允许使用动态内存,就可以使用heap_1

它的实现原理很简单,首先定义一个大数组:

/* Allocate the memory for the heap. */
##if ( configAPPLICATION_ALLOCATED_HEAP == 1 )
​
/* The application writer has already defined the array used for the RTOS
* heap -  probably so it can be placed in a special segment or address. */
    extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
##else
    static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
##endif /* configAPPLICATION_ALLOCATED_HEAP */
​

然后,对于pvPortMalloc调用时,从这个数组中分配空间。

FreeRTOS在创建任务时,需要2个内核对象:task control block(TCB)、stack。 使用heap_1时,内存分配过程如下图所示:

  • A:创建任务之前整个数组都是空闲的

  • B:创建第1个任务之后,蓝色区域被分配出去了

  • C:创建3个任务之后的数组使用情况

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-8/image3.png

8.2.2 Heap_2

Heap_2之所以还保留,只是为了兼容以前的代码。新设计中不再推荐使用Heap_2。建议使用Heap_4来替代Heap_2,更加高效。

Heap_2也是在数组上分配内存,跟Heap_1不一样的地方在于:

  • Heap_2使用最佳匹配算法(best fit)来分配内存

  • 它支持vPortFree

最佳匹配算法:

  • 假设heap有3块空闲内存:5字节、25字节、100字节

  • pvPortMalloc想申请20字节

  • 找出最小的、能满足pvPortMalloc的内存:25字节

  • 把它划分为20字节、5字节

    • 返回这20字节的地址

    • 剩下的5字节仍然是空闲状态,留给后续的pvPortMalloc使用

与Heap_4相比,Heap_2不会合并相邻的空闲内存,所以Heap_2会导致严重的"碎片化"问题。

但是,如果申请、分配内存时大小总是相同的,这类场景下Heap_2没有碎片化的问题。所以它适合这种场景:频繁地创建、删除任务,但是任务的栈大小都是相同的(创建任务时,需要分配TCB和栈,TCB总是一样的)。

虽然不再推荐使用heap_2,但是它的效率还是远高于malloc、free。

使用heap_2时,内存分配过程如下图所示:

  • A:创建了3个任务

  • B:删除了一个任务,空闲内存有3部分:顶层的、被删除任务的TCB空间、被删除任务的Stack空间

  • C:创建了一个新任务,因为TCB、栈大小跟前面被删除任务的TCB、栈大小一致,所以刚好分配到原来的内存

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-8/image4.png

8.2.3 Heap_3

Heap_3使用标准C库里的malloc、free函数,所以堆大小由链接器的配置决定,配置项configTOTAL_HEAP_SIZE不再起作用。

C库里的malloc、free函数并非线程安全的,Heap_3中先暂停FreeRTOS的调度器,再去调用这些函数,使用这种方法实现了线程安全。

8.2.4 Heap_4

跟Heap_1、Heap_2一样,Heap_4也是使用大数组来分配内存。

Heap_4使用 首次适应算法(first fit)来分配内存 。它还会把相邻的空闲内存合并为一个更大的空闲内存,这有助于较少内存的碎片问题。

首次适应算法:

  • 假设堆中有3块空闲内存:5字节、200字节、100字节

  • pvPortMalloc想申请20字节

  • 找出第1个能满足pvPortMalloc的内存:200字节

  • 把它划分为20字节、180字节

  • 返回这20字节的地址

  • 剩下的180字节仍然是空闲状态,留给后续的pvPortMalloc使用

Heap_4会把相邻空闲内存合并为一个大的空闲内存,可以较少内存的碎片化问题。适用于这种场景:频繁地分配、释放不同大小的内存。

Heap_4的使用过程举例如下:

  • A:创建了3个任务

  • B:删除了一个任务,空闲内存有2部分:

  • 顶层的

  • 被删除任务的TCB空间、被删除任务的Stack空间合并起来的

  • C:分配了一个Queue,从第1个空闲块中分配空间

  • D:分配了一个User数据,从Queue之后的空闲块中分配

  • E:释放的Queue,User前后都有一块空闲内存

  • F:释放了User数据,User前后的内存、User本身占据的内存,合并为一个大的空闲内存

http://photos.100ask.net/rtos-docs/FreeRTOS/DShanMCU-F103/chapter-8/image5.png

Heap_4执行的时间是不确定的,但是它的效率高于标准库的malloc、free。

8.2.5 Heap_5

Heap_5分配内存、释放内存的算法跟Heap_4是一样的。

相比于Heap_4,Heap_5并不局限于管理一个大数组:它可以管理多块、分隔开的内存。

在嵌入式系统中,内存的地址可能并不连续,这种场景下可以使用Heap_5。

既然内存时分隔开的,那么就需要进行初始化:确定这些内存块在哪、多大:

  • 在使用pvPortMalloc之前,必须先指定内存块的信息

  • 使用vPortDefineHeapRegions来指定这些信息

怎么指定一块内存?使用如下结构体:

typedef struct HeapRegion
{
    uint8_t * pucStartAddress; // 起始地址
    size_t xSizeInBytes;       // 大小
} HeapRegion_t;
​

怎么指定多块内存?使用一个HeapRegion_t数组,在这个数组中,低地址在前、高地址在后。 比如:

HeapRegion_t xHeapRegions[] =
{
  { ( uint8_t * ) 0x80000000UL, 0x10000 }, // 起始地址0x80000000,大小0x10000
  { ( uint8_t * ) 0x90000000UL, 0xa0000 }, // 起始地址0x90000000,大小0xa0000
  { NULL, 0 } // 表示数组结束
 };
​

vPortDefineHeapRegions函数原型如下:

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
```c
​
把xHeapRegions数组传给vPortDefineHeapRegions函数,即可初始化Heap_5。
​
## 8.3 Heap相关的函数
​
### 8.3.1 pvPortMalloc/vPortFree
​
函数原型:
​
```c
void * pvPortMalloc( size_t xWantedSize );
void vPortFree( void * pv );
​

8.3 Heap相关的函数

8.3.1 pvPortMalloc/vPortFree

函数原型:

void * pvPortMalloc( size_t xWantedSize );
void vPortFree( void * pv );
​

作用:分配内存、释放内存。

如果分配内存不成功,则返回值为NULL。

8.3.2 xPortGetFreeHeapSize

函数原型:

size_t xPortGetFreeHeapSize( void );
​

当前还有多少空闲内存,这函数可以用来优化内存的使用情况。比如当所有内核对象都分配好后,执行此函数返回2000,那么configTOTAL_HEAP_SIZE就可减小2000。

注意:在heap_3中无法使用。

8.3.3 xPortGetMinimumEverFreeHeapSize

函数原型:

size_t xPortGetMinimumEverFreeHeapSize( void );
​

返回:程序运行过程中,空闲内存容量的最小值。

注意:只有heap_4、heap_5支持此函数。

8.3.4 malloc失败的钩子函数

在pvPortMalloc函数内部:

void * pvPortMalloc( size_t xWantedSize )vPortDefineHeapRegions
{
    ......
    #if ( configUSE_MALLOC_FAILED_HOOK == 1 ){
            if( pvReturn == NULL )
            {
                extern void vApplicationMallocFailedHook( void );
                vApplicationMallocFailedHook();
            }
        }
    #endif

    return pvReturn;
}

所以,如果想使用这个钩子函数:

  • 在FreeRTOSConfig.h中,把configUSE_MALLOC_FAILED_HOOK定义为1

  • 提供vApplicationMallocFailedHook函数

  • pvPortMalloc失败时,才会调用此函数

07.动态和静态创建任务

任务:

  • 做什么事:函数

  • 栈和 TCB

    • 可以动态分配也可以事先静态分配

  • 优先级

一个任务备切换出来之后, 如何才能再次找到它:

  • 在一个链表中找到任务A、B、C

  • 在链表中存放任务控制块(TCB )(task contol block)

在动态分配内存的时候 根据你输入的栈的大小自动分配栈和TCB

在静态分配内存的时候,需要事先准备好栈和TCB结构体

动态:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
const char * const pcName, // 任务的名字
const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority,    // 优先级
TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务

静态:

TaskHandle_t xTaskCreateStatic ( 
    TaskFunction_t pxTaskCode,   // 函数指针, 任务函数
    const char * const pcName,   // 任务的名字
    const uint32_t ulStackDepth, // 栈大小,单位为word,10表示40字节
    void * const pvParameters,   // 调用任务函数时传入的参数
    UBaseType_t uxPriority,      // 优先级
    StackType_t * const puxStackBuffer, // 静态分配的栈,就是一个buffer
    StaticTask_t * const pxTaskBuffer // 静态分配的任务结构体的指针,用它来操作这个任务
);

测试:

使用静态分配的时候,需要为任务提供buff 和TCB结构体

static StackType_t g_pucStackOfLightTask[128];  //提供Buff  g 为全局的缩写
static StaticTask_t g_TCBofLightTask;            //提供TCB任务结构体
static TaskHandle_t xLightTaskHandle;              //光任务 句柄结构体  以FreeRTOS 的规范, x表示某些结构体  

static StackType_t g_pucStackOfColorTask[128];
static StaticTask_t g_TCBofColorTask;
static TaskHandle_t xColorTaskHandle; 

使用时是这样

 /* 创建任务:光 */
    xLightTaskHandle = xTaskCreateStatic(Led_Test, "LightTask", 128, NULL, osPriorityNormal, g_pucStackOfLightTask, &g_TCBofLightTask);

  /* 创建任务:色 */
    xColorTaskHandle = xTaskCreateStatic(ColorLED_Test, "ColorTask", 128, NULL, osPriorityNormal, g_pucStackOfColorTask, &g_TCBofColorTask);

任务句柄还没学到。 AI说 主要有以下作用:一是用于识别和管理任务,可进行挂起、恢复、删除等操作;二是在任务间通信与同步中,可指定消息的接收者或发送者,以及用于同步操作;三是能查询任务状态和获取任务属性信息。

TCB的简要介绍:

一、存储任务状态信息

  1. 记录任务当前的运行状态,如就绪态、运行态、阻塞态等。

  2. 保存任务的优先级,决定任务在可运行状态下获取 CPU 时间片的优先级顺序。

二、管理任务资源

  1. 可能包含任务所使用的栈信息,包括栈的起始地址和大小。

  2. 可以存储与任务相关的同步对象指针,如信号量、互斥量等,以便任务进行同步和通信操作。

三、支持任务调度

  1. 操作系统在进行任务调度时,通过检查 TCB 中的信息来决定下一个要执行的任务。

  2. TCB 使得操作系统可以快速地切换任务,保存和恢复任务的上下文。

总之,任务控制块是 FreeRTOS 管理任务的核心数据结构,它为任务的创建、运行、暂停、恢复和删除等操作提供了必要的信息和控制机制。

动态任务在创建时会自动分配内存和初始化任务块等等。但有可能因为内存不够等原因失败,所以返回值为创建任务的成功或失败。

静态任务在创建时需要用户提前准备buff和 TCB控制块 。 所以他一定能创建成功。他的参数中没有任务句柄。他的返回值是任务句柄

08.估算栈的大小

栈的作用是

  • 返回地址 比如LR寄存器、其他寄存器

    • 取决于函数的调用深度

  • 局部变量

    • 取决于代码

  • 保存现场(在任务被切换时)

    • 现场时个寄存器 16 * 4 = 64 字节

如何评估栈的大小

  • 选取最复杂的调用关系

    • n级调用 * (被调用者寄存器R4 - R11共8个 + LR寄存器)也就是一次调用最多使用36 个字节

    • 所以 调用深度越深 所需要的栈的空间就越大

  • 选取创建局部变量最大的函数。

09.一个函数创建多个任务

定义结构体,通过在函数内定义 void* 类型的指针变量 来接收我们定义的结构体。

通过创建值不同的结构体,来作为创建任务创建时传入函数的参数

通过多次创建任务,但任务在调用时传入函数的 参数不同。 可以做到同一个函数,创建多个不同的任务。

注意使用struct 而不能使用Typedef struct 否则任务在传参时会报错~

10.删除任务

删除任务 使用 vTaskDelete 函数

它的参数为 任务在创建时 返回 或 传入的 任务句柄

使用这个函数可以直接删除函数,但是多次的删除和创建 ,每次创建都会去分配内存。频繁地动态分配内存很容易导致内存的碎片化,直到分配不到内存。

并且在删除任务之后。这个任务就终止了。没法做一些清除的工作。

所以,在删除任务时,我们并不会经常使用vTaskDelete函数。

一般来说是让任务自己来根据(比如遥控器的按键)自己停止。自己做一些清除的工作

11.优先级与阻塞

这里会通过提升任务的优先级来达到改善播放的效果

可以通过修改优先级 来达到优化音乐播放,

学习使用vTaskdelay的使用。使用自己写的会阻塞任务。如果阻塞的delay在高优先级的任务,会导致只会运行高优先级的任务。

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

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

相关文章

鸿蒙OS开发之动画相关示例分享, 关于弹出倒计时动画的实战案例源码分享

基础动画案例 Entry Component struct Index {StatebtnWidth:number 200 // 按钮的宽度StatebtnHeight:number 100 // 按钮的高度build() {Row(){Column(){Button("测试").width(this.btnWidth).height(this.btnHeight)// 按钮: 用来启动动画Button("动画开始…

USB2.0主机设备检测过程以及信号分析

一,USB协议发展 USB接口自1994年推出以来,经过30年的发展,从USB1.0发展到了现在的USB4.0,传输速率也从最开始的1.5Mbps,大幅提高到了最新的40Gbps。 USB协议按照速度等级和连接方式分可分为7个版本,但是从…

docker -私有镜像仓库 - harbor安装

文章目录 1、镜像仓库简介2、Harbor简介3、下载与安装3.1、下载3.2、安装3.2.1、上传harbor-offline-installer-v2.8.2.tgz到虚拟机中解压并修改配置文件3.2.2、解压tgz包3.2.3、切换到解压缩后的目录下3.2.4、准备配置文件3.2.5、修改配置文件 4、启动Harbor5、启动关闭命令6、…

为什么这款智能在线派单软件成为行业首选?

智能在线派单软件通过自动化任务分配等提升效率,ZohoDesk因其全方位服务管理、智能分配、定制性强、数据分析等功能,成为企业优选。实例涵盖物流、家政、维修、医疗等行业,提高效率和客户满意度。 一、智能在线派单软件有什么功能 在深入探讨…

【Java】包装类【主线学习笔记】

文章目录 前言包装类基本数据类型与包装类之间的转换基本数据类型转换为包装类可以通过以下几种方式:包装类转换为基本数据类型可以通过以下几种方式:初始化值不同与String之间的转换 前言 Java是一门功能强大且广泛应用的编程语言,具有跨平台…

带你重新深入了解STM32单片机

目录 一. 前言 二. 片上资源外设 三. 单片机命名规则 四. STM32的系统结构 五. STM32F103C8T6的引脚定义 六. 启动配置 一. 前言 本篇文章主要讲述对STM32单片机的介绍,包括片上资源和外设,STM32产品系列,内存存储器容量以及STM32的系统…

Java每日面试题(JVM)(day15)

目录 Java对象内存布局markWord 数据结构JDK1.8 JVM 内存结构JDK1.8堆内存结构GC垃圾回收如何发现垃圾如何回收垃圾 JVM调优参数 Java对象内存布局 markWord 数据结构 JDK1.8 JVM 内存结构 程序计数器: 线程私有,记录代码执行的位置. Java虚拟机栈: 线程私有&#…

服务器操作系统【sar 命令】

sar 安装、语法参数说明以及示例 文章目录 功能概述一、功能介绍1.安装配置2. 配置3. 启动二、sar 语法及参数说明三、示例及释义1.汇报 io 传输速率信息2.内存分页信息3.块设备状态信息4.hugepages 利用率统计信息5.列长度和负载平均值6.内存利用率统计信息7.swap 交换空间利用…

中国的互联网电商,终于还是“连上了”

什么才是更好的互联网? 答案很简单:真正的互联。 9月26日,据市场消息,京东物流和菜鸟速递将分别接入淘天、京东平台。同时,京东也将在“双11”前开通支付宝支付,时隔13年再度携手阿里支付体系。 消息一出…

【Redis入门到精通八】Redis事务与MySQL事务对比

目录 事务 1.MySQL中事务的特性 2.Redis事务与MySQL事务的区别 3.Redis事务操作演示 事务 什么是事务呢?事务的概念其实就是把一系列操作绑定成一组,让这一组操作能够批量执行,不过在MySQL中有复杂的机制能够保证这一组操作执行并且一定能…

降AI率不再难:芝士AI去痕工具,让论文原创性飙升~~~

降AI率不再难:芝士AI去痕工具,让你的论文原创性飙升 如何有效降低AIGC论文的重复率,也就是我们说的aigc如何降重?AIGC疑似度过高确实是个比较愁人的问题。 如果你用AI帮忙写了论文,就一定要在交稿之前做一下AIGC降重…

征程6 上基于 DEB 工具实现包管理

1.引言 在开发、调测过程中,开发人员需要将系统软件、应用软件部署到 Soc 板端,以用于运行调试。传统的部署方式是通过解压复制或者调用部署脚本。这样的部署方式需要有着方式不统一、维护投入大的缺点。 在 linux 系统上,大多采用包管理的…

【CSS】背景

background-color 颜色background-image 图像background-size 缩放background-repeat 平铺background-position 定位background-clip 裁剪区域background-origin 开始区域background-attachment 滚动方式 background-color 颜色 <style>div{width: 200px;height: 100px;…

TOF系列—深度图滤波

本篇文章主要介绍TOF深度图的后处理&#xff0c;鉴于自身水平所限&#xff0c;如有错误&#xff0c;欢迎批评指正。&#xff08;欢迎进Q群交流&#xff1a;874653199&#xff09; TOF由于其本身的特性&#xff0c;导致其所获得的深度图存在以下问题&#xff1a; 1.对空的地方存…

SpringBoot与MyBatis-Plus的整合与综合实例

MyBatis 是一款优秀的持久层框架&#xff0c;它支持定制化SQL、存储过程、以及高级映射。MyBatis3 提供的注解可以取代 XML。例如&#xff0c;使用注解 Select 直接编写 SQL 完成数据查询。MyBatis-Plus 是一个对 MyBatis 进行增强的工具&#xff0c;在 MyBatis 的基础上只做增…

剑指 offer 刷题集

目录 数组 1. LCR 121. 寻找目标值 - 二维数组 2. LCR 120. 寻找文件副本 3. LCR 128. 库存管理 I 4. LCR 131. 砍竹子 I 5. LCR 132. 砍竹子 II 6. LCR 135. 报数 7. LCR 139. 训练计划 I 8. LCR 158. 库存管理 II 9. LCR 159. 库存管理 III 10. LCR 160. 数据流中…

从GPS接收机灵敏度出发--理论计算GPS最低的跟踪灵敏度

思博伦售后团队 对射频工程师来说&#xff0c;自己设计的接收机灵敏度&#xff08;Receiver Sensitivity&#xff09;是最关注也是最重要的参数之一。所以我们首先来看看接收机灵敏度的定义&#xff1a; 在满足一定误码率的条件下&#xff0c;接收机能容许接受到的最小信号电平…

FPGA学习(3)-38译码器实现

目录 1.38译码器概述 2.VIVADO步骤 2.1创建工程&#xff0c;添加源文件 2.2添加仿真文件&#xff0c;并进行仿真 ​2.3添加管脚约束 2.4生成bit文件 2.5 烧录程序 3.实验结果 1.38译码器概述 即三个输入A2A1A0&#xff0c;输出的十进制数&#xff0c;例如LHL(010)2…

class 026 哈希表、有序表和比较器的用法

这篇文章是看了“左程云”老师在b站上的讲解之后写的, 自己感觉已经能理解了, 所以就将整个过程写下来了。 这个是“左程云”老师个人空间的b站的链接, 数据结构与算法讲的很好很好, 希望大家可以多多支持左程云老师, 真心推荐. https://space.bilibili.com/8888480?spm_id_f…

Docker实践与应用:深度探索与丰富案例

一、引言 在当今的软件开发和运维领域&#xff0c;Docker已经成为了一种不可或缺的技术。它以容器化的方式改变了软件的开发、部署和运行模式&#xff0c;为企业和开发者带来了前所未有的便利和效率提升。本文将深入探讨Docker的实践操作以及丰富的应用举例&#xff0c;带您全面…