由于本节为实践课程,建议看原视频:
【【2.3】FreeRTOS多任务编程示例①——Kevin带你读《STM32Cube高效开发教程高级篇》】
如何在 cmake 工程里添加第三方库?
对于纯 C 库,如用户自己编写的 LED.C LED.H,以及像 cjson 等小型库
假设你要把 ThirdParty/MyLib/ 目录下的所有文件都加入编译,你可以将以下代码放入你的 CMakeLists.txt 中:
- 扫描源文件并加入编译
使用 file(GLOB_RECURSE …) 可以递归扫描目标文件夹及其所有子文件夹。
# 使用 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 即可。)
- 处理头文件 (.h)
这里有一个关于头文件的常见误区:你不需要把每个 .h 文件都塞进 target_sources 里参与编译。编译器只需要知道“去哪个文件夹找头文件”即可。
你需要做的是把包含头文件的目录加进去:
# 告诉编译器去哪里找头文件 (填的是目录路径,不是通配符)
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/ 根目录下,结构如下:
你的工程目录/
├── 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:
# 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:
# 将 lvgl 库链接到你的主工程 ${CMAKE_PROJECT_NAME} 上
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE
lvgl
)(注意:LVGL 在它的 CMakeLists 中定义的 Target 名字就是 lvgl。如果是其他库,你需要看它们文档里定义的库名叫什么)
建立工程
将基础工程的 ioc 文件复制到新文件夹里并重命名,就可以复用已有的工程设置。
创建新任务

建立两个任务,优先级都设置为 normal,其中一个动态分配内存,一个静态分配。
我们先不使用 osdelay,而是 HAL_Delay:
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 始终处于就绪状态 。
改变延时函数
保持优先级不变,更改高优先级任务函数的延时方式:
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 使用权都在空闲任务手里。
严格的周期循环
将任务函数更改为如下所示:
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 */
}

发表回复