
做项目这些年从点亮一枚LED到调通USB、CAN、FreeRTOS回头再看“STM32简介”这四个字其实最想说的不是规格书里的参数表而是这片芯片为什么能承包这么多人的第一块开发板、第一个毕业设计、第一款量产原型。如果你正准备入门嵌入式或者已经买了板子却不知道从哪里着手这篇文章会用一套完整的实操主线——从认引脚、搭环境到定时器、串口、USB再到FreeRTOS和调试排障带你真正把STM32用起来。STM32能做什么本质上它就是一个带丰富外设的Cortex-M内核单片机GPIO控制、定时器测频率、串口通信、USB设备、CAN总线、ADC采集、I2C/SPI接传感器几乎所有电子竞赛、智能硬件、工业控制场景它都能覆盖。适合谁来学无论是刚学完C语言想碰硬件的大学生还是工作中需要快速出原型的技术人员顺着这篇文章的路线走至少能少走一个月的弯路。1. 从一块最小系统板认识STM32它到底能干什么1.1 STM32是什么凭什么成为项目标配STM32是意法半导体基于ARM Cortex-M内核推出的一大系列单片机名字里的“32”指32位数据处理。和8位机比如老牌51相比它最大的区别不只是“位数高”而是外设资源的丰富度和开发方式的现代化多路定时器、多路ADC、USART、SPI、I2C、USB、CAN、DMA再加上中断控制器和低功耗模式一颗芯片就能顶起一个完整的产品原型。很多初学者问同样是单片机为什么学校、竞赛、招聘都默认提STM32我理解有三个核心原因。第一生态太成熟了从标准外设库到HAL库再到STM32CubeMX图形化配置几乎每一步都有官方工具兜底网上资料和开源项目密度极高遇到问题搜一下基本都有答案。第二价格和性能的平衡点选得很好以最经典的STM32F103C8T6为例几块钱到十几块钱的价格能拿到72MHz主频、64KB Flash、20KB RAM、多个定时器和通信接口做毕业设计和中小型产品完全够用。第三系列覆盖足够广从低功耗的L系列、高性能的H7系列到带无线功能的WB系列同一个写代码的思路可以平滑迁移学一次受益很久。选型层面如果只是入门和学习STM32F103系列是绝对的主流如果要做带屏显、音频或算法处理的界面型项目可以直接看STM32F407带FPU和更快的时钟如果追求极致的低功耗关注STM32L4系列。这里要提醒一句不建议一上来就追最新的H7很多坑都来自过于复杂的内核和外设时钟配置先把F1玩明白后面换型号只是一两天的事。1.2 引脚识别、第一脚确认与最小系统板自检新手最容易问的问题里排在前面的绝对有“STM32芯片第一脚怎么确认”。别看这是个小知识点接反了芯片直接冒烟这种事我在实验室见得太多了。最常见的LQFP封装芯片表面有一个圆形凹点或者斜切角有凹点的那一角就是第一脚所在的位置。把芯片摆正让凹点朝左上角那么从左下角开始逆时针数依次是第1脚、第2脚一直数到最后一脚就像绕芯片转一圈。如果是没有圆点只有丝印的芯片就找型号丝印的起始方向丝印左上角那一侧通常就是第1脚。还有一种更保险的确认方式打开对应型号的数据手册翻到引脚定义图手册里会用方框明确标出第1脚的位置并把每个引脚的功能写清楚。如果手里只有万用表没有手册也可以先测量对地电阻几乎所有STM32的VDD、VDDA引脚和GND之间都有明显的二极管特性用蜂鸣器档去扫找到多个对GND短路的引脚基本就能圈定电源脚的位置再结合丝印方向确认第1脚。这个方法在捡到散新芯片、没有包装说明时特别有用。最小系统板的自检也有固定套路。上电前先用万用表测VDD和GND之间是否短路正常应该只有电容充放电的微弱变化然后确认3.3V电源、8MHz晶振或25MHz、复位按键和BOOT跳线。第一次烧录不成功时别急着怀疑程序先用示波器看晶振有没有起振检查BOOT0是不是被拉高进入了ISP模式。我见过很多“点不亮”的板子最后都是BOOT0跳线没拨回去这种低级原因。2. 开发环境搭建从Keil到VSCode的完整链路2.1 标准库、HAL库和CubeMX怎么选搭建环境之前先要选一种开发方式这是所有后续代码的地基。目前主流的有三条路线标准外设库、HAL库、LL库。标准库是老工程师的青春寄存器封装程度适中代码直接、执行效率高但官方已经停止更新新芯片不再支持而且复杂外设的初始化代码量不小。HAL库是官方当前主推的配合STM32CubeMX图形化配置点几下鼠标就能生成初始化代码可读性好、跨系列移植方便代价是代码量更大、运行效率略低。LL库则是在HAL库基础上更接近寄存器的轻量封装适合对性能和代码体积有要求的场景。我的建议很直接做项目、做毕业设计、追求快速出结果选HAL库加CubeMX没有悬念。想深入理解底层、日后做驱动移植或者商业产品追求极致效率标准库或LL库都可以。实际工作中大多数人不会只押注一种而是会用CubeMX搭骨架在需要高频执行的底层函数里直接操作寄存器。比如定时器输入捕获的中断回调如果对实时性要求高直接在中断里读寄存器效果比调两层库函数好得多。2.2 Keil5搭建工程与多目标芯片支持Keil MDK目前依然是STM32开发最普及的工具。关于“Keil5兼容C51和STM32”这个说法准确理解是MDK也就是Keil5同时支持ARM内核芯片和8051内核芯片但需要在Pack Installer里分别安装C51和STM32的器件支持包。也就是说一套Keil5装好之后只要把两个Pack都装进同一个安装目录就可以在新建工程时选择芯片厂商和具体型号不需要安装两个不同版本的Keil。新建工程的流程建议固定下来新建文件夹按项目名拆成User、Core、HAL、Drivers、App等子目录。打开Keil5点击Project - New uVision Project选择芯片型号比如STM32F103C8。在Manage Run-Time Environment或手动添加文件时把启动文件、系统时钟文件、HAL库源文件全部归位。配置魔术棒选项卡里的C/C宏定义F1系列通常需要定义USE_STDPERIPH_DRIVER或者STM32F10X_HD取决于你用的是标准库还是HAL库生成的工程模板。配置调试器选择ST-Link或J-Link在Settings里设置Flash Download勾选Reset and Run。芯片包安装是很多人卡壳的一步。在Pack Installer里找到对应厂商的Device Family Pack点击Install即可。如果在线下载慢可以直接去官方站点下载pack文件然后双击安装。安装后如果新建工程看不到芯片检查Keil的安装路径是否包含中文或空格以及Pack文件是否装到了默认目录。2.3 VSCode配置STM32开发环境VSCode现在很多老手也在用主要原因有两个写代码体验比Keil舒服太多Git集成和AI补全也方便。配置STM32开发环境的核心思路是“Keil负责编译下载VSCode负责编辑调试”。最直接的方案是安装EIDE扩展它能直接加载Keil工程文件识别里面的源文件列表、宏定义和头文件路径然后调用Arm Compiler或GCC完成编译。另一个方案是PlatformIO它对STM32的支持已经很成熟会自动下载工具链。调试方面配置Cortex-Debug扩展配合OpenOCD或者J-Link。launch.json里需要指定device名称、interface类型、openocd路径和配置脚本。一个实际可用的配置大概是下面这样{ version: 0.2.0, configurations: [ { name: STM32 Debug, cwd: ${workspaceFolder}, type: cortex-debug, request: launch, servertype: openocd, device: STM32F103C8, configFiles: [ interface/stlink-v2.cfg, target/stm32f1x.cfg ], executable: ${workspaceFolder}/build/your_project.elf, runToEntryPoint: main } ] }如果你用的是J-Link对应把servertype改成jlinkconfigFiles换成JLink的device配置即可。很多人在launch.json上卡住多半是executable路径不对或者OpenOCD版本和配置脚本不兼容换成官方推荐版本基本能解决。2.4 工程模板与芯片包安装实操工程模板这件事我非常建议自己手搓一次哪怕最后还会用CubeMX生成。手搓一次能让你真正明白启动文件是干嘛的、系统时钟怎么配置、中断向量表怎么链接。完整的流程是用CubeMX生成一个最小工程或者直接复制官方示例然后把HAL库的Inc、Src、CMSIS目录移植到自己的文件夹在Keil里手动添加源文件和头文件路径。第一次跑通会花一些时间但之后你会对每个文件的作用都心里有数。芯片包安装的另一个高频坑是版本不匹配。比如从GitHub下载老开源项目时提示缺少某个pack版本不要盲目装最新版优先看项目文档里要求的Keil版本和pack版本。Keil的Pack Installewr支持从本地文件安装把下载到的.pack文件双击或通过Import选择路径即可。卸载旧版本前建议先把当前工程的输出文件保存好避免重装后找不到工程配置。3. 核心外设实操定时器、串口和USB3.1 定时器模式与输入捕获测频率定时器是STM32外设里的“瑞士军刀”功能包括定时中断、PWM输出、输入捕获、输出比较和编码器模式。“stm32定时器模式”怎么选取决于你要做什么。基础定时器只能做时基通用定时器TIM2-TIM5能做PWM和输入捕获高级定时器TIM1、TIM8还能出互补PWM和刹车功能也就是电机控制里说的“stm32刹车”。比如控制电机时刹车功能用来在故障时快速关断PWM输出硬件上直接拉到安全状态比软件干预可靠得多。输入捕获测频率是我最常用到的一个功能。拿STM32F103来说TIM2挂载在APB1总线上如果APB1时钟是72MHz定时器时钟就是72MHz。设置预分频器PSC为71计数频率就变成72MHz/(711)1MHz也就是每1微秒计数一次。然后用捕获通道的上升沿获取当前计数值CCR当第二次上升沿到来时两次计数差值就是信号的周期单位是微秒频率就是1000000除以周期。// 以TIM2_CH1为例测量外部信号频率 void TIM2_CH1_Capture_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // PA0 配置为浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // 预分频71 - 计数频率1MHz TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler 71; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); // 上升沿捕获映射到CH1 TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0; TIM_ICInit(TIM2, TIM_ICInitStructure); TIM_Cmd(TIM2, ENABLE); }需要注意两个细节一个是被测信号幅值要和芯片电平匹配3.3V电平直接进5V需要分压或加电平转换否则容易烧引脚另一个是捕获中断里读取寄存器要及时避免后面数据覆盖前面的值。实际测下来这个方案测几百Hz到几十kHz的方波都很稳。3.2 串口收发与中断处理串口是调试阶段使用率最高的外设“stm32 串口接收”的关键在于正确处理中断。最基础的做法是HAL_UART_Receive_IT开一个单字节接收中断在回调函数HAL_UART_RxCpltCallback里把数据存进缓存。但真实项目里更实用的做法是用空闲中断IDLE判断一帧数据接收完成这样可以接收不定长数据帧比如“AA 55 01 02 FF”这种协议帧。uint8_t rx_buf[128]; volatile uint16_t rx_len 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { // 单字节接收完成继续开启下一个字节接收 HAL_UART_Receive_IT(huart1, temp_char, 1); } }串口调试PID是另一类高频需求做法很简单单片机把角度、速度、目标值等变量按固定格式通过串口发出来上位机用串口助手或Python脚本解析再用matplotlib画曲线。这种方式比看串口日志直观得多。我习惯在中断里加一个超时判断如果接收超时20ms就认为一帧结束这样不必依赖IDLE中断也能处理不定长数据。串口的坑主要集中在波特率失配、接线交叉RX接TX、TX接RX和共地问题。还有一种情况是调试时程序一开串口就进HardFault多半是接收缓存越界或者中断里做了太多耗时操作比如在回调里直接做浮点运算这是典型的反面教材。3.3 把STM32做成USB设备的基本流程“stm32 如何做USB设备”是个门槛较高的热门话题很多人在OS移植或驱动层被劝退。其实用CubeMX做USB设备比想象中简单核心需求是让PC识别到你的芯片并把它当作某种标准USB设备。最常见的做法是把STM32配置成USB虚拟串口CDCPC端会多出一个COM口和普通串口一样收发数据。用CubeMX的流程是打开USB外设选择Device设备模式然后在USB_DEVICE中间件里选择Communication Device ClassVirtual Port Com配置完成后生成代码主函数调用MX_USB_DEVICE_Init()即可。硬件上F103需要48MHz的USB时钟如果外部晶振是8MHzPLL配置要保证能分出48MHz给USB。芯片的USB D和D-引脚直接连到USB座子D上有一个1.5k欧姆上拉电阻大部分开发板已经集成到芯片的USB内部逻辑小部分板子需要外部上拉具体看原理图。我踩过的坑有两个。一是忽略了USB电源检测引脚VBUS“stm32 usb电路”里没有把VBUS检测脚接上导致插上电脑后芯片没有检测到USB插入事件设备枚举失败。二是选择了HID设备后上位机不方便直接发自定义数据每次都要写驱动或使用第三方库而CDC虚拟串口对快速开发更友好。如果只是做调试用CDC是首选。4. 项目中的系统工程从传感器到交互界面4.1 超声波测距、按键与显示类项目的通用套路以“stm32超声波测距”为例项目虽然小但能把GPIO、定时器、中断、串口全串起来。常用的HC-SR04模块有4个引脚VCC、GND、Trig和Echo。触发测距时把Trig引脚拉高至少10微秒模块会发射8个40kHz脉冲然后Echo引脚输出一个与往返时间成正比的高电平。距离的计算公式是距离(cm) 高电平时间(us) / 58。为什么是58因为声速约340m/s往返耗时除以2再除以10000换算成厘米化简后就是1厘米需要约58微秒。// 触发一次测距 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(10); HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 等待Echo高电平测量时间 uint32_t t1 DWT-CYCCNT; while (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_RESET); while (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_SET); uint32_t t2 DWT-CYCCNT; float distance (t2 - t1) * 1.0f / (SystemCoreClock / 1000000) / 58.0f;按键模块的电路设计看起来简单但“stm32按键模块电路设计”里放消抖电容的很多放上拉电阻的不够多。如果按键一端接地、另一端接GPIO就必须开启内部上拉或者外部加上拉电阻否则按键悬空时电平不确定。软件消抖建议用定时器“轮询状态机”而不是阻塞延时否则按键扫描期间整个系统都卡住。显示类项目最常用的是OLEDI2C接口的SSD1306方案布线最少。“stm32 bh1750 oled i2c proteus完整原理图”这类需求本质上是两个I2C设备挂同一总线的场景BH1750光照传感器和OLED可以共用一条I2C总线前提是设备地址不冲突OLED一般是0x3CBH1750是0x23不冲突。接线时注意SCL、SDA各加上拉电阻用Proteus仿真时如果时序不稳定把I2C时钟频率降到50kHz左右一般就能解决。4.2 FreeRTOS与LVGL组合从裸机到多任务项目复杂度上去之后裸机循环会越来越难维护。比如要同时处理超声波测距、OLED刷新、按键扫描和串口上报主循环里每个任务的实时性都受影响。这个阶段用FreeRTOS是自然的选择。“stm32应用freertos”现在已经有官方CubeMX支持直接在中间件里勾选FreeRTOS它会自动生成任务句柄、信号量、队列不再像以前那样手动移植port.c文件。CubeMX生成FreeRTOS工程之后建议按功能把任务拆开传感器任务、显示任务、通信任务各占一个任务任务之间用队列传数据而不是直接用全局变量。全局变量在多任务环境里容易引发竞争问题队列是更安全的方式。任务堆栈大小的配置也有讲究默认128字512字节对简单任务够用但如果任务里有浮点printf或LVGL这类重函数一定要扩到512字或以上否则跑一会儿就会栈溢出。“stm32 移植lvgl”用来做图形界面是另一个热门方向。LVGL对芯片的Flash和RAM有要求个人经验是至少需要几百KB Flash才能跑得像样所以F103这种64KB Flash的芯片只适合跑最简单的DemoF407或H7会更顺手。移植的重点是修改lv_conf.h里的颜色深度、内存大小以及提供一个周期性调用的lv_tick_inc系统时钟节拍函数。第五版到第八版的API差异较大找开源项目时先看清版本号否则编译错误会让人崩溃。4.3 电机控制步进电机、FOC与差速小车电机控制是毕业设计里的常客。“五线四相步进电机stm32”和“stm32控制伺服电机485”这两类需求很典型。五线四相步进电机通常用ULN2003驱动A、B、C、D四相按“A-AB-B-BC-C-CD-D-DA”八拍顺序通电每次切换让转子转半步。代码上就是建一个相位表用定时器中断周期性改变GPIO输出电平。// 五线四相步进电机八拍驱动核心状态表 const uint8_t step_table[8] { 0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09 }; // 每来一次中断index加1取模8即可顺序通电“stm32聚焦FOC代码”就比较硬核了涉及Clarke变换、Park变换、SVPWM和PID电流环一般需要带FPU的M4内核芯片比如F405或F407还要配电流采样和编码器或霍尔传感器。这个方向不建议新手直接从零自己写先跑通官方的电机控制库或者开源方案能省大量时间。两轮差速小车的控制模型也值得一提。“两轮差速小车stm32控制”的核心是根据左右轮速差计算转向公式是左轮速度 (线速度 - 角速度×轮距/2)右轮速度 (线速度 角速度×轮距/2)。实际代码里要把这个换算成两个电机的目标PWM值再用编码器测速做闭环。先开环跑直再上闭环PID最后再加遥控和避障一步步来就不会乱。5. 调试与排障刚从板子上踩出来的十几个坑5.1 JTAG禁用、调试器连不上与IO波形观察“stm32禁用jtag”的需求来源于Stlink的引脚冲突。STM32F103的PA15、PB3、PB4默认是JTAG引脚如果你要把它们当作普通GPIO用必须先禁用JTAG。在标准库里是调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE)在HAL库里则需要配置AFIO的重映射寄存器。注意这个操作会关闭JTAG接口但SWD仍然可用所以不要担心调试口彻底没了。禁用之后这几脚就能当普通IO用了。“vscode stm32调试powerlink如何设置launch.json”这类问题本质就是调试器连不上时排查流程的延伸。连不上调试器的常见原因优先级排序电源没供上 - SWDIO和SWCLK接反了 - 芯片被读保护了 - BOOT0处于1状态 - 调试器驱动不对。排查时先看调试器的指示灯再测目标板电压最后才考虑软件配置。用过ST-Link V2连接F103的目标板经常遇到的现象是“检测不到目标”这时可以尝试把连接速度从4MHz降到1MHz很多抗干扰差的板子就能连上。观察IO输出波形一方面可以用示波器或逻辑分析仪另一方面Keil自带的逻辑分析仪也能看。在Debug模式下打开View - Watch Windows - Logic Analyzer添加变量或寄存器地址右键设置波形类型和位宽就能看到定时器翻转、PWM占空比变化。对没有独立示波器的人来说这算是最省钱的波形查看方式。5.2 延时函数卡死、CAN通信突然失败与I2C乱码“stm32延时函数delay卡死”是死机类问题的常客。卡死的原因主要有三类一是SysTick时基没配置好HAL_Delay依赖SysTick中断禁掉全局中断后调用HAL_Delay会直接卡死二是在中断回调里调用了延时函数由于CPU被中断占住SysTick中断得不到执行形成死锁三是在FreeRTOS任务里用了裸机式的阻塞延时导致低优先级任务饿死。解决办法是检查时基配置、中断回调里只做标记不延时、RTOS环境下统一用vTaskDelay。“stm32 can通信突然连不上”在调试现场很常见。CAN总线是差分信号CANH和CANL两端需要各有一个120欧终端电阻至少总线两端各一个。如果之前通讯正常、突然失败优先检查线缆是不是被拉开了、屏蔽层接地是否良好其次检查波特率是否和另一端一致。STM32的CAN波特率计算公式是APB1时钟 / (1 BS1 BS2) / 预分频任何一个参数错了都连不上。还有一种隐蔽问题程序初始化时把CAN外设时钟关了或重映射配错引脚会导致CAN收发器忙但不发数据。I2C的乱码问题十有八九是上拉电阻缺失或时序太快。STM32硬件I2C在F1系列上口碑一直一般很多人干脆用GPIO模拟I2C。如果你的OLED显示乱码先看地址是不是0x3C再看是不是忘了共地最后考虑把时钟频率降到100kHz以下。显示屏上电后黑屏先查复位引脚和DC引脚的电平很多初始化代码里会默认DCPIN_SET是数据模式跟模块硬件配置不一致就会显示异常。5.3 常见问题速查表现象常见原因排查与解决芯片发烫电源接反或IO短路断电测VDD-GND阻值检查引脚接线无法烧录程序SWD接线错误或芯片读保护调整接线使用ST-Link Utility解除保护串口乱码USB转串口工具不共地或波特率错误确认共地换波特率换线材定时器不工作时钟树没配置APB1/APB2分频检查RCC配置用CubeMX重新生成时钟树CAN连接失败终端电阻缺失或波特率不一致在总线上加120欧电阻核对BS1/BS2OLED无显示I2C地址错误或复位脚未拉高用I2C扫描器读设备地址检查RST引脚程序跑飞数组越界或堆栈溢出检查数组边界增大任务堆栈下载后不运行Flash Download里忘了勾Reset and Run魔术棒Flash Download勾选Reset and Run打开keil工程报错缺pack芯片包版本不匹配从官网下载对应pack双击安装调试时不能设断点优化级别过高魔术棒C/C里把优化级别改为Level 05.4 学会用逻辑分析仪和串口日志定位问题很多人把排障变成猜谜是因为缺少一个全局视角。我的习惯是“串口日志 逻辑分析仪”双管齐下主循环每100ms打印一次关键状态变量GPIO翻转时用逻辑分析仪抓时序。比如超声波Echo信号一直不回来先看Trig有没有按预期触发再看Echo有没有拉高逻辑分析仪一出波形问题出在模块还是代码立刻清楚。Keil的逻辑分析仪代替不了硬件分析仪但看GPIO翻转时序够用。“stm32查看IO输出波形”可以这么操作进入调试模式后在Logic Analyzer窗口添加PA1对应的寄存器地址右键选择Bit类型运行程序波形就出来了。这个功能很多人根本不知道其实特别适合验证PWM频率、串口波特率和周期性任务是否正常。纯软件的坑也不可忽视。“keilc stm32查看IO输出波形”若遇到编译错误提示找不到某某文件多数是头文件路径没配置全。把魔术棒里的C/C Include Paths一项一项检查一遍养成每次加文件就同步添加路径的习惯能少很多无意义的排查时间。写在最后这篇文章里讲到的每一条经验几乎都是我从第一块开发板开始一个一个坑踩出来的。如果让我给刚接触STM32的朋友一个建议我不会让你直接去啃上千页的参考手册而是希望你按这条主线走先把最小系统板跑通点灯和串口然后用定时器做一次输入捕获测频率再做一次USB虚拟串口通信等这几项都熟练了再决定要不要上FreeRTOS和LVGL。每一步都亲手搭、亲手调比看一百篇教程都管用。遇到卡住的时候回头翻翻这篇文章里的排障表大概率能找到答案。