1. 决定做这套教程之前,我心里积压了好几年的困惑

说实话,决定花一年时间做一套STM32教程这件事,最初并不是什么宏伟计划。起因特别简单——2023年年初我整理自己收藏夹里的学习资料,发现里面躺着十几个“从零开始学STM32”的视频,但当年我点开它们的时候,没有一个让我真正学会。

不是那些教程讲得不对,而是它们普遍存在三个问题:第一,知识点全是碎的,今天讲GPIO,明天讲串口,后天讲中断,但没人告诉我这些东西怎么拼成一个完整项目;第二,开发环境的坑没人提前说,Keil5装好了却识别不到芯片、驱动装不上、固件包下载慢到怀疑人生,这些破事就能劝退一半新手;第三,就算照着例程把代码敲进去了,板子上的灯也亮了,我还是不知道自己到底干了什么——换个芯片型号、换个引脚、加个功能,立刻就懵。

那段时间我还在做嵌入式开发的本职工作,每天跟STM32F1、F4打交道,看多了“能跑但不知道为啥能跑”的代码,也看多了“明明该出成果却卡在一个低级错误上三天”的年轻人。我越来越觉得,这个领域缺的不是教程数量,而是一门真正把“知识”和“工程”之间的沟填上的课。

后来我在B站发过几条开发笔记,评论区陆续有人问我能不能出系统性教程。我说让我想想,这一想就想了一年。很多人以为花一年做教程是因为内容太多录不完,其实不是。真正的原因是我一直在反复琢磨同一个问题: 一个初学者到底需要什么? 这个问题不解决,录一百集也是浪费时间。

现在这套教程终于做完了,从去年的初夏录到今年的初夏,一共XX集,每集我至少重录三遍,写坏的开发板有三块,烧掉的各种模块数不清。我把这一年里所有的思考、取舍、踩坑和重做写在这篇文章里,希望能帮到正在学或者打算学STM32的朋友,也希望能给想自己做教程的人一点参考。

2. 教程内容架构的基础,是先想清楚“学完能干什么”

很多人拿到一套教程的第一反应是看目录,看讲不讲定时器、讲不讲DMA、讲不讲以太网。但我在设计这套教程的时候,第一反应是另一件事: 学完这套课,一个普通人能独立做出什么东西?

这个问题听起来很虚,实际上决定了一整套课程的下限和上限。如果答案是“能点亮一个LED”,那课程只需要三天就能录完;如果答案是“能做一个四开关buck-boost双向升降压数字电源”,那这套课根本不该叫入门教程,应该叫电力电子专项班。我给自己定的目标是: 学完的人应该能独立完成一个带传感器、带通信、带执行机构的小型嵌入式系统,比如智能鱼缸、数字温湿度计报警器这类完整作品。

这个目标直接把课程分成了三个台阶:底层基础、中间层互联、上层实战。很多人讲嵌入式喜欢一上来就讲寄存器、讲启动文件、讲链接脚本,我也讲过,但我把它们放在很靠后的位置。为什么?因为对一个第一次接触单片机的学习者来说,“灯亮了”的成就感,比“我理解了AHB总线的时钟树”重要得多——兴趣一旦没了,后面学什么都白搭。

2.1 环境搭建花了整整两集的篇幅,这是很多人忽略的关键点

在所有学习STM32的痛点里,环境搭建是最容易被人轻视、却最能劝退新人的环节。我统计过自己评论区早期提问,有将近四成问题跟代码本身没关系,全卡在环境上:芯片包装不上、Keil5破解不成功、驱动识别不到、下载器连不上目标板。

所以这套教程的第一集不讲任何技术,就干一件事——把开发环境从头到尾装干净。这里我特别想分享一个很多教程不会讲的细节: Keil5安装STM32芯片包时,很多人走着走着就停了,不是因为下载慢,而是因为不知道要区分包名。 F1系列对应的是Keil.STM32F1xx_DFP,F4系列对应的是Keil.STM32F4xx_DFP,你装F1的包去打F4的代码,工程永远编译不过。这个我在教程里专门截了图,用红框圈出来,因为我自己当年就在这卡了两个小时。

驱动的坑也不少。很多新买的开发板用的是DAP-Link或者CH340串口芯片,Win10以上的系统通常能自动识别,但有些板子用的是老的CP2102,或者某些需要手动安装驱动的山寨版ST-Link,系统死活不给过。这时候很多人第一反应是“板子坏了”,其实只要装一个驱动精灵或者去芯片官网下一个官方驱动就解决了。

