Day 1:环境搭建与工程创建
1.1 CubeMX 配置清单
新建工程,选择 STM32F407VET6,逐项配置:
| 配置项 | 设置 | 备注 |
|---|---|---|
| RCC → HSE | Crystal/Ceramic Resonator | 外部 8MHz 晶振 |
| SYS → Debug | Serial Wire | 必须选,否则只能烧录一次 |
| SYS → Timebase Source | TIM6 | 把 SysTick 留给 FreeRTOS |
| Clock → HCLK | 168 MHz | 输入 168 回车,自动配 PLL |
| USART1 | Asynchronous, 115200 | PA9/PA10 默认 |
| GPIO | PA6 = Output | 选择一个 LED信号 输出引脚 |
| FREERTOS → Interface | CMSIS_V2 | V2 API 更完整 |
| FREERTOS → Heap | heap_4 | 通用,支持动态分配释放 |
| FREERTOS → Total Heap | 20480 (20KB) | F407 有 192KB SRAM,充裕 |
| Project Manager | Toolchain = STM32CubeIDE | 在Cube上开发 |
Project Manager 额外勾选:
- Generate peripheral initialization as a pair of .c/.h files
- Keep User Code when re-generating
1.2 代码生成失败排查
点击 Project → Generate Code 时,如果窗口闪一下消失、代码没生成,检查:
- 先按 Ctrl+S 保存 .ioc 文件(最常见原因)
- 工程路径不能有中文或空格
- 编码问题:右键工程 → Properties → Resource → Text file encoding → 改为 UTF-8
- 固件包缺失:如果提示 “necessary firmware package is missing”,需要下载 STM32CubeF4 固件包。可以离线从 ST 官网下载 zip,解压到
C:\Users\xxx\STM32Cube\Repository\STM32Cube_FW_F4_V1.28.3
1.3 验证硬件
生成代码后,先在 main.c 的 while(1) 里加裸机测试:
| |
编译烧录,确认 LED 闪烁。然后删掉这两行,准备上 FreeRTOS。
Day 2:第一个 FreeRTOS 多任务程序
2.1 核心代码
在 freertos.c 的三个 USER CODE 区域添加代码:
① Includes 区域:
| |
② Variables 区域:
| |
③ Init 区域(MX_FREERTOS_Init 函数内):
| |
④ Application 区域(文件最底部):
| |
2.2 确认 main.c 代码
确认 CubeMX 生成的 main.c 里已经包含了初始化调用代码,否则 FreeRTOS 将不会启动。
一般来说,生成代码时是会自己生成好初始化代码的,如下:
| |
同时 main.c 要包含头文件:
| |
2.3 学习概念
2.3.1 芯片启动后, main() 里发生了什么?
| |
osKernelStart() 启动调度器后,调度器通过抢占式优先级调度管理 CPU。任何时刻,它只执行就绪态中优先级最高的任务。同优先级任务之间采用时间片轮转。
2.3.2 什么是“任务”?
任务本质上就是一个无限循环的C函数,但比普通函数多一个概念:它会堵塞。
| |
为什么 osDelay() 是关键代码?
因为FreeRTOS是 抢占式调度 。调度器只看一件事:当前哪个就绪任务的优先级最高。但如果最高优先级的任务目前在 osDelay(), 那它就会变成“阻塞态”(Blocked),调度器就会跳过它,去找下一个就绪的高优先级任务。
FreeRTOS默认开启 Time Slicing(时间片轮转), 同优先级之间,调度器默认每个tick(默认1ms)会强制切换一次,不能论任务是否主动阻塞。
如果没有osDelay,那么任务永远处于“运行态”或者“就绪态”,调度器每次检查它时它都在就绪,CPU被它独占了,那其它任务将永远没有出头之日了,这就是饿死(Starvation)。
2.3.3 任务状态
任务一共有五个状态,分别是就绪,运行,阻塞,挂起和删除。
| 状态 | 英文 | 定义 | 怎么进入 | 怎么退出 |
|---|---|---|---|---|
| 就绪 | Ready | 可以运行,等调度器选我 | 创建、阻塞到期、挂起恢复 | 被调度器选中执行 |
| 运行 | Running | 正在 CPU 上执行 | 调度器选中 | 被抢占(回就绪)、主动阻塞、被挂起 |
| 阻塞 | Blocked | 我在等某件事(时间/事件) | osDelay()、信号量等待、队列接收 | 延时到期、事件到达 |
| 挂起 | Suspended | 我被外部强制暂停了 | osThreadSuspend() | osThreadResume() |
| 删除 | Deleted | 我死了,等空闲任务收尸 | vTaskDelete() | 空闲任务清理内存后彻底消失 |
2.3.3 创建任务时,那些参数是什么意思?
| |
栈大小为什么是 512 * 4 ?
FreeRTOS 中,栈的单位是 word(字) , ARM Cortex-M 是 32 位架构, 1 word = 4 bytes。所以 512 * 4 也就是 512 个字, 2048个字节。 这个栈是任务的私有栈,保存着:
- 局部变量
- 函数调用时的返回地址
- 中断发生时自动保存的寄存器 如果栈设的太小,任务里函数调用层太深,或者局部函数太大,就会发生栈溢出,触发HardFault。
2.3.4 优先级数值(FreeRTOS 内部)
CMSIS-RTOS V2 封装了一层,但底层 FreeRTOS 的优先级是数字:
osPriorityIdle= 1osPriorityLow= 8osPriorityBelowNormal= 16osPriorityNormal= 24osPriorityAboveNormal= 32osPriorityHigh= 40osPriorityRealtime= 48osPriorityError= 错误
数字越大,优先级越高。osPriorityAboveNormal (32) > osPriorityNormal (24),所以调度器优先执行 AboveNormal 的任务。
2.4 调试器配置
我用的国产 J-Link OB Mini,CubeIDE 默认的 GDB 配置会报 localhost:2331 超时。
排查步骤:
- 确认接线:J-Link CLK→板子 SWCLK,DIO→SWDIO,GND→GND,3.3V→3.3V。(后面发现就是接线问题…我买的板子丝印有点问题)
- 设备管理器确认识别到 J-Link 设备
- 安装 SEGGER J-Link Software Pack
- 用 J-Link Commander 测试连接:
connect→STM32F407VE→SWD→4000 - 如果报
Failed to initialize DAP,尝试降速到speed 100
下载方式:
- 方式一:J-Flash 直接下载 hex
- 方式二:CubeIDE Run Configurations 里调试探头选 SEGGER J-LINK,手动启动 J-Link GDB Server 后再点 Debug
2.5 验证现象
烧录后:
- 串口助手(115200-8-1-N)每 500ms 收到
TaskA: Running - LED(PA6)以 1Hz 频率闪烁
- 两个现象同时出现,说明调度器正常工作了