AT24C02 EEPROM应用指南:I2C协议、驱动代码与硬件设计详解

发布时间:2026/7/31 1:53:53
AT24C02 EEPROM应用指南:I2C协议、驱动代码与硬件设计详解 1. 从“掉电即失”到“数据永生”为什么我们需要AT24C02做嵌入式开发的朋友对“掉电数据丢失”这个痛点应该都深有体会。你用STM32、51单片机或者ESP32辛辛苦苦采集了一堆传感器数据或者设置了一堆用户参数结果一拔电源一切归零又得从头再来。这种感觉就像你写了一晚上的文档没保存电脑突然蓝屏了一样让人崩溃。这时候一个能“记住”数据的小芯片就显得至关重要了。AT24C02就是这样一个在电子世界里充当“小本子”的角色。它是一种E2PROM中文叫电可擦除可编程只读存储器。这个名字听起来有点绕但拆开看就明白了“电可擦除”意味着你可以用电路信号而不是紫外线来擦掉里面的数据“可编程”是说你可以往里写新数据“只读”是相对于运行时的“读写”而言它主要用来存储需要长期保存、不常修改的数据。它和Flash、RAM有什么区别简单打个比方RAM像是你工作时的电脑桌面东西放上去读写飞快但一关机掉电就全没了Flash比如单片机的程序存储区像是你的硬盘能存大量数据且掉电不丢但擦写寿命有限通常几万到几十万次而且擦写是以“块”为单位比较麻烦而E2PROM特别是像AT24C02这种小容量的就像是你手边的一个便签本。它容量不大AT24C02只有256字节但可以按字节擦写非常灵活擦写寿命高达100万次以上专门用来存那些需要频繁更新但又不能丢的小数据比如设备的序列号、校准参数、运行时间计数器、用户设置等。所以当你看到项目里用到DS1302时钟芯片要存时间或者传感器需要记录校准值又或者一个产品需要记住用户最后一次的设置状态AT24C02这类芯片大概率就会出现在电路图上。它体积小、接口简单常用的I2C两线接口、功耗低、数据可靠是嵌入式系统里不可或缺的“记忆细胞”。2. 引脚与内部架构拆解这颗“记忆细胞”的物理构造拿到一颗AT24C02它通常是一个8引脚的小芯片常见封装有DIP-8直插和SOIC-8贴片。别看引脚少每个都肩负着重要使命。我们结合典型应用电路来理解A0, A1, A2 (引脚 1, 2, 3)器件地址选择引脚。这是I2C总线多设备共存的关键。I2C总线通过一个7位或10位的地址来寻址设备。对于AT24C02这3个引脚的电平接VCC或GND决定了该芯片地址的低3位。这允许你在同一条I2C总线上挂载最多8个AT24C02芯片2^38通过给它们不同的A0-A2配置来区分。如果电路板上只用一颗通常直接把这三个引脚都接地GND。WP (引脚 7)写保护引脚。这是一个非常实用的硬件保护功能。当WP引脚接高电平VCC时芯片的整个存储阵列将被写保护此时任何写入操作都会被忽略但读取操作正常。这可以防止程序跑飞时意外篡改关键数据。当WP引脚接地GND时允许正常的读写操作。在产品量产或数据固化后可以将此引脚接高电平实现硬件级的数据锁死。SCL (引脚 6)I2C时钟线。由主设备如单片机产生用于同步数据通信。需要接一个上拉电阻通常4.7kΩ~10kΩ到VCC。SDA (引脚 5)I2C数据线。双向开漏引脚用于传输地址和数据。必须接一个上拉电阻到VCC。VCC (引脚 8)电源正极。宽压工作常见范围1.8V~5.5V使其能兼容3.3V和5V系统。GND (引脚 4)电源地。注意I2C总线的上拉电阻必不可少因为SCL和SDA都是开漏输出不加上拉电阻总线无法拉高通信会完全失败。阻值根据总线速度和布线电容选择通常4.7kΩ适用于标准模式100kHz更高速率400kHz可能需要更小的电阻如2.2kΩ。内部架构浅析 AT24C02的内部可以想象成一个有256个“小房间”的矩阵每个房间存储单元能存放8位1字节数据。这些房间被组织成32页Page每页8字节。这个“分页”结构对写入操作有重要影响。此外芯片内部集成了高压电荷泵因此只需要单电源供电即可完成擦写操作无需外部提供高压。还有至关重要的写周期定时器在你发送完写入命令后芯片内部需要时间典型值5ms将数据从缓存真正“刻录”到存储单元在此期间芯片不会响应I2C通信这个特性决定了我们编程时必须处理“应答查询Acknowledge Polling”。3. I2C协议与AT24C02的专属对话规则AT24C02通过I2C总线与主控芯片通信。I2C协议本身是一个学问但针对AT24C02我们聚焦在最核心的交互流程上。一次完整的操作始于START条件SCL高时SDA由高变低终于STOP条件SCL高时SDA由低变高。核心是器件地址字Device Address Word 主设备发起START后发送的第一个字节就是器件地址。其格式如下[ 1 | 0 | 1 | 0 | A2 | A1 | A0 | R/W ]前4位固定为1010这是Atmel现Microchip给E2PROM产品定义的家族标识。接着3位 (A2, A1, A0)对应芯片上A2, A1, A0引脚的电平状态1为高0为低。这决定了芯片的地址。最后1位 (R/W)读写控制位。0表示主设备接下来要写入数据到从设备1表示主设备要从从设备读取数据。例如一颗A2A1A0全部接地的AT24C02其写操作的地址字节是0xA0(1010 000 0)读操作地址字节是0xA1(1010 000 1)。接下来的关键字地址Word Address发送完器件地址并收到ACK后对于读写操作都需要指定你要访问存储空间的哪个位置。AT24C02有256字节地址范围0x00~0xFF所以需要发送一个8位的字地址。这个地址是你要读写的起始地址。重要心得很多初学者在这里犯错以为字地址是“页地址”。记住它就是字节地址。对于AT24C020x00就是第一个字节0xFF是最后一个字节。3.1 字节写入与页写入效率与风险的权衡字节写入Byte Write 这是最直接的方式。流程为START - 发送写地址(0xA0) - 收到ACK - 发送字地址 - 收到ACK - 发送一个字节数据 - 收到ACK - STOP。 发送STOP信号后AT24C02开始内部写周期约5ms在此期间它不会应答任何请求。这种模式简单安全一次只写一个字节。页写入Page Write 为了提升连续写入的效率AT24C02支持页写入。在发送完起始字地址和第一个数据字节并收到ACK后不要发送STOP信号而是继续发送下一个数据字节。芯片会在收到每个字节后自动将内部字节地址计数器加1。你可以连续发送多达一页的数据对于AT24C02一页是8字节。流程示例写入地址0x10开始的3个数据 START - 0xA0 (ACK) - 0x10 (ACK) - Data1 (ACK) - Data2 (ACK) - Data3 (ACK) - STOP。警告页写入的“翻页”陷阱 这是AT24C02编程中最经典的坑。内部地址计数器在到达页边界如从0x17到0x18时会自动回滚到该页的起始地址0x10。如果你试图连续写入超过8个字节或者起始地址没对齐导致跨页数据就会在页内被覆盖而不是写到下一页。避坑策略在编写连续写入函数时务必先计算起始地址所在的页以及剩余字节数是否会导致跨页。如果会就必须拆分成多次页写入或字节写入操作。一个健壮的写入函数应该包含页边界检查逻辑。3.2 随机读取与顺序读取获取数据的两种姿态当前地址读取Current Address Read 芯片内部保存着一个“最后操作过的地址1”的地址指针。直接发送读地址0xA1芯片就会从这个指针指向的地址开始输出数据。这种方式很快但你不确定读的起始点是哪里除非你很清楚之前的操作序列。随机读取Random Read 这是最常用、最可控的读取方式。它需要一个“哑写Dummy Write”过程来设定起始地址。 流程START - 发送写地址0xA0 (ACK) - 发送要读取的字地址 (ACK) - 再次发送START称为重复起始条件Re-START- 发送读地址0xA1 (ACK) - 开始接收数据。 主设备在接收完一个字节后需要发送一个ACK信号芯片才会继续发送下一个地址的数据。当主设备不想再读时在接收最后一个字节后发送一个NACK然后发送STOP。顺序读取Sequential Read 在随机读取启动后只要主设备持续回复ACK芯片就会在每次发送完一个字节后自动将内部地址计数器加1并连续输出下一个地址的数据。直到到达存储空间末尾0xFF地址计数器会翻转到0x00实现循环读取。这非常适合读取大块连续数据。4. 驱动代码实战从原理到稳定可靠的C语言实现理解了协议我们来看代码。这里以通用的标准C语言为例假设你已有基本的I2C底层驱动I2C_Start(),I2C_Stop(),I2C_SendByte(),I2C_ReadByte()等函数。我们重点关注应用层逻辑和稳定性处理。4.1 基础单字节读写函数首先定义器件地址。假设A2A1A0接地。#define AT24C02_ADDR_WRITE 0xA0 // 写地址 #define AT24C02_ADDR_READ 0xA1 // 读地址 #define AT24C02_PAGE_SIZE 8 // 页大小单字节写入函数/** * brief 向AT24C02指定地址写入一个字节 * param addr: 要写入的地址 (0x00~0xFF) * param data: 要写入的数据 * retval 0: 成功, 非0: 失败 */ uint8_t AT24C02_WriteByte(uint16_t addr, uint8_t data) { /* 1. 启动I2C发送器件写地址 */ I2C_Start(); if (I2C_SendByte(AT24C02_ADDR_WRITE) ! I2C_ACK) { I2C_Stop(); return 1; // 器件无应答 } /* 2. 发送要写入的存储地址 */ if (I2C_SendByte((uint8_t)addr) ! I2C_ACK) { I2C_Stop(); return 2; // 地址无应答 } /* 3. 发送要写入的数据 */ if (I2C_SendByte(data) ! I2C_ACK) { I2C_Stop(); return 3; // 数据无应答 } /* 4. 停止条件触发芯片内部写周期 */ I2C_Stop(); /* 5. 等待写周期完成必须 */ AT24C02_WaitWriteComplete(); return 0; // 成功 }关键点在于第5步的等待。因为STOP信号后芯片进入忙状态必须等待其完成后才能进行下一次操作。实现等待函数应答查询法/** * brief 等待AT24C02内部写操作完成 * note 采用应答查询(Polling ACK)方式最可靠 */ void AT24C02_WaitWriteComplete(void) { uint8_t ack; uint16_t timeout 1000; // 超时计数防止死等 do { I2C_Start(); ack I2C_SendByte(AT24C02_ADDR_WRITE); // 尝试发送写地址 timeout--; if (timeout 0) { // 超时处理可能是芯片损坏或连接问题 break; } } while (ack ! I2C_ACK); // 如果芯片忙会返回NACK I2C_Stop(); // 查询成功后发送一个STOP结束本次查询 }实操心得这个等待循环是必须的。有些教程为了代码简单直接用delay_ms(5)硬延时。这在主循环中会阻塞系统影响实时性。而“应答查询”是效率最高且最可靠的方式它利用了芯片忙时不应答的特性一旦查询成功收到ACK就立即退出实际等待时间就是芯片的写周期时间。随机读取函数/** * brief 从AT24C02指定地址读取一个字节 * param addr: 要读取的地址 (0x00~0xFF) * retval 读取到的数据 */ uint8_t AT24C02_ReadByte(uint16_t addr) { uint8_t received_data; /* 1. 哑写过程发送写地址和要读取的起始地址 */ I2C_Start(); I2C_SendByte(AT24C02_ADDR_WRITE); I2C_SendByte((uint8_t)addr); /* 2. 发送重复起始条件切换为读模式 */ I2C_Start(); // 注意这里是重复起始不是先Stop再Start I2C_SendByte(AT24C02_ADDR_READ); /* 3. 读取数据主机最后发送NACK表示只读一个字节 */ received_data I2C_ReadByte(I2C_NACK); // 读一个字节后跟NACK /* 4. 发送停止条件 */ I2C_Stop(); return received_data; }4.2 高级功能多字节读写与页边界处理基于单字节操作我们可以构建更强大的多字节读写函数并妥善处理页边界。多字节写入函数自动处理页边界/** * brief 向AT24C02连续写入多个字节 * param addr: 起始地址 * param pData: 数据缓冲区指针 * param len: 要写入的数据长度 * retval 0: 成功, 非0: 失败 */ uint8_t AT24C02_WriteBuffer(uint16_t addr, uint8_t *pData, uint16_t len) { uint16_t bytes_to_write; uint16_t bytes_written 0; while (len 0) { /* 计算当前页剩余空间 */ uint8_t page_offset addr % AT24C02_PAGE_SIZE; bytes_to_write AT24C02_PAGE_SIZE - page_offset; /* 如果剩余数据长度小于页剩余空间则全部写入 */ if (bytes_to_write len) { bytes_to_write len; } /* 执行页写入 */ I2C_Start(); if (I2C_SendByte(AT24C02_ADDR_WRITE) ! I2C_ACK) goto error; if (I2C_SendByte((uint8_t)addr) ! I2C_ACK) goto error; for (uint16_t i 0; i bytes_to_write; i) { if (I2C_SendByte(pData[bytes_written i]) ! I2C_ACK) goto error; } I2C_Stop(); /* 等待本次页写入完成 */ AT24C02_WaitWriteComplete(); /* 更新指针和计数器 */ addr bytes_to_write; bytes_written bytes_to_write; len - bytes_to_write; pData bytes_to_write; // 注意指针移动 } return 0; error: I2C_Stop(); return 1; // 写入过程中出错 }这个函数的核心是while循环里的页边界计算。它确保了无论起始地址和长度如何每次循环写入的数据都不会跨越页边界从而避免了数据覆盖的灾难性错误。多字节顺序读取函数/** * brief 从AT24C02连续读取多个字节 * param addr: 起始地址 * param pBuffer: 接收数据缓冲区指针 * param len: 要读取的数据长度 */ void AT24C02_ReadBuffer(uint16_t addr, uint8_t *pBuffer, uint16_t len) { /* 1. 哑写过程设定起始地址 */ I2C_Start(); I2C_SendByte(AT24C02_ADDR_WRITE); I2C_SendByte((uint8_t)addr); /* 2. 重复起始切换读模式 */ I2C_Start(); I2C_SendByte(AT24C02_ADDR_READ); /* 3. 连续读取数据 */ for (uint16_t i 0; i len; i) { if (i len - 1) { // 最后一个字节发送NACK pBuffer[i] I2C_ReadByte(I2C_NACK); } else { // 非最后一个字节发送ACK以继续读取 pBuffer[i] I2C_ReadByte(I2C_ACK); } } /* 4. 停止条件 */ I2C_Stop(); }顺序读取利用了芯片内部地址自动递增的特性效率很高且无需担心页边界问题。5. 硬件设计要点与常见问题深度排坑即使代码逻辑正确硬件设计不当也会导致通信失败。以下是多年踩坑经验的总结。5.1 上拉电阻与总线电容看不见的“阻力”I2C总线是开漏结构上拉电阻Rp负责在总线空闲时将电平拉高。这个电阻的选择是个权衡电阻太大上升沿变缓充电慢在高总线电容下可能导致信号边沿达不到逻辑高电平通信失败。电阻太小当器件拉低总线时电流过大I Vcc / Rp增加功耗可能超出引脚的灌电流能力。计算公式与经验值 总线上升时间 tr 0.8473 * Rp * Cb其中Cb是总线总电容包括走线、引脚、寄生电容。对于标准模式100kHztr应小于1us。对于快速模式400kHztr应小于300ns。假设Vcc3.3VCb100pF一个适中的估计值标准模式Rp 1us / (0.8473 * 100pF) ≈ 11.8kΩ。常用4.7kΩ或10kΩ。快速模式Rp 300ns / (0.8473 * 100pF) ≈ 3.5kΩ。常用2.2kΩ。实测技巧如果通信不稳定特别是长导线连接时用示波器看SCL和SDA的波形。如果上升沿圆滑、缓慢就是上拉电阻过大或总线电容过大。可以尝试减小上拉电阻如从10kΩ换为2.2kΩ或者在总线两端并联一个几十到一百皮法的电容来滤除尖峰干扰但会增加Cb需重新评估。5.2 地址冲突与WP引脚悬空隐蔽的故障源地址冲突如果你的系统里有多个I2C设备务必确保它们的7位地址不冲突。AT24C02的地址高4位固定1010冲突常发生在A0-A2配置相同的多片AT24C02之间或者与其它地址为0x50-0x57范围的芯片冲突。用I2C总线扫描工具很多单片机例程里有在上电初期扫描一下所有地址是很好的排查习惯。WP引脚悬空这是一个经典错误。CMOS芯片的输入引脚悬空时电平是不确定的可能会在高低电平间振荡。如果WP引脚意外感应到高电平芯片就会进入写保护状态你的所有写入操作都会静默失败读取却正常让你百思不得其解。务必将WP引脚通过一个电阻如10kΩ明确上拉或下拉。需要写保护功能就上拉到VCC不需要就下拉到GND。5.3 电源与去耦稳定性的基石AT24C02在写入瞬间内部电荷泵工作电流消耗会有个尖峰可达3mA。如果电源走线长或阻抗大或者去耦电容不足会导致电源电压瞬间跌落可能引起写操作失败甚至数据错误。设计建议紧贴芯片VCC和GND引脚放置一个0.1uF的陶瓷去耦电容。这个电容为高频电流尖峰提供就近的泄放路径。如果系统电源噪声较大可以再并联一个10uF的钽电容或电解电容滤除低频干扰。电源走线尽量宽、短减少阻抗。5.4 “读出来是很大的数”问题深度排查这是搜索热词里的一个高频问题“at24c02读写出来是很大的数”。这通常不是芯片坏了而是通信时序或逻辑问题。系统性排查链路第一步检查硬件连接用万用表确认VCC和GND电压是否正常3.3V/5V。确认SCL和SDA线是否连接正确没有虚焊、短路。确认上拉电阻已正确焊接这是最常见的原因之一。确认A0-A2、WP引脚电平符合预期用万用表量电压。第二步用逻辑分析仪抓取I2C波形这是最直接的诊断工具。连接SCL和SDA通道设置触发条件为START。观察起始条件SCL高期间SDA是否有从高到低的跳变地址字节发送的器件地址是否正确例如0xA0注意是MSB先发。应答位在地址字节和第9个时钟周期SDA是否被从设备拉低ACK如果一直是高NACK说明从设备没应答。数据波形SDA数据线在SCL高电平期间是否稳定有没有异常的毛刺停止条件SCL高期间SDA是否有从低到高的跳变如果发现ACK缺失、波形畸变、电平幅度不够就针对性地排查硬件上拉电阻、电源、干扰。第三步检查软件逻辑地址错误最常见。写操作用了读地址0xA1或者A0-A2配置与代码中地址常量不匹配。未等待写完成连续两次写操作之间没有延时或没有进行应答查询第二次操作时芯片还在忙导致失败。页写入越界连续写入超过8字节或跨页写入导致数据被覆盖读出来的自然不是当初写入的数据。读取逻辑错误随机读时缺少“哑写”地址的步骤直接发读命令读出来的是当前地址指针的值可能是一个随机值很大的数。变量类型错误字地址是uint8_t0-255如果你用了uint16_t并且地址大于255发送时会被截断访问到错误位置。第四步简化测试编写一个最简单的测试函数只向一个固定地址如0x00写入一个已知值如0xAA然后读回。用逻辑分析仪看这一次最简单的操作是否成功。剥离复杂逻辑定位根本问题。6. 进阶应用数据存储结构设计与寿命管理当你要用AT24C02存储多个变量或一组数据时直接乱存很快就会导致管理混乱。我们需要一个简单的“文件系统”或存储结构。6.1 定义数据映射表最好的方法是在头文件里定义一个地址映射表用枚举或宏常量。// AT24C02数据存储结构定义 #define EEPROM_BASE_ADDR 0x00 typedef enum { ADDR_DEVICE_ID EEPROM_BASE_ADDR, // 设备ID, 1字节 ADDR_TEMP_CALIB_OFFSET, // 温度校准偏移, 2字节 ADDR_HUMIDITY_CALIB_GAIN 0x05, // 湿度校准增益, 2字节 ADDR_BOOT_COUNTER 0x10, // 启动次数计数器, 4字节 ADDR_USER_SETTINGS 0x20, // 用户设置结构体起始地址 // ... 其他数据定义 } eeprom_data_addr_t; // 示例读取启动计数器 uint32_t read_boot_count(void) { uint32_t count 0; uint8_t *p (uint8_t*)count; AT24C02_ReadBuffer(ADDR_BOOT_COUNTER, p, 4); return count; }这样你的代码里不会出现“魔数”Magic Number可读性和可维护性大大提升。6. 2 磨损均衡与数据验证AT24C02标称100万次擦写寿命但如果频繁更新同一个地址比如每秒写一次的系统状态标志不到12天就会达到极限。我们需要一些策略来延长使用寿命。策略一轮询写入对于需要频繁更新的数据如运行时间可以使用多个存储位置轮换写入。#define LOG_SLOT_SIZE 4 // 每个日志条目大小 #define LOG_SLOT_NUM 8 // 8个槽位轮换 uint8_t find_next_write_slot(void) { uint8_t slot_index 0; uint8_t max_count 0; // 读取每个槽位的“写入计数”标记找到计数最小的或最老的槽位 for (int i 0; i LOG_SLOT_NUM; i) { uint8_t count AT24C02_ReadByte(LOG_BASE_ADDR i * LOG_SLOT_SIZE); if (count max_count) { max_count count; slot_index i; } } return slot_index; }策略二数据校验为了防止数据因干扰或芯片偶尔错误而失效重要的数据应存储两次双备份并加上校验和如CRC8。typedef struct { uint16_t real_data; uint16_t backup_data; uint8_t crc; // 对前4个字节计算的CRC8 } safe_data_t; uint8_t read_safe_data(uint16_t addr, uint16_t *data) { safe_data_t sd; AT24C02_ReadBuffer(addr, (uint8_t*)sd, sizeof(sd)); // 计算CRC uint8_t calc_crc crc8_calc((uint8_t*)sd, 4); if (calc_crc sd.crc) { // CRC正确数据可信 if (sd.real_data sd.backup_data) { *data sd.real_data; return 0; // 成功 } else { // 主备数据不一致但CRC对极小概率事件可尝试恢复或报错 return 1; // 数据不一致错误 } } else { // CRC错误数据损坏 return 2; // 校验错误 } }6.3 与不同主控的联调要点STM32系列使用硬件I2C如I2C1时要特别注意时钟配置和中断/DMA的使用。标准库或HAL库的I2C函数可能已经处理了应答查询但你需要检查其超时机制。一个常见坑STM32的I2C时钟频率在I2C_CR2寄存器中配置必须至少是SCL频率的2倍对于标准模式或4倍对于快速模式。配置错误会导致时序错乱。ESP32ESP-IDF框架下的I2C驱动非常完善。注意ESP32的I2C引脚可以任意映射但需要在上层代码中正确配置i2c_config_t结构体。另外ESP32的工作电压是3.3V确保AT24C02也使用3.3V供电或者做好电平转换。51单片机通常用软件模拟I2C时序GPIO模拟SCL和SDA。关键在于确保时序严格符合数据手册要求特别是SCL高/低电平的保持时间。在模拟时序的I2C_Delay()函数中要用_nop_()或循环来实现精确的微秒级延时并且这个延时需要根据主频校准。7. 选型替代与项目实战思考AT24C02是Microchip原Atmel的产品。市面上有大量兼容芯片如ST的M24C02、ON Semi的CAT24C02等它们引脚和指令基本兼容但在细微时序、功耗上可能有差异替换时最好通读数据手册。什么时候需要更大容量的E2PROM如果你的数据超过256字节可以考虑AT24C04512B、AT24C081KB、AT24C162KB等。它们地址寻址方式略有不同利用器件地址中的部分位作为高位地址但I2C协议相通。对于更大容量64KB的存储需求如日志存储就该考虑SPI接口的Flash芯片如W25Q系列了。项目实战中的最后建议初始化时做一次自检上电后向一个固定测试地址写入一个已知模式如0x55、0xAA再读回验证。如果失败记录错误并采用默认参数保证系统至少能启动。写操作后务必验证对于关键参数写完后再读出来比较一下确保写入正确。虽然会增加时间但可靠性大幅提升。减少不必要的写操作在写之前先读出现有值如果和新值相同就跳过写操作。这能有效节约擦写次数。善用WP引脚在程序初始化完成、关键参数写入后如果这些参数在运行期不允许修改可以用一个GPIO控制WP引脚拉高实现硬件锁死。AT24C02就是这样一颗看似简单却处处需要谨慎对待的芯片。把它用好了你的嵌入式系统就拥有了可靠的“非易失记忆”。从读懂数据手册开始到写出健壮的驱动再到设计出稳定的存储结构每一步的思考和实践都是嵌入式工程师基本功的体现。