<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>STM32 on Blog</title><link>https://b0weny-qwq.github.io/Blog/tags/stm32/</link><description>Recent content in STM32 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/stm32/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><item><title>RTOS 前传：先把IDE的编译黑盒拆了</title><link>https://b0weny-qwq.github.io/Blog/p/stm32f407-rtos-prequel-build-from-scratch/</link><pubDate>Sun, 16 Aug 2026 01:42:00 +0800</pubDate><guid>https://b0weny-qwq.github.io/Blog/p/stm32f407-rtos-prequel-build-from-scratch/</guid><description>&lt;h1 id="rtos-前传先把ide的编译黑盒拆了"&gt;RTOS 前传：先把IDE的编译黑盒拆了
&lt;/h1&gt;&lt;p&gt;之前其实学过 &lt;code&gt;RTOS&lt;/code&gt;，但那时候纯纯把它当黑盒来用：会创建任务、会调度，能跑起来就完事了。&lt;code&gt;CMake&lt;/code&gt; 也差不多，自己甚至没怎么手写过。&lt;/p&gt;
&lt;p&gt;我发现自己每次开学，工程能力都会 &lt;code&gt;++&lt;/code&gt;；每次一放长假，又会再进化一次。原因可能也很简单：假期里有时间把那些最基础、最底层的东西重新磨一遍。刀磨了一整个假期，开学当然砍得更快。&lt;/p&gt;
&lt;p&gt;所以接下来准备更新一个新篇章，重新学 &lt;code&gt;RTOS&lt;/code&gt;。这次以 &lt;code&gt;STM32F407VET6&lt;/code&gt; 为例，不急着上操作系统，先从最基础的工程开始：补齐启动文件、系统初始化文件和链接脚本，再亲手写好 &lt;code&gt;CMake&lt;/code&gt;，让它真的编译起来。&lt;/p&gt;
&lt;p&gt;这一篇就是前传。看懂它最好有一点汇编语言和微机原理基础，不然中间可能会有点吃力。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;本系列均使用编辑器 + CLI，拒绝一切丑陋的 IDE，比如 Keil CubeMX。用 Keil 的你一辈子没法成长 ,就等着找底薪工作吧。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="先把官方仓库拉下来"&gt;先把官方仓库拉下来
&lt;/h2&gt;&lt;p&gt;先克隆 ST 官方的 &lt;a class="link" href="https://github.com/STMicroelectronics/STM32CubeF4" target="_blank" rel="noopener"
 &gt;STM32CubeF4&lt;/a&gt; 仓库。里面有一堆库、驱动和例程，也有我们需要的启动文件。&lt;/p&gt;
