FreeRTOS

一、简介 、特点

  FreeRTOS (Free 免费的  Real  Time  Operate  System 实时操作系统)。文件数量比UCOS少(4-9K字节)。特点:可裁剪(通过配置文件里的宏定义),任务数量、优先级不限,支持低功耗的Tickless模式,堆栈溢出检测。

二、源码获取   (官网: www.freertos.org)

  

  1、提取文件

      

  2、实现串口收发(printf重定向,调试)和系统嘀嗒定时器(涉及RTOS的系统时钟)如果实现了timer2定时器先注释,笔者发现两个都初始化后任务创建不成功(timer2定时器中断优先级比较高的原因,调低就可以了

      systic.c文件里初始化完后要实现中断服务函数,实现delay_us(u32 us) 和 delay_xms(u32 ms) 延时函数,初始化和中断这里跟我们平时写的有差异,只因他是专门服务于FreeRTOS,文件放到博客园下了。

       

      FreeRTOS 心跳由滴答定时器产生,根据 FreeRTOS 的系统时钟节拍设置好滴答定时器的周期,这样就会周期触发滴答定时器中断了。在滴答定时器中断服务函数中调用FreeRTOS 的 API 函数 xPortSysTickHandler()。      

      如上1、2做完,工程添加完毕后编译:除图二错误可能还有中断函数重复定义等,因为RTOS里又定义了一次,把stm32f10x_it.c里的注释掉。

三、配置FreeRTOSConfig.h

  实际使用中通过该文件完成裁剪和配置:INCLUDE 开头的宏表示使能或不使用对应的API函数。如图所示(部分):具体文件已保存到博客园文件下了,F1和F4均通用。

      

四、任务基础   (创建、删除、挂起、恢复)

  1、简介:任何一个时间点只能有一个任务运行,由调度器决定,其职责是保证任务执行时的上下文环境 (寄存器值、堆栈内容等)和任务上一次退出时相同。为此,任务必须有其堆栈(保存上下文环境)。

  2、特性:无限制、支持抢占、优先级,任务都有堆栈导致RAM增大,使用抢占须考虑重入问题。

  3、状态:运行态、就绪态、阻塞态、挂起态。(由图可知,挂起态任务恢复后不是直接进到运行态,而是进到就绪态,阻塞态同理)

        

  4、优先级:每个任务都能分配0~(configMAX_PRIORITIES-1)的优先级,共32级,0-31,数值越低优先级越低(与单片机中断优先级和UCOS相反),空闲任务优先级最低,为0。同一个优先级时通过时间片轮转调度器获取运行时间。

     5、任务创建: xTaskCreate() 动态创建:任务所需RAM从RTOS堆中分配,需提供heap_4.c内存管理文件,宏 configSUPPORT_DYNAMIC_ALLOCATION 须为 1; xTaskCreateStatic() ;静态创建:RAM需用户提供。

         创建的任务是就绪态,若没有比它高优先级的任务运行则立即进入运行态。不管调度器是否启用。

    int main(void)

    TaskHandle_t StartTask_Handler;     //任务句柄  ----堆栈大小,优先级等也可以使用宏定义 ,如果没有其它API使用任务句柄也可以为NULL。任务创建定义在主函数最后面,则main.c不需要while(1)。

    BaseType_t xTaskCreate( (TaskFunction_t )pxTaskCode,                 //任务函数   (动态创建)                                                                                                                           (实际参数如:stat_task)

                (const char * )const pcName,                   //任务名字,一般用于追踪和调试,长度不超过configMAX_TASK_NAME_LEN                                         (如:"stat_task")

                (const uint16_t) usStackDepth,                 //堆栈大小,申请的实际字节为传入数据的4倍。空闲任务堆栈大小为:configMINIMAL_STACK_SIZE      (如:128)

                (void *) const pvParameters,                     //传递给任务函数的参数                                   (如:NULL)

                (UBaseType_t) uxPriority,                          //任务优先级, 0~ configMAX_PRIORITIES-1 值越大优先级越高                 (如:3)

                (TaskHandle_t *) const pxCreatedTask   ) // 成功返回句柄, 其实就是任务堆栈,堆栈就是保存任务句柄,其它API操作这个任务就是使用任务句柄  (如:&StartTask_Handler)

                pdPASS :创建成功返回    errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 创建失败,堆内存不足(太大空间不够,太小任务创建失败)                      

     如下,图一先创建开始任务,再用开始任务去创建任务函数,图二直接创建任务函数,不要开始任务,实现效果一致,但更简洁:

                很多实际运用中就像图二这种创建几个任务函数,实现多任务就够了,什么队列信号量都不用,自写一个 send data() 函数集中处理数据

       

 

    TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,      //任务函数   (静态创建)

                const char * const pcName,                     //任务名字

                const uint32_t ulStackDepth,                   //任务堆栈大小,静态创建由用户给,一般是个数组,传入的参数就是数组大小

                void * const pvParameters,                      //传递给任务函数的参数

                UBaseType_t uxPriority,                           //任务优先级, 0~ configMAX_PRIORITIES-1

                StackType_t * const puxStackBuffer,       //任务堆栈,一般为数组,数组类型: StackType_t 

                StaticTask_t * const pxTaskBuffer    )       //任务控制块

                NULL:创建失败,puxStackBuffer 或 pxTaskBuffer 为 NULL时        其它值:任务创建成功,返回任务句柄

    xTaskCreateRestricted()  :此函数创建的任务会受到MPU的保护,要求MCU有MPU(内存保护单元),其它功能和 xTaskCreate() 一致。

                

  6、任务删除:删除后不会进入运行态,也不能再使用任务句柄,动态创建的任务删除后空间被释放,但如果任务中调用 pvPortMalloc()分配的则删除任务后调用vPortFree()释放,否则内存泄漏。

        vTaskDelete( TaskHandle_t xTaskToDelete )     参数:要删除的任务句柄   

  7、任务挂起和恢复

     (1)、挂起void vTaskSuspend( TaskHandle_t xTaskToSuspend)  参数:要挂起的任务句柄   参数为NULL表示挂起任务自己。特性:恢复后任务中变量保存的值不变。

     (2)、恢复:void vTaskResume( TaskHandle_t xTaskToResume)   参数:要恢复的任务句柄

     (3)、恢复(中断版本):BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume)   用于在中断服务函数中恢复一个任务。

                   返回值pdTRUE:优先级 >= 正在运行的任务,退出中断函数后必须进行一次上下文切换。pdFALSE:优先级 < 正在运行的任务,退出中断函数后不用进行上下文切换。  

