FreeRTOS总结

堆内存管理

  • 有五种内存分配方式
  • 常用的为heap_4方式

任务管理

  • 任务不能以任何方式实现函数返回,可以在任务的死循环外加上xTaskDelete( )

  • 创建任务:xTaskCreate( )

    1. 任务堆栈的大小,空闲任务的最小是configMINIMAL_STACK_SIZE,其他任务不能比他小
    2. 任务优先级,0~configMAX_PRIORITIES-1 ,数字越小,优先级越低
      1. configMAX_PRIORITIES尽量保证必要最小值 ,越大消耗RAM越多
  • 滴答中断:一般调度器都是基于时间片的抢占系统

    • 滴答中断频率:configTICK_RATE_HZ,要与滴答定时器的频率匹配
    • 典型值为100HZ,即10ms的时间片,但是要注意vTaskDelay函数的延时
    • pdMS_TO_TICKS,使得100HZ的滴答只能延时10ms的整数倍时间,所以我感觉1000HZ也还可以
  • 任务状态

    • 阻塞状态:
      • 时间性事件:例如延时vTaskDelay
      • 同步事件:进入阻塞等待数据到达,例如:队列、二进制和计数量、互斥量、事件组、任务通知等。如果设置10ms,10ms内数据到达,或者超过10ms数据没到达,都会离开阻塞状态
    • 暂停状态:进入暂停唯一方法就是vTaskSuspend,退出方法就是vTaskResume
    • 就绪状态:准备好运行,但是还没运行
  • 阻塞延时:vTaskDelay和vTaskDelayUntil

  • 空闲任务

    • 调度器启动时会自动创建空闲任务,当其他任务都阻塞时也有空闲任务时刻运行
    • 空闲任务钩子:vApplicationIdleHook
      • ***功能:***执行低优先级、后台或连续处理函数;测量空闲处理能力;将处理器置于低功耗模式
      • **注意:**钩子函数绝对不能阻塞和暂停;如果使用了vTaskDelete,空闲任务负责清理被删除任务的资源,还要确保空闲任务不会被饿死
  • 任务优先级:设置任务优先级:vTaskPrioritySet,获取任务优先级:vTaskPriorityGet

  • **任务删除:**vTaskDelete

  • **线程本地存储:**vTaskSetThreadLocalStoragePointer和pvTaskGetThreadLocalStoragePointer,根据任务数组的索引设置和读取数据

  • 调度算法*:***

    • 时间片优先的抢占式调度:用的最多,但注意资源不能被多个任务同时访问,否则可能破坏资源
      • 一个任务优先级高于运行状态任务会抢占运行状态任务
      • 同等优先级任务使用时间片共享处理时间
      • 自动会执行最高优先级就绪态的任务
    • 不含时间片的抢占式调度
      • 调度器转换新任务只有两种可能:高优先级任务就绪抢占,运行状态任务阻塞或者暂停
    • 协同调度
      • 只有运行态任务阻塞或者使用taskYIELD进行任务让步,才会发生切换

队列管理

  • FreeRTOS通过复制实现队列的方式,优点是可以直接发送栈变量到队列,不需要先分配缓冲区存放数据,发送和接收任务完全脱钩,复制实现队列不妨碍引用实现队列。

  • **多任务访问:**队列本身就是对象,可以被任意个任务或者ISR访问

  • 队列读取、写入阻塞:

    • 当任务从队列读取或者写入数据时,可以选择性阻塞时间,当阻塞状态接收到数据或者阻塞时间超时,任务都会转移到就绪状态。
    • 如果有多个任务等待队列数据,只会有一个任务会解除阻塞,最高优先级先解除,相同优先级则等待时间最长的先解除
  • **队列创建:**xQueueCreate

  • **队尾插入:**xQueueSendToBack

  • **队首插入:**xQueueSendToFront

  • **队列接收:**xQueueReceive

  • **数据量查询:**uxQueueMessagesWaiting

  • 从多个来源接收数据:

    • 简单:可以使用队列传输结构体,结构体包含来源和数据
  • 处理大数据和可变大小数据:最好使用队列传输数据的指针

    • 被指向的RAM的所有者必须明确定义
    • 被指向的RAM保持有效
  • **队列集:**能够从多个来源接收数据,但比结构体方法更繁琐效率更低

    • 创建队列集
    • 队列添加队列集
    • 队列集读取数据
  • **队列集创建:**xQueueCreateSet

  • **队列添加队列集:**xQueueAddToSet

  • **队列集读取队列:**xQueueSelectFromSet

  • **队列集移除队列:**xQueueRemoveFromSet

  • ***队列创建邮箱:***邮箱用来指长度为1的队列

    • 队列将数据从一个任务发送到另一个任务,发送者放置数据,接收者读取后移除数据
    • 邮箱将数据从一个任务发送到另一个任务,发送者放置新数据并覆盖原数据,接收者读取数据并不移除
  • **发送并覆盖邮箱数据:**xQueueOverwrite

  • **接收并不移除数据:**xQueuePeek