&lt;p&gt;&lt;img alt="STM32CubeF4 官方仓库" class="gallery-image" data-flex-basis="410px" data-flex-grow="171" height="1454" 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-rtos-prequel-build-from-scratch/image/index/github%E4%BB%93%E5%BA%93cubef4.png" srcset="https://b0weny-qwq.github.io/Blog/p/stm32f407-rtos-prequel-build-from-scratch/image/index/github%E4%BB%93%E5%BA%93cubef4_hu_3e452eec8af28110.png 800w, https://b0weny-qwq.github.io/Blog/p/stm32f407-rtos-prequel-build-from-scratch/image/index/github%E4%BB%93%E5%BA%93cubef4_hu_59e2e166f862b0b4.png 1600w, https://b0weny-qwq.github.io/Blog/p/stm32f407-rtos-prequel-build-from-scratch/image/index/github%E4%BB%93%E5%BA%93cubef4_hu_d41ac23b509940bb.png 2400w, https://b0weny-qwq.github.io/Blog/p/stm32f407-rtos-prequel-build-from-scratch/image/index/github%E4%BB%93%E5%BA%93cubef4.png 2487w" width="2487"&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;/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;gh repo clone STMicroelectronics/STM32CubeF4 -- --depth &lt;span class="m"&gt;1&lt;/span&gt; --recursive
&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;相比完整克隆，体积能小一半以上，快多了。没必要为了三个文件把整个 Git 历史请回家供着。&lt;/p&gt;
&lt;p&gt;接下来把工具配齐：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;arm-none-eabi-gcc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gdb-multiarch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cmake&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ninja&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openocd&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我自己都安装过了，这里就不演示了。&lt;/p&gt;
&lt;h2 id="一个最小工程需要什么"&gt;一个最小工程需要什么
&lt;/h2&gt;&lt;p&gt;&lt;img alt="STM32F407 最小工程" class="gallery-image" data-flex-basis="248px" data-flex-grow="103" height="952" 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-rtos-prequel-build-from-scratch/image/index/%E6%9C%80%E5%B0%8F%E5%B7%A5%E7%A8%8B.png" srcset="https://b0weny-qwq.github.io/Blog/p/stm32f407-rtos-prequel-build-from-scratch/image/index/%E6%9C%80%E5%B0%8F%E5%B7%A5%E7%A8%8B_hu_da6a6d5165f331d9.png 800w, https://b0weny-qwq.github.io/Blog/p/stm32f407-rtos-prequel-build-from-scratch/image/index/%E6%9C%80%E5%B0%8F%E5%B7%A5%E7%A8%8B.png 985w" width="985"&gt;&lt;/p&gt;
&lt;p&gt;先从 CubeF4 里复制三个关键文件：&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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;F407_RTOS_EXAMPLE/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── Startup/startup_stm32f407xx.s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── Platform/system_stm32f4xx.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── linker/STM32F407VETX_FLASH.ld
&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;STM32F407VGTX_FLASH.ld&lt;/code&gt;。它也能拿来改，但一定要注意：&lt;code&gt;F407VGT6&lt;/code&gt; 有 &lt;code&gt;1 MB Flash&lt;/code&gt;，这里使用的 &lt;code&gt;F407VET6&lt;/code&gt; 只有 &lt;code&gt;512 KB Flash&lt;/code&gt;。复制之后要把链接脚本改对，最好顺手重命名，免得哪天把自己也骗了。&lt;/p&gt;
&lt;p&gt;最基础的文件结构大概这样：&lt;/p&gt;
&lt;p&gt;&lt;img alt="最小工程必备文件" class="gallery-image" data-flex-basis="358px" data-flex-grow="149" height="354" 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-rtos-prequel-build-from-scratch/image/index/%E5%BF%85%E5%A4%87%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84.png" width="529"&gt;&lt;/p&gt;
&lt;p&gt;这几个文件到底是干啥的？这就要从编译开始说了。之前说用 IDE 的永远不会成长，原因就在这里：一点 &lt;code&gt;Build&lt;/code&gt;，后面的东西全黑了。你只知道没报错，牛逼，可以烧录；或者突然蹦出一个 &lt;code&gt;error&lt;/code&gt;，卧槽，牛魔的，快修改。&lt;/p&gt;
&lt;p&gt;但我们写的一堆 &lt;code&gt;.c&lt;/code&gt;、&lt;code&gt;.h&lt;/code&gt;，到底是怎么变成 MCU 能执行的机器码的？大致流程其实就这一条：&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;/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;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;.o 目标文件
&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;ELF
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ objcopy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BIN / HEX
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ OpenOCD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;STM32 Flash
&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;h2 id="arm-none-eabi-这一家子"&gt;&lt;code&gt;arm-none-eabi&lt;/code&gt; 这一家子
&lt;/h2&gt;&lt;p&gt;先简单认一下工具链，后面报错时至少知道该骂谁：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;工具&lt;/th&gt;
 &lt;th&gt;作用&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arm-none-eabi-gcc&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;编译 C，也可以统一调用汇编器和链接器&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arm-none-eabi-as&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;把汇编代码变成&lt;code&gt;.o&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arm-none-eabi-ld&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;把一堆&lt;code&gt;.o&lt;/code&gt; 链接成 ELF&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arm-none-eabi-objcopy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;把 ELF 转成 BIN 或 HEX&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arm-none-eabi-objdump&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;看段信息和反汇编&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arm-none-eabi-nm&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;看符号和地址&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arm-none-eabi-size&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;看 Flash 和 RAM 占用&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;arm-none-eabi-gdb&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;调试正在运行的程序&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;arm-none-eabi&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;/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;arm → 目标架构是 ARM
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;none → 不针对某个操作系统
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;eabi → 使用嵌入式应用二进制接口
&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;至于 MCU 是哪个内核，则由编译参数告诉 GCC，比如 F407 是 &lt;code&gt;Cortex-M4&lt;/code&gt;，F1 常见型号是 &lt;code&gt;Cortex-M3&lt;/code&gt;。&lt;code&gt;-mcpu=cortex-m4 -mthumb&lt;/code&gt; 这类参数就该出现在工具链或 CMake 配置里。&lt;/p&gt;
&lt;p&gt;真正决定“代码放到 Flash 哪儿、变量放到哪块 RAM”的，是下面这个文件。&lt;/p&gt;
&lt;h2 id="1-链接脚本告诉链接器这个家有多大"&gt;1. 链接脚本：告诉链接器这个家有多大
&lt;/h2&gt;&lt;p&gt;文件：&lt;code&gt;linker/STM32F407VETX_FLASH.ld&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;链接脚本干的事很直接：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;老子是什么型号？RAM、Flash 多大？每一段程序到底放哪里？&lt;/p&gt;

 &lt;/blockquote&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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ENTRY(Reset_Handler)
