第一次拿到 GEC6818 开发板的时候,我差点以为自己买了一块坏板子:接上电源和 USB 转串口线,屏幕上一个字符都不打印,调试串口完全沉默。折腾了大半天才发现,是启动拨码开关拨到了 eMMC 启动位置,而板子出厂时 eMMC 里根本没有系统。这块来自粤嵌的 GEC6818 用的是三星 S5P6818 八核处理器,是典型的嵌入式 Linux 教学板卡,从环境配置到实战应用,中间隔着不少类似的暗坑。这篇文章就围绕 GEC6818 开发板的环境配置与实战应用,把我踩过的坑、验证过的方法完整梳理一遍,希望能让正准备入手的你少走弯路。
这篇内容适合三类人:学过单片机(STM32、51 之类)想转向嵌入式 Linux 方向的学生,课程设计或毕业设计需要拿开发板做项目的同学,以及想快速验证产品原型的开发者。我会按照拿到板子之后的实际操作顺序来写:先认清硬件资源,再配 Ubuntu 主机环境,然后烧录系统、配置 NFS 文件共享,最后用一个 GPIO 点灯和按键读取的实战项目把整条链路串起来。
1. 先认清这块板子:S5P6818 的硬件底子与它能干的事
1.1 芯片底子:八核 A53 意味着什么
GEC6818 的核心是三星 S5P6818 处理器,8 个 Cortex-A53 核心,常见主频 1.4GHz,配 1GB DDR3 内存和 8GB eMMC 存储。这个配置放在今天当然不算亮眼,甚至被淘宝上各种新开发板吊打,但在嵌入式 Linux 教学板这个圈子里,它的地位一直很稳。
Cortex-A53 是 64 位 ARMv8 架构,和我后面要讲的裸机开发、Linux 驱动开发是同一个体系。它能跑完整的 Linux 系统,有进程调度、网络协议栈、文件系统这些完整机制,这是它和单片机最本质的区别。STM32 这类 MCU 跑的是裸机或 RTOS,一个程序独占 CPU,而 GEC6818 上跑 Linux,你可以同时开多个进程、用 socket 做网络通信、挂载远程文件系统,这些机制才是工业级嵌入式开发的核心。
板载接口也很齐全:RGB/LVDS 液晶接口、HDMI 输出、以太网口、多个 USB Host、USB OTG、调试串口、SD 卡座、音频接口、摄像头接口,还有一排扩展排针。接口多意味着你能做的项目类型多——做显示界面用 HDMI 或 LCD,做物联网网关用网口,做外设控制用 GPIO。
1.2 不同开发板类型横向对比
很多新人对开发板的类型没什么概念,这里放一个对比表,可能更直观。
| 类型 | 代表板卡 | 系统形态 | 适合学习内容 | 上手难度 |
|---|---|---|---|---|
| MCU 开发板 | STM32、ESP32、普中 51 | 裸机或 FreeRTOS | 寄存器操作、外设驱动、RTOS 内核 | 较低 |
| 嵌入式 Linux 板 | GEC6818、全志 T113 | Linux 系统 | Linux 驱动、应用开发、系统移植 | 中等 |
| 高性能 SoC 板 | RK3588、树莓派 5 | 完整 Linux/Android | AI 推理、桌面应用、复杂多媒体 | 较高 |
GEC6818 正好卡在中间档。和 RK3588 那些新平台比,性能确实落后,但反过来说,落后意味着资料多、教程全、问题都能搜到答案。对学习嵌入式 Linux 来说,一个资料完善、社区活跃的板子,远比一个性能强但只能自己摸索的板子更有价值。
1.3 想清楚自己适不适合 GEC6818
结合我自己和周围人的经验,GEC6818 最适合的是已经写过一些 C 语言、接触过单片机,但对 Linux 系统还很陌生的人。这板子是最好的中间跳板:学完它,你去看 RK3588、IMX6ULL 这些工业级板卡,上手成本会低很多。
不太适合的有两类人:完全没有编程基础的小白,建议先去补 C 语言和 Linux 基础命令;想做 AI 相关项目的,GEC6818 的 A53 核心没有 NPU,跑模型非常吃力,直接选带 NPU 的板子更合适。
2. Ubuntu 主机三件套:交叉编译链、串口终端和网络规划
2.1 交叉编译链:版本选对,后面省一半的事
嵌入式开发里"交叉编译"这四个字,很多新手第一次听到会懵。说白了就是:开发板上的处理器是 ARM 架构,而你电脑是 x86 架构,x86 上编译出来的程序,ARM 板子跑不了,必须用 ARM 版本的 gcc 编译。这个工具链和普通 gcc 的区别,就是它生成的是 ARM 指令集的可执行文件。
装交叉编译链有两种方案,我建议都了解,然后按需选。
方案一:用粤嵌官方资料自带的 arm-linux-gcc 4.5.1。这个是老牌工具链,教学环境里非常常见,但版本老,有个大坑:它本身是 32 位程序,装到 64 位 Ubuntu 上会报"No such file or directory",不是你命令敲错了,而是系统缺 32 位库。解决办法是装兼容库:
sudo apt install lib32z1 lib32ncurses5 lib32stdc++6装完之后解压工具链到 /opt 目录,配置环境变量:
sudo tar xjf arm-linux-gcc-4.5.1.tar.bz2 -C /opt export PATH=/opt/arm-linux-gcc-4.5.1/bin:$PATH把 export 那行写进 ~/.bashrc,以后每次开终端就自动生效。验证一下:
arm-linux-gcc -v能打印出版本信息就说明工具链没问题。
方案二:用 Ubuntu 软件源里的通用工具链。
sudo apt install gcc-arm-linux-gnueabihf好处是安装方便、更新及时,编译普通应用程序完全够用。但对部分厂商内核源码,老的 arm-linux-gcc 4.5.1 反而更稳妥,因为内核版本和编译器的匹配性更好。我的建议是:初期学应用开发用通用工具链,后面编译内核和驱动模块,老老实实切回官方工具链。
阶段性的小结论:工具链版本和板子系统的匹配非常重要,如果后面发现程序在开发板上运行报"not found"(文件明明存在),第一反应就应该是工具链版本不匹配。
2.2 串口终端:minicom 配置里藏着三个细节
调试嵌入式板子,串口是生命线。GEC6818 板子上一般有专门的调试串口,用 USB 转串口模块连接电脑。
Ubuntu 下我习惯用 minicom,轻量够用。安装和配置:
sudo apt install minicom ls /dev/ttyUSB*ls 这一步是为了确认串口设备名,一般是 ttyUSB0,如果同时插了多个 USB 转串口设备,可能变成 ttyUSB1、ttyUSB2。
接下来有个非常容易踩的坑:直接运行 minicom 会提示没有权限打开设备。需要把当前用户加入 dialout 组,这是 Ubuntu 管理串口权限的标准做法:
sudo usermod -aG dialout $USER改完必须注销重新登录,或者执行 newgrp dialout 让它立即生效,否则照样没权限。
然后配置 minicom:
sudo minicom -s进入设置界面后改三处:Serial Device 改成 /dev/ttyUSB0;Bps/Par/Bits 改成 115200 8N1;Hardware Flow Control 一定要关掉。第三点尤其重要,GEC6818 的调试串口没有接硬件流控线,如果开着,会出现"能发不能收"或者乱码的诡异现象。
我在 Windows 下也用过 SecureCRT、MobaXterm 这些工具,但我的建议是:如果你是长期做嵌入式,直接把主力开发环境放到 Ubuntu 上。串口工具、编译工具链、后面的 NFS、TFTP 服务全部在 Linux 下搞定,省去不同系统间切换的心智负担。
2.3 网络规划:让开发板和主机待在同一网段
开发和调试过程中,开发板和 Ubuntu 之间要频繁传文件,网络通信是基础。GEC6818 板载网口,连接方式有两种:插到路由器/交换机上,或者用网线直连电脑。
我推荐固定 IP 方案,避免 DHCP 分配的地址每次都不一样,导致调试脚本里的 IP 也要跟着改。参考规划:
- 开发板:192.168.1.10
- Ubuntu 主机(有线网卡):192.168.1.100
- 掩码:255.255.255.0
开发板进系统之后,用 ifconfig 临时配置:
ifconfig eth0 192.168.1.10 upUbuntu 主机端,如果是直连,可以在网络设置里把有线连接的 IPv4 改成手动,填上 192.168.1.100。
配置完先互 ping 一下。只有网络通了,后面 NFS、TFTP 才有意义。很多新手在 NFS 挂载上报错,排查半天,最后发现是两台机器压根不在同一网段,这种基础问题越早确认越好。
2.4 顺带说一句 VSCode 等工具的选择
开发机环境里,真正影响效率的是交叉编译链和网络服务,编辑器反而是最不重要的环节。我用 VSCode 通过 Remote SSH 插件远程连 Ubuntu 写代码,也有人在 Ubuntu 本地直接用 VSCode 或 vim,都行。
但有一点想提醒:很多初学者在 Windows 上花大量时间配置 Python 环境、Node.js 环境,这些热词我经常在搜索记录里看到。坦白说,做 GEC6818 这类板子的 Linux 开发,这些都不是主线,C 语言和 Shell 脚本才是主要工作语言。Python 在主机端做数据处理或脚本辅助可以,但别把精力耗在和目标无关的环境配置上。
3. 烧录与启动:从 SD 卡镜像到系统控制台
3.1 镜像从哪来、烧录到哪
GEC6818 的出厂镜像一般由粤嵌官方资料提供,文件里包含 uboot、kernel、rootfs 三部分,压缩包里可能是完整的 img 镜像文件,也可能是分区的多个 bin 文件。
把镜像烧到 SD 卡的方式有很多,Windows 下我推荐 balenaEtcher 或者 Win32DiskImager,操作简单,选镜像、选 SD 卡、烧录,三步完事。Ubuntu 下用 dd 命令:
sudo dd if=gec6818_sd.img of=/dev/sdb bs=4M status=progress注意 of 一定要写 /dev/sdb 整个设备,不能写成 /dev/sdb1 这类分区节点。烧录会清空 SD 卡全部内容,操作前确认卡里没有重要数据。
这里有个新手常踩的坑:烧录后直接拔卡上电,发现系统起不来。原因可能是镜像还在写入缓存里没有完全落盘。dd 命令结束后,执行 sync,等命令返回再拔卡。
3.2 启动拨码开关:决定命运的开关
GEC6818 底板上有一组拨码开关,用来选择启动介质:SD 卡启动、eMMC 启动、USB 下载模式。不同批次板子的开关位置可能不同,拿到板子先看说明书或丝印标识。
我第一次就是栽在这里。当时烧好了 SD 卡,信心满满地上电,结果串口零输出。排查到最后才发现,拨码开关一直拨在 eMMC 启动档位,而 eMMC 里没有系统,板子自然无法引导。
所以上电之前养成一个习惯:先确认拨码开关的位置。另外,如果烧好了 SD 卡却始终没反应,优先检查这块开关,而不是怀疑板子坏了。
3.3 启动日志怎么读:内核起来没有、卡在哪一步
上电之后,串口终端会刷出一大屏字符。我建议新手别急着跳过这些输出,里面蕴含着判断系统状态的关键信息。典型的启动流程分三个阶段。
U-Boot 阶段:开头几行会打印 U-Boot 版本,然后是 DDR 容量检测、环境变量加载。如果卡在这里反复重启,可能是启动介质选错,或者 SD 卡镜像不完整。
Kernel 阶段:U-Boot 之后会打印 Linux 内核版本、解压内核、初始化各种驱动。如果出现 "Kernel panic - not syncing: VFS: Unable to mount root fs",说明内核起来了但挂载根文件系统失败,问题通常出在 uboot 的 bootargs 参数,比如 root 设备指定错误。
文件系统阶段:如果看到 login: 提示符,恭喜,系统已经完全起来。默认账号密码以厂商配套资料为准,常见的是 root/root 或 root/123456,不同批次资料不一样,找到对应文档确认即可,也别乱试太多次,有些固件做了登录失败锁定。
3.4 什么时候需要把系统写进 eMMC
SD 卡启动适合前期开发调试,优点是系统坏了可以随时重烧,缺点是 SD 卡速度慢、占用一个卡槽、携带不方便。
项目原型确定后,可以把系统烧进 eMMC,这样拔掉 SD 卡也能开机。烧写方式通常是先 SD 卡启动进入系统,再执行厂商提供的 eMMC 烧写脚本或通过 fastboot 工具。这个过程操作前一定看清楚说明文档,尤其注意烧写过程中不能断电。
一旦 uboot 被写入损坏数据,恢复起来就麻烦了,可能要用短接或专用工具重新刷写。我自己在烧写 eMMC 时就很谨慎,先把完整可用的 SD 卡放在手边,作为救急的备份启动介质。
4. 打通"开发板与 Ubuntu"文件通道:NFS 挂载全流程
4.1 为什么强烈建议配 NFS
嵌入式开发中,代码在 Ubuntu 上交叉编译,最终要在开发板上运行。如果没有 NFS,你就得想尽办法把编译好的文件传到板子上:U 盘拷贝太慢、串口传输更是折磨、TFTP 虽然能传但操作繁琐。
NFS 的作用,是把 Ubuntu 上的某个目录直接挂载到开发板的某个目录下。开发板访问这个目录,就像访问本地文件一样。你在 Ubuntu 上编译好程序,直接在开发板上执行,改代码、编译、运行,循环极快,调试体验大幅提升。这也是网上很多人搜的"开发板挂载 ubuntu",本质就是这个。
4.2 Ubuntu 服务端配置步骤
配置 Ubuntu 为 NFS 服务端:
sudo apt install nfs-kernel-server mkdir -p /home/user/nfs创建共享目录后,编辑 /etc/exports,加入一行:
/home/user/nfs *(rw,sync,no_root_squash,no_subtree_check)各个参数的含义一定要弄明白,这不只是为了配通,更是为了后面能排查问题。
- rw:允许读写访问。
- sync:写入时同步落盘,避免数据丢失。
- no_root_squash:允许开发板上的 root 用户以 root 权限访问,这是权限没问题的关键。如果不加,开发板的 root 会被映射成匿名用户,创建文件时经常报 Permission denied。
- no_subtree_check:关闭子目录检查,减少访问限制。
改完配置文件,重启 NFS 服务:
sudo systemctl restart nfs-kernel-server showmount -e localhost如果输出里能看到共享的 /home/user/nfs,服务端就配好了。最后给共享目录开个宽松权限,开发调试期图省事:
chmod 777 /home/user/nfs4.3 开发板端挂载命令与参数
在 GEC6818 开发板上执行挂载命令:
mount -t nfs -o nolock 192.168.1.100:/home/user/nfs /mnt这里我特别加了 nolock 参数,原因很实际:很多开发板上的文件系统精简过,lockd 支持不完整,不加这个参数挂载后会卡死,或者文件操作时报奇怪的 I/O 错误。加上 nolock 直接绕过文件锁,开发调试场景完全没影响。
挂载成功后,开发板上 ls /mnt,应该能看到 Ubuntu 共享目录里的所有文件。
关于开机自动挂载,我的建议是初学阶段不要在 /etc/fstab 里写死。为什么?因为开发板开机时网络服务可能还没完全就绪,mount 命令就会失败,严重的时候系统会卡在挂载流程上等待超时,直接拖慢开机速度。手动敲一条命令虽然多花两秒钟,但可控性大大增强。
4.4 NFS 挂载失败的排查表
NFS 挂载报错是高频问题,我把最常见的错误和排查顺序整理成表:
| 错误现象 | 排查方向 |
|---|---|
| RPC: Timed out | 网络不通,先 ping 主机 IP,检查网线、IP 是否同网段 |
| Permission denied | 检查 /etc/exports 是否加了 no_root_squash,重启 NFS 服务 |
| Protocol not supported | 挂载参数加 vers=3,指定老版本 NFS 协议 |
| Input/output error | NFS 服务端重启过或目录不可用,showmount 确认共享是否正常 |
| 挂载成功但 ls 为空 | 检查 Ubuntu 共享目录是否真的有文件,属主和权限是否正确 |
特别说一下防火墙。Ubuntu 默认的 ufw 如果开着,会拦截 NFS 相关端口,导致客户端一直卡在 RPC 超时。验证方法:
sudo ufw status如果状态是 active,先临时关掉或者放行 2049 端口再测。开发调试期一句 sudo ufw disable 就能省掉大量排查时间。
5. 实战第一步:GPIO 点灯和按键读取
5.1 先看原理图,别猜引脚
开发环境全部跑通之后,就可以进入真正的代码实战了。先说点灯,这是嵌入式领域的 Hello World。
GEC6818 的扩展排针上引出了大量 GPIO,但你不要拿着杜邦线随便接一个针脚就以为它能当 GPIO 用。扩展接口上很多引脚是复用的,可能连在 LCD、串口、I2C 等外设上。动手之前,先打开底板原理图,找到自己准备用的排针位置,顺藤摸瓜查到它连到 S5P6818 的哪个 GPIO 控制器、哪个引脚号。
这一步非常关键,我见过太多次"代码写对了但灯不亮"的案例,最终都是 GPIO 编号搞错。原理图是硬件的真相,引脚复用关系一目了然。
5.2 Linux 下点灯的三条路
拿到正确的 GPIO 编号后,在 Linux 系统里点灯有三种方式,好坏差别很大。
第一种,如果官方内核已经把 LED 注册为标准 led 设备,直接操作 /sys/class/leds 下的接口即可。好处是简单、安全,坏处是可遇不可求,要看板子内核是否支持。
第二种,用 sysfs 通用 GPIO 接口。Linux 内核把 GPIO 控制抽象成了文件接口,操作方式就是把 GPIO 号写进一个虚拟文件系统。步骤如下:
echo 23 > /sys/class/gpio/export echo out > /sys/class/gpio/gpio23/direction echo 1 > /sys/class/gpio/gpio23/value顺理成章,echo 0 就是灭灯。这里的 23 是举例,实际 GPIO 编号要以你的原理图为准。
第三种,直接操作寄存器。应用层用 mmap 映射 /dev/mem,往 GPIO 控制器的寄存器地址写值。性能最高,但需要查 S5P6818 芯片手册里的 GPIO 基地址、寄存器偏移、控制位定义,门槛最高,适合后面学驱动开发时再深入研究。
对刚上手的同学,我推荐第二种 sysfs 方式,既能理解 GPIO 模型,代码量又小,出问题的概率最低。
5.3 交叉编译一个 C 语言点灯程序
用文件操作的方式写点灯程序,C 代码很直观:
#include <stdio.h> #include <stdlib.h> #include <string.h> #define GPIO_PIN "23" #define GPIO_PATH "/sys/class/gpio" void gpio_export(const char *pin) { FILE *fp = fopen(GPIO_PATH "/export", "w"); if (fp == NULL) { perror("export"); exit(1); } fprintf(fp, "%s", pin); fclose(fp); } void gpio_direction(const char *pin, const char *dir) { char path[128]; snprintf(path, sizeof(path), GPIO_PATH "/gpio%s/direction", pin); FILE *fp = fopen(path, "w"); if (fp == NULL) { perror("direction"); exit(1); } fprintf(fp, "%s", dir); fclose(fp); } void gpio_write(const char *pin, int value) { char path[128]; snprintf(path, sizeof(path), GPIO_PATH "/gpio%s/value", pin); FILE *fp = fopen(path, "w"); if (fp == NULL) { perror("value"); exit(1); } fprintf(fp, "%d", value); fclose(fp); } int main(void) { gpio_export(GPIO_PIN); gpio_direction(GPIO_PIN, "out"); while (1) { gpio_write(GPIO_PIN, 1); usleep(500000); gpio_write(GPIO_PIN, 0); usleep(500000); } return 0; }编译这个程序需要一个 Makefile,指定交叉编译链:
CC = arm-linux-gnueabihf-gcc TARGET = led_blink OBJS = main.o $(TARGET): $(OBJS) $(CC) -o $@ $^ %.o: %.c $(CC) -c -o $@ $< clean: rm -f $(TARGET) $(OBJS)make 之后生成 led_blink 可执行文件,把它放到 NFS 共享目录里。开发板上挂载好 NFS 后,直接运行:
/mnt/led_blink看到 LED 以固定频率闪烁,整个环境链路的验证就完成了。这一步看起来简单,但它是从"配置环境"跨到"实战应用"的标志性一步。
5.4 按键读取:从输出到输入
点灯跑通后,再做一个输入实验—按键读取,就能把 GPIO 的双向能力都体验一遍。
按键的原理很简单:按键按下时,引脚电平发生变化,要么从高变低,要么从低变高,具体取决于电路设计。查询原理图确认按键连接的 GPIO 编号后,步骤如下:
echo 24 > /sys/class/gpio/export echo in > /sys/class/gpio/gpio24/direction cat /sys/class/gpio/gpio24/value如果按键按下时引脚电平是拉低的,而外部电路没有加内部上拉/下拉电阻,读取到的可能是浮空电平,在 0 和 1 之间跳变,代码逻辑就会被干扰。解决方法是查 datasheet 确认是否能用内部上拉/下拉,不能的话就在外部电路加一个 10k 欧姆的电阻。
把按键状态和 LED 状态联动起来:按下一次灯亮,再按一次灯灭。这个组合实验工作量不大,但对理解 GPIO 输入输出模型、轮询机制、电路上下拉这些基础概念帮助很大。如果想更进一步,可以把轮询改成中断方式,那就要接触 Linux 中断子系统了,属于驱动开发的范畴,可以作为下一阶段的目标。
6. 最容易卡住人的五个问题:完整的排查链路
6.1 串口完全无输出
串口没有任何打印,是最让人慌的问题。按这个顺序排查,基本几分钟能定位。
先看拨码开关,确认板子是否真的进入了对应介质启动流程。再看连接线:GEC6818 的调试串口是独立的,别接到普通串口或者没引出的扩展引脚上。然后检查设备节点:ls /dev/ttyUSB* 是否存在,不存在可能是 USB 转串口模块驱动问题。再看权限:当前用户是否在 dialout 组,不在的话照前面方法加入。接着是 minicom 配置,波特率是否为 115200、硬件流控是否关闭。最后检查接线:USB 转串口模块的 TX 要接板子的 RX,RX 接板子的 TX,GND 共地,很多人忽略了 TX/RX 交叉,导致完全没有数据。
6.2 交叉编译的程序在开发板上运行报 not found
二进制的 not found 有两种含义,但很多新手本能地以为是文件不存在。排除文件路径问题后,用 file 命令检查可执行文件格式:
file led_blink如果输出是 ELF 32-bit LSB executable, ARM, EABI5,说明 ARM 格式没问题,那问题就出在动态库依赖上。用 readelf 检查依赖:
arm-linux-gnueabihf-readelf -d led_blink看到依赖了一堆 .so.0 文件,而开发板文件系统里根本没有,就会报 not found。解决办法有两个:换用与文件系统匹配的工具链版本,或者编译时加 -static 参数做静态编译,把所有库打进可执行文件里。开发调试期,静态编译省心省力。
6.3 NFS 挂载成功但写文件报错
这类问题通常不是网络问题,而是权限模型的问题。挂载成功后,在开发板上往 /mnt 里写文件:
touch /mnt/test如果报 Permission denied,排查方向很明确:先确认 /etc/exports 里有没有 no_root_squash 参数,没有的话加上并重启服务。再确认 Ubuntu 共享目录的权限,用 ls -ld 看属主和权限位,开发期间直接 chmod 777 最省事。修改完 exports 记得重启 NFS 服务,这是最容易遗漏的一步,别问我怎么知道的。
6.4 SD 卡烧好后还是起不来
烧录完成但系统起不来,优先查拨码开关位置。其次检查镜像文件是否完整,很多镜像附带 md5 校验值,烧录前先在 Ubuntu 上 md5sum 验证一遍。然后考虑 SD 卡本身的问题,烧录过程中是否弹出过错误,换一张卡重试。
我遇到过一种情况:同一张 SD 卡反复烧录多次之后,启动偶尔会卡死在 U-Boot 阶段,换卡后恢复正常。劣质 SD 卡或者老化卡在烧录时看似成功,实际某些扇区写入不可靠,这类问题排查起来很隐蔽,容易让人误以为是板子坏了。
6.5 开发板 ping 不通 Ubuntu 主机
网络问题先分层定位。开发板上 ifconfig 确认 IP 是否设置成功,Ubuntu 上 ip addr 确认有线网卡 IP 是否存在,直连模式下需要静态 IP。再观察网口指示灯:灯不亮基本是物理层问题,换网线、检查网口。排除物理层后看防火墙,sudo ufw disable 直接关掉测试,快速排除防火墙拦截。
一个实用的定位技巧:开发板先 ping 网关地址。如果网关通而主机不通,问题几乎肯定在 Ubuntu 本机网络配置;如果网关也不通,那问题出在开发板侧或者网线连接。这个二分定位法能帮你快速缩小排查范围,不用乱试一通。
这些坑我基本都踩过一遍,每一次排查到最后都会发现,问题本身不复杂,复杂的是没有一个清晰的排查顺序。如果你能把"先硬件、再网络、后软件"这个排查思路刻进脑子里,后面做任何嵌入式项目,都会觉得顺畅很多。