软件定时器

  • **软件定时器回调函数形式:**void ATimerCallback(TimerHandle_t xTimer)
    • 回调函数应该短小精悍,不能阻塞
    • 可以多个定时器使用同一回调函数,但是需要在函数里判断定时器句柄
    • 也可以每个定时器使用不同回调函数
  • **定时器周期:**指从启动软件定时器到执行软件定时器回调函数之间的时间
    • 一次性定时器:只执行一次回调函数
    • 自动重载:到期后自动重启,回调函数周期执行
    • 到期时间是从发送“启动定时器”命令到定时器命令队列的时间开始计算,而不是守护任务从命令队列收到“启动定时器”命令的时间开始计算的
  • 定时器状态:
    • 休眠:休眠的定时器存在,可以通过句柄调用,回调函数不会执行
      • xTimerCreate创建后就是休眠状态
      • 运行转休眠,
    • 运行:到规定的周期时间,自动执行回调函数
      • 调用xTimerStart,xTimerReset,xTimerChangePeriod都可以转换为运行态
  • RTOS守护任务
    • 在调度器启动时自动创建,优先级和栈大小由常量设置
    • 软件定时器API将命令从调用函数发送到守护任务,在守护任务的“定时器命令队列”上
    • 守护任务的调度:只有当守护任务为能够运行的最高优先级时,才会处理命令和执行定时器回调函数
    • 发送到定时器命令队列的命令包含时间戳,确保启动的定时器是从发送“启动定时器”命令到定时器命令队列的时间开始计算
  • **创建软件定时器:**xTimerCreate
  • 启动软件定时器:xTimerStart
  • **停止定时器:**xTimerStop
  • **删除定时器:**xTimerDelete
  • **定时器ID:**是一个标签值,可以随意使用,创建软件定时器时,会给ID分配初始值
    • vTimerSetTimerID,创建定时器时,会为软件定时器分配一个标识符 (ID), 此函数更改此标识符。
    • pvTimerGetTimerID,返回分配给软件计时器的 ID
  • **更改软件定时器周期:**xTimerChangePeriod
  • **重置软件定时器:**xTimerReset