五、任务查询、统计相关API(调试用)

  1、void vTaskList( char * pcWriteBuffer)   创建一个表格列出所有任务信息(任务名称,优先级,历史最低堆栈大小),参数:pcWriteBuffer :保存任务状态信息表的存储区,要大,可动态申请。

                   IDLE:空闲任务  Tmr:定时器任务

    Name: 创建任务的时候给任务分配的名字。
    State: 任务的壮态信息,B 是阻塞态,R 是就绪态,S 是挂起态,D 是删除态,X运行态。
    Priority:任务优先级。
    Stack: 任务堆栈的“高水位线”,就是堆栈历史最小剩余大小。
    Num: 任务编号,这个编号是唯一的,当多个任务使用同一个任务名的时候可以通过此编号来做区分。
  2、vTaskGetRunTimeStats()  提供了每个任务获取到CPU使用权的总时间。宏configGENERATE_RUN_TIME_STATS 和 configUSE_STATS_FORMATTING_FUNCTIONS 必须都为 1
    还需实现几个宏定义: portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),此宏用来初始化一个外设来 提供时间统计功能所需的时基,一般是定时器/计数器。此时基比 FreeRTOS的系统时钟高 10~20 倍就可以了。US级。
               portGET_RUN_TIME_COUNTER_VALUE()或者 portALT_GET_RUN_TIME_COUNTER_VALUE(Time),这两个宏实现其中一个就行了,这两个宏用于提供当前的时基的时间值。比较麻烦,参考开发手册。

      

六、内核控制函数(这些函数本质上是一个宏)

    

