学习《FreeRTOS源码详解与应用开发》笔记

1、注意:任务函数内部定义局部变量的内存大小不能大于此任务堆栈内存的大小。

2、FreeRTOS定义任务优先级时,0优先级(空闲中断占用)和最高优先级31级(定时器占用)不能用。

3、用start_task创建任务task1和task2时,start_task只创建一次就行,不用多次创建,所以创建完start_task后,就可以把start_task任务删除,用vTaskDelete()函数删除,要删除那个任务,只需把那个函数的任务句柄放在vTaskDelete后面的括号里面即可,或者直接在括号里面用一个NULL也可以。

4、vTaskList()函数可查询任务的名称、任务运行状态(就绪态、运行态、阻塞态和删除态等)、优先级、任务运行后剩余的堆栈余量等,是一个重量级的函数。注意,当定义一个数组变量去接收vTaskList()函数返回的指针值时,这个数组变量缓存要大些,因为任务多,占用堆栈内存大。且这个数组变量最好定义成全局变量,如果定义成函数局部变量,那内存是从定义这个数组变量的函数开始调用这个数组变量,就需要把这个任务函数的堆栈改大,否则程序会出错。

5、vTaskGetRunTimeStatus()函数,这个函数可查询当前运行的任务中哪个任务比较耗时,可把这个任务拆分成小的任务。减小任务耗时。

6、时序要求严格的时钟通信协议中,如在I2C、SPI正在读取数据,高优先级的任务到来时,高优先级就抢占CPU,导致I2C或SPI无法读取正确的数据,可用临界任务保护函数,在I2C\SPI读取数据时,禁止高优先级抢占CPU,使I2C和SPI读到正确的数据。可调用taskENTER_CRITICAL()函数进入任务保护,调用taskEXIT_CRITICAL()函数退出任务保护。可进行多次嵌套临界段代码保护。注意,在使用临界区保护函数时,要快进快出,避免一些紧急的事件任务需要处理时,临界区保护的任务还未完成,那紧急任务事件得不到实时处理,会出现问题的。如在电力系统和医疗急救中,紧急事件任务得不到处理,会出现大问题。

7、函数askENTER_CRITCAL_FROM_ISR()和函数taskEXIT_CRITCAL_FROM_ISR()是中断级临界区代码段保护函数,用在中断服务函数中,而且这个中断的优先级一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY(高于这个宏定义定义的中断优先级,不可调用FreeRTOS系统的API)。

8、在使用taskENTER_CRITCAL_FROM_ISR()时,先定义一个变量读取这个函数返回的值,等退出临界保护时,把这个变量作为实参传递给taskEXIT_CRITCAL_FROM_ISR()来退出临界保护。
用法如下:
uint32_t  taskISR_Value;
taskENTER_CRITCAL_FROM_ISR();
被保护代码段
taskEXIT_CRITCAL_FROM_ISR(taskISR_Value);

9、延时函数:vTaskDelay()相对延时函数和vTaskDelayUntil()绝对延时函数。

10、队列是用于任务与任务、任务与中断之间的通信。可在任务与任务、任务与中断之间传递信息,因此队列也叫做消息队列。队列可存储有限的、大小固定的数据项目。任务与任务、任务与中断之间要交流的数据保存在队列中,叫队列项目。队列能保存的最大数据项目数量叫队列的长度。创建队列的时候需指定数据项目的大小和队列的长度。注:信号量也是依据队列来实现的。

11、平时不用操作系统时,用串口中断接收一帧数据,但不知道这帧数据什么时候到来,通常我们会定义一个接收数据标志的全局变量来确定一帧数据是否接收完成,在main函数里面不停轮询判断这个接收标志位是否等于1,如果等于1,则确定接收到了新的帧数据并在main函数或其它子函数进行数据处理,这种方式效率低且耗费CPU。此时可用操作系统做信号量的任务同步,当串口中断服务函数/定时器确定接收到一帧数据,可发出一个信号量。在程序应用中创建一个任务去请求获取接收串口中断服务函数/定时器发出的信号量信息的信号量,当这个信号量无效时,任务会进入阻塞态,把CPU让给其他任务去执行;当信号量有效时,这个任务接着执行处理串口接收到的数据。这个信号量可实现任务与任务之间的同步,也可实现中断(串口中断/定时器中断/外部中断等)和任务之间的同步。