中断管理

  • 任务是软件功能,和运行的硬件无关。中断是硬件功能,最低优先级的中断可以抢断最高优先级任务,反之则不可以。

  • 专门用于ISR的API函数,在名称后添加FromISR,不要在ISR中调用没有后缀的函数

  • **使用单独中断安全函数的缺点:**有需要在ISR中调用第三方函数,但第三方函数用了正常的FreeRTOS API函数

    • 将中断处理推迟给任务,就可以在任务中调用
    • 如果有的话,将API改为FromISR结尾的函数
  • xHigherPriorityTaskWoken参数

    • 中断结束时,会回到打断处继续执行,但如果中断期间,有了更高优先级任务就绪,就应该执行更高优先级任务,而不是返回原点继续执行。如果不管的话,更高优先级任务将保持就绪状态,直到不在中断时的下一次调度器运行。

      • 切换更高优先级任务不会在中断内自动发生,设置了xHigherPriorityTaskWoken变量通知应该上下文切换
      • FromISR结尾的函数有xHigherPriorityTaskWoken变量,用于此目的,taskYIELD函数是在任务中请求任务切换的函数。
      • portYIELD_FROM_ISR是taskYIELD函数的中断版本
    • 示例代码如下:

      void vTimerISR( void * pvParameters )
      {
      	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
          xHigherPriorityTaskWoken = pdFALSE;
      
      
          xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
      
          /* Yield if xHigherPriorityTaskWoken is true.  The 
          actual macro used here is port specific. */
          portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
      }
      
  • 推迟中断处理

    • 中断所需的其他处理工作通常可以在任务中运行,所以可以将中断的工作推迟到任务
    • 以下情况强烈建议推迟到任务
      • 中断所需的处理并不简单
      • 任务处理能方便执行ISR内部无法执行的操作
      • 中断处理不确定,不知道处理工作需要多长时间
  • 二进制信号量:能够有效使任务和中断同步,可以认为使长度为1的队列

    • 设置推迟任务的优先级确保,该任务可以抢占系统其他任务

    • 在ISR实现中调用portYIELD_FROM_ISR,确保ISR返回到推迟中断处理任务

    • 创建二值信号量: xSemaphoreCreateBinary

    • **释放信号量:**xSemaphoreGive

    • **获取信号量:**xSemaphoreTake

  • **计数信号量:**可用于资源管理,计数事件

    • **创建计数信号量:**xSemaphoreCreateCounting
    • **释放信号量:**xSemaphoreGive
    • **获取信号量:**xSemaphoreTake
  • **推迟工作到守护任务:**xTimerPendFunctionCallFromISR()

  • 中断程序使用队列:

    • 使用xQueueSendToFront和xQueueSendToBack的ISR版本
    • 数据到达频率很高,队列效率不高
    • 更高效且适合生产代码方法:
      • 直接内存访问DMA
      • 接收到的字符复制到线程安全的RAM缓冲区
      • 直接在ISR内处理接收到的字符,队列只发送处理结果
  • 中断嵌套

    • 数字优先级和逻辑优先级:数字是分配给中断优先级的数字,逻辑是描述该中断相较于其他中断的优先级
    • configMAX_SYSCALL_INTERRUPT_PRIORITYD,低于此优先级中断可以被管理
    • configKERNEL_INTERRUPT_PRIORITY,设置滴答中断的优先级,最低优先级
    • 对时间精度要求非常严格的功能,可以考虑使用高于configMAX_SYSCALL_INTERRUPT_PRIORITYD的优先级
    • 必须始终configKERNEL_INTERRUPT_PRIORITY设置为尽可能最低的中断优先级
    • configMAX_SYSCALL_INTERRUPT_PRIORITYD的数值要注意,如cortex-m不允许设置为0

资源管理

  • 资源访问导致数据损坏的例子:A任务打印hello,A打印到he,B任务抢占A,打印abort,最终结果就是heabortllo。这是不正确的。

  • **函数重入:**函数可以安全的在多个任务调用,或既可以从中断也可以在任务使用,那么函数就是重入,也称作线程安全。每个任务都维护自己的栈和硬件寄存器值,除了访问栈上的数据或保存至寄存器的数据外,不访问其他数据,就是重入函数。

  • **相互排斥:**任务之间共享的资源进行访问时,必须使用相互排斥进行管理。使资源不被共享,被单一程序访问。

  • **临界区:**taskENTER_CRITICAL和taskEXIT_CRITICAL成对使用。FROM_ISR版本的taskENTER_CRITICAL会有返回值,返回值要传递给taskEXIT_CRITICAL。

    • 工作原理是禁用中断,即禁用所有可以管理的中断
    • 临界区必须短小,否则对中断响应产生不利响应
    • 临界区的嵌套是安全的,Freertos中两个宏定义是改变中断使能状态唯一合法方式
  • **暂停调度器:**如果临界区代码过长,可以使用暂停调度器,但恢复调度器操作较慢

    • vTaskSuspendAll,暂停调度器
    • xTaskrResumeAll,恢复调度器
  • **互斥量:**特殊的二进制信号量,获取后必须归还,归还后别的任务才能获取,否则可能死锁。

    • 创建互斥量:xSemaphoreCreateMutex
    • 获取互斥量:xSemaphoreTake
    • 释放互斥量:xSemaphoreGive
  • 互斥量和优先级密切相关的概念

    • **优先级反转:**优先级高的任务等待优先级低的任务释放互斥量,仔细思考资源访问尽量避免。
    • **优先级继承:**为了解决互斥量产生的优先级反转,rtos会将持有者的优先级提高至与等待互斥量的任务优先级相同,即继承优先级,归还后优先级恢复原状。不能在中断中使用互斥量
    • **死锁:**两个任务都在等待对方持有的资源而无法继续,充分考虑系统,识别并消除。
    • **递归互斥量:**当任务获取了互斥量,但执行过程中有函数继续获取互斥量,导致自己锁死自己。
      • 使用递归互斥量来避免,可以被同一任务多次获取,接着执行调用归还后,才能归还。类似成对使用。
      • 创建递归互斥量:xSemaphoreCreateRecursiveMutex
      • 获取递归互斥量:xSemaphoreTakeRecursive
      • 释放递归互斥量:xSemaphoreGiveRecursive
  • 互斥量和任务调度

    • 不同优先级任务获取同一互斥量,优先级高的任务先进入运行状态。

    • 如果A,B任务优先级相同,A先获取了互斥量,等到时间片结束切换B,接着回A,释放互斥量,再等到时间片结束,B才能运行

      • 推荐的更平等处理时间的例子

      • // 记录时间
        Xtime = xTaskGetTickCount();
        // 释放互斥量
        xSemaphoreGive
        // 滴答计数变化时,调用切换
        if(xTaskGetTickCount() != Xtime)
        {
        	taskYIELD();
        }
        
  • 守门人任务:指对任务拥有唯一所有权的任务,其他需要访问资源的任务只能通过守门人任务。

    • 方法简单,基本没有反转和死锁风险