七、临界段代码保护

   1、简介:指必须完整运行,不能被打断的代码段,如有的外设初始化需要严格的时序,如模拟IIC等,进入临界段代码时需要关中断,当处理完临界段代码后再开中断。FreeRTOS 系统本身就有很多的临界段代码, 这些代码都加了临界段代码保护。

   2、任务级临界段使用方法:

    void taskcritical_test(void){             //任务级临界段代码保护使用方法

      while(1)  {

         taskENTER_CRITICAL();                 (1)    //进入临界区

           total_num+=0.01f;                    //中间的是临界区代码,一定要精简,因为进入临界区会关闭中断,这样会导致优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断     

           printf("total_num 的值为: %.4f\r\n",total_num);        //得不到及时响应(如:两个定时器中断,一个定时器优先级高于宏,高于宏的可以正常运行,低于宏的不能运行,退出时才可以运行)。

         taskEXIT_CRITICAL();                        (2)     //退出临界区

           vTaskDelay(1000);

      }

    

  3、中断级临界段使用方法:

      void TIM3_IRQHandler(void) {

        if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)  {    //溢出中断

          status_value=taskENTER_CRITICAL_FROM_ISR();      (1)        //进入临界区

            total_num+=1;

            printf("float_num 的值为: %d\r\n",total_num);

          taskEXIT_CRITICAL_FROM_ISR(status_value);          (2)  //退出临界区

         } 

        TIM_ClearITPendingBit(TIM3,TIM_IT_Update);   //清除中断标志位

      

    (1)、设置定时器 3 的抢占优先级为 4高于 configMAX_SYSCALL_INTERRUPT_PRIORITY, 因此在调用函数 portDISABLE_INTERRUPTS()关闭中断的时候定时器 3 是不会受影响的。

    (2)、设置定时器 5 的抢占优先级为 5等于 configMAX_SYSCALL_INTERRUPT_PRIORITY, 因此在调用函数 portDISABLE_INTERRUPTS()关闭中断的时候定时器 5 中断肯定会被关闭的。

八、消息队列

  1、简介:队列可在任务与任务、任务与中断间传递消息,队列中可以存储有限的、大小固定的数据项目。队列能保存的最大数据项目数量叫队列长度(如char类型的数据项大小为1),创建队列时会指定数据项大小和队列长度。

      FreeRTOS 中信号量也是依据队列实现的。

  2、特点:

   (1)、数据存储:通常采用先进先出(FIFO)的缓冲机制,也可以使用 LIFO 的存储缓冲,即后进先出。数据发送到队列中会导致数据拷贝,即值传递(虽浪费一点时间,但消息发送到队列后原始的数据缓冲区就可以删除或覆写)。如果数据量大也可以向队列中发送指向这个消息的地址(址传递:址传递的话消息内容必须一直保持可见性)。

   (2)、多任务访问:队列不属于某个指定的任务。每个任务都可以向队列发送和读取消息。

   (3)、出队阻塞:任务从一个队列中读取消息时可指定阻塞时间(即等待消息),时间单位为时钟节拍数。为0则不阻塞。

   (4)、入队阻塞:队列满的情况下发送肯定失败。

    

           如图,此时队列剩余长度就是三了,发送完成x变量可以再次使用

    任务 B 从队列中读取完成后可清除消息或不清除。如过清除则其他任务或中断就不能获取这个消息,而队列剩余大小就会加一。

  3、创建队列(下列函数中相同的参数和返回值就没有重复说明)

    (1)、QueueHandle_t  xQueueCreate (UBaseType_t  uxQueueLength, UBaseType_t  uxItemSize)     //返回值成功:返回队列句柄,失败:返回NULL。

                         参数 :uxQueueLength :队列长度,即队列的项目数   

                             uxItemSize队列中每个项目(消息)的长度,单位:字节   

    (2)、静态创建:QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize, uint8_t * pucQueueStorageBuffer, StaticQueue_t * pxQueueBuffer)    参数:前两个与上面函数一致 ,返回同上。

                         参数 :pucQueueStorageBuffer:指向自行分配的uint8_t 类型消息存储数组,内存大于等于(uxQueueLength * uxItemsSize)字节。

                             pxQueueBuffer: 此参数指向一个 StaticQueue_t 类型的变量,用来保存队列结构体。

    (3)、动态创建:QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )  

                         参数 : ucQueueType :队列类型,由于信号量也是通过队列实现,也是通过此函数创建,因此创建时要指定用途,即类型:六种

                                     queueQUEUE_TYPE_BASE                 普通的消息队列     a、xQueueCreate() 创建时内部默认选择的就是这个
                                     queueQUEUE_TYPE_SET               队列集
                                     queueQUEUE_TYPE_MUTEX              互斥信号量
                                     queueQUEUE_TYPE_COUNTING_SEMAPHORE   计数型信号量
                                     queueQUEUE_TYPE_BINARY_SEMAPHORE               二值信号量
                                     queueQUEUE_TYPE_RECURSIVE_MUTEX                 递归互斥信号量 函 数
    d、静态创建:QueueHandle_t xQueueGenericCreateStatic( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t * pucQueueStorage, StaticQueue_t * pxStaticQueue, const uint8_t ucQueueType )  
                                    参数:与上面的一致,比起b、静态创建多了一个类型。
  4、向队列发送消息  (下列函数中相同的参数和返回值就没有重复说明)
     创建好队列后就可以向队列发送消息了,FreeRTOS 提供了 8 个向队列发送消息的 API 函数:
    

      (1)、BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait)     //返回值pdPASS: 成功   errQUEUE_FULL: 失败,队列满。

                参数:xQueue: 队列句柄,指明向哪个队列的句柄发送数据。

                   pvItemToQueue:指向要发送的消息,发送时候会将这个消息拷贝到队列中。

                      xTicksToWait: 阻塞时间,当队列满时任务进入阻塞态等待,0:队列满立即返回。portMAX_DELAY:死等,但宏INCLUDE_vTaskSuspend必须为1。

    (2)、此函数才是真正干活的,其它所有的任务级入队函数最终都是调用此函数

        BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )  

                   参数 :xCopyPosition: 入队方式,有三种入队方式:queueSEND_TO_BACK:   后向入队

                                       queueSEND_TO_FRONT:  前向入队
                                       queueOVERWRITE:      覆写入队             
                                上面讲解的入队 API 函数就是通过此参数来决定采用哪种入队方式的。
    (3)、如下函数用于中断服务函数的入队函数:
      BaseType_t xQueueSendFromISR(QueueHandle_t xQueue, const void * pvItemToQueue, BaseType_t * pxHigherPriorityTaskWoken)

                 参数 :pxHigherPriorityTaskWoken: 标记退出此函数后是否进行任务切换,此变量值自动设置,用户只需提供一 个变量来保存。当此值为 pdTRUE 时:退出中断服务函数前必须进行一次任务切换。

                                这些函数都是在中断中,不在任务中,所以没有阻塞   

    (4)、此函数才是真正干活的,其它所有的中断级入队函数最终都是调用此函数:

        BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue, const void* pvItemToQueue, BaseType_t* pxHigherPriorityTaskWoken, BaseType_t xCopyPosition)         

                 参数 :xCopyPosition: 入队方式,有三种入队方式:queueSEND_TO_BACK:    后向入队
                                         queueSEND_TO_FRONT: 前向入队
                                         queueOVERWRITE:           覆写入队
  5、队列上锁和解锁(不详解)
    上锁和解锁是两个API 函数:prvLockQueue() prvUnlockQueue()   本质上都是宏。
  6、从队列读取消息 
     

      (1)、BaseType_t xQueueReceive(QueueHandle_t xQueue, void * pvBuffer, TickType_t xTicksToWait)      返回: 读取成功:pdTRUE   读取失败:pdFALSE

        详解:读取成功会将队列中的这条数据删除,本质是个宏,真正执行的函数是 xQueueGenericReceive(),读取消息时采用拷贝方式,需要提供一个数组或缓冲区来保存,读取长度为创建队列时每个队列项目的长度。

                    参数:xQueue:         队列句柄,读取哪个队列数据     
                       pvBuffer:        保存数据的缓冲区       
                       xTicksToWait: 阻塞时间(等待队列有数据的最大时间,为0队列空立即返回)为 portMAX_DELAY 死等,但宏INCLUDE_vTaskSuspend 必须为 1
       (2)、BaseType_t xQueuePeek(QueueHandle_t xQueue, void * pvBuffer, TickType_t xTicksToWait)    与a函数区别为:读取成功不会将队列中的这条数据删除。

       (3)、 BaseType_t xQueueGenericReceive(QueueHandle_t xQueue, void* pvBuffer, TickType_t xTicksToWait ,BaseType_t xJustPeek)    //此函数才是真正干活的,如上a、b函数最终都是调用它:

                    参数:标记读取成功后是否删除队列项,为pdTRUE时不删除,a函数参数,pdFALSE删除,b函数参数。

       (4)、此函数是a函数的中断版本:BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue, void* pvBuffer, BaseType_t * pxTaskWoken

                     参数:中断没有阻塞概念,pxTaskWoken:标记此函数退出后是否进行任务切换,此变量自动设置,提供一个变量保存这个值即可,值为pdTRUE 时需要切换

       (5)、此函数是b函数的中断版本:BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, void * pvBuffer)  

  7、获取队列消息数量和剩余大小uxQueueSpacesAvailable()获取队列剩余大小;uxQueueMessagesWaiting()获取队列当前消息数量;

 

