This is my study note of freeRtos.

本文以STM32F10X为例子,freeRTOS以v9.0.0为例。

预准备

  • 下载源码包
    1
    官网下载链接:http://www.freertos.org/
  • 新建FreeRTOS文件夹
    在此文件夹中新加三个文件夹:src、port、include
    1
    2
    3
    4
    5
    6
    7
    1.src
    将FreeRTOSv9.0.0\FreeRTOS\Source目录下所有的".c"文件拷贝至src文件中。
    2.port
    将FreeRTOSv9.0.0\FreeRTOS\Source\portable目录下找到MemMang文件夹以及RVDS文件夹,拷贝到port文件夹中。
    3.include
    FreeRTOSv9.0.0\ FreeRTOS\Source找到include文件夹,直接拷贝至新建的freeRTOS文件夹中。
    此时,三个文件均在新建的freeRTOS文件夹中,完成文件复制。
  • 拷贝freeRTOS文件夹至工程中
  • 拷贝FreeRTOSConfig.h 文件到 user 文件夹
    1
    FreeRTOSv9.0.0\FreeRTOS\Demo下找到CORTEX_STM32F103_Keil这个文件夹,双击打开,目录下找到FreeRTOSConfig.h文件,拷贝至user文件夹。
  • keil中添加freeRTOS到工程组文件夹
    1
    接下来我们在开发环境里面新建 FreeRTOS/src 和 FreeRTOS/port 两个组文件夹,其中 FreeRTOS/src 用于存放 src 文件夹的内容,FreeRTOS/port 用于存放 port\MemMang 文件夹 与 port\RVDS\ARM_CM?文件夹的内容。?表示3、4或者7,具体看哪个型号的开发板,F3系列就为port\RVDS\ARM_CM3
  • 指定头文件路径
  • 修改stm32f10x.it
    1
    注释掉 PendSV_Handler()与 SVC_Handler()。
  • FreeRTOS.h
    1
    2
    #define xPortPendSVHandler PendSV_Handler
    #define vPortSVCHandler SVC_Handler

创建任务

