1. 嵌入式学习路线到底该怎么走
1.1 从点灯到串口,为什么这是最稳的入门路径
很多人一上来就问,嵌入式该怎么学,是不是得先把模电数电啃完再动手。我带过不少新人,也见过太多人卡在“准备阶段”大半年,最后连一块开发板都没点亮过。说句实在话,嵌入式这行当,动手优先级永远高于理论完备。你不需要等所有前置知识都齐了才开始,正确的做法是找一个最小可运行的系统,先让它跑起来,再顺着问题往下挖。
那什么是最小可运行系统?我的答案很明确:GPIO点灯 + 串口打印。点灯让你理解时钟树、寄存器配置、引脚复用这些底层概念;串口则让你拥有“眼睛”,能看到程序内部到底发生了什么。没有串口,你调试全靠猜;有了串口,你才能把变量打出来、把状态机跑通、把传感器数据读回来。所以我说,串口是嵌入式学习的第一个分水岭,过了这一关,后面学什么都有了抓手。
再往深一层说,为什么不是先学I2C、SPI或者ADC?因为这些外设的调试都依赖一个可靠的输出通道。你调DHT11,读出来全是0xFF,你怎么知道是时序不对还是引脚接错了?有串口,你可以在每个步骤打印状态;没有串口,你只能拿示波器硬看,门槛一下就上去了。所以我的建议很直接:拿到新板子的第一件事,不是跑例程,而是把串口调通,把printf重定向做好。这件事花你半天时间,但后面能省你几十个小时。
至于学习平台的选择,STM32的HAL库是目前最主流的选择,资料多、社区活跃、CubeMX工具能自动生成初始化代码,极大降低了入门门槛。你不需要一上来就啃标准库或者寄存器手册,先用HAL库把功能跑通,再回头去看底层实现,这个顺序对新手最友好。GD32、ESP32这些平台也是类似的路子,选一个资料多的先上手就行。
1.2 工具链选型:Keil、CubeMX和串口调试助手怎么配
工欲善其事,必先利其器。嵌入式开发的工具链选择,直接决定了你的学习效率。我见过太多人在这上面浪费时间,今天装个Keil,明天试个IAR,后天又去折腾PlatformIO,结果一周过去了环境还没配好。我的建议是:新手阶段,Keil MDK + STM32CubeMX + 串口调试助手,这三件套足够你走完前三个月的学习。
Keil MDK的优势在于生态成熟,几乎所有STM32的教程都基于它,你遇到问题搜一下就能找到答案。CubeMX则是ST官方出的图形化配置工具,你只需要在界面上点选引脚、配置时钟、开启外设,它就能生成完整的初始化代码。这两者配合使用,能把你的精力从繁琐的寄存器配置中解放出来,专注在业务逻辑上。当然,Keil是收费软件,但社区版对个人学习来说够用了,具体怎么获取这里就不展开了。
串口调试助手的选择也有讲究。Windows下常用的有SSCOM、XCOM、友善串口调试助手等,功能大同小异。我个人的习惯是用SSCOM,因为它支持时间戳、支持HEX和ASCII切换、支持多条快捷发送指令,调试协议的时候特别方便。Linux下直接用minicom或者picocom就行,命令行操作,轻量高效。如果你在Ubuntu下找不到串口设备,可以用ls /dev/ttyUSB*或者ls /dev/ttyACM*来查看,通常USB转串口芯片会映射成ttyUSB0或者ttyACM0。
这里插一句关于USB转串口模块的选型。市面上常见的芯片有CH340、CP2102、FT232等,价格从几块到几十块不等。CH340性价比最高,但驱动兼容性偶尔出问题,尤其是在Win7系统上,可能需要手动装驱动。CP2102和FT232稳定性更好,价格也贵一些。如果你用的是Win7,建议优先选CP2102的模块,省去折腾驱动的麻烦。另外,有些开发板自带USB转串口电路,直接插USB线就能用,那就更方便了。
注意:串口调试时,TX和RX一定要交叉连接,即开发板的TX接模块的RX,开发板的RX接模块的TX。这个坑我见过太多人踩,接反了就是收不到数据,查半天代码没问题,最后发现是线接错了。
2. 串口通信的核心细节与HAL库实操
2.1 HAL库串口初始化的关键参数怎么算
CubeMX配置串口看起来很简单,点几下就完事了,但里面的参数到底什么意思,很多人是懵的。我拿最常见的115200-8-N-1举例,把这几个参数拆开讲清楚。
波特率115200,意思是每秒传输115200个比特。串口是异步通信,没有时钟线,收发双方靠约定的波特率来同步。如果两边波特率不一致,收到的数据就是乱码。那115200是怎么来的?它是由APB总线时钟分频得到的。以STM32F103为例,USART1挂在APB2上,时钟通常是72MHz。HAL库会根据你设置的波特率自动计算分频系数,公式是:USARTDIV = fCK / (16 * 波特率)。72MHz除以16再除以115200,得到约39.0625,整数部分39,小数部分0.0625乘以16等于1,所以BRR寄存器的值就是0x271。这些HAL库都帮你算好了,但你要知道它背后是有计算的,不是随便填个数就行。
数据位8位,就是每次传输8个比特,正好一个字节。停止位1位,表示一帧数据结束。校验位None,不做奇偶校验。这套配置是最通用的,绝大多数模块和PC端都支持。有些特殊场景会用7位数据位加偶校验,比如某些老式设备,但新手阶段不用管,统一用8-N-1就行。
还有一个容易忽略的参数是过采样模式。STM32的串口支持16倍过采样和8倍过采样,默认是16倍。8倍过采样可以在相同时钟下支持更高的波特率,但抗噪能力会下降。一般场景用默认的16倍就行,不用改。
在CubeMX里配置完串口后,生成的代码会调用MX_USART1_UART_Init()函数,里面就是上面这些参数的赋值。你可以打开usart.c文件看一眼,对照着理解每个字段的含义。这一步很重要,不要只会在CubeMX里点鼠标,要能看懂生成的代码,这样出了问题你才知道去哪查。
2.2 printf重定向的三种实现方式与避坑指南
串口调通了,下一步就是让printf能输出到串口。标准C库的printf默认输出到stdout,在嵌入式环境里没有屏幕,所以需要重定向。常见的有三种做法,我逐一分析优缺点。
第一种:重写fputc函数。这是最常用的方式,代码量最少。你只需要在main.c里加上:
#include <stdio.h> int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; }然后在Keil的Target选项里勾选“Use MicroLIB”,就能直接用printf了。这种方式简单直接,但有个致命问题:它是阻塞发送的。每调用一次printf,CPU就会在HAL_UART_Transmit里死等,直到这个字节发完。如果你在主循环里频繁打印,会严重拖慢程序运行速度。我实测过,115200波特率下,打印一行20个字符的日志,大约耗时1.7毫秒。如果你的控制周期是1毫秒,那光打印就把CPU占满了。
第二种:重写_write函数。这是GCC工具链下的做法,和fputc类似,但函数名不同。如果你用的是STM32CubeIDE或者PlatformIO,就需要用这种方式。代码大概长这样:
int _write(int file, char *ptr, int len) { HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, HAL_MAX_DELAY); return len; }本质上和fputc一样,也是阻塞的,只是接口不同。
第三种:DMA发送 + 环形缓冲区。这是进阶做法,也是实际项目中最推荐的方式。你创建一个环形缓冲区,printf的时候把数据写进缓冲区就返回,DMA在后台自动把数据搬出去。这样CPU不用等,打印再多也不影响主循环。实现起来稍微复杂一点,需要配置DMA通道、写环形缓冲区的读写指针管理、处理DMA传输完成中断。但一旦调通,你会发现串口打印变得无比顺滑,想打多少打多少。
我的建议是:学习阶段先用fputc,快速验证功能;等项目稍微复杂一点,一定要换成DMA方式。这个过渡越早越好,因为一旦你习惯了阻塞打印,后面改起来会很痛苦。
注意:使用MicroLIB时,
printf不支持浮点数。如果你需要打印float,要么自己写格式化函数,要么改用标准库并开启浮点支持,但那样会增大代码体积。我一般建议在嵌入式里尽量避免用float打印,把浮点数转成整数再打,比如乘以1000后打整数,显示的时候自己加小数点。
2.3 串口DMA接收的配置与空闲中断配合
发送用DMA解决了,接收呢?很多人的做法是在主循环里轮询HAL_UART_Receive,或者开接收中断,每收到一个字节进一次中断。这两种方式在低速场景下能用,但一旦数据量大或者波特率高,就会出问题。轮询浪费CPU,中断太频繁会导致系统响应变慢。
最优雅的方案是DMA接收 + 串口空闲中断。原理是这样的:DMA负责把串口收到的数据自动搬到内存缓冲区,CPU完全不用管。当一帧数据接收完毕,串口总线会空闲一段时间,触发空闲中断。在空闲中断里,你计算DMA已经搬运了多少个字节,就知道这一帧数据有多长,然后处理即可。
配置步骤大致如下:在CubeMX里打开串口的DMA接收通道,模式选Normal或者Circular。然后在代码里调用HAL_UART_Receive_DMA启动接收,再使能空闲中断__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE)。空闲中断的处理函数需要自己写,因为HAL库默认不处理IDLE中断。大概逻辑是:
void USART1_IRQHandler(void) { if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&huart1); uint16_t len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx); // 处理接收到的len个字节 // ... HAL_UART_Receive_DMA(&huart1, buffer, BUFFER_SIZE); } HAL_UART_IRQHandler(&huart1); }这套方案的好处是CPU占用极低,且能自动识别帧边界。不管你的数据是一帧10个字节还是100个字节,只要帧与帧之间有总线空闲,就能正确切分。我实测过,在115200波特率下连续接收数据,CPU占用率不到1%,而用中断方式接收,CPU占用率会飙升到30%以上。
这里有个坑要注意:DMA的接收缓冲区大小要大于单帧最大长度,否则会溢出。另外,如果两帧数据之间没有空闲间隔,空闲中断就不会触发,这时候就需要用其他方式来判断帧边界,比如协议里加长度字段或者结束符。这个要根据你的实际协议来定。
3. 从串口出发,打通嵌入式学习的任督二脉
3.1 用串口调试PID:一个真实的调参记录
串口调通了,接下来拿什么练手?我强烈推荐用串口来调试PID控制器。为什么?因为PID调参是一个典型的“需要反复观察、反复调整”的过程,没有串口你根本不知道系统当前是什么状态。而且PID涉及定时器、ADC、PWM、串口等多个外设,是一个非常好的综合练习项目。
我拿一个直流电机速度环的例子来说。硬件连接是:PWM驱动电机,编码器反馈转速,串口负责打印设定值和实际值。控制周期1毫秒,在定时器中断里跑PID计算。调参的时候,我把设定值、实际值、PID输出这三个量通过串口打到PC上,用串口调试助手的波形显示功能实时看曲线。
第一次调,我先只加P。P给小了,响应慢,半天到不了设定值;P给大了,超调严重,转速冲上去又掉下来,来回振荡。通过串口波形,我能清楚看到振荡的频率和幅度,然后逐步调整。P调到差不多的时候,加上I消除静差,但I太大会导致积分饱和,转速上冲后要很久才能降下来。最后加D抑制超调,D太大会引入高频噪声,波形上能看到毛刺。
整个调参过程,如果没有串口波形,我根本不知道参数该往哪个方向调。串口在这里扮演的角色,就是你的眼睛。你不仅能看到最终结果,还能看到中间过程,这对于理解控制系统的动态行为至关重要。
实操心得:串口打印数据时,尽量用二进制格式而不是ASCII字符串。比如你要打印三个float,用ASCII打印可能是“123.45, 678.90, 111.22\r\n”,一共20多个字节。如果用二进制直接发12个字节,效率高得多。PC端写个简单的解析程序就能还原。数据量大的时候,这个差别非常明显。
3.2 串口DMA测速:你的DMA到底跑多快
热词里有个“dma测速”,我猜很多人是想知道DMA到底能跑多快,值不值得用。我做过一个简单的测试,分享下方法和结果。
测试平台是STM32F103C8T6,主频72MHz,串口波特率设为最高的4.5Mbps(需要外部晶振支持)。发送端用DMA把一块内存的数据搬到USART的DR寄存器,接收端用另一块板子接收并统计。测试数据量是1MB,记录从启动DMA到传输完成的时间。
实测结果:在4.5Mbps波特率下,DMA传输1MB数据大约耗时1.8秒,换算下来实际速率约4.4Mbps,接近理论值。CPU占用率几乎为零,因为整个传输过程CPU只负责启动DMA和等待完成中断,中间不需要干预。作为对比,用阻塞方式发送同样的数据,CPU占用率100%,耗时约1.9秒,差别不大,但CPU被完全占死,什么都干不了。
这个测试说明什么?DMA的价值不在于速度,而在于解放CPU。传输速率受限于串口波特率本身,DMA不会让串口变快,但它能让CPU在数据传输的同时去处理其他任务。在高速数据采集、多路串口通信、实时控制等场景下,这个特性至关重要。
如果你要做DMA测速,注意几个点:一是要确保内存到外设的DMA通道配置正确,二是要处理好传输完成中断,三是要排除串口本身的波特率限制。测出来的速度如果远低于波特率理论值,先检查是不是波特率设错了,再检查DMA的优先级和仲裁配置。
3.3 串口封装:写一个能复用的C模块
学到一定程度,你会发现每个项目都要重新写一遍串口初始化、发送、接收的代码,很烦。这时候就该做串口封装了。封装的目标是:提供一个统一的接口,上层业务不用关心底层是哪个串口、用的是DMA还是中断、缓冲区多大。
我自己的封装思路是这样的:定义一个结构体,包含串口句柄、发送缓冲区、接收缓冲区、读写指针、回调函数指针。然后提供几个核心函数:uart_init()、uart_send()、uart_register_rx_callback()。发送用DMA加环形缓冲区,接收用DMA加空闲中断,收到完整一帧后调用回调函数通知上层。
这样封装之后,换一个项目,只需要改一下初始化参数,上层代码几乎不用动。而且因为发送是异步的,你可以在任何地方调用uart_send()而不用担心阻塞。接收回调机制也让代码结构更清晰,不用在中断里写一大堆业务逻辑。
封装的时候有几个细节要注意:环形缓冲区的读写指针要用volatile修饰,因为它们在中断和主循环里都会被访问;发送缓冲区的满判断要处理好,满了之后是丢弃还是等待,要根据业务需求决定;回调函数里不要做耗时操作,否则会阻塞中断,影响实时性。
4. 常见问题与排查技巧实录
4.1 串口收不到数据?按这个顺序查
串口调不通是新手最常见的坑,我整理了一个排查顺序,按这个走基本能解决90%的问题。
| 排查步骤 | 检查内容 | 常见问题 |
|---|---|---|
| 1 | 硬件连接 | TX/RX是否交叉,GND是否共地 |
| 2 | 波特率 | 两边是否一致,时钟配置是否正确 |
| 3 | 引脚配置 | CubeMX里是否选对了串口引脚,是否被其他外设占用 |
| 4 | 中断优先级 | 是否被其他高优先级中断阻塞 |
| 5 | DMA配置 | 通道是否选对,方向是否正确 |
| 6 | 代码逻辑 | 是否调用了启动接收的函数,缓冲区是否溢出 |
第一步永远是查硬件。我见过太多人代码查了半天,最后发现是杜邦线断了或者TX/RX接反了。用万用表量一下通断,花不了一分钟。第二步查波特率,如果你用的是内部晶振,时钟精度不够,高波特率下会累积误差导致通信失败。建议用外部晶振,8MHz的晶振配72MHz主频,波特率误差能控制在0.1%以内。
第三步查引脚配置。CubeMX里如果串口引脚和别的外设冲突了,它会标红,但有时候你改了配置忘了重新生成代码,实际跑的代码还是旧的。这时候把工程清理一下重新编译,或者手动检查MX_USART1_UART_Init()里的引脚配置。
第四步查中断。如果你开了串口接收中断,但优先级设得很低,被其他中断一直打断,就可能丢数据。把串口中断优先级设高一点试试。第五步查DMA,DMA的通道和外设的对应关系是固定的,选错了通道DMA根本不工作。最后查代码逻辑,是不是忘了调用HAL_UART_Receive_DMA,或者缓冲区太小导致溢出。
4.2 Win7下串口被占用怎么排查
热词里有个“win7下怎么查看串口被哪个程序占用”,这个问题我遇到过好几次。现象是串口调试助手打不开串口,提示“串口已被占用”或者“拒绝访问”。原因通常是某个后台程序占着串口不放,比如另一个调试助手没关干净、或者某个驱动软件在后台轮询。
排查方法:打开设备管理器,找到对应的COM口,右键属性,看看有没有“占用”相关的提示。更彻底的办法是用Process Explorer这个工具,按Ctrl+F搜索“COM3”这样的字符串,它能列出所有打开了该串口句柄的进程。找到之后结束掉那个进程就行。
还有一个常见原因是虚拟串口软件。有些人装了蓝牙串口、USB转串口驱动之后,系统里会多出几个虚拟COM口,这些口可能被系统服务占用。在设备管理器里把不用的虚拟串口禁用掉,能减少很多麻烦。
如果以上方法都不行,重启电脑是最简单的解决办法。虽然听起来很low,但确实有效。重启之后第一时间打开串口调试助手,通常就能正常打开了。
4.3 DMA接收数据错位或丢失的排查思路
DMA接收出问题,表现通常是数据错位、丢帧、或者收到一堆乱码。排查思路如下:
先确认DMA的传输方向和数据宽度。串口接收是外设到内存,数据宽度通常是字节。如果设成了半字或者字,数据就会错位。CubeMX里配置DMA的时候,Mode选Normal还是Circular要看你的需求。Normal模式下,DMA搬完指定数量的数据就停了,需要重新启动;Circular模式下,DMA会自动从头开始,适合连续接收。
然后检查缓冲区大小和DMA计数器的关系。__HAL_DMA_GET_COUNTER返回的是剩余待传输的数量,用缓冲区总大小减去这个值,才是已经接收到的字节数。这个计算要在空闲中断里做,而且要在重新启动DMA之前做,否则计数器会被重置。
还有一个隐蔽的坑是DMA和CPU同时访问同一块内存。如果DMA正在往缓冲区写数据,CPU同时去读,可能读到半新半旧的数据。解决办法是用双缓冲区,DMA写A区的时候CPU读B区,交替进行。或者用内存屏障指令确保访问顺序。
最后,如果数据量很大,检查一下DMA的优先级和总线仲裁。多个DMA通道同时工作时,优先级低的通道可能被抢占,导致数据丢失。把串口DMA的优先级设高一点,能缓解这个问题。
4.4 HAL库驱动DHT11和OLED的踩坑记录
热词里有“hal库驱动dht11”和“hal库驱动oled代码”,这两个我都做过,坑不少,简单说几个。
DHT11是单总线器件,时序要求很严格。HAL库的HAL_Delay最小分辨率是1毫秒,而DHT11的时序是微秒级的,所以不能用HAL_Delay来做延时。我的做法是用定时器做一个微秒级延时函数,或者用__NOP()循环来凑。另外,DHT11对时序的容忍度很低,中断打断会导致读取失败。读DHT11的时候最好关掉全局中断,读完再开。但关中断时间不能太长,否则会影响其他外设。DHT11一次读取大概需要4毫秒,关中断4毫秒对大多数应用来说是可以接受的。
OLED一般用I2C或者SPI接口。I2C的HAL库驱动OLED,最容易出问题的地方是地址不对。OLED的I2C地址通常是0x78或0x7A,但HAL库的地址要左移一位,变成0x3C或0x3D。这个坑我踩过,查了半天才发现是地址没移位。另外,OLED初始化需要发送一堆命令,每条命令之间要有适当的延时,太快了屏幕不亮。SPI接口的OLED速度快很多,但线多几根,接线的时候注意别接错。
实操心得:调试OLED的时候,先写一个最简单的清屏函数,确认通信正常了再写显示字符的函数。如果清屏都不成功,那肯定是通信层的问题,不用往上查了。
5. 嵌入式学习路线的进阶方向
5.1 从裸机到RTOS,什么时候该跨这一步
裸机跑通了串口、定时器、ADC、DMA这些外设之后,下一步该不该上RTOS?我的答案是:当你发现主循环里的事情多到快处理不过来的时候,就该上了。
具体来说,如果你有多个任务需要“同时”运行,比如一边读传感器一边刷屏幕一边处理串口命令,用裸机的前后台架构会越来越吃力。你需要在主循环里轮询各个任务,每个任务都不能阻塞太久,否则会影响其他任务的响应。代码会变得很复杂,状态机满天飞。
这时候RTOS的价值就体现出来了。FreeRTOS是最主流的选择,资料多、移植方便、社区活跃。你可以把不同的功能拆成独立的任务,每个任务有自己的栈和优先级,RTOS负责调度。串口接收可以做成一个任务,阻塞在队列上等数据;传感器读取做成另一个任务,定时唤醒;屏幕刷新做成低优先级任务,有空就跑。代码结构一下子清晰很多。
但要注意,RTOS不是银弹。任务多了之后,栈空间、优先级反转、资源竞争这些问题都会冒出来。我的建议是先用裸机把功能跑通,对系统的时间需求有清晰认识之后,再考虑用RTOS重构。不要一上来就上RTOS,那样你连问题出在哪都搞不清楚。
5.2 嵌入式Linux和裸机的分界线在哪
很多人在学到一定程度后会纠结:是继续深耕裸机/RTOS,还是转嵌入式Linux?我的看法是,这两条路的技术栈差异很大,选择哪条取决于你的目标岗位和兴趣方向。
裸机/RTOS方向,核心能力是对硬件的深入理解和实时控制。你需要懂寄存器、懂时序、懂中断、懂DMA,能写出高效的底层驱动。这个方向在工业控制、汽车电子、消费电子等领域需求很大。嵌入式Linux方向,核心能力是系统集成和应用开发。你需要懂内核驱动、懂文件系统、懂网络编程、懂多线程,能搭建复杂的软件系统。这个方向在物联网网关、智能家居、边缘计算等领域更常见。
两者不是对立的,很多项目是Linux加单片机的组合架构。Linux负责上层应用和网络通信,单片机负责实时控制和传感器采集,两者通过串口或者SPI通信。所以我的建议是:先把裸机基础打牢,再根据兴趣选择深入方向。裸机基础扎实了,学Linux驱动也会更容易理解底层原理。
5.3 嵌入式AI测试:一个值得关注的新方向
热词里出现了“嵌入式ai测试”,这确实是一个正在兴起的方向。随着TinyML和边缘计算的发展,越来越多的AI模型被部署到单片机和嵌入式设备上。比如用STM32跑一个简单的手势识别模型,或者用ESP32做语音唤醒。
这个方向的门槛在于,你既要懂嵌入式,又要懂一点机器学习。模型训练通常在PC上完成,然后转换成C代码或者量化成整数运算,再部署到设备上。测试的时候,你需要关注模型的推理速度、内存占用、准确率等指标。串口在这里依然是重要的调试手段,你可以把推理结果和中间数据打出来分析。
如果你对AI感兴趣,可以从TensorFlow Lite for Microcontrollers入手,官方有STM32和ESP32的示例。先跑通一个Hello World级别的模型,再尝试自己训练一个简单的模型部署上去。这个方向目前人才缺口比较大,值得投入时间。
5.4 嵌入式面试题里那些高频考点
最后说下面试。嵌入式岗位的面试,串口和DMA几乎是必问的。常见的问题有:串口通信的帧格式是什么?波特率怎么计算?DMA的工作原理是什么?DMA和中断的区别是什么?什么时候用DMA什么时候用中断?
回答这些问题,不能只背概念,要结合你的实际项目经验。比如问DMA和中断的区别,你可以说:中断是CPU响应外设请求,每次传输都要进中断,适合数据量小的场景;DMA是外设直接访问内存,不需要CPU干预,适合大数据量高速传输。然后举一个你项目里的例子,比如用DMA接收串口数据,CPU占用率从30%降到1%,这样面试官就知道你是真做过。
另外,面试官很喜欢问“你遇到过什么问题,怎么解决的”。这时候你可以把上面那些踩坑经历讲出来,比如DHT11时序被中断打断导致读取失败,你是怎么发现并解决的。这种真实的问题排查经历,比背八股文有说服力得多。
我个人在实际带新人的过程中发现,那些学得快的,往往不是最聪明的,而是动手最多、踩坑最多、总结最多的。嵌入式这门手艺,看十本书不如焊一块板子,听十节课不如调通一个串口。你只要把串口这一关过了,后面的路会越走越宽。