还有一类常见问题是——下载器连不上芯片,提示“No target connected”或者“Cannot access target”。这种问题八成不是硬件坏了,而是BOOT引脚跳线不对,或者下载器接线接触不良。我在教程里专门做了一张错误检查和解决顺序的表格:先看电源灯亮不亮,再看设备管理器里有没有识别到调试器,再检查接线有没有接反,最后才考虑芯片锁死的情况。这一步一步查下来,九成问题都能自己解决。

2.2 GPIO、定时器、串口之外,我还加了三节“工程思维课”

基础外设的讲法上,我没有走“每个外设单独成章”的教科书路线,而是把它们分组打包,每一组都围绕一个“真实任务”来展开。比如GPIO不是单独讲一遍推挽输出和开漏输出的区别,而是直接带着做“按键控制LED亮灭”,在这个任务里自然引出上拉电阻、下拉电阻、消抖和输入模式的选择。

但比外设本身更重要的是,我在每章末尾都塞了一节“工程思维课”。这些内容很短,每节十几分钟,但我觉得是这套课程中最容易被低估的部分。

举个例子,我在GPIO那一章里讲了“ 芯片选型的基本思路 ”。很多人学完STM32F103C8T6之后,误以为所有项目都应该用这个芯片,其实不一定。如果你的项目只有十几个IO需求、不需要太多定时器,那STM32F030可能更合适——便宜一半还不止。反过来说,如果要做电机控制和数字电源这种需要高速ADC和多路高级定时器的项目,那还得看F3系列甚至F4系列。这种选型意识,学校里不教,但工作中每天都在用。

还有一个很重要的观点是“ 数据手册是第一手的老师 ”。现在很多学习者的习惯是:遇到问题先百度,或者先问AI,很少有人主动去翻芯片参考手册。其实STM32的参考手册写得相当清楚,每一个外设的框图、寄存器位定义、时序图全都有。学定时器的时候,照着参考手册里的框图看懂一遍时钟源选择,比背十遍“定时器有四种计数模式”有效得多。

3. 为了让代码能在“别人的板子”上跑通,我反复做了三件事

录制教程最痛苦的事情不是讲错要重录,而是 代码在录制现场能跑,一到观众手里就出问题 。这种问题几乎无法避免,因为STM32型号太杂了,市面上便宜的“最小系统板”用的FLASH容量、SRAM大小、晶振频率都有区别。同一份代码在F103C8T6上编译链接好好的,换到F103RCT6上可能就报错溢出,换到F103ZET6上可能引脚冲突。

我在整套教程的后期专门做了大量“移植适配”工作。每集示例代码我都标明了适用的芯片型号和最小系统板类型,默认用最便宜的STM32F103C8T6(也就是俗称的“蓝板”),因为这是绝大多数初学者手里买的那块板子。同时我也写了说明,教大家怎么把工程切换到F103RCT6或F103ZET6——主要是改Device里面的芯片型号、改Target选项卡里的Flash容量和C/C++宏定义,再把启动文件换掉,这一套操作流程在教程第9集做了完整演示。

但这只是第一层适配。更烦人的是 板载晶振不同导致的串口波特率不准 。有些板子用的是8MHz晶振,有些是12MHz,还有些小厂板子在PCB上放了一颗16MHz晶振。ST官方库默认8MHz,你拿着12MHz晶振的板子跑默认配置,最直接的现象就是串口打印乱码,或者定时器计时明显偏快偏慢。我专门做了一张表格,列出常用晶振频率下RCC配置需要改哪个参数,很多观众反馈“原来我的串口乱码是这个问题”,这让我觉得专门花一集讲这个非常值。

还有一类问题来自 代码编写习惯 。做教程过程中我发现,很多初学者喜欢把全部功能写在main.c一个文件里,几百行代码往那一丢,功能是能实现,但可读性极差,想改个阈值都得翻半天。所以从第4集开始,我强制引导大家建立模块化思维——每加一个外设,就对应建一个.c和.h文件,把初始化函数和业务逻辑分离。前期稍微麻烦一点,后期项目复杂了以后,你会感谢这种“不舒服”。

3.1 示例代码的“坏味道”是故意留的,接下来再带大家填坑