&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;Reset_Handler&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_estack = ORIGIN(RAM) + LENGTH(RAM);
&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&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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MEMORY
&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; CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;这里必须和手上的芯片对得上。写小一点一般只是浪费空间，写大了却可能把程序链接到根本不存在的 Flash 里，到时候就不是“为什么没亮”这么温柔了。&lt;/p&gt;
&lt;p&gt;所以 IDE 里选择芯片型号，不只是让界面左上角多一串字；它背后也在替你选启动文件、链接脚本和编译参数。IDE 帮你做了不代表这些东西不存在，只是以前它们被藏起来了。&lt;/p&gt;
&lt;p&gt;一句话总结：&lt;code&gt;arm-none-eabi-ld&lt;/code&gt; 负责链接，&lt;code&gt;.ld&lt;/code&gt; 负责告诉它怎样安排 STM32 的内存。&lt;/p&gt;
&lt;h2 id="2-startup_stm32f407xxs上电后第一棒"&gt;2. &lt;code&gt;startup_stm32f407xx.s&lt;/code&gt;：上电后第一棒
&lt;/h2&gt;&lt;p&gt;第二个关键文件是：&lt;code&gt;Startup/startup_stm32f407xx.s&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它有两重身份。构建时，它先被汇编成目标文件，再和 &lt;code&gt;main.o&lt;/code&gt;、&lt;code&gt;system_stm32f4xx.o&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;/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;startup_stm32f407xx.s
&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;startup_stm32f407xx.o
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 和其他 .o 一起链接
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;app.elf
&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;STM32 Flash
&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;/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;.isr_vector 中断向量表
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.text.Reset_Handler 复位启动代码
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.text.Default_Handler 默认中断处理代码
&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;.ld&lt;/code&gt; 文件，把向量表和代码放进 Flash。&lt;/p&gt;
&lt;p&gt;不过，CPU 并不是从 &lt;code&gt;.s&lt;/code&gt; 文件第一行开始往下读。像 &lt;code&gt;.syntax&lt;/code&gt;、&lt;code&gt;.cpu&lt;/code&gt;、&lt;code&gt;.section&lt;/code&gt;、&lt;code&gt;.word&lt;/code&gt; 这些，大多是给汇编器看的指令或者数据，不是 CPU 上电后逐句执行的代码。&lt;/p&gt;
&lt;p&gt;假设 &lt;code&gt;BOOT0 = 0&lt;/code&gt;，芯片从内部 Flash 启动，硬件会先读取向量表前两项：&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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0x08000000 的 32 位数 → 装进 MSP，成为初始栈顶
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0x08000004 的 32 位数 → 装进 PC，跳到复位入口
&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-asm" data-lang="asm"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nl"&gt;g_pfnVectors:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;.word&lt;/span&gt; &lt;span class="no"&gt;_estack&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;.word&lt;/span&gt; &lt;span class="no"&gt;Reset_Handler&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;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;/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;上电 / 复位
&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;CPU 读取 _estack
&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;CPU 读取 Reset_Handler 地址
&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;跳进 Reset_Handler
&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;SystemInit → 初始化 .data/.bss → main
&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;所以更准确的说法不是“CPU 从 startup 第一行开始执行”，而是：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;CPU 根据 startup 生成的向量表找到 &lt;code&gt;Reset_Handler&lt;/code&gt;，再开始执行它生成的复位代码。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;到这里，&lt;code&gt;.s&lt;/code&gt; 文件也没那么吓人了。它本质上就是在 C 世界开门之前，先把栈、内存和入口准备好。&lt;/p&gt;
&lt;h2 id="3-system_stm32f4xxc终于回到舒适区"&gt;3. &lt;code&gt;system_stm32f4xx.c&lt;/code&gt;：终于回到舒适区
&lt;/h2&gt;&lt;p&gt;最后一个关键文件是 &lt;code&gt;Platform/system_stm32f4xx.c&lt;/code&gt;。唯一一个 &lt;code&gt;.c&lt;/code&gt;，是不是瞬间亲切多了？&lt;/p&gt;
&lt;p&gt;这个文件来自 CMSIS，处在 startup 和 &lt;code&gt;main()&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;/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;Reset_Handler
&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;SystemInit() ← system_stm32f4xx.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;初始化 .data/.bss
&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;main()
&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-asm" data-lang="asm"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bl&lt;/span&gt; &lt;span class="no"&gt;SystemInit&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;startup.o&lt;/code&gt; 自己没有这个函数。链接时，&lt;code&gt;ld&lt;/code&gt; 会去 &lt;code&gt;system_stm32f4xx.o&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;/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;SystemInit&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SystemCoreClockUpdate&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="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;SystemCoreClock&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;h3 id="systeminit"&gt;&lt;code&gt;SystemInit()&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;它会在复位后、进入 &lt;code&gt;main()&lt;/code&gt; 前运行，主要负责把 RCC 恢复到一个确定的基础状态、打开 HSI、处理 FPU 访问权限，以及按配置设置向量表或外部存储器。&lt;/p&gt;
&lt;p&gt;它一般不会替你完成这些事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;配置最终的 168 MHz 主频&lt;/li&gt;
&lt;li&gt;初始化 GPIO 和串口&lt;/li&gt;
&lt;li&gt;初始化 HAL&lt;/li&gt;
&lt;li&gt;初始化 FreeRTOS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些还是后面的板级初始化代码来干。&lt;/p&gt;
&lt;p&gt;这时候就有看到这里的吴彦祖、彭于晏困惑了：我不是已经在 CubeMX 的时钟树里把主频改成 168 MHz 了吗，怎么这里还是 16 MHz？&lt;/p&gt;
&lt;p&gt;因为 CubeMX 生成的时钟配置，是进入后续初始化阶段才执行的。&lt;code&gt;SystemInit()&lt;/code&gt; 只是先把芯片送回一个大家都认识的起点，然后你的代码再去开 HSE、配 PLL、切系统时钟。&lt;/p&gt;
&lt;h3 id="systemcoreclock"&gt;&lt;code&gt;SystemCoreClock&lt;/code&gt;
&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;/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;uint32_t&lt;/span&gt; &lt;span class="n"&gt;SystemCoreClock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16000000&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;它只是一个记录当前 HCLK 的软件变量，很多地方会拿它计算 &lt;code&gt;SysTick&lt;/code&gt;、RTOS Tick、串口波特率和定时器参数。&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="n"&gt;SystemCoreClock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;168000000&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;并不会让 MCU 原地超频到 168 MHz，只会让软件以为它已经到了。真正的时钟还是得改 RCC 寄存器，然后调用 &lt;code&gt;SystemCoreClockUpdate()&lt;/code&gt; 重新读取并计算。&lt;/p&gt;
&lt;h3 id="hse_value"&gt;&lt;code&gt;HSE_VALUE&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;HSE_VALUE&lt;/code&gt; 表示软件认为外部晶振是多少 Hz：&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="cp"&gt;#ifndef HSE_VALUE
&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 HSE_VALUE 25000000U
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#endif
&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;这不代表程序已经启用了 25 MHz HSE，更不代表你板子上真的焊了 25 MHz 晶振。板子是 8 MHz，就应该通过编译宏传入正确的值：&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-cmake" data-lang="cmake"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;target_compile_definitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt; &lt;span class="s"&gt;PRIVATE&lt;/span&gt; &lt;span class="s"&gt;HSE_VALUE=8000000U&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&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;p&gt;还有一个容易漏掉的点：这份启动文件会在 &lt;code&gt;.data&lt;/code&gt; 复制和 &lt;code&gt;.bss&lt;/code&gt; 清零之前调用 &lt;code&gt;SystemInit()&lt;/code&gt;。所以它不能随便依赖已经初始化好的普通全局变量、堆或者完整的 C 运行库，主要还是直接操作硬件寄存器。&lt;/p&gt;
&lt;p&gt;一句话总结：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;startup 负责把 C 程序的运行环境搭起来，&lt;code&gt;system_stm32f4xx.c&lt;/code&gt; 负责把 Cortex-M4 和 STM32F4 拉回一个确定的基础状态，后面的 Platform 代码再配置这块板真正需要的时钟和外设。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="最后交给-cmake"&gt;最后交给 CMake
&lt;/h2&gt;&lt;p&gt;必备文件当然还有 &lt;code&gt;main.c&lt;/code&gt;，这我就懒得说了。剩下主要是两个和 CMake 有关的文件：&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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;arm-none-eabi-gcc.cmake # 指定交叉编译工具链和 CPU 参数
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CMakeLists.txt # 描述源文件、头文件、链接脚本和产物
&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;工具链文件告诉 CMake 去哪里找 &lt;code&gt;arm-none-eabi-gcc&lt;/code&gt;，目标是 &lt;code&gt;Cortex-M4&lt;/code&gt;，要使用什么浮点 ABI；&lt;code&gt;CMakeLists.txt&lt;/code&gt; 再把 startup、system、main 和链接脚本串起来，最后顺手生成 &lt;code&gt;.elf&lt;/code&gt;、&lt;code&gt;.bin&lt;/code&gt;、&lt;code&gt;.hex&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;具体 CMake 我这里就不展开了。了解基本逻辑之后完全可以让 AI 写，再自己检查参数和路径。这里还要顺便吐槽一下 py：这种东西简单到极致，有啥难度，用的时候学就行了，我完全不知道刻意学习的意义在哪里。&lt;/p&gt;
&lt;p&gt;写好之后，工程结构如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="完整工程结构" class="gallery-image" data-flex-basis="159px" data-flex-grow="66" height="843" 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-rtos-prequel-build-from-scratch/image/index/%E5%B7%A5%E7%A8%8B%E7%BB%93%E6%9E%84.png" width="559"&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;cmake -S . -B build -G Ninja
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmake --build build
&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;恭喜你亲手拆开了 IDE 的编译黑盒。下一步再往里面塞 &lt;code&gt;RTOS&lt;/code&gt;，至少这次知道它到底站在什么东西上面了。牛逼吧。&lt;/p&gt;</description></item><item><title>华为比赛复盘：差一点赶不上飞机，也差一点打得更好</title><link>https://b0weny-qwq.github.io/Blog/p/huawei-competition-2026-review/</link><pubDate>Fri, 10 Jul 2026 00:20:00 +0800</pubDate><guid>https://b0weny-qwq.github.io/Blog/p/huawei-competition-2026-review/</guid><description>&lt;h1 id="华为比赛复盘差一点赶不上飞机也差一点打得更好"&gt;华为比赛复盘：差一点赶不上飞机，也差一点打得更好
&lt;/h1&gt;&lt;p align="center"&gt;
 &lt;img src="奖状.jpg" alt="奖状" width="60%"&gt;