九、信号量

   1、简介:信号量一般用来进行资源管理和任务同步,信号量分为二值信号量、计数型信号量、互斥信号量和递归互斥信号量。

  二值信号量

   2、二值信号量:(1)、二值信号量其实就是一个只有一个队列项的队列,此队列要么是满的,要么是空 的,这不正好就是二值吗?

              通常用于互斥访问或同步,与互斥信号量类似,区别在于互斥信号量拥有优先级继承机制,二值信号量没有。因此更适合用于任务与任务或任务与中断间的同步。而互斥信号量适用于简单的互斥访问。

          (2)、应用场景:网络中,一般用一个任务去轮询查询 MCU 的 ETH(如 STM32 的以太网MAC)外设是否有数据,轮询即浪费CPU资源,又阻止了其他任务运行。理想是无数据时阻塞把CPU让出。如:使用二值信号量,任务通过获取信号量判断是否有数据,没有则阻塞,(一般通过中断判断是否有数据,如STM32的MAC的DMA中断)中断服务函数释放信号量,任务接收信号量。也可使用队列替代,在中断中发送数据到队列,队列无效任务阻塞,直至有数据。

  3、创建二值信号量:和队列一样,要想使用就要先创建二值信号量:

       

    (1)、SemaphoreHandle_t xSemaphoreCreateBinary( void )    此函数创建时 RAM 由 FreeRTOS 内存管理部分来动态分配。创建好的二值信号量默认为空,即使用函数 xSemaphoreTake()获取不到,相当于宏。

    (2)、如下函数创建信号量所需要的RAM由用户分配,相当于宏,具体是调用 xQueueGenericCreateStatic() 函数实现。

      SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )   //返回:创建失败: NULL,  成功:二值信号量句柄

               参数:pxSemaphoreBuffer :此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体

  4、优先级翻转

 

    使用二值信号量时常见的一个问题——优先级翻转,在可剥夺内核中比较常见,但在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果。如下示意图:

      

      如上, H 优先级实际上降到了 L 的水平。因为 H 要一直等待直到 L 释放其占用的共享资源。由于 M 剥夺了 L 的 CPU 使用权,使得 H 情况更糟,就相当于 M 的优先级高于 H,导致优先级翻转。

     注意:先级翻转只会在使用共享资源时发生,因此在任务设计中应该尽可能避免共享资源的竞争。合理使用优先级继承和优先级屏蔽也能避免此问题,如互斥信号量。  

  互斥信号量
    1、简介:互斥信号量其实就是一个拥有优先级继承的二值信号量,适合用于需要互斥访问的应用中。相当于钥匙,当任务要使用资源时须先获得这个钥匙,使用完资源后必须归还,这样其他任务才能拿钥匙去使用资源。
    2、使用场景:当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的 “优先级翻转”的影响降到最低,但并不能完全的消除优先级翻转。
    3、不能用于中断:(1)、有优先级继承机制,所以只能用于任务;
              (2)、中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
    4、创建互斥信号量:
      
      (1)、SemaphoreHandle_t xSemaphoreCreateMutex( void ) //所需内存动态分配,本质为宏,真正完成创建的是 xQueueCreateMutex()。   返回:失败:NULL     成功:句柄
      (2)、SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer ) //内存用户分配,本质为宏,真正由 xQueueCreateMutexStatic ()完成 。 返回同上
                          参数:pxMutexBuffer:此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体
      当 Queue_t 用于表示队列时pcHead 和 pcTail 指向队列存储区域,用于互斥信号量时将 pcHead 指向NULL 来表示 pcTail 保存着互斥队列的所有者,pxMutexHolder 指向有互斥信号量的任务控制块。

  递归互斥信号量

  1、简介:特殊互斥信号量,已经获取了互斥信号量的任务就不能再次获取这个互斥信号量,但递归互斥信号量中可以再次获取,且次数不限。

   2、创建:(这里不详解了)

      

  计数型信号量

  1、简介:二值信号量相当于长度为 1 的队列,那么 计数型信号量就是长度大于 1 的队列。同二值信号量一样,用户不需要关心队列中存储了什么 数据,只需关心队列是否为空即可。

   2、如何使用:事件发生时在处理函数中释放信号量(增加信号量的计数值),其他任务获取信号量(信号量计数值减一,值为队列结构体变量uxMessagesWaiting)处理事件。值为 0 说明没有资源了 ,任务使用完资源后要释放信号量,创建的信号量初值为 0。

   3、创建计数型信号量:

       

     (1)、SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount )  //所需内存动态分配,本质为宏,真正完成创建的是 xQueueCreateCountingSemaphore()  。

                            参数:uxMaxCount:   最大计数值,当信号量值等于此值的时候释放信号量就会失败
                               uxInitialCount: 初始值
                            返回:失败:NULL   成功:句柄

     (2)、SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t * pxSemaphoreBuffer )  //返回:失败:NULL     成功:句柄

        内存用户分配,相当于宏,执行函数:xQueueCreateCountingSemaphoreStatic()                               

                                 参数:pxSemaphoreBuffer:指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体 

  释放信号量(同队列一样,不管什么类型信号量,释放时也分为任务级和中断级)

          

       1、BaseType_t xSemaphoreGivexSemaphore )    //参数:信号量句柄   返回:成功:pdPASS     失败:errQUEUE_FULL

       2、如下函数用于中断中释放信号量,只能释放二值信号量和计数型信号量,不能用来在中断中释放互斥信号量。

       BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t * pxHigherPriorityTaskWoken)    //返回:成功:pdPASS     失败:errQUEUE_FULL

                         参数: xSemaphore :                       信号量句柄

                             pxHigherPriorityTaskWoken:退出后是否任务切换,此变量自动设置,提供一个变量保存这个值即可,为 pdTRUE 时需要切换。

   获取信号量(不管什么类型信号量,都使用如下函数获取)

      

      1、BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xBlockTime)     //返回:pdTRUE:获取信号量成功    pdFALSE:超时,获取失败

                      参数:xSemaphore要获取的信号量句柄

                         xBlockTime:     阻塞时间  

         2、BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, BaseType_t * pxHigherPriorityTaskWoken)  //返回:pdPASS: 获取信号量成功   pdFALSE: 获取失败

                        参数: pxHigherPriorityTaskWoken: 标记退出函数后是否任务切换,此变量自动设置,提供一个变量保存这个值即可,为 pdTRUE 时需要切换。