这里我要坦白一个不少同行看了可能会觉得奇怪的做法: 我的示例代码是故意写得“不够好”的。

什么意思呢?比如学习串口接收的时候,很多人喜欢直接在主循环里用阻塞方式查询接收标志位。这种做法对于Demo来说完全没问题,但不是工程实践中的好写法。我不会一上来就教大家用中断加空闲中断加DMA接收这一套最优雅的解法,因为初学者根本理解不了那么多概念叠加,学完不消化,转头就会忘。

所以我采用“两步走”:第一版先用最简单的阻塞方式把功能跑通,让大家理解串口数据是怎么接收的,存在哪个寄存器里;然后第二版提出问题——“如果接收的时候CPU一直在等,系统是不是就卡住了?”再引出中断接收;最后第三版才讲DMA加空闲中断的高级用法。每一版代码之间不是替换关系,而是演进关系。

这样做有一个很大的好处:观众看完以后,不是只会抄一个“标准答案”,而是理解了这个“标准答案”是从什么问题中演化出来的。我在后台收到过不少评论说“原来DMA是为了解决阻塞才出现的”,我觉得这就是我做第三版代码的意义。

当然,这也大大增加了工作量。每一集至少有一版代码要反复测试,确认它“坏”得恰到好处——既要能暴露问题,又要能正常运行,不能真的把自己卡死。

3.2 硬件玄学问题,我把全部排障过程剪进了正片

录到中后期,我遇到了一堆“理论上根本不可能发生”的问题。比如有个案例是用定时器输出PWM控制舵机,代码逻辑完全没问题,示波器也看了确实有波形,但舵机就是抖个不停。后来排查了一下午,发现是舵机的GND没有和开发板共地,信号线上全是噪音干扰。这种问题在教科书里根本不会写,因为它太“低级”了,但在实际项目中太常见了。

我觉得这种排障过程恰恰是教程最值钱的部分。所以我没有把这些“翻车现场”剪掉,反而全部保留在正片里,每一处都做了复盘,告诉大家我是怎么一步步缩小排查范围的。我的排查方法其实很简单,总结起来就四步:先看电源再看信号,先查硬件再查软件,先跑最小系统再引用全部模块,先怀疑自己写错再怀疑外设芯片坏了。这套方法论虽然简单,但能解决嵌入式开发中八成以上的诡异问题。

类似的经验还有很多:STM32的复位脚不小心被复用成GPIO导致程序无限复位;ADC采集引脚悬空时读到随机数误判为传感器信号;I2C上拉电阻没焊导致通信时好时坏;用STM32直接驱动5V的LCD模块而没做电平转换导致读回数据异常。这些坑每一行代码都查不出问题,但系统就是不对。你只能通过系统性的排查方法去定位。

4. 实战项目我选了四个方向,每一个都有明确的“技能升级点”

整套教程的最后一部分是四个完整的实战项目。在选题上我纠结了很久,因为要同时满足三个条件:做出来有成就感、覆盖前面学过的知识点、不能太复杂导致观众做不完。

最终定下来的四个项目分别是智能鱼缸、数字温湿度计报警器、基于STM32的逆变器方案、以及一个四开关buck-boost双向升降压数字电源的入门版。前两个偏入门,后两个偏进阶,难度之间留了合理的梯度。

智能鱼缸这个项目很有代表性,它把传感器、LCD显示、按键交互、继电器控制、定时任务这些基础模块全部串起来了。你第一次会发现,代码的量级从“外设Demo”变成了“一个小系统”,因为鱼缸需要同时读取温度、水位、光照,还要控制加热棒、水泵、LED灯,各个模块之间的配合顺序和优先级,是新手普遍会遇到的第一个坎。这个项目的代码架构我写了整整三天才理顺,因为要把所有功能组织成合理的状态机,而不是所有人都在main函数里挤成一团。

数字温湿度计报警器看起来简单,但它有一个特别好的学习点—— 如何把“检测到异常”转换成“让人注意到异常” 。这里我用到了DHT11温湿度传感器、蜂鸣器、RGBLED灯、以及可调阈值的按键菜单,还加了一个小功能:当温度超过阈值时,系统会每秒自动刷新显示并蜂鸣报警,直到人工确认解除。这里面就涉及到了一个工程里非常重要的概念——报警事件的状态管理。什么时候允许再次触发、消音之后要不要重报、报警优先级怎么排序,这些都是在入门教程里几乎不会讲到的东西。

