
1. 为什么嵌入式自学总在半路翻车我带过不少想转嵌入式的朋友也在社区里看过太多“从入门到放弃”的帖子。嵌入式这个方向很奇怪——它不像纯软件开发那样装个环境就能跑起来看效果你写的每一行代码最终都要在真实的硬件上接受检验。很多人一开始信心满满买块开发板跟着视频点亮一颗LED感觉自己已经入门了。然后呢然后就没有然后了。卡在中断、卡在时钟树配置、卡在驱动框架、卡在系统移植最后板子吃灰热情消磨殆尽。这个标题说“能救一个是一个”听起来很悲壮但确实点出了一个现实嵌入式自学的信息密度太低了。网上教程要么是学院派的理论堆砌要么是零散的Demo片段缺少一条从零到能干活之间的完整路径。我写这篇东西就是想把我自己走过的弯路、踩过的坑以及这些年积累下来的学习框架摊开来讲清楚。不管你是电子专业的学生还是想从纯软转嵌入式的开发者或者是工作几年想补底层知识的工程师这篇文章都能给你一个可落地的参照。我会按照一条真实的学习路径来组织内容先讲整体思路怎么规划再拆解环境搭建和工具链选择接着深入驱动开发和系统裁剪这两个分水岭最后聊嵌入式AI部署和项目实战。每一部分我都会给出具体的操作步骤、参数选择理由以及我在实践中总结的注意事项。这些内容你在教科书上找不到在碎片化的视频教程里也拼不全。1.1 嵌入式的本质是“软硬结合”而非“写代码”很多人对嵌入式的理解停留在“用C语言写单片机程序”这个层面。这个认知不能说错但太窄了。嵌入式的核心特征是资源受限环境下的软硬件协同设计。你写代码的时候脑子里要同时装着两件事软件逻辑怎么跑硬件资源怎么用。举个例子你在PC上写一个串口收发程序开个缓冲区几百字节随便怎么折腾都行。但在嵌入式环境里MCU的RAM可能只有64KB你要在有限的资源里平衡实时性、功耗和功能。再往上走到了嵌入式Linux层面你需要理解设备树怎么描述硬件、驱动怎么和内核交互、系统裁剪怎么在有限存储里塞进必要的组件。这些都不是纯软件思维能覆盖的。所以自学的第一件事是建立“软硬一体”的认知框架。看到任何一个嵌入式项目你都要问自己硬件资源有哪些实时性要求是什么功耗约束是什么成本卡在什么范围这四个问题会决定你的技术选型和学习重点。1.2 学习路径的两种错位只跑Demo和只啃理论我观察下来自学嵌入式的人基本分两类。一类是“Demo收集者”——跟着教程把每个外设的例程跑一遍串口通了、I2C读了、SPI写了然后觉得差不多了但真让他从零做一个项目完全不知道从哪里下手。另一类是“理论派”——把Cortex-M权威指南翻了三遍中断向量表、流水线、总线架构如数家珍但让他写个定时器中断点灯他可能卡在寄存器配置上半天出不来。这两类问题的根源是一样的缺少中间层的“工程化训练”。跑Demo和啃理论都只是手段真正的能力是面对一个需求你能拆解出需要哪些外设、需要什么驱动、需要什么样的任务调度、资源怎么分配。这种能力只能在“做一个完整项目”的过程中获得但前提是你得先把基础打牢。我建议的学习节奏是基础阶段以“理解一个外设的工作原理能自己配置寄存器或调用HAL库点亮它”为目标不贪多。每学完一个外设立刻做一个小的综合练习比如用定时器串口做一个简易的脉冲计数器。到了Linux阶段先玩通系统启动流程再碰驱动。这样每一步都有反馈不容易崩。1.3 2026年嵌入式学习者的新变量和五年前相比现在学嵌入式的环境有两个明显变化。一个是工具链的成熟VSCode配合插件已经能覆盖从编辑、编译到调试的完整流程CLion也在嵌入式领域发力体验比早年用EclipseMakefile好了不止一个档次。另一个是AI能力的下沉嵌入式AI开发不再是实验室里的东西TFLite Micro、ONNX Runtime这些框架让MCU和边缘设备上跑模型变得可行。这意味着你的学习路线需要更新。以前可能花大量时间折腾环境配置现在可以把精力更多放在核心能力上。但反过来工具越方便越容易让人忽视底层原理。我见过用PlatformIO很溜但说不清链接脚本怎么工作的开发者遇到诡异的启动问题就抓瞎。所以我的建议是工具用来提效原理用来兜底两条腿走路。2. 环境搭建与工具链选型少折腾就是快嵌入式开发最劝退的环节往往不是代码本身而是环境。编译器版本对不上、调试器连不上、库找不到、头文件路径不对——这些问题能消耗掉初学者一半的热情。我在这部分会把环境搭建的要点讲透帮你把这块硬骨头啃下来。2.1 VSCode还是CLion嵌入式IDE的真实对比先给结论MCU裸机开发首选VSCodeLinux应用和驱动开发可以CLion和VSCode搭配用。这不是拍脑袋是我实际用下来的体会。VSCode的优势在于插件生态和轻量。嵌入式开发常用的插件有这几个Cortex-DebugARM Cortex-M调试的核心插件配合OpenOCD或J-Link可以打断点、看寄存器、看外设寄存器。配置稍微麻烦一点但一次配好能用很久。C/C微软官方的智能提示插件配置好c_cpp_properties.json里的头文件路径和宏定义代码补全和跳转就很顺畅。CMake Tools如果你用CMake管理项目这个插件能让你在VSCode里完成配置、编译、烧录的全流程。PlatformIO IDE集成了大量开发板的框架和工具链适合快速上手但灵活性不如自己搭。Remote - SSH在Linux开发板上调试时通过SSH连过去编辑和编译体验很流畅。CLion的优势在于重构和代码分析能力更强对CMake的支持是原生级别的。如果你做嵌入式Linux应用开发代码量比较大CLion的代码导航和重构会省不少力。但CLion是收费的虽然有社区版功能有限制。而且CLion的调试配置对嵌入式场景不如VSCode灵活。我的实际搭配是MCU项目用VSCode Cortex-Debug CMake ToolsLinux驱动用VSCode远程连开发机应用层代码用CLion写。两个工具各取所长没必要死守一个。2.2 交叉编译工具链的选择与配置交叉编译是嵌入式开发绕不开的概念。简单说就是在x86电脑上编译出能在ARM或其他架构上运行的程序。这个过程中最容易出问题的就是工具链版本和系统库的匹配。选工具链的时候要看几个东西目标架构arm-linux-gnueabihf还是aarch64-linux-gnu、C库版本glibc还是musl、内核头文件版本。这三个对不上编译出来的程序要么跑不起来要么跑起来出各种诡异问题。我一般这样操作先从芯片厂商或开发板厂商提供的SDK里找推荐的工具链这是最稳的。如果没有就去Linaro或Bootlin下载对应的预编译工具链。下载后解压到/opt目录然后在.bashrc里加PATHexport PATH/opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH export CROSS_COMPILEaarch64-none-linux-gnu- export ARCHarm64验证工具链是否可用aarch64-none-linux-gnu-gcc -v能看到版本信息就说明配置成功了。这里有个细节CROSS_COMPILE的值是工具链前缀不带gcc后缀编译内核和U-Boot的时候会用到这个变量。注意不要混用不同来源的工具链。比如用厂商A的工具链编译内核用厂商B的工具链编译应用程序很容易出现ABI不兼容的问题。统一用一个工具链省心。2.3 调试工具链从ST-Link到J-Link的实操差异调试器这块ST-Link和J-Link是两大主流。ST-Link便宜甚至板载J-Link贵但稳定性和速度更好。初学阶段用板载ST-Link就够了等你要调试复杂项目或者需要高速Trace的时候再考虑J-Link。OpenOCD是开源的调试工具配合GDB可以完成大部分调试任务。VSCode的Cortex-Debug插件底层就是调用OpenOCD和GDB。配置的时候主要填这几个参数{ servertype: openocd, configFiles: [ interface/stlink.cfg, target/stm32f1x.cfg ], svdFile: STM32F103.svd }svdFile是关键它让调试器能识别外设寄存器你可以在调试界面直接看GPIO、定时器、串口寄存器的值不用手动算地址。这个SVD文件一般从芯片厂商的官网下载或者用STM32CubeMX生成。调试的时候我经常用这几个技巧条件断点、数据观察点、实时变量监控。条件断点适合在循环里抓特定状态数据观察点适合追踪某个变量被谁改了实时变量监控可以配合SWO或者RTT输出不打断程序运行。3. 嵌入式Linux驱动开发从点灯到设备树到了Linux层面很多人的学习曲线会突然变陡。裸机开发你直接操作寄存器就行Linux下你得理解内核框架、设备模型、驱动匹配机制。这一段我会把驱动开发的核心脉络讲清楚让你知道每一步在做什么、为什么这么做。3.1 字符设备驱动的骨架与避坑点字符设备是Linux驱动里最基础的类型也是理解驱动模型的入口。一个完整的字符设备驱动包含这几个部分模块加载/卸载函数、file_operations结构体、设备号申请、cdev注册、创建设备节点。写第一个驱动的时候我建议直接从内核源码里的drivers/char/目录找个简单的例子改比如misc设备或者leds驱动。不要从零开始写容易在细节上卡住。核心结构体file_operations定义了驱动对外的接口static const struct file_operations mydev_fops { .owner THIS_MODULE, .open mydev_open, .release mydev_release, .read mydev_read, .write mydev_write, .unlocked_ioctl mydev_ioctl, };模块初始化的时候要做几件事申请设备号、初始化cdev、添加到内核、创建类和设备节点。这里有个容易踩的坑设备号的分配方式。用alloc_chrdev_region动态分配比较省事但要记住卸载时用unregister_chrdev_region释放。用register_chrdev_region指定主设备号的话要确保没有冲突。另一个坑是并发控制。字符设备的read/write可能被多个进程同时调用如果你的驱动里有共享数据必须加锁。简单的用互斥锁复杂的场景用自旋锁或信号量。我见过因为没有加锁导致数据错乱的案例调试起来很痛苦。实操心得写驱动的时候每个函数入口加pr_debug或者dev_dbg输出配合动态调试echo file mydev.c p /sys/kernel/debug/dynamic_debug/control可以按需打开日志不影响性能。3.2 设备树配置硬件描述的语言设备树是嵌入式Linux驱动开发的分水岭。在设备树出现之前硬件信息是硬编码在内核里的改一个引脚可能要重新编译内核。设备树把硬件描述从内核代码里剥离出来用文本文件描述板级硬件内核启动时解析设备树来匹配驱动。设备树的语法不难但概念容易绕晕。我这样理解设备树是给内核看的“硬件说明书”。它告诉内核这块板子上有个I2C控制器地址是0x40005400它上面挂了个温度传感器地址是0x48中断引脚连在GPIO1_5上。内核根据这些信息去加载对应的驱动驱动从设备树里读取参数来初始化硬件。一个典型的I2C设备节点长这样i2c1 { status okay; clock-frequency 100000; lm75: temperature-sensor48 { compatible national,lm75; reg 0x48; interrupt-parent gpio1; interrupts 5 IRQ_TYPE_EDGE_FALLING; }; };compatible是驱动匹配的关键字驱动里定义of_device_id表来匹配这个字符串。reg是设备地址。interrupts描述中断信息。驱动加载后可以通过of_property_read_u32等函数读取这些属性。写设备树的时候要注意引脚复用配置。很多SoC的引脚可以配置成GPIO、I2C、SPI、PWM等不同功能设备树里要通过pinctrl子系统来配置。比如iomuxc { pinctrl_i2c1: i2c1grp { fsl,pins MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0 MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0 ; }; };这里的宏定义和配置值需要查芯片的数据手册和参考手册配置错了设备就不工作而且不会有明显报错排查起来很费时间。3.3 平台设备驱动模型与probe流程Linux设备驱动模型的核心是总线-设备-驱动的匹配机制。对于SoC内部的外设用的是平台设备platform device和平台驱动platform driver模型。设备树里的节点会被转换成platform_device驱动注册platform_driver两者通过compatible属性匹配匹配成功后调用驱动的probe函数。probe函数是驱动的入口点在这里完成硬件初始化、申请资源、注册字符设备或输入设备等操作。典型的probe流程从设备树获取硬件参数寄存器基地址、中断号、时钟等申请IO内存并映射申请中断使能时钟初始化硬件寄存器注册设备节点或子系统接口static int mydrv_probe(struct platform_device *pdev) { struct resource *res; void __iomem *base; int irq; res platform_get_resource(pdev, IORESOURCE_MEM, 0); base devm_ioremap_resource(pdev-dev, res); if (IS_ERR(base)) return PTR_ERR(base); irq platform_get_irq(pdev, 0); if (irq 0) return irq; ret devm_request_irq(pdev-dev, irq, mydrv_isr, IRQF_TRIGGER_RISING, mydrv, NULL); ... }用devm_开头的资源管理函数可以自动释放资源避免在remove函数里手动处理减少出错概率。这是现代驱动开发的推荐做法。常见问题probe函数执行失败但没有任何日志。这种情况一般是设备树节点写错了或者驱动没有编译进内核。检查/sys/bus/platform/devices/下有没有对应的设备节点如果没有说明设备树没解析到如果有设备但驱动没加载检查compatible是否匹配。4. 系统裁剪与优化把Linux塞进小存储嵌入式Linux和桌面Linux最大的区别是资源约束。桌面系统有几十GB的存储和几GB的内存嵌入式设备可能只有256MB存储和64MB内存。系统裁剪就是要在保证功能的前提下把体积压下来。4.1 Buildroot与Yocto的选型逻辑Buildroot和Yocto是两个主流的嵌入式Linux构建系统。简单说Buildroot适合中小项目Yocto适合复杂产品。Buildroot的构建流程很直接menuconfig选配置make编译输出rootfs、内核、工具链。整个构建过程快学习曲线平缓。缺点是包的依赖管理比较粗糙如果需要深度定制或者维护多个产品变体会比较费劲。Yocto基于BitBake构建引擎和层layer的概念灵活性强可以精细控制每个包的编译选项。但上手难度大构建一次要下载几十GB的源码编译几个小时。适合有专门团队维护的大型项目。我的建议是初学者先用Buildroot理解嵌入式系统的组成和构建流程。等你能独立做一个完整的系统镜像了再根据项目需要决定是否转Yocto。不要一上来就啃Yocto容易在构建错误里迷失。4.2 内核裁剪从十几MB到几MB的实操内核裁剪的目标是去掉不需要的驱动和功能减小内核镜像体积同时加快启动速度。裁剪的基本流程是先跑通完整内核然后逐步去掉不需要的配置项每次去掉一部分就重新编译测试确保系统还能正常启动。具体操作上用make menuconfig进入配置界面。几个关键裁剪方向CPU架构和平台只选目标SoC去掉其他平台的配置。设备驱动只保留用到的外设驱动比如你只用了I2C和SPI就把PCI、USB、网络等不需要的驱动去掉。文件系统只保留实际使用的文件系统类型比如squashfs和ext4。网络协议如果设备不需要网络把TCP/IP协议栈整个去掉。调试选项发布版本去掉所有debug选项和符号信息。裁剪的时候有个技巧用make savedefconfig保存最小配置方便后续恢复和对比。另外内核配置项之间有依赖关系关掉一个选项可能会导致另一个选项自动关闭所以每次裁剪后都要检查启动日志有没有报错。一个常用的裁剪效果参考完整内核镜像可能12MB裁剪后能到3MB左右。启动时间从10秒缩短到3秒。这些优化在量产产品里非常关键。4.3 根文件系统优化体积与功能的平衡根文件系统的裁剪和内核裁剪思路类似但涉及的组件更多。Buildroot默认生成的rootfs可能包含大量文档、调试工具和不需要的库。优化的时候关注这几个方面C库选择glibc功能全但体积大musl和uClibc体积小但兼容性稍差。如果应用不依赖glibc特有功能用musl能省不少空间。BusyBox替代用BusyBox提供的精简版工具替代GNU coreutils能大幅减小体积。去strip编译后的二进制文件用strip去掉符号表和调试信息。压缩格式squashfs比ext4压缩率高适合只读根文件系统。我做过一个对比默认Buildroot配置生成的rootfs是8MB左右经过C库替换、BusyBox配置精简、strip处理后能压到2MB以内启动后内存占用也明显降低。注意裁剪过度会导致系统缺少必要的工具后续调试很麻烦。建议保留基本的shell、网络工具和日志查看工具方便现场排查问题。5. 嵌入式AI部署从模型到MCU嵌入式AI是这两年最热的方向之一。把神经网络模型部署到MCU或边缘设备上涉及到模型压缩、量化、推理框架选择等一系列工程问题。这块我实际做过几个项目踩的坑不少下面把关键路径讲清楚。5.1 TFLite Micro与ONNX Runtime的部署对比在MCU上跑模型目前主流的选择是TensorFlow Lite for MicrocontrollersTFLite Micro和ONNX Runtime。两者各有侧重。TFLite Micro是Google专门为MCU设计的推理框架特点是内存占用小、可移植性好支持Cortex-M系列和部分RISC-V核。它把模型转换成FlatBuffer格式推理时不需要动态内存分配适合资源极度受限的场景。部署流程是PC上训练模型→转成TFLite格式→量化→用xxd转成C数组→集成到工程里。ONNX Runtime的优势是模型格式支持更广PyTorch、TensorFlow等框架训练的模型都能转成ONNX格式。ONNX Runtime有专门针对嵌入式优化的版本但整体资源消耗比TFLite Micro大一些更适合Cortex-A系列的边缘设备。我的经验是Cortex-M系列MCU优先用TFLite MicroCortex-A或带NPU的边缘芯片用ONNX Runtime或厂商自带的推理框架。5.2 模型量化与算子兼容性处理直接把FP32模型部署到MCU上基本不可行参数量和计算量都太大了。量化是必须的步骤把FP32的权重和激活值转成INT8模型体积缩小4倍推理速度提升2-4倍精度损失通常在1%以内。TFLite的量化流程converter tf.lite.TFLiteConverter.from_saved_model(saved_model_dir) converter.optimizations [tf.lite.Optimize.DEFAULT] converter.representative_dataset representative_data_gen converter.target_spec.supported_ops [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] converter.inference_input_type tf.int8 converter.inference_output_type tf.int8 tflite_quant_model converter.convert()关键是representative_dataset需要提供一批代表性数据让转换器统计激活值的分布确定量化参数。这个数据集要覆盖实际场景的各种输入否则量化后的精度会明显下降。算子兼容性是另一个坑。TFLite Micro支持的算子集比桌面版少模型里如果有不支持的算子转换时会报错。解决方法有两个一是修改模型结构用支持的算子替换二是自己实现自定义算子。前者更实际后者工作量大。我遇到过一个案例模型里用了LeakyReLUTFLite Micro不支持改成ReLU后精度掉了一点但通过调整训练时的超参补回来了。5.3 推理性能调优的实操技巧模型跑起来之后下一步是优化推理速度。影响性能的因素主要有这几个算子实现效率、内存访问模式、CPU频率和缓存配置。几个实测有效的优化手段启用CMSIS-NNARM的CMSIS-NN库对常见算子做了汇编级优化在Cortex-M上能提升2-5倍速度。TFLite Micro默认会链接这个库但要确保编译选项开对了。调整Tensor Arena大小TFLite Micro需要一块内存作为Tensor Arena大小要刚好够用。太小会推理失败太大会浪费RAM。可以用MicroInterpreter的arena_used_bytes()接口查看实际用量。算子融合把ConvBNReLU融合成一个算子减少中间结果的读写。这个在模型转换时由优化器自动完成但需要确保转换配置正确。数据布局优化NHWC和NCHW的选择会影响缓存命中率。Cortex-M上一般用NHWC因为CMSIS-NN针对这个布局做了优化。我做过一个关键词唤醒的模型在STM32F407上优化前推理一次要80ms启用CMSIS-NN并调整内存布局后降到25ms满足了实时性要求。实操心得调试嵌入式AI性能的时候用DWT计数器或者GPIO翻转来测量耗时比用系统tick更精确。另外推理时间要留20%的余量因为实际运行时的中断和任务调度会影响。6. 项目实战与常见问题排查学到最后都要落到项目上。嵌入式项目的特点是和硬件强相关很多问题不是代码逻辑错误而是硬件配置、时序、电气特性导致的。这部分我整理了几个典型项目的实操要点和问题排查方法。6.1 从零做一个数据采集网关的完整流程我拿一个实际做过的项目来拆解一个基于STM32MP157的工业数据采集网关通过RS485采集传感器数据通过4G上传到服务器本地用SQLite存储历史数据。第一步是硬件设计。确定主控芯片、外设接口、电源方案。STM32MP157有双核Cortex-A7和Cortex-M4Linux跑在A7上M4跑实时任务。RS485用UART加收发器4G用USB接口的模块存储用eMMC。第二步是系统搭建。用Yocto构建Linux镜像配置内核开启UART、USB、eMMC驱动。设备树里配置引脚复用和RS485收发方向控制引脚。第三步是驱动开发。RS485本身用标准UART驱动但要加方向控制。在设备树里配置rs485-rts-active-low等属性或者用GPIO手动控制收发切换。这里有个坑收发切换的时序切换太快会丢数据切换太慢会影响总线效率。实测下来在9600波特率下收发切换延时1ms比较稳。第四步是应用开发。用C写数据采集程序通过串口读传感器解析Modbus协议存SQLite通过MQTT上传。多线程处理采集线程、存储线程、上传线程分开用消息队列通信。第五步是部署和优化。裁剪系统、配置开机自启、加看门狗、做日志轮转。上线前跑72小时压力测试观察内存泄漏和异常重启。这个项目涉及了嵌入式开发的完整链路硬件、系统、驱动、应用、部署。做完一个这样的项目你对嵌入式的理解会上一个台阶。6.2 常见问题速查表问题现象可能原因排查方法解决思路系统启动卡在Starting kernel设备树错误、内核配置问题打开earlycon看内核启动日志检查设备树中memory节点和串口节点驱动probe不执行compatible不匹配、设备树节点未启用查看/sys/bus/platform/devices/确认设备树status为okaycompatible与驱动一致串口乱码波特率不匹配、时钟配置错误用示波器测波特率检查设备树时钟配置和驱动里的波特率设置I2C通信失败引脚复用错误、上拉电阻缺失用i2cdetect扫描总线检查pinctrl配置确认硬件上有上拉系统运行一段时间后死机内存泄漏、看门狗未喂查看内存使用趋势检查内核日志用valgrind排查应用检查看门狗配置AI推理结果异常量化参数错误、输入数据格式不对对比PC和MCU上的推理结果检查量化校准数据集确认输入归一化方式6.3 调试手段与经验教训嵌入式调试和纯软件调试最大的区别是你无法随时打印日志。很多问题发生在系统启动早期串口还没初始化或者发生在中断里打印会影响时序。所以需要多种调试手段配合。我常用的手段有这几个GPIO翻转示波器看时序RTT输出日志不影响实时性ftrace跟踪内核函数调用perf分析性能瓶颈JTAG单步定位崩溃点。每种手段适用场景不同要灵活选用。踩过的坑里印象最深的是一个时序问题。驱动里读写寄存器之间没有加延时在PC上模拟跑没问题到硬件上就偶发失败。后来查手册发现芯片要求两次操作之间至少间隔50ns加了ndelay(50)后问题消失。这个教训是永远不要假设硬件操作是瞬时的该加的延时和内存屏障一个都不能少。另一个教训是关于错误处理的。早期写驱动的时候probe函数里某个资源申请失败直接return了但前面申请的资源没释放导致模块重新加载时失败。后来养成习惯用goto错误处理标签按申请顺序逆序释放问题就少了。7. 持续进阶的方向选择嵌入式这个领域很宽没人能全部精通。走到一定程度后你需要选一个方向深入。常见的方向有驱动开发、系统优化、边缘AI、实时系统、安全。每个方向的技术栈和职业路径都不一样。我的建议是先把通用基础打牢然后在实际项目中找到自己感兴趣的点。比如你在做数据采集项目时发现对驱动调试特别有感觉那就往驱动方向深入研究内核子系统、总线框架、电源管理。如果你对模型部署和性能优化更感兴趣就往边缘AI方向走学习模型压缩、算子优化、异构计算。不管选哪个方向有几件事是共通的读源码的能力、看数据手册的耐心、动手验证的习惯。嵌入式的知识更新不算快底层的东西几年不变但新芯片、新框架、新工具层出不穷。保持学习状态比掌握某个具体技术更重要。最后说一句实在话嵌入式自学确实不容易但也没有难到学不会的程度。关键是别贪快别只看不练别遇到问题就换方向。把一块板子玩透比买十块板子各跑一个Demo有价值得多。