当前位置 博文首页 > 文章内容

    笔记:FreeRTOS 要点总结

    作者: 栏目:未分类 时间:2020-07-03 9:24:22

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    一、基本配置

    1.1 数据类型

    FreeRTOS 使用的数据类型主要分为 stdint.h 文件中定义的和自己定义的两种
    FreeRTOS 主要自定义了以下四种数据类型:
    TickType_t----32 位无符号数( 32位MCU,配置configUSE_16_BIT_TICKS = 0)
    BaseType_t----32 位有符号数(32位MCU)
    UBaseType_t---32 位无符号数(BaseType_t类型无符号版本)
    StackType_t----32 位变量(栈变量数据类型定义,32位MCU)

    1.2 代码风格

    变量

    • uint32_t 定义的变量都加上前缀 ul。u 代表 unsigned 无符号,l 代表 long 长整型。
    • uint16_t 定义的变量都加上前缀 us。u 代表 unsigned 无符号,s 代表 short 短整型。
    • uint8_t 定义的变量都加上前缀 uc。u 代表 unsigned 无符号,c 代表 char 字符型。
    • stdint.h 文件中未定义的变量类型,在定义变量时需要加上前缀 x,比如 BaseType_t 和
      TickType_t 定义的变量。
    • stdint.h 文件中未定义的无符号变量类型,在定义变量时要加上前缀 u,比如 UBaseType_t 定义
      的变量要加上前缀 ux。
    • size_t 定义的变量也要加上前缀 ux。
    • 枚举变量会加上前缀 e。
    • 指针变量会加上前缀 p,比如 uint16_t 定义的指针变量会加上前缀 pus。
    • 根据 MISRA 代码规则,char 定义的变量只能用于 ASCII 字符,前缀使用 c。
    • 根据 MISRA 代码规则,char *定义的指针变量只能用于 ASCII 字符串,前缀使用 pc。

    函数

    • 加上了 static 声明的函数,定义时要加上前缀 prv,这个是单词 private 的缩写。
    • 带有返回值的函数,根据返回值的数据类型,加上相应的前缀,如果没有返回值,即 void 类型
      ,函数的前缀加上字母 v。
    • 根据文件名,文件中相应的函数定义时也将文件名加到函数命名中,比如 tasks.c 文件中函数
      vTaskDelete,函数中的 task 就是文件名中的 task。

    宏定义

    • 根据宏定义所在的文件,文件中的宏定义声明时也将文件名加到宏定义中,比如宏定义
      configUSE_PREEMPTION 是定义在文件 FreeRTOSConfig.h 里面。宏定义中的 config 就是文
      件名中的 config。另外注意,前缀要小写。
    • 除了前缀,其余部分全部大写,同时用下划线分开。

    排版和注释

    • 缩进
      Tab 制表符用于缩进,Tab 一次缩进 4 个字符空间。
    • 注释
      FreeRTOS 中注释不会超过 80 个字符宽度,除非对函数的参数进行注释时。源码中主要是采用/* */
      的形式进行注释,不采用 C++中的双斜杠风格来注释。

    1.3 关键配置

    configCPU_CLOCK_HZ---配置MCU主频,单位Hz
    configTICK_RATE_HZ---配置系统时钟节拍数,单位 Hz
    configMAX_PRIORITIES---配置用户可用最大优先级数,0~configMAX_PRIORITIES-1
    configTOTAL_HEAP_SIZE---配置堆大小
    configUSE_TASK_NOTIFICATIONS---配置任务通知
    configUSE_TIME_SLICING---配置时间片轮转调度
    configLIBRARY_LOWEST_INTERRUPT_PRIORITY---受RTOS管理的最小中断优先级
    configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY---受RTOS管理的最大中断优先级
    

    二、任务管理

    2.1 任务栈

    #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
    

    1,局部变量,函数调用时的现场保护和返回地址,函数的形参,进入中断函数前和中断嵌套等都需要栈空间,栈空间定义小了会造成系统崩溃
    2,实际分配的栈大小可以在最小栈需求的基础上乘以一个安全系数,一般取 1.5-2
    3,栈生长方向从高地址向低地址生长(M4 和 M3 是这种方式)

    2.2 系统栈

    任务栈不使用系统栈控件,中断函数和中断嵌套使用
    Cortex-M3 和 M4 内核具有双堆栈指针,MSP 主堆栈指针和 PSP 进程堆栈指针
    在 FreeRTOS 操作系统中,主堆栈指针 MSP 是给系统栈空间使用的,进
    程堆栈指针 PSP 是给任务栈使用的
    对于 Cortex-M3 内核和未使用 FPU(浮点运算单元)功能的 Cortex-M4 内核,需要64字节
    对于具有 FPU(浮点运算单元)功能的 Cortex-M4 内核,需要200字节

    2.3 任务优先级

    1. FreeRTOS 中任务的最高优先级是通过 FreeRTOSConfig.h 文件中的 configMAX_PRIORITIES 进行配置的,用户实际可以使用的优先级范围是 0 到 configMAX_PRIORITIES – 1
    2. 用户配置任务的优先级数值越小,那么此任务的优先级越低,空闲任务的优先级是 0
    3. 建议用户配置宏定义 configMAX_PRIORITIES 的最大值不要超过 32
    4. FreeRTOS 中处于运行状态的任务永远是当前能够运行的最高优先级任务

    2.3.1 任务优先级分配

    1. IRQ 任务:IRQ 任务是指通过中断服务程序进行触发的任务,此类任务应该设置为所有任务里面优先级最高的
    2. 高优先级后台任务:比如按键检测,触摸检测,USB 消息处理,串口消息处理等,都可以归为这一类任务 低优先级的时间片调度任务:比如 emWin 的界面显示,LED 数码管的显示等不需要实时执行的都可以归为这一类任务
      实际应用中用户不必拘泥于将这些任务都设置为优先级 1 的同优先级任务,可以设置多个优先级,只需注意这类任务不需要高实时性
    3. 空闲任务:空闲任务是系统任务
    4. 特别注意:IRQ 任务和高优先级任务必须设置为阻塞式(调用消息等待或者延迟等函数即可), 只有这样,高优先级任务才会释放 CPU 的使用权,,从而低优先级任务才有机会得到执行

    2.3.2 中断优先级与任务优先级

    • 中断的优先级永远高于任何任务的优先级,即任务在执行的过程中,中断来了就开始执行中断服务程序
    • 对于 STM32F103,F407 和 F429 来说,中断优先级的数值越小,优先级越高。而 FreeRTOS的任务优先级是,任务优先级数值越小,任务优先级越低

    2.3.2 任务调度

    2.2.3 相关API

    vTaskStartScheduler-任务启动

    • 空闲任务和可选的定时器任务是在调用这个函数后自动创建
    • 正常情况下这个函数是不会返回的,运行到这里极有可能是用于定时器任务或者空闲任务的 heap 空 间不足造成创建失败,此时需要加大 FreeRTOSConfig.h 文件中定义的 heap 大小
    • 如果在此类任务函数里面填的任务 ID 是 NULL,即数值 0 的话,那么执行生效的就是当前正在执行的任务

    系统框架写法

    int main(void)
    {
        /* 初始化外设 */
    
        xTaskCreate();          /* 创建开始任务 */  
    
        vTaskStartScheduler();  /* 开启任务调度 */
    }
    
    void start_task(void *pvParameters)
    {
        taskENTER_CRITICAL();           /*  */进入临界区
    
        xTaskCreate();  /* 创建任务  1 */
        xTaskCreate();  /* 创建任务  2 */
        xTaskCreate();  /* 创建任务  3 */
    
        vTaskDelete(StartTask_Handler); /* 删除开始任务 */
        taskEXIT_CRITICAL();            /* 退出临界区 */
    }

    任务创建、挂起、删除

    /* 相关配置 */
    #define configSUPPORT_DYNAMIC_ALLOCATION        1                       /* 支持动态内存申请  */
    //#define configSUPPORT_STATIC_ALLOCATION           1                       /* 支持静态内存申请 */
    #define configTOTAL_HEAP_SIZE                   ((size_t)(20*1024))     /* 系统所有总的堆大小,heap_x.h需要,动态申请 */
    
    /* 任务创建 */
    /* 任务创建宏定义,便于修改 */
    
    #define START_TASK_PRIO         1       /* 任务优先级 */
    #define START_STK_SIZE          256     /* 任务堆栈大小 */
    
    TaskHandle_t StartTask_Handler;         /* 任务句柄 */
    void start_task(void *pvParameters);    /* 任务函数 */
    
    /* 任务创建函数 */
    
    xTaskCreate((TaskFunction_t )start_task,            /* 任务函数 */
                (const char*    )"start_task",          /* 任务名称 */
                (uint16_t       )START_STK_SIZE,        /* 任务堆栈大小 */
                (void*          )NULL,                  /* 传递给任务函数的参数 */
                (UBaseType_t    )START_TASK_PRIO,       /* 任务优先级 */
                (TaskHandle_t*  )&StartTask_Handler);   /* 任务句柄 */
    
    /* 任务挂起 */
    vTaskSuspend(Task1Task_Handler);
    
    /* 任务内解挂 */
    vTaskResume(Task1Task_Handler);
    
    /* 中断内解挂 */
    BaseType_t YieldRequired;
    YieldRequired=xTaskResumeFromISR(Task1Task_Handler);
    portYIELD_FROM_ISR(YieldRequired);    /* 判断是否需要调度到恢复的任务 */
    
    /* 任务删除 */
    vTaskDelete(Task1Task_Handler);

    任务调度

    • 嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法
    抢占式务调度
    • 每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数,抢占式调度器会获得就绪列表中优先级最高的任务,并运行这个任
    • 根据抢占式调度器,当前的任务要么被高优先级任务抢占,
      要么通过调用阻塞式 API 来释放 CPU 使用权让低优先级任务执行,没有用户任务执行时就执行空闲任务
    时间片调度
    • 实现 Round-robin 调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,
      并为每个任务分配一个时间片
    • 每个任务都有相同的优先级,任务会运行固定的时间片个数或者遇到阻塞式的 API 函数
    • 在 FreeRTOS 操作系统中只有同优先级任务才会使用时间片调度
    • 默认情况下,此宏定义已经在 FreeRTOS.h 文件里面使能
    • FreeRTOS的时间片仅支持一个时钟周期,比如你的系统时钟节拍是1000Hz,那么时间片的大小就是1ms,时间片调度仅存在于同优先级任务
    • 在 RTOS 中,最小的时间单位为一个 tick,即 SysTick 的中断周期,RT-Thread 和 μC/OS可以指定时间片的大小为多个 tick,但是 FreeRTOS不一样,时间片只能是一个 tick。与其说 FreeRTOS 支持时间片,倒不如说它的时间片就是正常的任务调度
    /* 相关配置,时间片长度即1/Tick中断频率 */
    #define configUSE_PREEMPTION                    1                       /* 1使用抢占式内核,0使用协程 */
    #define configUSE_TIME_SLICING                  1                       /* 1使能时间片调度(默认式使能的) */
    #define configTICK_RATE_HZ                      (20)                    /* 时钟节拍频率,20HZ = 50ms */
    
    /* 两个任务优先级相等 */
    #define TASK1_TASK_PRIO     2
    #define TASK1_STK_SIZE      128 
    TaskHandle_t Task1Task_Handler;
    void task1_task(void *pvParameters);
    
    #define TASK2_TASK_PRIO     2
    #define TASK2_STK_SIZE      128 
    TaskHandle_t Task2Task_Handler;
    void task2_task(void *pvParameters);

    三、系统延时

    const TickType_t xDelayTime = pdMS_TO_TICKS(300);/* 将延时ms转换为系统节拍 */
    const TickType_t xDelayTime = 300 / portTICK_RATE_MS;/* 将延时ms转换为系统节拍 */
    /* TickType_t 等价于 portTickType */
    /* 系统延时函数,单位:系统节拍,阻塞延时   portMAX_DELAY-最大延时等待 */
    
    /* 相对延时 */
    const TickType_t xDelayTime = pdMS_TO_TICKS(300);
    vTaskDelay(xDelayTime);
    
    /* 绝对延时 */
    static portTickType xLastWakeTime;
    const portTickType xFrequency = pdMS_TO_TICKS(500); 
    vTaskDelayUntil(&xLastWakeTime,xFrequency);

    如果想精确周期性执行某个任务,可以调用系统节拍钩子函数vApplicationTickHook(),它在系统节拍中断服务函数中被调用,因此这个函数中的代码必须简洁

    四、软件定时器

    /* 定时器结构体、函数声明 */
    TimerHandle_t   MyTimer_Handle;    /* 定时器句柄 */
    void ReloadCallback(TimerHandle_t xTimer);      /* 定时器回调函数 */
    
    /* 创建软件定时器 */
    MyTimer_Handle=xTimerCreate((const char*        )"ReloadTimer",         /* 定时器名称 */
                                (TickType_t         )1000,                  /* 周期1s(1000个时钟节拍) */
                                (UBaseType_t        )pdTRUE,                /* 周期模式 */
                                (void*              )1,                     /* 定时器ID */
                                (TimerCallbackFunction_t)ReloadCallback);   /* 定时器回调函数 */
    /* 定时器回调函数 */
    void ReloadCallback(TimerHandle_t xTimer)
    {
        //do something
    }
    
    xTimerStart(MyTimer_Handle,0);  /* 开启定时器 */
    xTimerStop(MyTimer_Handle,0);   /* 定时器停止 */
    xTimerReset(MyTimer_Handle, 0); /* 定时器复位 */

    五、消息队列

    /* 消息队列宏定义 */
    #define MESSAGE_Q_NUM   4           /* 发送数据的消息队列的数量 */
    #define MESSAGE_Q_ITEM_NUM  200     /* 每个消息的空间大小 */
    QueueHandle_t Message_Queue;        /* 信息队列句柄 */
    
    /* 创建消息队列 */
    Message_Queue=xQueueCreate(MESSAGE_Q_NUM,MESSAGE_Q_ITEM_NUM);
    
    /* 任务内消息发送 */
    u8 sendData[MESSAGE_Q_ITEM_NUM];
    BaseType_t err;
    err=xQueueSend(Message_Queue,&senddata,10);    /* 10为发送等待时间,有可能队列已满,err = errQUEUE_FULL 或 err = pdPASS */
    
    /* 中断内消息发送 */
    8 sendData[MESSAGE_Q_ITEM_NUM];
    BaseType_t xHigherPriorityTaskWoken;
    xQueueSendFromISR(Message_Queue,sendData,&xHigherPriorityTaskWoken);    /* 向队列中发送数据,返回值,依然是 满了或Pass,第三个参数是判断高优先级 */接受到队列后,退出中断,是否需要调度
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);/*如果需要的话进行一次任务切换  */
    
    /* 任务内消息接收 */
    u8 *receiveData;
    xQueueReceive(Message_Queue,receiveData,portMAX_DELAY)    /* 返回值为pdPASS 或 errQUEUE_EMPTY,这里等待时间用portMAX_DELAY阻塞,所以不用再判断 */
    
    /* 中断内消息接收 */
    u8 *receiveData;
    err=xQueueReceiveFromISR(Message_Queue,receiveData,&xTaskWokenByReceive); /* 向队列中接受数据,返回值, FAIL或Pass,第三个参数是判断高优先级接受到队列后,退出中断,是否需要调度 */
    portYIELD_FROM_ISR(xTaskWokenByReceive);/* 如果需要的话进行一次任务切换 */
    
    u8 remain_size;     /* 消息队列剩余大小 */
    remain_size=uxQueueSpacesAvailable(Message_Queue);  /* 得到队列剩余大小 */
    u8 used_size;       /* 消息队列使用大小 */
    used_size=uxQueueMessagesWaiting(Message_Queue);    /* 得到队列使用大小 */

    六、信号量

    • FreeRTOS的信号量包括二进制信号量、计数信号量、互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量
      信号量API函数实际上都是宏,它使用现有的队列机制。这些宏定义在semphr.h文件中。如果使用信号量或者互斥量,需要包含semphr.h头文件。
      • 二进制信号量、计数信号量和互斥量信号量的创建API函数是独立的,但是获取和释放API函数都是相同的;递归互斥信号量的创建、获取和释放API函数都是独立的

    6.1 二值信号量

    /* 二值信号量创建 */
    SemaphoreHandle_t BinarySemaphore;  /* 二值信号量句柄 */
    BinarySemaphore=xSemaphoreCreateBinary();   /* 创建二值信号量 */
    
    /* 二值信号量等待 */
    BaseType_t err;
    err = xSemaphoreTake(BinarySemaphore,portMAX_DELAY);    /* 获取信号量 */
    
    /* 二值信号量发送 */
    BaseType_t err;
    err = xSemaphoreGive(BinarySemaphore);  /* 释放二值信号量 */
    

    6.2 计数信号量

    /* 计数信号量创建 */
    SemaphoreHandle_t CountSemaphore;/* 计数型信号量句柄 */
    CountSemaphore=xSemaphoreCreateCounting(255,0);     /* 创建计数型信号量,最大计数和初始化计数,参数没改动的话,为long,所以最大值可以设计为不止255 */
    
    /* 计数信号量等待 */
    UBaseType_t semavalue;
    xSemaphoreTake(CountSemaphore,portMAX_DELAY);   /* 等待数值信号量,阻塞 */
    semavalue=uxSemaphoreGetCount(CountSemaphore);  /* 获取数值信号量值 */
    
    /* 计数信号量发送 */
    BaseType_t err;
    err=xSemaphoreGive(CountSemaphore);/* 释放计数型信号量 */

    6.3 互斥信号量

    /* 创建互斥信号量 */
    SemaphoreHandle_t MutexSemaphore; /* 互斥信号量句柄 */
    MutexSemaphore=xSemaphoreCreateMutex();     /* 创建互斥信号量 */
    
    /* 互斥信号量等待 */
    xSemaphoreTake(MutexSemaphore,portMAX_DELAY);   /* 获取互斥信号量,因为是阻塞,也就不需要查看什么返回值 */
    
    /* 互斥信号量发送 */    
    xSemaphoreGive(MutexSemaphore);                 /* 释放互斥信号量 */

    6.4 递归信号量

    SemaphoreHandle_t RecursiveMutex;/* 递归互斥信号量句柄 */
    RecursiveMutex = xSemaphoreCreateRecursiveMutex();        /* 创建递归互斥信号量 */
    
    /* 递归互斥信号量等待 */
    xSemaphoreTakeRecursive(RecursiveMutex,10);        /* 10为等待节拍 */
    
    /* 递归互斥信号量发送 */
    xSemaphoreGiveRecursive(RecursiveMutex);        /* 发送递归互斥信号量 */

    七、事件标志组

    /* 例子,3个事件 */
    #define EVENTBIT_0  (1<<0)               
    #define EVENTBIT_1  (1<<1)
    #define EVENTBIT_2  (1<<2)
    #define EVENTBIT_ALL    (EVENTBIT_0|EVENTBIT_1|EVENTBIT_2)
    
    EventGroupHandle_t EventGroupHandler;    /* 事件标志组句柄 */
    EventGroupHandler=xEventGroupCreate();   /* 创建事件标志组 */
    
    /* 事件标志组置位 */
    xEventGroupSetBits(EventGroupHandler,EVENTBIT_1);        /* 事件1置位 */
    
    /* 事件标志组清除 */
    xEventGroupClearBits(EventGroupHandler,EVENTBIT_1);       /* 事件1清除 */
    
    /* 事件标志组获取 */
    EventBits_t NewValue;
    NewValue = xEventGroupGetBits(EventGroupHandler);   
    
    /* 事件标志组等待 */
    EventValue=xEventGroupWaitBits((EventGroupHandle_t  )EventGroupHandler,     /* 句柄 */   
                                   (EventBits_t         )EVENTBIT_ALL,          /* 标志位 */
                                   (BaseType_t          )pdTRUE,                /* 获取成功后 清除  */
                                   (BaseType_t          )pdTRUE,                /* 等待所有标志位 置位 */
                                   (TickType_t          )portMAX_DELAY);        /* 阻塞 */

    八、任务通知

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

    /* 通知值发送,设置通知值,可发送一个数据 */
    u8 data;
    BaseType_t err;
    err=xTaskNotify((TaskHandle_t   )Task_Handler,      /* 接收任务通知的任务句柄 */
                    (uint32_t       )data,                      /* 任务通知值 */
                    (eNotifyAction  )eSetValueWithOverwrite);   /* 覆写的方式发送任务通知 */
    
    
    /* 通知值发送,设置通知值,可做标记位组 */
    #define EVENTBIT_1  (1<<1)
    xTaskNotify((TaskHandle_t   )Task_Handler,      /* 接收任务通知的任务句柄 */
                (uint32_t       )EVENTBIT_1,        /* 要更新的bit */
                (eNotifyAction  )eSetBits);         /* 更新指定的bit */
    
    /* 通知值获取,获取通知值,并判断是否需要清零 */
    BaseType_t err;
    uint32_t NotifyValue;
    err=xTaskNotifyWait((uint32_t   )0x00,              /* 进入函数,没有接受到通知,不清除任何bit */
                        (uint32_t   )ULONG_MAX,         /* 退出函数,接受到通知,清除所有(0xffffffffUL)位的bit, */
                        (uint32_t*  )&NotifyValue,      /* 保存任务通知值 */
                        (TickType_t )portMAX_DELAY);    /* 阻塞时间 */

    九、临界段

    /* 任务内临界段处理 */
    taskEXIT_CRITICAL();
    /* 任务处理 */
    taskEXIT_CRITICAL();
    
    
    /* 中断内临界段处理 */
    taskENTER_CRITICAL_FROM_ISR();
    /* 中断内处理 */
    taskEXIT_CRITICAL_FROM_ISR();

    十、内存管理

    /* 内存申请 */
    u8 *buffer;
    buffer=pvPortMalloc(30);            /* 申请内存,30个字节 */
    
    /* 释放内存 */
    vPortFree(buffer); 
    
    /* 获取内存剩余空间 */
    u32 freeSize;
    freeSize = xPortGetFreeHeapSize();