12、二值信号量适合用于同步,如任务与任务之间的同步,任务和中断之间的同步。互斥信号量适合用于简单的互斥访问。

13、在使用二值信号量时,会产生任务优先级翻转。解决任务优先级翻转可以使用互斥信号量。

14、互斥信号量使用的API操作函数和二值信号量使用的API操作函数是相同的。所以互斥信号量也可设置阻塞时间,与二值信号量不同的是,互斥信号量具有优先级继承的特性。当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能地降低了高优先级任务处于阻塞时间,并且将已经出现的“优先级翻转”的影响降到最低。如,现有三个依次为低、中、高的三个任务,中高优先级任务处于就绪挂起态,低优先级任务在运行,此时低优先级任务想要访问共享资源,则需要获取该资源的信号量。低优先级任务获取信号量后并开始访问共享资源,此时,高优先级任务开始运行,也想访问低优先级任务正在使用的共享资源,但是该共享资源的信号量还在被低优先级任务占用着,高优先级任务只能进入挂起态,默默的等待低优先级任务释放该共享资源的信号量。就在高优先级任务默默等待看着低优先级任务什么时候释放共享资源的信号量时,中优先级任务等待的事件发生后,中级优先级任务也开始运行了,由于中优先级任务的优先级高于低优先级任务的优先级,所以就抢占了CPU的使用权。一直等到中优先级任务把自己的事件处理完成后,才把CPU使用权给低优先级任务,低优先级任务继续使用CPU去访问共享资源,待低优先级任务访问共享资源完成了,低优先级任务释放共享资源的信号量,最后高优先级任务才获取低优先级任务释放的信号量去访问共享资源。这种现象看起来就是中优先级任务的优先级要比高优先级任务的优先级高。此时高优先级任务可以使用互斥信号量,把低优先级任务的优先级提高到与自己优先级一样,在低优先级在使用信号量时,高优先级任务进入挂起态,中优先级任务事件发生后想要获取CPU的使用权,但由于高优先级任务把低优先级任务的优先级提高了,中优先级任务只好等待这两个高优先级任务运行完后,才可获得CPU使用权。而原先的高优先级任务等到原低优先级任务释放信号量以后,就可以获取信号量去访问原低优先级任务访问的共享资源。

15、互斥信号量不能用于中断服务器中。原因有两个:一、互斥信号量有优先级继承机制,只能应用在任务中,不能用于中断服务函数。二、中断服务函数中不能因为要等待互斥信号量二设置阻塞时间进入阻塞态。

16、递归信号量与互斥信号量一样,不能用在中服务函数中。

17、创建的软件定时器是休眠状态的,要调用开启软件定时器的函数开启定时器。

18、任务通知可完成消息队列、信号量、事件标志组的功能,不过任务通知是只能实现一对一的,也就是一个任务对一个任务。

19、接收任务可以因为接收任务通知而进入阻塞态,但是发送任务不会因为任务通知发送失败而阻塞。但是队列就有入队阻塞和出对阻塞,信号量是根据队列来实现的,信号量也会出现跟队列一样的情况。

20、虽然任务通知可以模拟二值信号量,但任务通知不能用在资源管理上,只有二值信号量能用。

21、降低系统主频可以降低CPU的功耗。

22、内存管理,heap1特点如下:
(1)适用于那些一旦创建好任务、信号量和队列就再也不会删除的应用,实际上大多数的FreeRTOS 应用都是这样的。
(2)具有可确定性(执行所花费的时间大多数都是一样的),而且不会导致内存碎片。
(3)代码实现和内存分配过程都非常简单,内存是从一个静态数组中分配到的,也就是适合于那些不需要动态内存分配的应用。

22、内存管理,heap2能申请内存特点如下:
(1)可以使用在那些可能会重复的删除任务、队列、信号量等的应用中,要注意有内存碎片产生!
(2)如果分配和释放的内存 n 大小是随机的,那么就要慎重使用了,比如下面的示例:

=》如果一个应用动态的创建和删除任务,而且任务需要分配的堆栈大小都是一样的,那么 heap_2 就非常合适。如果任务所需的堆栈大小每次都是不同,那么 heap_2 就不适合了,因为这样会导致内存碎片产生,最终导致任务分配不到合适的堆栈!不过 heap_4 就很适合这种场景了。