&lt;/p&gt;
&lt;p&gt;回忆一下 &lt;code&gt;7.4&lt;/code&gt; 的华为比赛。&lt;/p&gt;
&lt;p&gt;事情从 &lt;code&gt;7.3&lt;/code&gt; 晚上就开始离谱了。那天晚上十点半左右，我一度真以为自己要赶不上飞机了，结果突然发现航班延期到了第二天凌晨一点。只能说，还好还好。&lt;/p&gt;
&lt;p&gt;本来想着到机场之后，或者上飞机之后，还能再复习一点比赛相关的东西。结果刚坐上去，整个人就被期末熬夜攒下来的疲惫直接击昏，进入昏迷模式。再醒过来的时候，已经快到南京机场了，时间差不多是凌晨两点半。&lt;/p&gt;
&lt;p&gt;然后又出了一个很蠢的问题。&lt;/p&gt;
&lt;p&gt;因为换了书包，我完全忘记自己昏迷前把机票和身份证塞到了哪里。下飞机之后翻了半天没找到，还以为落在飞机上了，于是在行李等候区硬等了一个小时。地勤都快劝我走了，结果我刚坐上网约车，就发现东西其实一直在书包的一个小口袋里。&lt;/p&gt;
&lt;p&gt;只能说，人困到一定程度，智商是真的会掉线。&lt;/p&gt;
&lt;p&gt;最后到酒店洗完澡，已经凌晨四点半了。睡了大概三个小时，第二天就直接去比赛。&lt;/p&gt;
&lt;h2 id="比赛本身"&gt;比赛本身
&lt;/h2&gt;&lt;p&gt;这次华为比赛给我的第一感觉，是它比我预想中正式很多。&lt;/p&gt;
&lt;p&gt;一开始我对这类企业比赛多少有点刻板印象，总觉得可能只是喊大学生来做点便宜方案，或者做一场偏宣传性质的活动。但实际体验下来，华为的硬件终端和赛题设计都挺认真，至少不是随便糊一个题目出来应付学生。&lt;/p&gt;
&lt;p&gt;可惜最大的问题还是状态。&lt;/p&gt;
&lt;p&gt;因为前面一直被期末周压着，我们其实没能充分准备。赛题整体围绕 &lt;code&gt;STM32F103&lt;/code&gt; 开发，一个方向偏硬件，涉及激光雷达后端信号、运放和数据处理；另一个方向偏软件，主要是触控笔的无线充电和软件开发。&lt;/p&gt;
&lt;p&gt;我们比较不幸，抽到了第一个方向。&lt;/p&gt;
&lt;p&gt;现场十六强队伍水平都还可以，基本是 &lt;code&gt;211&lt;/code&gt;、&lt;code&gt;985&lt;/code&gt; 的研究生，甚至还有博士生，不少人还是 &lt;code&gt;DSP&lt;/code&gt;、电机控制相关方向的。这个阵容放在那里，压力还是很明显的。&lt;/p&gt;
&lt;h2 id="现场做题"&gt;现场做题
&lt;/h2&gt;&lt;p&gt;第一问是恢复高电压输出。&lt;/p&gt;
&lt;p&gt;本质上其实就是 &lt;code&gt;EN&lt;/code&gt; 没开。那个 &lt;code&gt;Boost&lt;/code&gt; 电路做得挺厉害，可以从 &lt;code&gt;5.5V&lt;/code&gt; 升到 &lt;code&gt;85V&lt;/code&gt; 左右，高压输出恢复得还算轻松。&lt;/p&gt;
&lt;p&gt;但后面的串口打印 &lt;code&gt;ADC&lt;/code&gt; 数据就开始卡了。&lt;/p&gt;
&lt;p&gt;一方面是 &lt;code&gt;CubeIDE&lt;/code&gt; 用得确实不多，临场切环境本来就不顺手；另一方面是 &lt;code&gt;HAL&lt;/code&gt; 库的字节发送接口更偏 &lt;code&gt;8bit&lt;/code&gt; 数据，而 &lt;code&gt;ADC&lt;/code&gt; 返回的是 &lt;code&gt;32&lt;/code&gt; 位结构。怎么把数据正确转换、组织、发送出去，我们现场试了几种方式都没完全打通。&lt;/p&gt;
&lt;p&gt;这部分确实有点难受。&lt;/p&gt;
&lt;p&gt;第二问是实现高压输出电压调节。&lt;/p&gt;
&lt;p&gt;这个相对简单，用 &lt;code&gt;PWM&lt;/code&gt; 模拟 &lt;code&gt;DAC&lt;/code&gt;，影响反馈端 &lt;code&gt;FB&lt;/code&gt;，进而调节高压输出，基本就能解决。中间还被高压电了两下，倒不是特别疼，但确实有点麻。只能说，做这种题还是要对硬件保持敬畏。&lt;/p&gt;
&lt;p&gt;第三问是信号运放问题，要求调整输入输出，让波形尽量不失真。&lt;/p&gt;
&lt;p&gt;这部分更能感受到课本知识和现场工程之间的差距。课本上学过运放，知道一些基本概念，但真正到了现场，要根据实际信号、实际电路、实际现象去调整，完全是另一回事。这一方面感觉还是得学一下。我们的方向其实已经找对了，只是时间不够，没能把结果继续往前推。&lt;/p&gt;
&lt;p&gt;赛后先是下午茶和领取纪念品,之后是答辩,晚饭去园区的海底捞狠狠猪了一把,一桌900额度也可以说是吃到爽哈哈哈。&lt;/p&gt;
&lt;p align="center"&gt;
 &lt;img src="吃饭合照.jpg" alt="吃饭合照" width="60%"&gt;
