<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>FreeRTOS on Blog</title><link>https://b0weny-qwq.github.io/Blog/tags/freertos/</link><description>Recent content in FreeRTOS on Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Mon, 17 Aug 2026 15:52:00 +0800</lastBuildDate><atom:link href="https://b0weny-qwq.github.io/Blog/tags/freertos/index.xml" rel="self" type="application/rss+xml"/><item><title>RTOS 正传（一）：FreeRTOS 的目录、移植层和配置文件</title><link>https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/</link><pubDate>Mon, 17 Aug 2026 15:52:00 +0800</pubDate><guid>https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/</guid><description>&lt;h1 id="rtos-正传一freertos-的目录移植层和配置文件"&gt;RTOS 正传（一）：FreeRTOS 的目录、移植层和配置文件
&lt;/h1&gt;&lt;p&gt;上一篇把启动文件、链接脚本和 CMake 都搭好了，这一篇就是正传，开始把 FreeRTOS 搬进来。&lt;/p&gt;
&lt;p&gt;学习 RTOS，注重适配层而不是内核，这一点很关键。这种大型开源项目，本来就是一个组织、一群人在维护，想把里面所有东西全部理解，本身就很困难。&lt;/p&gt;
&lt;p&gt;当然，这不是说你不能成为贡献者。真想走这条路，也可以学阿酥在 Coding 那样一路混到大厂 Offer。但如果眼前只是想把 F407 上的系统跑起来，那就先把下面几个问题弄明白：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;哪些 FreeRTOS 文件要编进工程？&lt;/li&gt;
&lt;li&gt;F407 + GCC 应该选哪个端口？&lt;/li&gt;
&lt;li&gt;堆实现到底选哪个？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FreeRTOSConfig.h&lt;/code&gt; 里一大堆宏，哪些真的要管？&lt;/li&gt;
&lt;li&gt;哪些中断可以调用 &lt;code&gt;xxxFromISR()&lt;/code&gt;？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题先弄明白，系统就不再是“复制一个 Demo，能亮就算成功”的黑盒。&lt;/p&gt;
&lt;h2 id="rtos-到底适合什么场景"&gt;RTOS 到底适合什么场景
&lt;/h2&gt;&lt;p&gt;我们接下来就依照 FreeRTOS 来介绍。OS 本质上就是调度器，就是多任务处理，所以它明显更适合业务逻辑复杂、控制频率没到 us 级的场景，比如智能穿戴和飞控。&lt;/p&gt;
&lt;p&gt;没错，我这里把飞控也算作低频率控制系统。一是因为它的数据处理很复杂，二是因为电调本身的控制频率也不高，PX4 就是跑在 NuttX 上的。&lt;/p&gt;
&lt;p&gt;但你说我搞高精度伺服、工业电机驱动，上个 RTOS，这就不符合应用场景了。这是很关键的问题。&lt;/p&gt;
&lt;h2 id="先把内核仓库拉下来"&gt;先把内核仓库拉下来
&lt;/h2&gt;&lt;p&gt;这次直接看官方的 &lt;a class="link" href="https://github.com/FreeRTOS/FreeRTOS-Kernel" target="_blank" rel="noopener"
 &gt;FreeRTOS-Kernel&lt;/a&gt; 仓库。它只放内核源码、头文件和各种 CPU/编译器端口，比把 FreeRTOS-Plus、TCP、MQTT 全带上的聚合仓库清爽得多。&lt;/p&gt;
&lt;p&gt;&lt;img alt="FreeRTOS-Kernel 官方仓库" class="gallery-image" data-flex-basis="384px" data-flex-grow="160" height="1600" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786953157804.png" srcset="https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786953157804_hu_48e7d92945518e22.png 800w, https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786953157804_hu_637c4f36668a6f24.png 1600w, https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786953157804_hu_f5c5639d26902f27.png 2400w, https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786953157804.png 2560w" width="2560"&gt;&lt;/p&gt;
&lt;p&gt;只学习当前版本的话，浅克隆就够了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone --depth &lt;span class="m"&gt;1&lt;/span&gt; --branch V11.3.0 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; https://github.com/FreeRTOS/FreeRTOS-Kernel.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;官方文档在 &lt;a class="link" href="https://www.freertos.org/" target="_blank" rel="noopener"
 &gt;www.freertos.org&lt;/a&gt;。内容肯定最全，但第一次打开确实像面对一本全英文词典：每个 API 都能查，问题是根本不知道应该先查哪个。&lt;/p&gt;