逆变器方案和buck-boost数字电源这两个进阶项目,说实话已经超出了“入门”范畴,后台不少观众问我是不是标题党。我在这里想解释一下:这两个项目并不是让大家直接照抄就能做出一个能并网的逆变器,而是把复杂的电力电子系统拆成了“STM32能干的那些活”来学——PWM生成、死区插入、ADC采样触发同步、PID闭环控制、LQR状态反馈控制的基本代码框架。真正的功率部分我讲得很克制,因为它涉及到硬件电路、磁性元件设计、高压安全问题,这不是一套MCU教程能覆盖的。

但即便只是“单片机视角”的简化版,这两个项目也足以让学习者感受到工业级控制的复杂度。尤其是四开关buck-boost那个项目,光是把双向同步整流的PWM逻辑讲清楚,我就录了两集,因为它在纯软件层面就涉及到了四个开关管的导通时序、两个方向的电流路径、以及BUCK模式和BOOST模式切换时的连续性。这些都是学完基础知识后真正拉开差距的内容。

4.1 为什么我会在教程中选“鱼缸”而不是选“智能小车”

选题过程中,很多朋友建议我做智能小车,因为网络上的成品和资料最多,受众也广。但我最终没有选它,原因是智能小车这个方向存在一个致命问题—— 它太容易被外观和效果蒙蔽了 。你把小车跑起来,拍个视频发到网上,点赞很高,但你真的学到东西了吗?不一定,因为小车的核心难度在电机驱动和运动控制,而这部分大多数教程本质上是在调用现成库,图形化拼积木。

鱼缸这个项目不一样。它表面上看起来只是几个传感器加继电器,但它训练的是另外一套能力—— 多任务协同和状态机设计 。你需要在功能之间做取舍:当温度传感器读数异常的时候,是继续加热还是立刻停止?当显示刷新和按键扫描冲突的时候,优先级怎么排?这些问题没有标准答案,但每个人思考过以后,代码的“味道”就完全不同了。

这是我做教程一直坚持的选型原则: 优先选那些能让人“想事情”的项目,而不是让人“抄代码”的项目。

5. 评论区里反复出现的几个问题,恰恰说明教程哪里做得还不够

做教程最大的收获不是播放量,而是评论区里那些五花八门的问题。我把过去一年的提问做了分类统计,发现几个高频问题特别值得展开说说,也顺便回应一下那些对我教程的争议和质疑。

5.1 “我照着教程做怎么还是不行”——关于复现失败的思考

最让我揪心的评论是:“UP主我每一步都照着做了,为什么还是不行?”这种问题背后通常不是观众不用心,而是教程默认了很多上下文,而这些“默认事项”其实并不普及。比如电源、线材、杜邦线老化导致接触不良,这类硬件上的隐性差异,新手根本没概念。

所以我在第一版教程发布后,花了大量时间补充“前置信息”,每集开头加了一段“本集需要提前准备什么”,把开发板的型号、使用的调试器、需要外接的模块、传感器型号全部明确列出。我还做了一张兼容性对照表,什么型号的板子搭配什么传感器可能需要改哪行代码,放在配套文档里供大家自查。

即便如此,仍然有个别观众提醒我,“教程里用的是XX型号的传感器,我手头的是YY型号,针脚定义不一样,能不能通用?”这些反馈让我意识到,教程要尽可能做成“思路教学”而不是“跟练视频”。所以在后期录制的视频里,我会刻意加入“如果换一个传感器,要怎么查数据手册确认接线”的环节,让学习者掌握举一反三的方法,而不是背一套固定的接线图。

5.2 “Keil5装不上”——这类环境问题占了提问里的一大半

统计结论很直接: 所有提问中,环境搭建类问题占了很大比重,具体症状五花八门,但根因高度集中。

最常见的几个场景,我在这里集中说一次:第一,Keil5安装完成后,菜单里找不到目标芯片,这是芯片包没装或者装错版本。第二,编译报错“cannot open source input file xxx.h”或者“device has no compile option”,这是工程设备型号选错了,新建工程时没有选对具体的芯片型号。第三,点击Download后提示“Flash Timeout. Reset the Target and try it again”,大多是程序里关了调试端口,或者下载器接线没接好。第四,Win11系统下DAP-Link连不上,需要把设备管理器的驱动强制更新为WinUSB驱动,我自己在录制的时候也卡过一次。