=》如果一个应用中所使用的队列存储区域每次都不同,那么 heap_2 就不适合了,和上面一样,此时可以使用 heap_4。
=》应用需要调用 pvPortMalloc()和 pvPortFree()来申请和释放内存,而不是通过其他FreeRTOS 的其他 API 函数来间接的调用,这种情况下 heap_2 不适合。
(3)如果应用中的任务、队列、信号量和互斥信号量具有不可预料性(如所需的内存大小不能确定,每次所需的内存都不相同,  或者说大多数情况下所需的内存都是不同的)的话可能会导致内存碎片。虽然这是小概率事件,但是还是要引起我们的注意!
(4)具有不可确定性,但是也远比标准 C 中的 mallo()和 free()效率高!heap_2 基本上可以适用于大多数的需要动态分配内的工程中,而 heap_4 更是具有将内存碎片合并成一个大的空闲内存块(就是内存碎片回收)的功能。

23、内存管理,heap3内存分配方法是对标准C中的函数malloc()和free()的简单封装,FreeRTOS对这两函数做了线程保护。特点如下:

(1)需要编译器提供一个内存堆,编译器库要提供 malloc()和 free()函数。若使用STM32的话可以通过修改启动文件中的Heap_Size 来修改内存堆的大小。
(2)具有不确定性。
(3)可能会增加代码量。

24、内存管理,heap4提供了一个最优的匹配算法,不像heap2,heap4会将内存碎片合成一个大的可用内存块。它提供了内存合并算法。
特点如下:
(1)可以用在那些需要重复创建和删除任务、队列、信号量和互斥信号量等的应用中。
(2)不会像 heap_2 那样产生严重的内存碎片,即使分配的内存大小是随机的。
(3)具有不确定性,但是远比 C 标准库中的 malloc()和 free()效率高。heap_4 非常适合于那些需要直接调用函数pvPortMalloc()和 vPortFree()来申请和释放内存的应用,注意,我们移植 FreeRTOS 的时候就选择的 heap_4!heap_4也使用链表结构来管理空闲内存块,链表结构体与 heap_2 一样。 heap_4 也定义了两个局部静态变量 pxStart 和 pxEnd来表示链表头和尾,其中 pxEnd是指向 BlockLink_t 的指针。


25、内存管理,heap_5 基本上和 heap_4 一样,只是 heap_5 支持内存堆使用不连续的内存块。 heap_5 允许内存堆跨越多个不连续的内存段。比如 STM32 的内部 RAM 可以作为内存堆,但是 STM32 内部 RAM 比较小,遇到那些需要大容量 RAM 的应用就不行了,如音视频处理。不过 STM32 可以外接 SRAM 甚至大容量的 SDRAM,如果使用 heap_4 的话你就只能在内部 RAM 和外部SRAM 或 SDRAM 之间二选一了,使用 heap_5 的话就不存在这个问题,两个都可以一起作为内存堆来用。

26、用pvPortMalloc()申请内存、pvPortFree()释放内存,也可查看内存使用剩余量。可用一个指针变量如uint8_t buffer去接收申请到的内存。释放内存以后需要将 buffer 设置为 NULL,函数 pvPortFree()释放内存以后并不会将 buffer清零,此时 buffer 还是上次申请到的内存地址,如果此时再调用指针 buffer 的话你会发现还是可以使用的!感觉好像没有释放内存,所以需要将 buffer 清零。内存申请、内存释放、查看内存使用剩余量如下:
uint8_t *buffer,times = 0;
uint32_t freemem;

申请内存
buffer = pvPortMalloc(20); //申请内存,20个字节 
printf("申请到的内存地址为:%#x\r\n",(int)buffer);

释放内存
if(buffer!=NULL){
   vPortFree(buffer); //释放内存
}
buffer = NULL;

使用内存
if(buffer!=NULL) //buffer 可用,使用 buffer (5)
{
   times++;
   sprintf((char*)buffer,"User %d Times",times);//向buffer中填写一些数据
   printf("%s\r\n",buffer);                     //把刚刚写进去的数据打印出来
}

查看内存剩余大小
freemem = xPortGetFreeHeapSize(); //获取剩余内存大小
注意:我们申请20字节的内存大小,但实际申请到的内存大小要比20字节大,为什么会这样?因为在申请内存的时候,会自动进行内存堆的开始地址对齐,申请的内存地址也需要进行字节对齐。所以我们申请的是20字节,但是经过字节对齐后计算,实际会分配大于20字节的内存给我们。
 

 

 

 

你可能感兴趣的:(单片机,C语言编程,STM32)