&lt;p&gt;&lt;img alt="FreeRTOS 官方文档" class="gallery-image" data-flex-basis="384px" data-flex-grow="160" height="1600" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786951997138.png" srcset="https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786951997138_hu_300ea6c776a6f6d2.png 800w, https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786951997138_hu_4fc0aa649574de11.png 1600w, https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786951997138_hu_22d038d6ca2931a9.png 2400w, https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786951997138.png 2560w" width="2560"&gt;&lt;/p&gt;
&lt;p&gt;我还是那个观点，没有什么比上手更重要。应用本来就比原理简单，设计岗薪资更高这一点事实足以佐证。所以这篇不准备逐行做源码解析，只介绍文件作用、怎么裁剪，以及核心移植层是怎么做的。源码主要还得靠自己慢慢啃，工程先跑起来再说。&lt;/p&gt;
&lt;h2 id="克隆下来为什么这么吓人"&gt;克隆下来为什么这么吓人
&lt;/h2&gt;&lt;p&gt;&lt;img alt="FreeRTOS-Kernel V11.3.0 文件结构" class="gallery-image" data-flex-basis="192px" data-flex-grow="80" height="1026" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786952115881.png" srcset="https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786952115881_hu_851493b892a5f754.png 800w, https://b0weny-qwq.github.io/Blog/p/stm32f407-freertos-kernel-port-config/image/index/1786952115881.png 821w" width="821"&gt;&lt;/p&gt;
&lt;p&gt;点开目录第一眼确实容易晕：一个 &lt;code&gt;tasks.c&lt;/code&gt; 就有 8872 行，旁边还躺着一堆不认识的配置文件。别慌，真正要烧进 F407 的内容并没有这么多。&lt;/p&gt;
&lt;p&gt;这个仓库里的东西大致分成四类：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;tasks.c&lt;/code&gt;、&lt;code&gt;queue.c&lt;/code&gt;、&lt;code&gt;list.c&lt;/code&gt; 这些通用内核源码；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;portable/&lt;/code&gt; 下面针对不同 CPU 和编译器准备的移植代码；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;include/&lt;/code&gt; 里的公共 API 和内核头文件；&lt;/li&gt;
&lt;li&gt;GitHub、合规、发布和文档文件。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;后面不再给每个文件单独截一张图了。最好把仓库放在旁边，对着目录往下看，会比只看文章顺得多。&lt;/p&gt;
&lt;h2 id="先分清自己站在哪一层"&gt;先分清自己站在哪一层
&lt;/h2&gt;&lt;p&gt;这里最关键的不是记文件名，而是先搞清楚自己现在扮演什么角色。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;应用开发者&lt;/strong&gt;主要使用 &lt;code&gt;task.h&lt;/code&gt;、&lt;code&gt;queue.h&lt;/code&gt;、&lt;code&gt;semphr.h&lt;/code&gt; 这些 API，再根据业务配置 &lt;code&gt;FreeRTOSConfig.h&lt;/code&gt;。任务怎么分、栈给多少、优先级怎么排，这些才是日常工作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;移植者&lt;/strong&gt;要把内核接到具体 CPU 和工具链上。F407 已经有官方 ARM_CM4F 端口，我们只需要选对、编进来，再接好 SysTick、PendSV、SVC 和中断优先级，不用自己从零写上下文切换。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;内核维护者&lt;/strong&gt;才需要深挖 &lt;code&gt;tasks.c&lt;/code&gt;、&lt;code&gt;queue.c&lt;/code&gt;、&lt;code&gt;list.c&lt;/code&gt;，研究就绪链表、阻塞链表、优先级继承和调度细节。&lt;/p&gt;
&lt;p&gt;还有一类是&lt;strong&gt;仓库维护者&lt;/strong&gt;。&lt;code&gt;.github/&lt;/code&gt;、MISRA、SPDX、拼写检查这些文件主要服务于 FreeRTOS 项目本身。它们不是垃圾，只是不会进入我们的固件。&lt;/p&gt;
&lt;h2 id="顶层目录先过一遍"&gt;顶层目录先过一遍
&lt;/h2&gt;&lt;p&gt;把截图里的内容整理一下，大概就是这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FreeRTOS-Kernel/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── .github/ # CI、Issue/PR 模板、贡献规范
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── examples/ # CMake 示例和配置模板
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── include/ # 内核头文件
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── portable/ # CPU、编译器和堆的移植实现
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── tasks.c # 任务和调度器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── queue.c # 队列、信号量、互斥锁
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── list.c # 内核链表
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── timers.c # 软件定时器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── event_groups.c # 事件组
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── stream_buffer.c # Stream/Message Buffer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── croutine.c # 旧式协程
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── CMakeLists.txt # 官方 CMake 入口
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── README.md # 仓库说明
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── History.txt # 版本历史
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── LICENSE.md # MIT 许可证
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── MISRA.md # MISRA 合规与偏离说明
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── manifest.yml # 发布元数据
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── cspell.config.yaml # 英文拼写检查
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── .gitattributes # Git 文件属性
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── .gitmodules # 第三方端口子模块
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── .git-blame-ignore-revs # blame 时忽略格式化提交
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── *SPDX2.3.spdx* # 多种格式的 SBOM
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── GitHub-FreeRTOS-Kernel-Home.url
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── Quick_Start_Guide.url # 网页快捷方式
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;看起来还是不少，但真正的内核主体其实就是根目录那几个 &lt;code&gt;.c&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="根目录的-7-个-c"&gt;根目录的 7 个 &lt;code&gt;.c&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;tasks.c&lt;/code&gt; 是调度器主体。任务创建、删除、延时、优先级、就绪与阻塞状态，最后都绕不开它。应用开发者先理解任务状态怎么变化就够了；真要维护内核，再进去研究各条链表怎么转。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;queue.c&lt;/code&gt; 不只实现 Queue。Semaphore、Mutex、Recursive Mutex、Queue Set 也复用了它的队列结构。应用层最值得先搞懂的是阻塞超时和 Mutex 的优先级继承。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;list.c&lt;/code&gt; 只有两百多行，却是内核调度的地基。就绪任务、延时任务、挂起任务都要挂到链表上。应用不会直接调用它，内核维护者则必须把它看明白。&lt;/p&gt;
&lt;p&gt;上面三个是所有端口都要带的通用内核文件。剩下四个按功能看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;timers.c&lt;/code&gt;：软件定时器和 Timer Service Task；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;event_groups.c&lt;/code&gt;：事件组；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stream_buffer.c&lt;/code&gt;：Stream Buffer 和 Message Buffer；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;croutine.c&lt;/code&gt;：旧式协程，新项目基本不用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;官方 CMake 会把这 7 个文件都放进内核目标，再靠配置宏和链接器裁掉没启用的部分。所以这里说“按需使用”，不是让人刚入门就随手删源码。&lt;/p&gt;
&lt;h2 id="include平时调用的-api-都在这儿"&gt;&lt;code&gt;include/&lt;/code&gt;：平时调用的 API 都在这儿
&lt;/h2&gt;&lt;p&gt;这个目录大部分是声明和宏，不是另一份内核实现。应用最常碰到的是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FreeRTOS.h 总入口，读取 FreeRTOSConfig.h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;task.h 任务、延时、通知、调度器 API
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;queue.h 队列和 Queue Set
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;semphr.h 信号量和互斥锁
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;timers.h 软件定时器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;event_groups.h 事件组
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stream_buffer.h Stream Buffer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;message_buffer.h Message Buffer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;下面这些就更偏内核和移植：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;list.h 内核链表，应用不要直接依赖
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;portable.h 内核和端口层之间的接口
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;projdefs.h 通用类型、返回值和基础定义
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stack_macros.h 栈溢出检查宏
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;atomic.h 原子操作宏
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;剩下的 &lt;code&gt;mpu_*.h&lt;/code&gt; 是 MPU 端口用的，&lt;code&gt;newlib-freertos.h&lt;/code&gt;、&lt;code&gt;picolibc-freertos.h&lt;/code&gt; 负责 C 库适配；&lt;code&gt;StackMacros.h&lt;/code&gt; 和 &lt;code&gt;deprecated_definitions.h&lt;/code&gt; 主要照顾旧工程。现在知道它们大概在哪儿就行，不用每个都点开拜读。&lt;/p&gt;
&lt;p&gt;应用层可以正常包含公共 API，但不要为了“适配板子”去改内核头文件。芯片和板级差异应该留在端口、中断入口和自己的 &lt;code&gt;FreeRTOSConfig.h&lt;/code&gt; 里。&lt;/p&gt;
&lt;h2 id="portable真正和芯片接上的地方"&gt;&lt;code&gt;portable/&lt;/code&gt;：真正和芯片接上的地方
&lt;/h2&gt;&lt;p&gt;这个目录最大不是因为它最复杂，而是 FreeRTOS 要同时照顾 GCC、IAR、Keil，以及 Cortex-M、RISC-V、AVR、PIC 等一大群组合。基本规则就是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;portable/&amp;lt;编译器&amp;gt;/&amp;lt;CPU 架构&amp;gt;/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;我们现在用的是 &lt;code&gt;STM32F407VET6 + arm-none-eabi-gcc&lt;/code&gt;。F407 是带 FPU 的 Cortex-M4，所以选：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;portable/GCC/ARM_CM4F/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── port.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── portmacro.h
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;port.c&lt;/code&gt; 负责初始任务栈、启动第一个任务，以及 SysTick、PendSV、SVC 这些和上下文切换有关的东西。&lt;code&gt;portmacro.h&lt;/code&gt; 则定义栈类型、字节对齐、临界区和任务切换宏。&lt;/p&gt;
&lt;p&gt;其他 CPU 和编译器目录都不用编译。你不是在做一个全架构发行版，没必要把整个 &lt;code&gt;portable/&lt;/code&gt; 一锅端进工程。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;portable/MemMang/&lt;/code&gt; 下面还有 5 个堆实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;heap_1.c&lt;/code&gt;：只能分配，不能释放，最简单；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;heap_2.c&lt;/code&gt;：能释放但不合并相邻空闲块，新项目一般不选；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;heap_3.c&lt;/code&gt;：包装 C 库的 &lt;code&gt;malloc()&lt;/code&gt; 和 &lt;code&gt;free()&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;heap_4.c&lt;/code&gt;：能释放，也会合并空闲块，普通 MCU 工程最常用；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;heap_5.c&lt;/code&gt;：能力类似 &lt;code&gt;heap_4.c&lt;/code&gt;，但支持多个不连续内存区。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;动态分配时只能选一个。这里先用 &lt;code&gt;heap_4.c&lt;/code&gt;，别把五个一起编进去开会。&lt;/p&gt;
&lt;h2 id="那些不会进固件的文件"&gt;那些不会进固件的文件
&lt;/h2&gt;&lt;p&gt;根目录剩下的东西也简单过一下，免得每次看到都怀疑是不是漏编了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.github/&lt;/code&gt;：CI、测试、PR 模板和贡献流程；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;examples/&lt;/code&gt;：CMake 示例、Coverity/MISRA 示例和 &lt;code&gt;FreeRTOSConfig.h&lt;/code&gt; 模板；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CMakeLists.txt&lt;/code&gt;：官方构建入口；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;README.md&lt;/code&gt;、&lt;code&gt;History.txt&lt;/code&gt;、&lt;code&gt;LICENSE.md&lt;/code&gt;：说明、历史和许可证；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MISRA.md&lt;/code&gt;：MISRA C:2012 的合规范围和偏离理由；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;manifest.yml&lt;/code&gt;：发布系统读取的版本和许可证信息；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cspell.config.yaml&lt;/code&gt;：英文拼写检查；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.gitattributes&lt;/code&gt;、&lt;code&gt;.gitmodules&lt;/code&gt;、&lt;code&gt;.git-blame-ignore-revs&lt;/code&gt;：Git 自己用的配置；&lt;/li&gt;
&lt;li&gt;4 个 &lt;code&gt;SPDX2.3&lt;/code&gt; 文件：同一份 SBOM 的文本、JSON、XML、YAML 版本；&lt;/li&gt;
&lt;li&gt;两个 &lt;code&gt;.url&lt;/code&gt;：官方主页和快速入门的网页快捷方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它们都不会被编译器变成 MCU 指令。想给 FreeRTOS 官方仓库提 PR 时再认真研究，现在知道不用塞进 F407 工程就够了。&lt;/p&gt;
&lt;h2 id="f407-工程最后到底拿哪些"&gt;F407 工程最后到底拿哪些
&lt;/h2&gt;&lt;p&gt;绕了一圈，真正需要的东西可以缩成这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FreeRTOS-Kernel/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── tasks.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── queue.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── list.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── timers.c # 开软件定时器时使用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── event_groups.c # 开事件组时使用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── stream_buffer.c # 开 Stream/Message Buffer 时使用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── include/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── portable/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── GCC/ARM_CM4F/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ├── port.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ └── portmacro.h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── MemMang/heap_4.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;自己的工程/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── FreeRTOSConfig.h
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;边界也就清楚了：内核维护者去深究 &lt;code&gt;tasks.c + queue.c + list.c&lt;/code&gt;；做新端口的人研究 &lt;code&gt;port.c&lt;/code&gt;、&lt;code&gt;portmacro.h&lt;/code&gt; 和异常机制；我们现在主要负责选对 ARM_CM4F 端口，再把任务、内存和中断规则写进 &lt;code&gt;FreeRTOSConfig.h&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这个配置文件不在内核根目录，因为它本来就属于具体应用。官方只在 &lt;code&gt;examples/template_configuration/FreeRTOSConfig.h&lt;/code&gt; 放了一份模板。接下来就开始拆它。&lt;/p&gt;
&lt;h2 id="freertosconfigh-配置分类"&gt;&lt;code&gt;FreeRTOSConfig.h&lt;/code&gt; 配置分类
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FreeRTOSConfig.h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 1. 时钟与时间
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 2. 调度器与任务
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 3. 任务附加能力
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 4. 任务通信与同步
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 5. 软件定时器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 6. 内存、堆和任务栈
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 7. 中断与临界区
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 8. Hook 回调
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 9. 故障检测、断言、调试统计
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 10. C 库、POSIX 兼容
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 11. 旧协程
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 12. MPU、TrustZone、安全隔离
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 13. 多核 SMP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── 14. API 裁剪开关
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;先说好，这 14 类不是让你全背下来。第一版真能把人折磨疯的，其实就时钟、中断优先级、任务栈、堆和几个调试开关。剩下那些 MPU、TrustZone、多核，看见名字很高级，但和现在这颗 F407 没啥关系，先别给自己加戏。&lt;/p&gt;
&lt;p&gt;我们还是一组一组过，不然后面报错了连该骂谁都不知道。&lt;/p&gt;
&lt;h3 id="1-时钟与时间"&gt;1. 时钟与时间
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;configCPU_CLOCK_HZ&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;configSYSTICK_CLOCK_HZ&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;configTICK_RATE_HZ&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;configTICK_TYPE_WIDTH_IN_BITS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;configUSE_TICKLESS_IDLE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这组就是告诉 FreeRTOS：CPU 到底跑多快，一秒钟产生多少个 Tick。&lt;/p&gt;
&lt;p&gt;最容易写错的是 &lt;code&gt;configCPU_CLOCK_HZ&lt;/code&gt;。它填的是&lt;strong&gt;启动调度器时真正生效的 HCLK&lt;/strong&gt;，不是芯片宣传页上的最高主频。假如时钟还没配，现在实际跑的是 16 MHz，那就老老实实填 16 MHz。你代码根本没切到 168 MHz，却在这里写个 168 MHz，后面的延时自然全是歪的。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;configSYSTICK_CLOCK_HZ&lt;/code&gt; 只有在 SysTick 和 CPU 时钟不同源时才需要单独写。现在两边一样就别重复定义，省得以后改了一个忘了另一个。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;configTICK_RATE_HZ&lt;/code&gt; 第一版用 &lt;code&gt;1000&lt;/code&gt; 就行，也就是 1 Tick = 1 ms。写延时的时候别偷懒：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;vTaskDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pdMS_TO_TICKS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;直接写 &lt;code&gt;vTaskDelay(10)&lt;/code&gt; 看起来很爽，但哪天 Tick 频率一改，这个 10 就不一定还是 10 ms 了。&lt;/p&gt;
&lt;p&gt;F407 用 32 位 Tick：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_32_BITS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;configUSE_TICKLESS_IDLE&lt;/code&gt; 是低功耗用的，空闲时把周期 Tick 停掉。第一版先关，系统都还没跑稳就别急着省那点电。&lt;/p&gt;
&lt;h3 id="2-调度器与任务"&gt;2. 调度器与任务
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_PREEMPTION 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_TIME_SLICING 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configMAX_PRIORITIES 5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configIDLE_SHOULD_YIELD 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;configUSE_PREEMPTION = 1&lt;/code&gt; 就是抢占式调度。高优先级任务一旦就绪，不需要等低优先级任务自己良心发现让出 CPU，直接抢过来运行。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;configUSE_TIME_SLICING = 1&lt;/code&gt; 是让同优先级任务轮流跑。两个任务优先级一样，又都不阻塞，那就每个 Tick 换一次。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;configMAX_PRIORITIES&lt;/code&gt; 也不是越大越牛逼。第一版 5 级其实就够分了：控制、采样通信、普通业务、显示日志、Idle。你一上来开几十个优先级，最后只会得到一张自己都看不懂的任务关系网。&lt;/p&gt;
&lt;p&gt;ARM_CM4F 可以打开 &lt;code&gt;configUSE_PORT_OPTIMISED_TASK_SELECTION&lt;/code&gt;，让内核用 Cortex-M 的指令快速找最高优先级任务。不过开了以后 &lt;code&gt;configMAX_PRIORITIES&lt;/code&gt; 不能超过 32，这点记住就行。&lt;/p&gt;
&lt;h3 id="3-任务附加能力"&gt;3. 任务附加能力
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configMAX_TASK_NAME_LEN 16
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configMINIMAL_STACK_SIZE 128
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_APPLICATION_TASK_TAG 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;任务名长度设 16，调试器里能看清名字就行。通知槽第一版一个也够用，线程局部指针和 Task Tag 暂时都关掉。&lt;/p&gt;
&lt;p&gt;重点还是 &lt;code&gt;configMINIMAL_STACK_SIZE&lt;/code&gt;。这个 &lt;code&gt;128&lt;/code&gt; 不是 128 字节。ARM_CM4F 的 &lt;code&gt;StackType_t&lt;/code&gt; 是 32 位，所以这里实际是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;128 word × 4 byte = 512 byte
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;而且它主要是 Idle Task 的栈大小，不代表所有任务都照着 128 抄。任务里要是有大数组、浮点计算、&lt;code&gt;printf&lt;/code&gt;，栈很快就能给你吃干净。后面一定要看栈水位，不能凭感觉拍一个数字。&lt;/p&gt;
&lt;h3 id="4-任务通信与同步"&gt;4. 任务通信与同步
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_TASK_NOTIFICATIONS 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_MUTEXES 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_RECURSIVE_MUTEXES 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_COUNTING_SEMAPHORES 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_QUEUE_SETS 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_EVENT_GROUPS 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_STREAM_BUFFERS 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这一组看着多，其实就是任务之间怎么说话。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Task Notification：一对一通知，快而且省内存，能用它就先用它。&lt;/li&gt;
&lt;li&gt;Queue：真的有数据要传，比如传一帧传感器结构体，就用队列。&lt;/li&gt;
&lt;li&gt;Semaphore：做事件同步或者资源计数。&lt;/li&gt;
&lt;li&gt;Mutex：保护共享资源，它有优先级继承，不要拿二值信号量硬顶。&lt;/li&gt;
&lt;li&gt;Event Group：用几个 bit 同时等多个事件。&lt;/li&gt;
&lt;li&gt;Stream Buffer：连续字节流，串口接收这类场景比较常见。&lt;/li&gt;
&lt;li&gt;Message Buffer：底层还是 Stream Buffer，但是会保留消息边界。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Queue、Semaphore、Mutex 都主要在 &lt;code&gt;queue.c&lt;/code&gt; 里；Event Group 需要 &lt;code&gt;event_groups.c&lt;/code&gt;；Stream 和 Message Buffer 需要 &lt;code&gt;stream_buffer.c&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;中断里又是另一套规矩，只能调用带 &lt;code&gt;FromISR&lt;/code&gt; 的接口：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;BaseType_t&lt;/span&gt; &lt;span class="n"&gt;xHigherPriorityTaskWoken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pdFALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;vTaskNotifyGiveFromISR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xTaskHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;xHigherPriorityTaskWoken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;portYIELD_FROM_ISR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xHigherPriorityTaskWoken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;最后这个 &lt;code&gt;portYIELD_FROM_ISR()&lt;/code&gt; 不是装饰品。中断唤醒了更高优先级任务，就应该让它在退出中断后马上运行。&lt;/p&gt;
&lt;h3 id="5-软件定时器"&gt;5. 软件定时器
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;configUSE_TIMERS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;configTIMER_TASK_PRIORITY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;configTIMER_TASK_STACK_DEPTH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;configTIMER_QUEUE_LENGTH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;软件定时器开启以后，FreeRTOS 会多创建一个 Timer Service Task。所有软件定时器到期，最终都是这个任务来执行回调。&lt;/p&gt;
&lt;p&gt;所以它和硬件定时器完全不是一回事。回调不是在某个神秘的精准时刻凭空执行，它得排队，还会受 Timer Task 优先级和其他回调耗时影响。&lt;/p&gt;
&lt;p&gt;第一版不用就直接：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_TIMERS 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;要用就把 &lt;code&gt;timers.c&lt;/code&gt; 编进来，再配置任务优先级、栈和命令队列长度。回调里别延时，别疯狂打印，更别塞一大坨计算。一个回调卡住，后面的定时器全跟着排队。&lt;/p&gt;
&lt;h3 id="6-内存堆和任务栈"&gt;6. 内存、堆和任务栈
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configSUPPORT_STATIC_ALLOCATION 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configSUPPORT_DYNAMIC_ALLOCATION 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configTOTAL_HEAP_SIZE (32 * 1024)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configAPPLICATION_ALLOCATED_HEAP 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configENABLE_HEAP_PROTECTOR 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configHEAP_CLEAR_MEMORY_ON_FREE 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里先按 &lt;code&gt;heap_4.c + 动态分配&lt;/code&gt; 来。&lt;code&gt;heap_4.c&lt;/code&gt; 能释放，也会合并相邻空闲块，对这种普通 MCU 工程够用了。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;configTOTAL_HEAP_SIZE&lt;/code&gt; 是 FreeRTOS 自己的堆，动态创建出来的任务栈、TCB、队列和信号量都从这里拿内存。它和链接脚本里的 C 库堆不是一回事，也不会自动接管普通 &lt;code&gt;malloc()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;上面写 32 KB 只是一个起步例子，不是说所有工程都必须 32 KB。最后到底要多少，得把每个任务栈、队列长度、对象数量都算进去，再配合：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;xPortGetFreeHeapSize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;xPortGetMinimumEverFreeHeapSize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;uxTaskGetStackHighWaterMark&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里还有个经典坑：&lt;code&gt;configTOTAL_HEAP_SIZE&lt;/code&gt; 的单位是&lt;strong&gt;字节&lt;/strong&gt;，但是 &lt;code&gt;xTaskCreate()&lt;/code&gt; 的栈深度在 ARM_CM4F 上通常是 32 位 &lt;strong&gt;word&lt;/strong&gt;。两个数字放在一起很像，单位完全不一样。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;configAPPLICATION_ALLOCATED_HEAP = 0&lt;/code&gt; 表示 &lt;code&gt;ucHeap[]&lt;/code&gt; 还是由 &lt;code&gt;heap_4.c&lt;/code&gt; 自己定义；设成 1 以后就得由应用提供。任务栈单独放进另一块内存、释放时清零、堆指针保护这些东西第一版也先关，真有对应需求再开。&lt;/p&gt;
&lt;p&gt;静态分配以后再单独讲。它不是把开关改成 1 就结束了，Idle Task 的静态内存得自己提供；如果开了软件定时器，Timer Task 的内存也一样。&lt;/p&gt;
&lt;h3 id="7-中断与临界区"&gt;7. 中断与临界区
&lt;/h3&gt;&lt;p&gt;这一组真得认真看。前面那些东西配错，大不了功能不对；中断优先级配错，经常是平时能跑，一上负载就随机爆炸，最恶心。&lt;/p&gt;
&lt;p&gt;STM32F407 有 4 个 NVIC 优先级位。这里按 4 位全部用于抢占优先级、不分子优先级来配：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configPRIO_BITS 4
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configKERNEL_INTERRUPT_PRIORITY \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; (configLIBRARY_LOWEST_INTERRUPT_PRIORITY &amp;lt;&amp;lt; (8 - configPRIO_BITS))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY &amp;lt;&amp;lt; (8 - configPRIO_BITS))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configCHECK_HANDLER_INSTALLATION 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Cortex-M 的优先级数字越小，实际优先级反而越高，这个反人类设计先接受。&lt;/p&gt;
&lt;p&gt;按上面这套配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;优先级 0 ~ 4 不会被 FreeRTOS 临界区挡住，但禁止调用 FreeRTOS API
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;优先级 5 ~ 15 可以调用对应的 xxxFromISR() API
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;优先级 15 给 SysTick 和 PendSV，放到最低
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;configMAX_SYSCALL_INTERRUPT_PRIORITY&lt;/code&gt; 千万不能填 0。还有，CMSIS 里写的优先级 5 是没左移的逻辑值，FreeRTOS 这里要的是左移后写进寄存器的值。网上很多教程两个混着写，抄完还能跑只能说命硬。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;configCHECK_HANDLER_INSTALLATION = 1&lt;/code&gt; 会检查 SVC 和 PendSV 有没有正确挂进向量表，但它依赖 &lt;code&gt;configASSERT&lt;/code&gt;。这两个要一起开，不然新版内核会直接编译报错。&lt;/p&gt;
&lt;h3 id="8-hook-回调"&gt;8. Hook 回调
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_IDLE_HOOK 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_TICK_HOOK 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_MALLOC_FAILED_HOOK 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_DAEMON_TASK_STARTUP_HOOK 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_SB_COMPLETED_CALLBACK 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Hook 就是 FreeRTOS 在几个固定时机回调你写的函数。&lt;/p&gt;
&lt;p&gt;Idle Hook 会跟着 Idle Task 跑，Tick Hook 每个 Tick 都会进，而且是在中断上下文里。第一版都先关，尤其别在 Tick Hook 里打印日志，那是在给自己制造新问题。&lt;/p&gt;
&lt;p&gt;但是 &lt;code&gt;configUSE_MALLOC_FAILED_HOOK&lt;/code&gt; 我建议调试阶段直接打开：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;vApplicationMallocFailedHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;taskDISABLE_INTERRUPTS&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(;;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;内存分配失败以后停在这里，至少调试器能告诉你是堆没了。不开的话，错误可能继续往后传，最后在一个毫不相干的地方炸掉，到时候又要查半天。&lt;/p&gt;
&lt;h3 id="9-故障检测断言与统计"&gt;9. 故障检测、断言与统计
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configCHECK_FOR_STACK_OVERFLOW 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configGENERATE_RUN_TIME_STATS 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_TRACE_FACILITY 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_STATS_FORMATTING_FUNCTIONS 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configQUEUE_REGISTRY_SIZE 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;configCHECK_FOR_STACK_OVERFLOW = 2&lt;/code&gt; 会检查任务栈末尾的填充值有没有被踩。开了以后还要实现：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;vApplicationStackOverflowHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TaskHandle_t&lt;/span&gt; &lt;span class="n"&gt;xTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pcTaskName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后是 &lt;code&gt;configASSERT&lt;/code&gt;，调试阶段别关：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configASSERT(x) \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; do \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; { \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; if ((x) == 0) \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; { \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; taskDISABLE_INTERRUPTS(); \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; for (;;) \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; { \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; } \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; } \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt; } while (0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;FreeRTOS 很多错误不是靠返回一个 &lt;code&gt;error&lt;/code&gt; 告诉你，而是靠断言把程序钉在现场。中断优先级配错、参数不合法、Handler 没挂好，都可能从这里抓出来。&lt;/p&gt;
&lt;p&gt;运行时间统计、Trace、格式化任务列表先关。它们当然有用，但运行时间统计还需要额外的高频计数器，格式化函数又会把字符串处理带进来。先把系统跑起来，后面需要分析 CPU 占用时再开。&lt;/p&gt;
&lt;h3 id="10-c-库与-posix-兼容"&gt;10. C 库与 POSIX 兼容
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_NEWLIB_REENTRANT 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_POSIX_ERRNO 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configENABLE_BACKWARD_COMPATIBILITY 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这一组第一版全关。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;configUSE_POSIX_ERRNO&lt;/code&gt; 只是让每个任务有自己的 &lt;code&gt;FreeRTOS_errno&lt;/code&gt;，不是说打开以后 FreeRTOS 就突然变成 Linux 了。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;configUSE_NEWLIB_REENTRANT&lt;/code&gt; 会给每个任务塞一份 Newlib 重入状态，内存开销也会跟着涨。而且它不等于“从此 &lt;code&gt;printf&lt;/code&gt; 随便多线程调用都安全”。串口日志最好还是统一丢给一个日志任务，或者至少用互斥锁保护。&lt;/p&gt;
&lt;p&gt;新工程也没必要兼容一堆旧 API 名字，所以 &lt;code&gt;configENABLE_BACKWARD_COMPATIBILITY&lt;/code&gt; 关掉。&lt;/p&gt;
&lt;h3 id="11-旧协程"&gt;11. 旧协程
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_CO_ROUTINES 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configMAX_CO_ROUTINE_PRIORITIES 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这个东西是给 RAM 极小的老设备准备的。它没有普通任务那样的独立栈，走的还是合作式调度。&lt;/p&gt;
&lt;p&gt;F407 又不是穷到这点内存都没有，没必要为了省一点 RAM 同时养两套调度模型。关掉，也不用编译 &lt;code&gt;croutine.c&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="12-mputrustzone-与安全隔离"&gt;12. MPU、TrustZone 与安全隔离
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;configENABLE_MPU
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;configENABLE_TRUSTZONE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;configRUN_FREERTOS_SECURE_ONLY
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;configENABLE_FPU
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这几个宏出现在通用模板的 ARMv8-M 配置区里。F407 是 ARMv7-M Cortex-M4F，没有 TrustZone，所以不要照着模板把它们全抄进来。&lt;/p&gt;
&lt;p&gt;F407 自己有 MPU，但我们现在选的是标准 &lt;code&gt;portable/GCC/ARM_CM4F&lt;/code&gt;，不是 &lt;code&gt;ARM_CM4_MPU&lt;/code&gt;。只定义一个 &lt;code&gt;configENABLE_MPU&lt;/code&gt; 没用，真正做 MPU 隔离还得换端口、划内存区域、区分特权任务和非特权任务，这已经是下一阶段的东西了。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;configTOTAL_MPU_REGIONS&lt;/code&gt; 和 &lt;code&gt;configUSE_MPU_WRAPPERS_V1&lt;/code&gt; 也都是 MPU 端口才会认真处理的东西。标准 ARM_CM4F 端口下先不用管。&lt;/p&gt;
&lt;p&gt;至于 FPU，选中 &lt;code&gt;ARM_CM4F&lt;/code&gt; 端口并使用正确的浮点编译参数后，端口会负责浮点上下文；这里也不用额外定义 &lt;code&gt;configENABLE_FPU&lt;/code&gt;。这个宏主要是给模板里对应的 ARMv8-M 端口准备的。&lt;/p&gt;
&lt;h3 id="13-多核-smp"&gt;13. 多核 SMP
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configNUMBER_OF_CORES 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configRUN_MULTIPLE_PRIORITIES 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define configUSE_CORE_AFFINITY 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;STM32F407 就一个 Cortex-M4F 核心，这组没什么好纠结的。核心数设 1，亲和性关掉，定时器任务绑哪个核这种问题也和我们没关系。&lt;/p&gt;
&lt;p&gt;所以 &lt;code&gt;configTASK_DEFAULT_CORE_AFFINITY&lt;/code&gt;、&lt;code&gt;configTIMER_SERVICE_TASK_CORE_AFFINITY&lt;/code&gt; 这些宏可以直接忽略，它们是多核开启以后才有意义。&lt;/p&gt;
&lt;p&gt;官方模板是给所有端口看的，所以里面出现 SMP 不代表 F407 突然长出了第二个核。&lt;/p&gt;
&lt;h3 id="14-api-裁剪开关"&gt;14. API 裁剪开关
&lt;/h3&gt;&lt;p&gt;最后这组 &lt;code&gt;INCLUDE_*&lt;/code&gt; 不是在选择调度模式，它只是决定某个 API 要不要编进来。&lt;/p&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_vTaskDelay 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;开了以后才能用 &lt;code&gt;vTaskDelay()&lt;/code&gt;。第一版我会先保留这些：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_vTaskDelay 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_xTaskDelayUntil 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_vTaskDelete 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_xTaskGetSchedulerState 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_xTaskGetCurrentTaskHandle 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_uxTaskGetStackHighWaterMark 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;周期任务尽量用 &lt;code&gt;xTaskDelayUntil()&lt;/code&gt;，它是按固定时间点唤醒，比“运行完再延时一段时间”更适合做稳定周期。&lt;/p&gt;
&lt;p&gt;下面这些没有明确需求就先关：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_vTaskSuspend 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_eTaskGetState 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_xTaskAbortDelay 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_xTaskGetHandle 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_xTimerPendFunctionCall 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define INCLUDE_xTaskResumeFromISR 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;尤其别把 &lt;code&gt;vTaskSuspend()&lt;/code&gt; 当成万能同步工具。任务之间有事件关系，就老老实实用通知、信号量或者队列，不然代码跑着跑着就不知道是谁把谁挂起了。&lt;/p&gt;
&lt;h2 id="总结一下"&gt;总结一下
&lt;/h2&gt;&lt;p&gt;这么一大串看完，其实真正的优先级就几条：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先确认 CPU 真实时钟和 Tick 频率，不然所有时间都是假的。&lt;/li&gt;
&lt;li&gt;把 NVIC 分组和 FreeRTOS 中断优先级配对，搞清楚哪些中断能调用 &lt;code&gt;FromISR&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;打开 &lt;code&gt;configASSERT&lt;/code&gt;、栈溢出检查和 Malloc Failed Hook，让程序死也死得明白。&lt;/li&gt;
&lt;li&gt;算任务栈和 FreeRTOS 堆，别拿“现在还没崩”当内存够用的证据。&lt;/li&gt;
&lt;li&gt;软件定时器、事件组、Stream Buffer 用到哪个再开哪个。&lt;/li&gt;
&lt;li&gt;协程、MPU、TrustZone、SMP 暂时全部放一边。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以 &lt;code&gt;FreeRTOSConfig.h&lt;/code&gt; 不是官方宏大全，更不是把所有功能都设成 1 就完事。它应该老老实实记录这块板子现在跑多快、任务怎么调度、中断能做到什么程度、内存又准备拿出多少。这个文件配明白了，FreeRTOS 才算真的进了自己的工程，而不是从别人的 Demo 里复制过来勉强亮机。&lt;/p&gt;</description></item></channel></rss>