十、事件标志组(不详解)

  1、简介:使用信号量只能与单个的事件或任务进行同步。有时某个任务可能需要与多个事件或任务进行同步,为此FreeRTOS 提供了事件标志组。

   2、创建事件标志组:

      

   3、设置事件位:

      

   4、 获取事件标志组值:

      

 十一、任务通知

  从 v8.2.0 版本开始,FreeRTOS 新增了任务通知(Task Notifictions)功能,可以用来代替信号量、消息队列、事件标志组等这些东西。且效率更高可选功能,使用时将宏 configUSE_TASK_NOTIFICATIONS 定义为 1。

  1、简介:任务通知是一个事件,假如某个任务通知的接收任务因为等待任务通知而阻塞的话,向这个接收任务发送任务通知后就会解除这个任务的阻塞状态,也可以更新接收任务的任务通知值:

        ● 不覆盖接收任务的通知值(如果上次发送给接收任务的通知还没被处理)。

        ● 覆盖接收任务的通知值。

        ● 更新接收任务通知值的一个或多个 bit。

        ● 增加接收任务的通知值。

  2、特点:●  只能有一个接收任务,其实大多数的应用都是这种情况。

        ●  接收任务可以因为接收任务通知失败而进入阻塞态,但是发送任务不会因为任务通知发送失败而阻塞。

        ● 只能发送32位的数据值(可以模拟一个轻量级消息邮箱而不是轻量级消息队列,任务通知值就是消息邮箱的值)。

  3、发送任务通知:

      

      (1)、BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction )  //此函数是个宏,真正执行的函数 xTaskGenericNotify()

                    参数:xTaskToNotify: 任务句柄,指定任务通知是发送给哪个任务的。

                       ulValue: 任务通知值。
                       eAction: 任务通知更新的方法,eNotifyAction 是个枚举类型,在文件 task.h 中有如下 定义:
                                         typedef enum {
                                                eNoAction = 0,
                                                eSetBits,         //更新指定的 bit
                                                eIncrement,                 //通知值加一
                                                eSetValueWithOverwrite,  //覆写的方式更新通知值
                                                eSetValueWithoutOverwrite //不覆写通知值
                                               } eNotifyAction;  
                   返回值:pdFAIL: 当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有 更新成功就返回 pdFAIL。
                       pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS。
      (2)、任务通知通用发送函数(任务级):xTaskNotify()、xTaskNotifyGive() 、 xTaskNotifyAndQuery()都是调用如下函数:
          BaseType_t xTaskGenericNotify(   TaskHandle_t xTaskToNotify,          //任务句柄
                              uint32_t ulValue,              //任务通知值
                           eNotifyAction eAction,             //任务通知更新方式
                           uint32_t * pulPreviousNotificationValue )//保存更新前的 //任务通知值
      (3)、任务通知发送函数(中断级):三个,xTaskNotifyFromISR() 、xTaskNotifyAndQueryFromISR()、 vTaskNotifyGiveFromISR() ,前两个最终调用:xTaskGenericNotifyFromISR():
          BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify,       //返回同上
                              uint32_t ulValue,
                              eNotifyAction eAction,
                              uint32_t * pulPreviousNotificationValue,
                              BaseType_t * pxHigherPriorityTaskWoken //是否任务切换,此变量自动设置,提供一个变量保存这个值即可,pdTRUE 退出中断服务函数前进行一次任务切换

   4、获取任务通知:

      

      (1)、uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait

          参数:xClearCountOnExit: 参数为 pdFALSE:在退出函数时任务通知值减一,类似计数型信号量。参数为 pdTRUE:在退出函数时任务任务通知值清零,类似二值信号量。

             xTickToWait: 阻塞时间。
         返回值:任何值 : 任务通知值减少或者清零之前的值。
      (2)、BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t * pulNotificationValue, TickType_t xTicksToWait )
         参数:ulBitsToClearOnEntry:没有接收到任务通知时将任务通知值与此参数的取反值按位与运算,参数为 0xffffffff 或 ULONG_MAX 时将任务通知值清零。
            ulBitsToClearOnExit:如果接收到了任务通知,做完相应处理退出函数前将通知值与此参数的取反值按位与运算,参数为 0xffffffff 或ULONG_MAX 时将通知值清零。
            pulNotificationValue:此参数用来保存任务通知值。
            xTickToWait: 阻塞时间。
        返回值:pdTRUE: 任务通知获取成功
            pdFALSE: 获取失败
十二、低功耗Tickless模式
十二、内存管理
十二、空闲任务

十、软件定时器

  FreeRTOS 提供了软件定时器功能,不过精度没有MCU 自带硬件定时器高,但 对于精度要求不高的周期性处理任务来说够了。(不详解)

 

 

 

 

 

 

 

 

 

 

  

 

posted @ 2023-06-06 20:07  耿通宇  阅读(482)  评论(0编辑  收藏  举报