静态创建任务

  • configSUPPORT_STATIC_ALLOCATION 这个宏 定 义 必 须为 1

    1
    FreeRTOS.h文件夹中
  • 实现两个函数 :vApplicationGetIdleTaskMemory()与 vApplicationGetTimerTaskMemory()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    设定的空闲(Idle)任务与定时器(Timer)任务的堆栈大小,必须由用户自己分配,而不能是动态分配

    /* 空闲任务任务堆栈 */
    static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
    /* 定时器任务堆栈 */
    static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];
    /* 空闲任务控制块 */
    static StaticTask_t Idle_Task_TCB;
    /* 定时器任务控制块 */
    static StaticTask_t Timer_Task_TCB;
    /**
    **********************************************************************
    * @brief 获取空闲任务的任务堆栈和任务控制块内存
    * ppxTimerTaskTCBBuffer : 任务控制块内存
    * ppxTimerTaskStackBuffer : 任务堆栈内存
    * pulTimerTaskStackSize : 任务堆栈大小
    * @date 2018-xx-xx
    **********************************************************************
    */
    void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
    StackType_t **ppxIdleTaskStackBuffer,
    uint32_t *pulIdleTaskStackSize)
    {
    *ppxIdleTaskTCBBuffer=&Idle_Task_TCB;/* 任务控制块内存 */
    *ppxIdleTaskStackBuffer=Idle_Task_Stack;/* 任务堆栈内存 */
    *pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;/* 任务堆栈大小 */
    }
    /**
    *********************************************************************
    * @brief 获取定时器任务的任务堆栈和任务控制块内存
    * ppxTimerTaskTCBBuffer : 任务控制块内存
    * ppxTimerTaskStackBuffer : 任务堆栈内存
    * pulTimerTaskStackSize : 任务堆栈大小
    **********************************************************************
    */
    void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
    StackType_t **ppxTimerTaskStackBuffer,
    uint32_t *pulTimerTaskStackSize)
    {
    *ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任务控制块内存 */
    *ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任务堆栈内存 */
    *pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
    }
  • 定义任务栈

    1
    2
    3
    4
    5
    每一个任务都是独立的,他们的运行环境都单独的保存在他们的栈空间当中。
    /* AppTaskCreate任务任务堆栈 */
    static StackType_t AppTaskCreate_Stack[128];
    /* LED任务堆栈 */
    static StackType_t LED_Task_Stack[128];
  • 定义句柄

    1
    任务创建成功以后会返回此任务的任务句柄,这个句柄就是任务的堆栈。
  • 定义任务控制块

    1
    任务控制块为任务的身份证。
  • 创建静态任务函数

    1
    2
    3
    4
    5
    6
    7
    AppTaskCreate_Handle = xTaskCreateStatic((TaskFunction_t	)AppTaskCreate,		//任务函数
    (const char* )"AppTaskCreate", //任务名称
    (uint32_t )128, //任务堆栈大小
    (void* )NULL, //传递给任务函数的参数
    (UBaseType_t )3, //任务优先级
    (StackType_t* )AppTaskCreate_Stack, //任务堆栈
    (StaticTask_t* )&AppTaskCreate_TCB); //任务控制块
  • 启动任务vTaskStartScheduler()

  • 创建动态任务函数

    1
    2
    3
    4
    5
    6
    xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
    (const char* )"LED_Task",/* 任务名字 */
    (uint16_t )512, /* 任务栈大小 */
    (void* )NULL, /* 任务入口函数参数 */
    (UBaseType_t )2, /* 任务的优先级 */
    (TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 *

任务管理

平时通过while实现任务的调度为前后台系统调度,中断为前台,while为后台,资源消耗少但是实时性很差,freeRTOS为抢占式的实时多任务系统。

状态划分

  • 阻塞
  • 就绪
  • 运行
  • 挂起

常用函数

1
2
3
4
5
6
7
8
9
10
挂起:
vTaskSuspend();
vTaskSuspendAll();
恢复:
vTaskResume();
删除:
vTaskDelete();
延时:
vTaskDelay();
vTaskDelayUntil();

消息队列

队列读取采用的是先进先出(FIFO)模式。

堵塞时间

1
2
3
4
当需要取数据时队列为空,那此时应该如何处理呢?有以下三种方式:
1.堵塞时间为0,即不等待直接返回
2.堵塞时间为0 ~ portMAX_DELAY,即再等等
3.堵塞时间为portMAX_DELAY,即无限等待
  • 消息队列创建函数 xQueueCreate()
    1
    xQueueCreate(UBase Type_t uxQueueLength, UBaseType_t uxItemSize)用于创建一个新的队列并返回可用于访问这个队列的队列句柄。第一个参数为队列长度,第二个参数为消息单元的大小。
  • 静态创建函数 xQueueCreteStatic()
    1
    xQueueCreteStatic(UBase Type_t uxQueueLength, UBaseType_t uxItemSize, uint8_t *pucQueueStorageBuffer, StaticQueue_t *pxQueueBuffer),第一个为队列深度,第二个为单元长度,第三个队列的存储区域,第四个为队列的数据结构
  • 读队列 xQueueReceive()
    1
    2
    3
    读取一个数据后,队列中的数据会被移除。
    xQueueReceive(QueueHandle xQueue, void *pvBuffer, TickType_t xTicksToWait)
    第一个参数为句柄,第二个参数为指针,指向需要存储的数据,第三个超时最大时间。
  • 写队列 xQueueSend()
    1
    2
    3
    4
    可以写头也可以写尾。
    xQueueSend(QueueHandle_t xQueue,const void *pvItemToQueue,
    TickType_t xTicksToWait);
    第一个参数为句柄、第二个参数为内容、第三个参数为等待时间
  • 队列删除 xQueueDelete()
    1
    2
    队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都 会被系统回收清空,而且不能再次使用这个消息队列了。
    vQueueDelete(xQueue);
  • 复位 xQueueReset
    1
    xQueueReset( QueueHandle_t pxQueue);
  • 查询 uxQueueSpaceAvailable
    1
    2
    查询可用的数据个数
    uxQueueSpaceAvailable(const QueueHandle_t xQueue);

信号量

分类

  • 二值信号量
  • 计数信号量
  • 互斥信号量
  • 递归信号量

对应函数

  • 创建二值信号量xSemaphoreCreateBinary

    1
    2
    xSemaphoreCreateBinary()
    用于创建一个二值信号量,返回一个句柄。
  • 创建计数信号量xSemphoreCreateCounting

FreeRTOSConfig.h 中把宏 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1

1
2
xSemphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount)
用于创建一个计数信号量,返回一个句柄,第一个信号量为最大值,第二个为信号量初始值。
  • 信号量删除函数 vSemphoreDelete()

    1
    2
    vSemphoreDelete(SemphoreHandle_t xSemphore);
    删除一个信号量。
  • 信号量释放函数 xSemphoreGive()

    1
    2
    xSemaphoreGive(SemphoreHandle_t xSemphore);
    释放一个信号量的宏(二值、计数、互斥)。
  • 信号量释放函数(带中断保护) xSemphoreGiveISR()

    1
    2
    xSemphoreGiveISR(SemphoreHandle_t xSemphore);
    释放一个信号量的宏(二值、计数, 但是不包含互斥)。
  • 信号量获取xSemphoreTake()

    1
    2
    信号量获取xSemphoreTake(xSemphore, xBlockTime);
    xSemaphoreTake()函数用于获取信号量,不带中断保护。
  • 信号量获取xSemphoreTakeFromISR()

    1
    xSemaphoreTakeFromISR()是函数 xSemaphoreTake()的中断版本,用于获取信号量。

互斥量

  • 优先级继承机制
    1
    当获取到互斥变量,被低优先级的进程获取到后,为了防止优先级颠倒问题,暂时将获取到互斥量的低优先级进程的优先级提高至当前最高的优先级,这样就能保证下次获取到资源的进程为优先级最高的进程。

函数

  • 互斥量创建函数 xSemphoreCreateMutex()
    1
    xSemphoreCreateMutex创建一个互斥量,并返回一个句柄。
  • 递归 xSemphoreCreateRecursiveMutex()
    1
    创建的互斥量可被同一个任务获取多次。
  • 互斥量删除函数 vsamphoreDelete()
  • 互斥量获取函数 xsemphoreTake()
    1
    实际调用的函数就是 xQueueGenericReceive() 。
  • 递归互斥量获取函数 xSemaphoreTakeRecursive()
  • 互斥量释放函数 xSemphoreGive()
  • 递归释放函数 xSemaphoreGiveRecursive()