事件组

  • 事件组的特性:

    • 允许任务阻塞状态下等待一个或多个事件组合发生
    • 事件组会解除所有在等待同一个事件或事件组合的任务的阻塞
    • 通常可以用一个事件组代替多个二进制信号量
  • 事件组标志是一个布尔值,每一位都代表一个事件是否发生

    • configUSE_16_BIT_TICKS为1,则事件组为16位,但其中的高八位保留,即包含8个可用事件位
    • configUSE_16_BIT_TICKS为0,则事件组为32位,但其中的高八位保留,即包含24个可用事件位
  • **事件组创建:**xEventGroupCreate

  • **事件组位设置:**xEventGroupSetBits

  • **读取事件组位:**xEventGroupWaitBits

  • **多任务事件组相互同步:**xEventGroupSync

任务通知

  • 任务通知允许任务之间交互,不要单独的通信对象。通过任务通知,任务或ISR可以直接向接收任务发送事件。

  • 通常用来替代二值信号量

  • 优势与劣势:

    • 任务通知比队列、信号量等操作快得多
    • 所需RAM也要小的多
    • 无法向ISR发送事件和数据
    • 无法启用多个接收任务
    • 无法缓冲多个数据项
    • 无法向多个任务广播
    • 无法在阻塞状态等待发送完成
  • **发送任务通知:**xTaskNotify

  • **发送任务通知的简单版:**xTaskNotifyGive

  • **接收任务通知:**xTaskNotifyWait

  • **接收任务通知的简单版:**ulTaskNotifyTake

低功耗支持

  • **与节能有关的宏:**portSUPPRESS_TICKS_AND_SLEEP

开发者支持

  • **断言:**cofigASSERT
  • **任务状态信息快照:**uxTaskGetSystemState,比较重要的信息主要有
    • 到目前为止分配给任务的总运行时间
    • 任务剩余的最小堆栈空间量
  • **提供可读的任务信息ASCII表格:**vTaskList
    • 很耗费CPU,仅用于调试阶段
  • **将运行时统计信息格式化为可读表格:**vTaskGetRunTimeStats
    • 很耗费CPU,仅用于调试阶段
    • 还需要实现一个计数器

故障排除

  • 演示工程添加任务导致演示工程崩溃
    • 延时工程的堆空间很精确,没有足够的堆空间
  • 中断使用API导致应用崩溃
    • 使用FROM_ISR结尾的API函数
  • 有时应用程序在中断服务程序中崩溃
    • 中断是否导致了栈溢出
  • 调度器启动第一个任务时崩溃
    • 有些处理器启动调度器哦i之前必须处于特权模式
    • 确保移植没问题,中断处理程序没问题
  • 应用程序在调度器启动前崩溃
    • 启动前不允许上下文切换
  • 调度器暂停或者临界区调用API函数,导致应用程序崩溃
    • 调度器暂停不能调用API函数
    • 临界区内不能调用API函数

你可能感兴趣的:(单片机)