想靠嵌入式找一份不错的工作,简历上没有两三个能拿得出手的实战项目,基本聊不了几分钟就被筛掉了。我做了这么多年嵌入式开发,也面试过不少候选人,越来越确定一件事:嵌入式这行,完全不像互联网后端那样可以靠学框架、背八股进出,它是真刀真枪跑硬件的东西。面试官问你项目,不是为了听你吹牛,而是想判断你会不会排坑、懂不懂时序、能不能把一套代码在真正的芯片上跑稳。
这篇文章里,我把身边同事、学员和我自己走过的路重新梳理了一遍,挑出四个方向完全不同的嵌入式实战项目。这四个项目不是简单堆硬件,而是有目的地覆盖了嵌入式岗位最常见的几大能力区:STM32裸机开发、嵌入式Linux系统移植与应用、驱动与底层接口、端侧AI推理与测试。按我的经验,你只要认认真真做完其中两个,并且能把里面的细节讲透,绝大多数嵌入式软件、嵌入式Linux方向的面试,你都能稳稳接住。
1. 嵌入式学习的分水岭:你到底能不能靠项目立住
1.1 面试官看项目时心里在想什么
先说个结论:面试官看项目,从来不看你做了什么名字,而是看你拆问题的能力、调试的能力、看datasheet的能力。
一个合格的嵌入式岗位,你入职后要面对的永远是具体问题:串口丢数据了,到底是波特率不对还是中断优先级被抢占?屏幕花屏了,是LCD时序配错还是DMA搬运与刷新赛跑?系统启动挂载根文件系统失败,是U-Boot环境变量错还是内核缺了NFS驱动?这些问题没有一道能在教科书里找到现成答案,全要靠你真正做过、烧过、修过,才能形成感觉。
所以面试官问“你这个项目用了什么芯片”“RTOS怎么切换任务”“Flash写寿命怎么处理”,表面听的是技术名词,实际听的是你有没有踩过坑、踩完有没有总结。这也是为什么我在带人入门时反复强调:千万别为了堆名字去写十几个项目,写深比写多重要得多。
1.2 怎么组合两个项目,覆盖面试大部分考点
从求职角度看,项目组合要撑开覆盖面。只做STM32彩灯、温湿度这种入门级Demo,面试官一听就知道没难度;只做一个Linux移植还讲不清根文件系统,也会被追问到尴尬。
我更推荐“一底一顶”的组合思路:底层项目围绕MCU,把GPIO、中断、定时器、串口、I2C/SPI、按键扫描、状态机、低功耗这堆基本功打牢;顶层项目围绕嵌入式Linux,把bootloader启动参数、内核配置、根文件系统挂载、进程通信、应用层设计这些系统级能力打通。两个项目能把“软硬结合”和“系统视角”两条线都体现出来,面试时你说话的底气都不一样。
如果你精力允许,再加一个嵌入式AI相关的验证性项目,哪怕只是把一个MNIST或关键词识别模型部署到MCU上做推理测试,也足够让你从一堆“只会写寄存器”的候选人里冒出来。下面我就把这四个方向的项目一个个掰开讲。
2. 项目一:基于STM32的环境监控与数据采集系统
2.1 硬件选型和功能拆解
这个项目我称之为“嵌入式基本功集大成者”。它的功能定义很简单:用STM32采集温湿度、光照强度、空气质量等环境参数,通过OLED/LCD显示,同时把数据通过UART或Wi-Fi模块上报给上位机或云平台。听起来不复杂,但它要串联的东西非常全。
我的推荐方案是主控选择STM32F103系列或者更主流的STM32F407,传感器选型上:
- 温度湿度:SHT30或DHT11。SHT30走I2C,精度高一点;DHT11便宜但时序是单总线,反而更适合拿来练底层时序。
- 光照强度:BH1750,I2C接口,功耗低,数据手册时序非常明确,适合培养读手册的习惯。
- 空气质量:可以用SGP30或者MQ系列,前者走I2C,能输出eCO2和TVOC,后者是模拟量读取,需要借助ADC采集。
这些传感器难度层层递进:单总线通信、I2C时序、模拟量采集,刚好把嵌入式驱动三大入口全部覆盖。显示方面我建议用0.96寸SSD1306驱动的OLED,I2C或SPI都行。通信上报可以选择ESP8266或ESP32模块,走AT指令集,这样不需要额外学复杂的socket编程;但如果你的目标岗位偏嵌入式Linux,也可以把上报端改成Linux上位机,用串口协议交互,这样两个项目还能串成闭环。
2.2 核心代码要点:状态机、DMA、低功耗
这个项目看起来简单,真正面试能加分的地方藏在细节里。
先说数据采集链路。I2C读取SHT30时,如果只用阻塞式读取,每次读传感器前CPU都要死等I2C外设完成,这在系统里任务一多就成灾难。我建议用状态机来管理单总线和I2C通信:把“发送读命令”“等待响应”“读取数据”“解析校验”拆成几个状态,在定时器中断或RTOS任务里轮转,一个状态跑完寄存状态,让出CPU。这样主循环还能干别的事。
然后是显示刷新。OLED刷新本身不能放在主循环里反复刷,否则传感器采集和按键响应都会卡。更合理的做法是把显示缓冲放在内存里,传感器数据更新时只改缓冲里的几个数字,显示刷新交给DMA或定时器触发,避免占用CPU。很多初学者在这里踩坑,以为OLED刷新慢是硬件问题,其实是自己把所有任务都串在一个大循环里了。
事件型系统的基础是“生产者消费者”模型:传感器中断或定时器节拍产生数据,主循环消费数据并更新显示。配合一个简单的消息队列,就能把系统扩展到按键、通信上报等多个模块。这些思想比代码本身更重要。
2.3 面试官最喜欢追问的几个点
- 你传感器采集时CPU占用率多少?怎么测的?这个问题能拉开差距,因为你得真去测过才知道,答案藏在RTOS提供的任务统计或者靠GPIO拉高拉低用示波器看。
- I2C地址你怎么确定的?SHT30有不同地址引脚,手册里写得清楚,能答上来说明你会看手册而不是照抄例程。
- 低功耗模式设计了没?一个电池供电的设备,如果CPU不能进Sleep,那设计就是失败的。STM32的Stop模式配合RTC唤醒是标准套路。
- ADC采集的噪声你怎么处理?滑动滤波、中值滤波这些方法得能说清楚,尤其要说为什么对突变数据要做限幅。
这个项目做完,MCU方向70%以上的面试题你都接触过了。
3. 项目二:嵌入式Linux工程网关(含NFS根文件系统挂载)
3.1 为什么嵌入式Linux项目含金量高
MCU项目做得再熟,也只能覆盖嵌入式岗位里偏底层、偏硬件的工作。现在大量嵌入式岗位集中在车载、工控、物联网网关、智能摄像头这些方向,核心系统是嵌入式Linux。这类岗位要求的不是“点灯”,而是你对操作系统、文件系统、进程通信的理解。
这个综合网关项目我建议这样做:在开发板上移植嵌入式Linux,板子选型可以用NXP i.MX6ULL、正点原子或华清远见的配套开发板,或者树莓派方案(注意树莓派更接近通用Linux,系统定制深度不够,对面试帮助会打折)。功能上实现一个数据采集网关:采集下位机MCU通过串口发送的数据,解析后存进SQLite,再通过MQTT/HTTP上报到云端,同时提供一个Web页面做参数配置。
这个项目真正的难点不在应用层写代码,而在系统定制能力。从U-Boot到内核到根文件系统到应用,全链路都得是你自己跑通的。面试官特别爱问“你怎么把根文件系统放到板子上的”“启动流程每一步谁加载谁”这类问题,你只要真实做过一遍,回答起来会非常流畅。
3.2 NFS挂载根文件系统的具体配置与调试
嵌入式Linux开发过程中,最常用的调试手段之一就是NFS挂载根文件系统。每次改完文件系统不用反复烧写Flash,直接通过网络从服务器加载,开发效率能提升一大截。这里很多人栽在细节上,我把关键步骤拆出来。
首先是环境准备。宿主机(Ubuntu)上要装并启用NFS服务,配置/etc/exports导出你要共享的根文件系统目录。注意开发场景下NFS v3往往比NFS v4更好用,v4的锁和权限机制在简单嵌入式环境里容易出幺蛾子。常见做法是在/etc/exports里加上:
/tftpboot/rootfs *(rw,sync,no_root_squash,no_subtree_check)配置完执行 exportfs -ra 让配置生效,然后在开发板上进入U-Boot,设置内核启动参数。重点参数是:
setenv bootargs 'console=ttyS0,115200 root=/dev/nfs nfsroot=192.168.1.100:/tftpboot/rootfs,v3 ip=192.168.1.50:192.168.1.100:::::eth0' saveenv把命令拆开看:root=/dev/nfs 告诉内核根文件系统由NFS提供;nfsroot后面的v3指定使用NFS协议版本3;ip参数里第一个是开发板IP,冒号后面是NFS服务器IP,最后eth0是网络接口名。这里最容易踩的两个坑:一是网络参数里漏了服务器IP,导致内核挂载时找不到NFS服务;二是内核配置时少了CONFIG_NFS_FS、CONFIG_ROOT_NFS和CONFIG_NFS_V3这几个选项,内核没有编译出NFS客户端和网络根文件系统支持。
我第一次做这个的时候,开发板启动卡在“VFS: Unable to mount root fs via NFS”,排查了半天发现是内核移植的时候把NFS配置整个漏掉了,后来回去make menuconfig重新配置内核,把网络文件系统相关选项全选上,重新编译内核镜像烧写进去才解决。
还有个细节是:如果开发板和其他设备有网络冲突,可以临时用网线直连宿主机,把服务器IP和板子IP设成同一网段,省掉交换机这个中间环节。很多“挂载超时”的诡异问题,其实都是路由器把NFS端口或数据包丢了,直连是最稳的。
3.3 应用层设计能力和进程通信
系统能跑起来之后,网关应用层怎么做同样是个加分点。
我这里强烈建议用多进程+多线程而不是单线程死循环:一个串口采集线程负责读取下位机上来的数据帧,解析后写入消息队列;一个业务进程从队列取数据,写SQLite并处理上报;Web配置模块用轻量级服务器实现。进程之间用共享内存、消息队列或套接字通信。
整个过程里你会反复用到嵌入式Linux的经典知识:信号处理、线程同步、文件IO、Socket编程、数据库操作。而且这些都是可以放到简历上、面试官一定会追问的方向。比如“数据库写频繁怎么办”这种问题,你至少要答出“用批量插入代替单条插入”“SQLite的WAL模式降低锁竞争”这类有工程味道的答案。
遇到核心业务需要保证可靠性的时候,还可以考虑给关键数据加一个环形缓冲区,串口采集线程只管写,上报线程只管读,缓冲区快满时及时落盘。这个设计理念跟MCU项目里“生产者消费者”模型一脉相承,面试时你把两个项目的思想打通了讲,面试官会明显觉得你有体系化的能力。
4. 项目三:按键非阻塞扫描与输入管理框架
4.1 从最简单的键盘扫描说起
很多刚入门的朋友写按键程序,都是一上来就来个死循环delay消抖外加不断检测引脚电平。代码大概长这样:
while (1) { if (GPIO_ReadPin(KEY_PORT, KEY_PIN) == 0) { delay_ms(20); if (GPIO_ReadPin(KEY_PORT, KEY_PIN) == 0) { // 处理按键 } } }这个写法在玩具工程里没问题,但稍微往真正产品方向走一步就崩了:你的主循环被delay占掉20毫秒,传感器采集、显示刷新、通信上报全都被卡住;如果用两个按键分别做短按长按连击,代码直接变成一坨难以维护的分支堆。这就是为什么我在项目一里反复强调状态机——处理按键,本质就是处理一个有限状态机的状态转移。
所谓的“非阻塞扫描”,核心思想就是:不依赖任何长时间的忙等待,用一个固定节拍(比如1ms或5ms的定时器中断)去采样按键引脚,把电平变化记录到状态机中,消抖、边沿检测、长按连击全都在这个节拍里完成。主循环永远可以自由运行,只在需要处理事件时读取按键状态。
4.2 消抖、短按、长按、连击的状态机设计
一个稳定好用的按键模块,至少要让代码能区分五种状态:空闲、按下消抖中、确认按下、持续按住中、释放消抖中。每一次定时器中断到来,做一次状态转移。
举个简单例子,短按和长按检测可以用事件+计时的方式实现:
typedef enum { KEY_STATE_IDLE, KEY_STATE_DEBOUNCE_PRESS, KEY_STATE_PRESSED, KEY_STATE_DEBOUNCE_RELEASE } key_state_t; typedef struct { key_state_t state; uint32_t press_cnt; uint32_t release_cnt; uint8_t level_now; uint8_t level_last; } key_t;扫描函数在每个节拍执行:
void key_scan(key_t *key, uint8_t pin_level) { switch (key->state) { case KEY_STATE_IDLE: if (pin_level == ACTIVE_LOW) { key->state = KEY_STATE_DEBOUNCE_PRESS; key->press_cnt = 0; } break; case KEY_STATE_DEBOUNCE_PRESS: if (++key->press_cnt >= DEBOUNCE_MS) { if (pin_level == ACTIVE_LOW) { key->state = KEY_STATE_PRESSED; key_event_post(KEY_EVENT_SHORT_PRESS); } else { key->state = KEY_STATE_IDLE; } } break; // 其余状态类似... } }每一次状态转移都发生在定时中断里,不占用主循环。至于长按和连击,本质就是PRESSED状态下记录按下的累计时间,超过长按阈值触发长按事件;需要连击的话,在持续按住过程中按照固定周期重复发事件。
这里要特别提醒:中断函数里绝对不要做耗时操作,比如printf、malloc、复杂的字符串处理。按键扫描中断里只做状态判断和计数,真正的事件分发放到主循环。不然你的系统会频繁出莫名其妙的问题,比如按一下键屏幕卡一下,就是因为printf这种重活把中断拖死了。
4.3 代码分层:把按键、显示、业务解耦
很多项目的代码到最后变成“一个大文件里几千行”,核心问题是没有做分层。
我的建议是至少分成三层:驱动层(BSP)负责直接操作GPIO、I2C、SPI这些外设,不包含业务逻辑;中间层(HAL/Service)提供按键事件、传感器数据、显示刷新这样的服务接口,对上对外设细节不可见;应用层里面才是业务逻辑,比如“温度超过阈值就报警”“长按三秒进入配置模式”。按键模块对外只提供一个key_event_post或者一个事件回调,上层完全不关心GPIO是怎么配置的。
这样分层有什么好处?第一,你换一块开发板或者换个按键接的引脚,只需要修改BSP层,业务代码一行不动;第二,面试时你能跟HR和技术面讲清楚“模块划分”和“可移植性”这种抽象概念,而不是只会讲“我点了个灯”。嵌入式工程师的进阶之路,就是从“写能运行的代码”到“写能维护的代码”,这个项目恰好是练分层能力的最佳载体。
5. 项目四:嵌入式AI推理与自动化测试小项目
5.1 嵌入式AI项目到底做多大多深才合适
现在“嵌入式AI”几乎成了热词,很多岗位描述里都写了“具备AI部署经验优先”。很多人一听就慌,觉得要学深度学习、要训练大模型。其实嵌入式AI岗位更需要的,是你理解模型怎么在端侧跑起来、怎么量化、怎么测精度,而不是让你从头训练大模型。
这个项目我建议做成“部署验证”性质:在STM32H7或带有NPU的芯片上,用TensorFlow Lite Micro或STM32Cube.AI部署一个轻量级模型。任务选择可以是MNIST手写数字识别、唤醒词检测或简单的图像分类。整个流程分四步:提前在PC上训练一个模型、转为TFLite格式、量化成INT8、部署到MCU并通过串口打印推理结果。
我第一次做的时候选了图像分类,心想“识别猫狗”多酷,结果模型下载下来一看参数几百万,MCU根本跑不动。后来换成GAP8和STM32上的微型模型,参数量降到几万级,推理一次才几十毫秒,这才跑通。所以做嵌入式AI项目,千万别贪模型规模,关键是全链路走通。
5.2 量化你真的搞懂了吗
嵌入式AI面试中,高频问题永远是“为什么量化?”“INT8量化精度掉多少?”“量化后Latency快了为什么”。
这里有个非常关键的工程经验:量化不是简单把float32变成int8,它涉及权重和激活值的取值范围映射。最常见的方案是“全整型量化”(Full Integer Quantization),它会在量化时统计每一层激活值的min/max,然后计算出scale和zero point,推理时用定点运算替代浮点运算。这个前置统计过程叫校准(Calibration),需要一小批代表性数据,不能随机瞎给。
我在实际做的时候发现,用默认的量化参数跑MNIST,精度从99%掉到94%左右,看起来不少,但其实完全够用。如果精度掉太多,优先检查校准数据够不够、有没有覆盖真实输入分布,之后考虑对敏感层做混合精度。这些经验远比“我会用命令量化”值钱,能讲出来说明你真做优化过。
5.3 端侧推理的自动化测试方案
一个AI项目没有测试,等于没做。嵌入式AI的测试跟普通软件测试差别很大,核心指标是:精度、推理时延(Latency)、内存占用(RAM/Flash)和功耗。
我的做法是把测试做成自动化流程:PC端准备好测试图片集,通过串口或USB批量发送给开发板,开发板跑完推理后把预测结果、耗时、峰值内存回传PC,PC上对比标签算准确率。整个过程写成一个Python脚本,一键执行,数据自动落到CSV里。
用这种自动化测试,你能很自然地回答面试官的连环追问:“你的模型准确率多少?测了多少张图?每张图推理耗时多少?内存余量多少?”,每一问都能拿出具体数据。这就是为什么这个项目面试说服力强的根本原因——你用实证数据替代了空洞描述。
6. 面试实战:项目讲解的加分技巧和避坑清单
6.1 一定要避开的三个回答方式
我面过的候选人里,项目介绍翻车的原因高度集中在三种。
第一种是“念简历”:把项目名字、芯片型号、功能列表背一遍,没有任何个人思考。面试官问“为什么这么设计”,回答“我看教程这么写的”,这种基本凉了。
第二种是“只讲成功不讲坑”:整个项目讲得像说明书一样顺滑。真实项目一定会遇到问题,比如串口乱码、NFS挂载失败、量化后精度暴跌、按键误触发。你不讲问题,面试官反而觉得你没深度参与,因为真正写过代码的人一定知道哪里会卡壳。
第三种是“术语轰炸”:满嘴RTOS、DMA、信号量、模型量化,但被问到具体实现就含含糊糊。术语是拿来组织语言的,不是拿来掩盖不懂的。你宁可用最朴素的话把一个小问题讲透,也不要堆一堆自己撑不住的名词。
6.2 用“背景-方案-难点-量化结果”的框架讲项目
我给团队准备过一套讲项目的表达公式:背景痛点、方案设计、实施过程、难点突破、量化结果。工作之后申请升职答辩也是这个结构。
比如项目二,你可以这样说:背景是设备现场需要从多个传感器采集数据并上报云端;方案是在开发板上跑嵌入式Linux,下位机串口上报,网关用多进程架构处理数据,Web页面显示实时状态;实施过程中遇到根文件系统挂载失败,排查发现内核缺NFS配置,重新配置编译后解决;最终网关稳定运行48小时,数据上报成功率99.5%。这个描述既有技术深度,又有时间和数字,面试官一下子就能判断你确实动手做过。
还有一个小技巧:主动暴露一个小问题并展示“排查思路”。比如“按键消抖参数一开始设太短,误触发率高,后来我从5ms调到20ms并用状态机做边沿检测,误触发率降到0.3%以下”。主动暴露问题,其实是在暗示下面两个亮点:你懂参数要实测、你懂统计分析。
6.3 校招机考和面试准备的联动策略
如果目标是校招或者转岗,大厂嵌入式岗位通常会有机考环节,笔试题目主要集中在嵌入式C语言、Linux命令进程、网络基础、数据结构少量内容。机考成绩只是一张入场券,真正决定去留的是项目深挖。
我的建议是:机考以刷题为主,项目则以深度复盘为主。每做完一个项目,给自己留两天把代码从头重读一遍,把每个模块这么写的原因、其他方案的利弊、测试数据整理成文档。这些素材面试时直接拿来用。
7. 最后分享几点我带项目的心得
我在带人和自己做项目的过程中,总结几个特别想告诉你的经验:
第一,项目难度要“小步快跑”。第一个项目千万别C/V大而全,上来就搞Linux+AI,会把自己劝退。先把STM32传感器采集这种小闭环跑通,建立“代码烧进芯片真的能控制硬件”的正反馈,再慢慢加大。
第二,调试工具的投资不可省。逻辑分析仪、示波器、串口助手、万用表,这些工具不贵但要趁早用熟。很多嵌入式问题不靠猜,靠看波形、抓信号、量电平就能定位。你能不能用示波器看一个I2C波形,是区分入门和高手的潜在线索。
第三,把你做过的项目做成一个可见的Thingsboard或GitHub仓库,拍照、录屏、写README都算。面试官不一定会看,但你自己整理的过程本身就是深度复盘,会让你对项目的理解上一个台阶。我在面试时最怕遇到“项目是我做的,但代码都不是我写的也讲不清细节”的候选人,那些能讲清细节的人,往往在项目复盘上没少下功夫。
如果你现在正准备换工作或找第一份嵌入式工作,别急着背八股。扎扎实实做完四个项目里的两个,把每一行代码为什么这么写想明白,把遇到的每一个坑都记录下来,面试的时候你自然会有底气。嵌入式这行最迷人的地方就在这——你的能力,就藏在那些你亲手焊过、烧过、排过错、最终跑起来的板子里。