&lt;/p&gt;
&lt;h2 id="结果和复盘"&gt;结果和复盘
&lt;/h2&gt;&lt;p&gt;整体来说，这次题目不算特别离谱，甚至可以说没有想象中那么难。但因为备赛、环境、状态这些问题叠在一起，最后结果不算理想。&lt;/p&gt;
&lt;p&gt;说白了，还是菜。&lt;/p&gt;
&lt;p&gt;不过没想到的是，这样最后还能拿到第六名。算下来，这应该是我目前技术比赛里拿到的最高奖项了。短时间、高压力、现场出问题的比赛，确实最能看出一个人的综合水平：基础够不够扎实，环境熟不熟，状态扛不扛得住，临场判断稳不稳，全都会暴露出来。&lt;/p&gt;
&lt;p&gt;这次喜提 &lt;code&gt;2k&lt;/code&gt;，虽然其中 &lt;code&gt;1k&lt;/code&gt; 直接变成了机票钱，但至少没给实验室丢脸。&lt;/p&gt;
&lt;p&gt;另外也认识了一些 &lt;code&gt;HR&lt;/code&gt;，聊下来能感觉到他们确实很懂就业，也能从企业视角看到很多学生平时不太会注意的东西。这部分收获也挺重要。&lt;/p&gt;
&lt;p&gt;总的来说，这次比赛有遗憾，但也有价值。&lt;/p&gt;
&lt;p&gt;遗憾是明明可以打得更好，价值是它把很多短板直接摆到了面前：准备不够、工具链不够熟、临场状态不够好，对硬件题的处理经验也还不够。&lt;/p&gt;
&lt;p&gt;就这样。&lt;/p&gt;
&lt;p&gt;来年必须干回来。&lt;/p&gt;</description></item><item><title>工具链搭建完成</title><link>https://b0weny-qwq.github.io/Blog/p/toolchain-bringup-stm32f1-mspm0/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://b0weny-qwq.github.io/Blog/p/toolchain-bringup-stm32f1-mspm0/</guid><description>&lt;h1 id="工具链搭建完成"&gt;工具链搭建完成
&lt;/h1&gt;&lt;p&gt;这段时间主要在折腾自己的嵌入式工具链。到 &lt;code&gt;5.18&lt;/code&gt; 这一天，基本可以算是阶段性搭建完成了。&lt;/p&gt;
&lt;p&gt;以前做嵌入式，很多流程其实是被厂商 &lt;code&gt;IDE&lt;/code&gt;、图形配置工具和各种零散脚本拖着走。能跑是能跑，但每次换平台、换板子、换环境，都像是在重新和工具链打一架。现在我更想把这些东西统一到一套可复现、可调用、可交给 &lt;code&gt;agent&lt;/code&gt; 执行的流程里：构建、烧录、检查、日志、错误定位，都尽量走明确的命令和工程结构。&lt;/p&gt;
&lt;p&gt;这次最重要的成果，是测试成功了两条 &lt;code&gt;ARM&lt;/code&gt; 架构相关的烧录链路。&lt;/p&gt;
&lt;p&gt;一条是经典的 &lt;code&gt;STM32F1&lt;/code&gt;。这基本算是我最熟悉的一类 &lt;code&gt;MCU&lt;/code&gt;，也是很多嵌入式开发者绕不开的起点。能把它从传统开发方式里抽出来，用现代一点的工程结构完成构建和烧录，对我来说像是把老朋友重新接进新的工作流。&lt;/p&gt;
&lt;p&gt;另一条是 &lt;code&gt;TI MSPM0&lt;/code&gt;。相比 &lt;code&gt;STM32F1&lt;/code&gt;，它更像是一个新的切入点：同样是 &lt;code&gt;ARM&lt;/code&gt; 生态，但厂商工具、SDK 组织方式、烧录链路和调试习惯都不完全一样。把 &lt;code&gt;MSPM0&lt;/code&gt; 也跑通，说明这套工具链不是只为某一个芯片临时拼出来的，而是有机会扩展成更通用的嵌入式开发底座。&lt;/p&gt;
&lt;p&gt;从结果看，&lt;code&gt;STM32F1&lt;/code&gt; 和 &lt;code&gt;MSPM0&lt;/code&gt; 两边都能完成烧录验证，这件事让我很兴奋。工程流开始从“某个项目能跑”变成“某类流程能复用”。只要构建、烧录、调试这些基础动作足够稳定，后面再接入更多芯片、板卡和模板，就不用每次都从一片混乱里重新开始。&lt;/p&gt;
&lt;h2 id="下一步认识-zephyr"&gt;下一步：认识 Zephyr
&lt;/h2&gt;&lt;p&gt;工具链跑通之后，我开始对 &lt;code&gt;Zephyr&lt;/code&gt; 这个 &lt;code&gt;RTOS&lt;/code&gt; 产生兴趣。&lt;/p&gt;
&lt;p&gt;以前接触 &lt;code&gt;RTOS&lt;/code&gt;，更多是从裸机工程往上加：任务、调度、信号量、队列、驱动抽象，一层一层把东西堆起来。这样学很扎实，但也容易停留在“我会移植一个系统”或者“我会用一套接口”的阶段。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Zephyr&lt;/code&gt; 给我的感觉不太一样。它不像只是一个单纯的内核，更像一整套面向现代嵌入式工程的系统：设备树、配置系统、构建系统、驱动模型、板级支持、跨平台抽象，这些东西组合在一起，才接近我想理解的“工程化嵌入式”。&lt;/p&gt;
&lt;p&gt;我想认识它，不只是为了会用一个新的 &lt;code&gt;RTOS&lt;/code&gt;，而是想看看成熟的嵌入式系统到底怎么组织复杂性。为什么它能支持那么多板子，为什么驱动可以被抽象成统一模型，为什么配置和构建能被系统化管理，这些问题都值得拆开看。&lt;/p&gt;
&lt;p&gt;工具链搭好只是第一步。烧录跑通代表入口打通了，有意思的事情还在后面：把底层硬件、构建系统、&lt;code&gt;RTOS&lt;/code&gt; 思想和 &lt;code&gt;agent&lt;/code&gt; 工作流接到一起，形成一套自己能长期维护、长期扩展的嵌入式工程体系。&lt;/p&gt;
&lt;p&gt;这是我接下来想继续推进的方向。&lt;/p&gt;</description></item></channel></rss>