我把这些高频问题做成了专栏,放在教程合集的最前面,算是一个“排障前置包”。有意思的是,这个专栏发布之后,环境类提问的比例明显下降了不少,说明很多人不是不想自己查,而是不知道自己遇到的问题是“常见问题”,总以为自己的电脑出了独有故障。

5.3 关于“学完这一套课程,能不能进阶到更专业的领域”

还有一类问题来自正在准备毕业论文或者找工作的朋友,比如“学完能不能做毕业设计”“要不要再学一下单片机通信接口”“车载以太网跟平时用的以太网区别大不大”“SNMP在嵌入式设备上怎么实现”。这些问题其实已经超越了我这套教程的范围,但恰恰说明观众有进一步深入的需求。

STM32的价值从来不只是学会一款芯片,而是建立一套“看手册、查时序、写驱动、调接口、排故障”的能力模型。当你具备这套底层能力以后,不管后面面对的是K210这种AI芯片、树莓派这种Linux主机、还是工业现场的各种总线设备,你都能快速迁移过去。K210和STM32之间的通信、STM32控制伺服电机485通信、STM32 HR4988步进电机驱动,这些具体场景背后用到的技术无外乎串口、SPI、I2C、CAN再加一点协议解析的功底,而这些基础恰恰是整套教程覆盖的范围。

所以如果你问我的教程学完够不够做毕业设计,我的答案是: 基础肯定够了,剩下的就是具体项目的深入。 教程里教的GPIO操作、定时器中断、串口DMA、看门狗复位、功耗考虑,这些招式在绝大部分毕业设计和入门工作中都够用。至于更专业的算法(比如LQR、卡尔曼滤波)和更复杂的通信协议栈,那是下一个阶段的事情,需要的不是另一套视频课,而是一颗愿意啃文档和调试日志的心。

6. 制作这套教程过程中,我自己也被重塑了一遍

这一年的制作周期,对我自己来说也是一次彻底的重塑。最初我只是想“分享知识”,但做了一半以后,我意识到“分享知识”和“教会别人”之间隔着一整条河。

录第一版视频的时候,我语速很快,术语用得信手拈来,寄存器名字一口气念出五六个不带喘。后来我拿给一个完全零基础的朋友试看,他在第3分钟的时候问我:“什么是复用功能?为什么GPIO还要复用?”那一刻我才意识到,我讲话的对象应该是他,而不是当初刚毕业时拿到了STM32官方手册、花了一周才连点亮LED的自己。

所以整套教程在第三集以后几乎推翻重录了一遍,重新定义了“零基础”的粒度:什么是寄存器、什么是位带操作、什么是时钟、为什么单片机需要时钟,全部用最通俗的比喻来讲。时钟我比喻成“人的心跳”,复位我比喻成“电脑重启一下”,中断嵌套比喻成“正打着电话又来了个更重要的电话,你得先处理那个”。这些比喻也许在专业领域不够严谨,但我的目的是让学习者先把概念建立起来,再慢慢精确化。

另一个重塑发生在“承认模糊”这件事上。做教程以前,我回答问题喜欢直接给结论。但这一年里我发现, 对一个学习者来说,知道“为什么”比知道“是什么”重要得多。 有观众问:“为什么我配置了两个串口,第二个串口总是收不到数据?”我没有直接说“你忘记使能时钟了”,而是带着他一步步查:先看GPIO复用寄存器有没有配,再看串口的时钟有没有使能,最后看中断有没有开启,每一步都解释清楚为什么这么查。这种“延迟给答案”的教学方式一开始让我很焦虑,担心观众觉得啰嗦,但后来收到的反馈表明,这种方式的学习效果明显更好。

现在整套教程完结了,我回头看,最满意的不是某一集讲得多精彩,而是整条学习路径走下来,几乎每一个关键节点都有“如果这里出错了会有什么现象,原因是什么,怎么排查”的完整闭环。我希望观众学完以后,脑海里留下的不是一串代码碎片,而是一套遇到问题时的思考路径。

最后分享一个小经验给也想做教程的朋友:录教程之前,找一位真正零基础的朋友,把你第一集的视频脚本讲给他听,能听懂才算过关。如果他听不懂,不是他笨,是你的脚本还没准备好。这一条,是我这一年最深的体会。

更多推荐