FreeRTOS学习笔记 · 第六讲:2.3freertos多任务编程示例

由于本节为实践课程,建议看原视频:

【【2.3】FreeRTOS多任务编程示例①——Kevin带你读《STM32Cube高效开发教程高级篇》】


如何在 cmake 工程里添加第三方库?

对于纯 C 库,如用户自己编写的 LED.C LED.H,以及像 cjson 等小型库

假设你要把 ThirdParty/MyLib/ 目录下的所有文件都加入编译,你可以将以下代码放入你的 CMakeLists.txt 中:

  1. 扫描源文件并加入编译
    使用 file(GLOB_RECURSE …) 可以递归扫描目标文件夹及其所有子文件夹。
CMake
# 使用 GLOB_RECURSE 递归扫描所有的 .c 文件,并带上 CONFIGURE_DEPENDS 防止文件更新不触发编译的问题
file(GLOB_RECURSE THIRDPARTY_SOURCES CONFIGURE_DEPENDS 
    "ThirdParty/MyLib/*.c"
    # 如果有汇编文件也可以一起加上
    # "ThirdParty/MyLib/*.s" 
)

# 将扫描到的源文件列表加入到工程的编译源中
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    ${THIRDPARTY_SOURCES}
)

(注:如果不需要扫描子文件夹,只需要当前文件夹,把 GLOB_RECURSE 换成 GLOB 即可。)

  1. 处理头文件 (.h)
    这里有一个关于头文件的常见误区:你不需要把每个 .h 文件都塞进 target_sources 里参与编译。编译器只需要知道“去哪个文件夹找头文件”即可。

你需要做的是把包含头文件的目录加进去:

CMake
# 告诉编译器去哪里找头文件 (填的是目录路径,不是通配符)
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    ThirdParty/MyLib
    ThirdParty/MyLib/inc  # 如果它的头文件在单独的 inc 文件夹下
)

对于自带cmake配置的库,如FreeRTOS、LVGL等

第 1 步:整理目录与配置文件

假设你把 LVGL 下载到了 ThirdParty/lvgl 目录下。
对于 LVGL 来说,它通常需要一个配置文件 lv_conf.h。你可以把这个文件放在 ThirdParty/ 根目录下,结构如下:

Plaintext
你的工程目录/
├── Core/
├── ThirdParty/
│   ├── lv_conf.h        <-- LVGL 的配置文件 (从 lv_conf_template.h 复制改名而来)
│   └── lvgl/            <-- LVGL 源码文件夹 (里面有它自己的 CMakeLists.txt)
├── CMakeLists.txt       <-- CubeMX生成的主 CMake 文件

第 2 步:在主 CMakeLists.txt 中添加子目录

打开 STM32CubeMX 生成的主 CMakeLists.txt。

使用 add_subdirectory() 命令引入 LVGL:

CMake
# 1. 告诉编译器去哪里找 lv_conf.h (这一步对 LVGL 很重要)
# 必须把包含 lv_conf.h 的目录加到编译器的搜索路径中
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    ${CMAKE_SOURCE_DIR}/ThirdParty
)

# 2. 将 lvgl 文件夹作为一个整体(子 CMake 工程)添加进来
add_subdirectory(ThirdParty/lvgl)

原理:当 CMake 执行到 add_subdirectory 时,它会跑去执行 ThirdParty/lvgl/CMakeLists.txt,根据 LVGL 作者写好的规则去组织编译,并生成一个名为 lvgl 的库目标(Target)。

第 3 步:将 LVGL 链接到你的 STM32 工程

上一步只是让 CMake 知道了如何编译 LVGL,但还需要把你编译出来的 STM32 固件和 LVGL 库“绑定”在一起。

在主 CMakeLists.txt 中找到链接库的地方,使用 target_link_libraries:

CMake
# 将 lvgl 库链接到你的主工程 ${CMAKE_PROJECT_NAME} 上
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE 
    lvgl
)

(注意:LVGL 在它的 CMakeLists 中定义的 Target 名字就是 lvgl。如果是其他库,你需要看它们文档里定义的库名叫什么)

建立工程

将基础工程的 ioc 文件复制到新文件夹里并重命名,就可以复用已有的工程设置。

01MultiTasks.docx

创建新任务

建立两个任务,优先级都设置为 normal,其中一个动态分配内存,一个静态分配。

我们先不使用 osdelay,而是 HAL_Delay:

C
void AppTask_LEDR(void *argument)
{
  /* USER CODE BEGIN AppTask_LEDR */
  /* Infinite loop */
  for(;;)
  {
    LEDR_Toggle();
    HAL_Delay(500);
  }
  /* USER CODE END AppTask_LEDR */
}


void AppTask_LEDG(void *argument)
{
  /* USER CODE BEGIN AppTask_LEDG */
  
  /* Infinite loop */
  for(;;)
  {
    LEDG_Toggle();
    HAL_Delay(1000);
  }
  /* USER CODE END AppTask_LEDG */
}

然后点击调试,观察现象。

可以发现两个灯都正常闪烁。这是因为两个任务优先级相同,虽然都使用 hal_delay 使程序不会主动让出 cpu,但是时间片轮转使得两个任务依然轮流占据时间片。

调整优先级

现在我们让其中一个任务的优先级变为 belownormal:

任务函数不变,点击调试,观察现象。

这时我们发现只有绿色灯在闪了。这是因为 LEDG 的优先级要高于 LEDR,并且 LEDG 一直处于运行状态,不会让出 CPU 的使用权,导致 LEDR 始终处于就绪状态 。

改变延时函数

保持优先级不变,更改高优先级任务函数的延时方式:

C
void AppTask_LEDR(void *argument)
{
  /* USER CODE BEGIN AppTask_LEDR */
  /* Infinite loop */
  for(;;)
  {
    LEDR_Toggle();
    HAL_Delay(500);
    //osDelay(500);
  }
  /* USER CODE END AppTask_LEDR */
}

void AppTask_LEDG(void *argument)
{
  /* USER CODE BEGIN AppTask_LEDG */
  /* Infinite loop */
  for(;;)
  {
    LEDG_Toggle();
    //HAL_Delay(1000);
    osDelay(1000);
  }
  /* USER CODE END AppTask_LEDG */
}

现在灯就可以正常闪烁了,时序图如下:

task1(也就是 LEDG)仅仅在翻转 io 口的时候抢占 cpu 时间,其余时间片被 task2 挤用,空闲任务无法获得 cpu 使用权。

如果我们两个任务都使用 vtaskdelay 函数呢?

两个任务都只在翻转 io 口时获得 cpu 使用权,系统绝大部分的 cpu 使用权都在空闲任务手里。

严格的周期循环

将任务函数更改为如下所示:

C
void AppTask_LEDR(void *argument)
{
  /* USER CODE BEGIN AppTask_LEDR */
  /* Infinite loop */
  for(;;)
  {
    LEDR_Toggle();
    //HAL_Delay(500);
    osDelay(500);
  }
  /* USER CODE END AppTask_LEDR */
}


void AppTask_LEDG(void *argument)
{
  /* USER CODE BEGIN AppTask_LEDG */
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = pdMS_TO_TICKS( 1000 );
  /* Infinite loop */
  for(;;)
  {
    LEDG_Toggle();
    osDelay(100);
    LEDG_Toggle();
    osDelay(100);
    LEDG_Toggle();
    osDelay(100);
    LEDG_Toggle();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
      //osDelayUntil(xLastWakeTime += xFrequency);//这是CMSIS2的对应封装函数
  }
  /* USER CODE END AppTask_LEDG */
}

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


目录