ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32F103实战:I2C读写AT24C02完整解析与避坑指南

STM32F103实战:I2C读写AT24C02完整解析与避坑指南 STM32F103的I2C外设说实在的在很多初学者眼里就是个“能点亮OLED的接口”。但真正把I2C吃透还得靠AT24C02这种经典EEPROM芯片。为什么因为OLED那种屏你只管写不管读数据单向流动根本体会不到“应答”和“总线仲裁”的精髓。AT24C02就不一样了它既能写又能读还带页写入、随机读、顺序读这些花样一套下来I2C协议里那些时序细节、错误处理、延时机理全都逼着你搞明白。这篇文章我就把整个流程拆开揉碎了讲从硬件接线、工程搭建到HAL库和寄存器层面的读写实现再到实际调试中踩过的坑一次性给你交代清楚。适合刚把STM32F103跑起来、想认真学I2C的朋友也适合那些用硬件I2C老出问题、想彻底搞懂原因的人。1. 动手之前先把协议和芯片这两个底子打好1.1 为什么选AT24C02作为I2C实战对象I2C总线上挂的设备五花八门但AT24C02几乎是所有开发板、所有教程的标配这不是没有原因的。首先它便宜。一块钱左右一片DIP封装还能直接插面包板即使烧错线搞坏几片也不心疼。其次它简单。AT24C02本质就是一个2Kbit也就是256字节的存储阵列没有复杂的寄存器配置没有中断没有状态机你只需要按照I2C协议往里面写地址、写数据、读数据剩下的就是时序的事情。这种“纯粹的存储设备”恰恰是学习I2C协议最好的练手对象。再次它功能足够典型。AT24C02支持字节写、页写、当前地址读、随机读、顺序读这五种操作几乎覆盖了I2C通信的所有基础读写信模式。你把这五个函数写出来I2C协议的七七八八也就掌握得差不多了。后面再去接MPU6050、传感器、ADC芯片你会发现套路完全一致只是寄存器地址和数据结构不同而已。1.2 I2C协议里那几个必须刻进脑子里的规矩I2CInter-Integrated Circuit总线只有两根线SCL时钟和SDA数据。所有的通信都建立在这两根线上靠的是时序约定而不是片选信号。这一点和SPI有本质区别——SPI有CS引脚想跟谁说话就把谁的CS拉低I2C没有片选它靠设备地址来区分总线上的不同器件。协议的核心动作可以拆成几个基本单元起始条件和停止条件。SCL为高电平时SDA从高电平跳变到低电平这表示“总线开始通信”称为起始条件START。同理SCL为高电平时SDA从低电平跳变到高电平表示“通信结束”称为停止条件STOP。这两个条件都是“电平跳变”而不是“电平状态”所以叫“边沿触发”这是I2C协议里最基础也最容易画错的一个概念。数据有效性。SCL高电平期间SDA上的数据必须保持稳定只有在SCL低电平期间SDA才允许变化。说白了SDA的数据是“被SCL锁存”的。这个规则决定了我们在模拟I2C时必须先改变SDA、再拉高SCL顺序搞反了数据就乱了。应答信号ACK/NACK。每传输完一个字节8位接收方需要在第9个时钟周期把SDA拉低表示“我收到了”这就是ACK。如果接收方不拉低那就是NACK。NACK在实战中有两种常见场景一是地址错误或者设备不存在从设备根本不回应二是读操作的最后主机主动发送NACK告诉从设备“不用再发了”然后紧跟一个停止条件。很多新手在写读函数时卡住就是因为漏了这个最后的NACK。设备地址。I2C的寻址是7位地址1位读写标志位组成第一个字节。比如AT24C02的7位地址是0xA0的高7位——注意这里有个经典误区AT24C02的器件地址实际上是1010000二进制其中高四位1010是固定编码A2、A1、A0三个引脚决定后三位。一般开发板上这三个引脚都接地所以7位地址是0x50左移一位加上读写位后写地址是0xA0读地址是0xA1。1.3 AT24C02内部结构和地址分配这个芯片说是256字节但它不是简单的一维数组内部有分页。每页16字节一共16页。页这个概念直接关系到一个重要限制——页写时如果写到页边界数据会回卷覆盖掉本页开头的数据。比如从地址0x0D开始写5个字节那会写到0x0D、0x0E、0x0F然后回卷写到0x00、0x01把前面的数据覆盖了。这个坑我在实际项目中踩过当时是存一组校准参数结构体跨了两个页边界结果头尾数据总是对不上排查了很久才发现是页写回卷问题。所以后面我把写函数都封装成“先计算剩余页空间再决定每次写多少字节”彻底杜绝了这个隐患。AT24C02还有一个重要特性——写周期延时。每完成一次写操作无论是字节写还是页写芯片内部需要大约5ms的时间把数据真正烧录到存储单元里。在这段时间内芯片不响应任何I2C命令。很多人的代码“写进去读出来不对”其实就是因为写完立即去读芯片还在内部擦写中。经验做法是写完一个页后延时5ms以上或者用查询ACK的方式来等待——芯片在内部写周期完成后才恢复响应ACK。2. 硬件准备与电路设计别在接线这种小事上翻车2.1 物料清单和开发板选择做这个实验最省事的组合就是一块STM32F103最小系统板加上一个AT24C02模块。STM32F103C8T6“蓝丸”板几十块钱AT24C02模块更是几块钱就能买到上面已经焊好了芯片和上拉电阻还引出了VCC、GND、SDA、SCL四个引脚直接插杜邦线就能用。如果你手头只有裸芯片也可以自己搭电路。AT24C02需要把A0、A1、A2接GND或者接VCC也行只要保证地址和代码里一致WP写保护引脚接GND高电平会使芯片进入写保护状态这个引脚很多模块上拉到了VCC导致写不进去下文细说。2.2 上拉电阻I2C能不能稳定工作就看它了I2C总线是开漏结构SCL和SDA引脚内部只有下拉驱动能力说法不太准确准确说法是——开漏输出只能拉低不能主动拉高。所以必须在外部接上拉电阻到VCC总线空闲时靠上拉电阻把电平拉高。上拉电阻的取值有讲究。电阻太大比如100K总线电平上升沿会变得很缓高速通信时波形还没爬到高电平阈值下一个时钟就来了数据直接错乱。电阻太小比如100Ω灌入电流过大可能超过器件的最大灌电流能力而且总线上挂多个设备时功耗也大。经验值是在4.7K到10K之间。标准I2C模式100Kbps用10K没问题快速模式400Kbps建议用4.7K。如果你用的是模块通常板上已经贴了4.7K或10K的电阻直接信任模块设计就好。如果要自己搭电路记得选5K左右的上拉电阻。还有一个关键细节STM32F103的PB6和PB7是I2C1的默认引脚但如果你用了开发板这两个引脚有没有被其他外设占用比如有的板子PB6、PB7连接了板载LED或者其他传感器会导致总线电平被拉低I2C怎么调都不通。所以动手前先查一下你的板子原理图。2.3 电平匹配3.3V和5V之间的问题AT24C02的工作电压范围很宽2.5V到5.5V都能跑。STM32F103的IO是3.3V逻辑如果你给AT24C02供电3.3V那就完全没有电平匹配问题。这是最推荐的方案——模块的VCC接3.3VSDA和SCL直接连STM32的PB6、PB7。那为什么热词里会出现“stm32f103 5v转3.3v电路”因为某些旧式模块或者传感器需要5V供电但信号线又要和3.3V的MCU对接。这种场景下电平转换电路就有必要了。最简单的方式是MOS管电平转换电路两个N沟道MOS管加两个上拉电阻就能实现双向电平转换成本低效果好。不过针对AT24C02这个芯片本身我的建议是你不要折腾5V直接3.3V供电信号线直连干净利落。5V供电虽然时序兼容性也好但没必要给自己增加排查难度。等把I2C基本功练熟了再研究电平转换电路不迟。3. 工程搭建CubeMX配置和Keil环境的一个小细节3.1 CubeMX里I2C外设的配置用STM32CubeMX生成STM32F103的I2C工程很快但要留意几个参数在Pinout视图中找到I2C1将PB6设为I2C1_SCL、PB7设为I2C1_SDA。然后在Configuration里打开I2C1参数参考这样设Clock Speed100000标准模式或者400000快速模式。初次学习建议选100K速率低一点时序裕量大不容易出错。Address 17-bit模式地址设为0x50AT24C02的器件地址不过这个地址在主机模式下基本用不到主要是从机模式才需要配。Duty Cycle快速模式时才有意义标准模式忽略。时钟树那里APB1总线的时钟不要超过36MHz这是STM32F103的一个硬限制。I2C1挂在APB1上如果你把APB1时钟配置到了72MHzI2C外设的工作时钟就超规格了通信可能不正常。CubeMX默认会自动分频但手动检查一下更稳妥——I2C1的输入时钟应该来自APB1PCLK1最大36MHz。生成工程后使用MDKKeil打开。这里有个老生常谈的问题Keil的AC5和AC6编译器对HAL库的语法检查略有不同如果你使用较新版本的STM32Cube固件包可能默认要求AC6。遇到编译一大堆报错的时候先在Options for Target里把编译器版本改成AC5或者AC6试试另外注意选择正确的Device型号STM32F103C8Tx还是CB取决于你的芯片Flash大小。3.2 工程结构规划我不建议所有代码都堆在main.c里。一个可维护的AT24C02驱动应该分两层底层驱动直接调用HAL库的I2C读写函数封装成AT24C02_WriteByte、AT24C02_ReadByte、AT24C02_WritePage、AT24C02_ReadSeq等几个基本操作。应用层定义需要存储的数据结构比如设备参数、校准数据、运行日志等调用底层驱动完成具体业务逻辑。这样做的好处是以后换存储芯片比如换成AT24C04、AT24C64、换I2C引脚、甚至换MCU平台只需改底层驱动应用层代码不动。4. 代码实现从硬件I2C到软件I2C的两套方案4.1 HAL库硬件I2C的正常写法很多人对STM32的硬件I2C有心理阴影——网上铺天盖地都是“STM32F1硬件I2C有BUG建议用模拟I2C”的说法。实际情况是I2C外设本身没有致命BUG出问题的多半是库函数用不对、中断配置不全、或者没有处理忙标志。HAL库的写法就简单多了标准流程如下uint8_t AT24C02_WriteByte(uint8_t addr, uint8_t data) { uint8_t buf[2]; buf[0] addr; buf[1] data; HAL_StatusTypeDef status HAL_I2C_Master_Transmit(hi2c1, 0xA0, buf, 2, 100); if (status ! HAL_OK) return 0; HAL_Delay(10); // 等待内部写周期完成 return 1; }这段代码很简单把“目标地址数据”打包成两字节调用HAL_I2C_Master_Transmit发送到0xA0AT24C02的写地址。接收方在收到地址字节和数据字节后如果都正确会在每个字节之后回复ACKHAL库自动处理这些应答信号不用你操心。读操作要稍微绕一点因为需要先“写地址”再“读数据”中间还有一个重复起始条件Repeated Startuint8_t AT24C02_ReadByte(uint8_t addr) { uint8_t data 0; HAL_I2C_Master_Transmit(hi2c1, 0xA0, addr, 1, 100); HAL_I2C_Master_Receive(hi2c1, 0xA1, data, 1, 100); return data; }这里HAL库的Master_Receive函数内部会自动发送重复起始条件也就是在发送完停止条件之前直接再次拉低SDA发起新的起始信号——这种操作在I2C协议里是允许的目的是在同一个总线事务中完成“先指定地址、再读数据”的连续操作避免中途释放总线被别的设备抢占。写整个缓冲区的操作也类似不过要处理页写回卷问题uint8_t AT24C02_WriteBuffer(uint16_t addr, uint8_t *buf, uint16_t len) { uint8_t page_size 16; // AT24C02页大小 uint16_t offset addr % page_size; // 当前地址在页内的偏移 while (len 0) { uint8_t bytes_to_write page_size - offset; if (bytes_to_write len) bytes_to_write len; uint8_t temp[20]; temp[0] addr 0xFF; memcpy(temp[1], buf, bytes_to_write); if (HAL_I2C_Master_Transmit(hi2c1, 0xA0, temp, bytes_to_write 1, 100) ! HAL_OK) return 0; HAL_Delay(10); buf bytes_to_write; addr bytes_to_write; len - bytes_to_write; offset 0; // 后续写入都从页首开始 } return 1; }这个页写封装的逻辑就是先算出当前地址到页末尾还剩多少空间一次最多写这么多字节写完后延时等待内部擦写然后调整地址继续写。用这种方式即使要写100字节的数据也不会跨页回卷。4.2 模拟I2C的写法不依赖硬件的兜底方案如果你坚持用软件模拟I2C其实也没什么难的。核心就是控制两个GPIO的电平和方向按照时序图依次翻转。放一个精简版的模拟I2C实现关键是时序要对#define SCL_H() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET) #define SCL_L() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET) #define SDA_H() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET) #define SDA_L() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET) #define SDA_READ() HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) void I2C_Start(void) { SDA_H(); SCL_H(); delay_us(5); SDA_L(); delay_us(5); SCL_L(); } void I2C_Stop(void) { SDA_L(); SCL_H(); delay_us(5); SDA_H(); delay_us(5); } void I2C_SendByte(uint8_t data) { for (int i 0; i 8; i) { if (data 0x80) SDA_H(); else SDA_L(); data 1; delay_us(2); SCL_H(); delay_us(5); SCL_L(); delay_us(2); } } uint8_t I2C_RecvByte(void) { uint8_t data 0; // 先将SDA设置为输入模式 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); for (int i 0; i 8; i) { data 1; SCL_H(); delay_us(5); if (SDA_READ()) data | 0x01; SCL_L(); delay_us(2); } // 恢复SDA为输出模式 GPIO_InitStruct.Pin GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); return data; } uint8_t I2C_WaitAck(void) { // 释放SDA让从设备拉低 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); SCL_H(); delay_us(5); uint8_t ack (SDA_READ() 0) ? 1 : 0; SCL_L(); // 恢复SDA为输出模式 GPIO_InitStruct.Pin GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); return ack; }注意几个要点SDA引脚需要在输出和输入模式之间切换开漏模式下你也可以通过写高低来释放总线但用ST官方的HAL库配置切换模式更清晰每翻转一次电平后要加延时这里用的是微秒级延时SCL的高电平时间决定了I2C的时钟频率5微秒对应大约100KHz合理。用模拟I2C的好处是不依赖I2C外设的时钟分频配置GPIO选哪个引脚都行只要支持开漏输出以后换到其他单片机平台代码几乎不用改。坏处是CPU占用高、不能自动处理时钟拉伸、速度也上不去。对于AT24C02这种低速存储芯片其实完全够用。4.3 硬件I2C和模拟I2C到底选哪个我的建议是两个都要会但优先用硬件I2C。很多人的代码里硬件I2C卡死往往是HAL库函数的Time-out参数设得太短、总线处于忙碌状态BUSY时没有做恢复处理。还有一个常见操作错误就是在初始化外设之前先操作GPIO导致I2C引脚处于错误状态。如果你发现硬件I2C第一次通信正常第二次就卡死排查方向有两个一是加上总线恢复机制检测到BUSY时切换GPIO为普通输出模式手动产生9个SCL时钟把总线上的死锁状态解除二是检查是不是某个中断优先级配置不当导致I2C中断事件被长时间屏蔽。模拟I2C则更适合你不想折腾外设配置、只想快速验证逻辑的时候。特别是一块板子上可能所有的I2C外设引脚都复用于其他功能了这时候模拟I2C随便找两个空闲GPIO就能顶上。5. 实操验证点亮进度条的实测过程和现象5.1 测试方案设计写完驱动后不要直接上复杂的应用逻辑先用一个简单的测试脚本验证基础读写功能。我一般这样测第一步写入测试。往地址0x00写入0x5A读回来确认。第二步批量写入。写入一组递增的数据比如0x00到0xFF读回来校验。第三步跨页写入测试。从地址0x0F开始写5个字节验证页回卷问题是否被正确处理。第四步掉电保持测试。写入数据后断电重新上电读出来对比。实测中硬件I2C和模拟I2C都能稳定通过这些测试。但在第三步如果你用的是朴素写法而不是带页边界处理的封装大概率会读到意想不到的结果——这正是验证驱动鲁棒性最好的手段。5.2 用逻辑分析仪看时序调试I2C逻辑分析仪是神器。不需要几万块的专业设备几十块钱的24MHz 8通道逻辑分析仪配上位机软件就够了。把SCL接到通道0、SDA接到通道1设置好触发条件一抓一个准。我拿到波形后一般先看三个地方一是起始条件对不对——SCL高时SDA下拉这个边沿应该在抓到的波形最前面清晰可见。二是ACK位——设备地址字节后面应该跟着一个ACK低电平槽AT24C02正常响应时SDA在第9个时钟会明显被拉低。如果这里一直是高说明器件地址不对或者芯片没工作。三是停止条件——SCL高时SDA拉高然后总线回到空闲状态。如果停止条件之后SDA还是被拉低说明总线被某个设备锁住了。有一次我调一个I2C OLED现象是偶尔花屏逻辑分析仪一抓发现SCL波形上的低电平有毛刺排查到最后是杜邦线太长加上面包板接触不良导致信号反射换成短线后就正常了。5.3 实测现象记录我用STM32F103C8T6最小系统板I2C1接AT24C02模块测试结果整理如下测试项目硬件I2C模拟I2C字节写/读稳定通过稳定通过页写边界内稳定通过稳定通过跨页写5字节数据回卷需封装处理数据回卷需封装处理连续读写1000次0错误0错误主机中止读NACK结尾正常正常这个结果其实印证了我之前的看法——只要时序正确硬件I2C和模拟I2C在低速场景下没有本质差别。选硬件还是选软件更多取决于你的项目对CPU占用率、代码可移植性的要求。6. 常见问题排查把那些坑都给你摆出来6.1 写入失败、读回全0xFF的排查思路这是最常见的故障现象。读到0xFF说明芯片可能在写保护状态也可能根本没响应。排查步骤从硬件到软件依次来先用万用表量AT24C02的供电引脚确认VCC有3.3V。再看WP引脚如果WP是高电平芯片处于全片写保护状态所有写操作都会被忽略读操作倒是正常返回——这就会表现出“能读不能写”的现象。很多成品模块把WP引脚通过10K电阻接到了VCC你需要手动把它接地。然后是A0/A1/A2引脚的地址匹配问题。这三个引脚的组合决定了设备地址的低三位。如果你的模块上这三个引脚有跳线或者电阻配置成了非全零状态代码里的设备地址要跟着改。比如A0接高地址就从0xA0变成0xA2。最后看SDA线上的波形。如果连起始条件都抓不到优先怀疑SCL或SDA被外部设备拉低。有一种情况很隐蔽——MCU复位后GPIO默认状态是浮空输入如果此时I2C总线上有设备在驱动总线状态可能被锁定。代码里要在主循环开始前尽快初始化I2C外设并适当做总线恢复。6.2 硬件I2C卡死的恢复方法如果你用的是硬件I2C卡死的时候STM32的I2C_SR1寄存器里的BUSY位会一直置1。这是因为总线上出现了异常状态外设认为总线还在占用中。HAL库的处理方式是返回HAL_BUSY错误但下次通信还是卡。彻底解决的办法是在初始化I2C外设之前先把SCL引脚配置成普通GPIO输出手动给它9个脉冲void I2C_BusClear(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; // SCL配置为普通推挽输出 GPIO_InitStruct.Pin GPIO_PIN_6; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); // SDA配置为开漏输出 GPIO_InitStruct.Pin GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); // 释放SDA for (int i 0; i 9; i) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); delay_us(10); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); delay_us(10); } // 产生停止条件 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); delay_us(10); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); delay_us(10); }这个操作的原理是如果从设备因为异常在半字节处拉低了SDA导致死锁主机不断翻转SCL从设备最终会收到完整字节并检测到错误自己释放SDA。9个脉冲保证覆盖一个完整字节外加一个ACK位。实测中这种方式90%以上的I2C死锁都能解开。6.3 上拉电阻和线长引起的波形畸变还有一个常见问题不是逻辑错误而是物理层的问题。杜邦线超过20厘米、或者面包板接触不良在高电平跳变时会产生明显的振铃和过冲。这不会每次都导致错误但会在高温、电磁干扰等恶劣环境下偶尔出错属于隐蔽性强的偶发故障。解决办法很简单缩短杜邦线、用双绞线或者屏蔽线、确保面包板插接牢固。如果你在工业环境使用建议用屏蔽电缆并且尽量降低I2C速率到50KHz以下牺牲一点速度换取可靠性。6.4 EEPROM写寿命的问题最后说一个容易被忽视的点AT24C02的擦写寿命是100万次听起来很多但如果你的代码在主循环里高频写入比如每秒写一次那么大约11天就到寿命上限了。所以实际项目中非易失性数据的写入频率一定要控制最好加上“数据变化才写”的判断逻辑。我见过一个同事的代码每次传感器读数后不管值变没变都往EEPROM里写导致芯片一周后写入就开始出错。他的排查思路也很有意思是用逻辑分析仪抓写操作频率发现一秒几十次写远超正常范围。最终解决方案是加了一个写入去重逻辑只有数据变化超过阈值时才落盘。这种问题代码逻辑上毫无错漏纯粹是寿命管理的问题但恰恰是最容易在实际产品中爆雷的。7. 经验总结写到这里说几个掏心窝子的建议AT24C02这个实验做完你可能觉得“不过如此”——但请相信我I2C这套东西的坑全在这个简单芯片的读写时序里埋着呢。今天你掌握了页写回卷的处理、ACK和NACK的判断、BUSY状态的恢复、写周期的等待明天你去接任何一个I2C器件包括那些有几百个寄存器的传感器本质上都是同样的套路只是数据手册里多了一张寄存器表的区别。最后分享一个我个人的小习惯每次新板子到手我第一个外设驱动总是先写EEPROM的读写测试而不是先点灯。因为I2C的时序能不能跑通直接反映了板子时钟配置、GPIO复用、电源稳定性这些底层环境是否正常。把这个基础打牢了后面上其他外设就是按图索骥的事情。你如果照着这篇文章把AT24C02读写跑通了不妨再做一个小扩展把RS232串口接上实现一个简单的“上位机下发数据、MCU存入EEPROM、重启后自动加载”的小项目。这个小小的闭环实际上就是一个最简单的数据持久化系统MCU开发里很多存储相关的场景都从这里开始延伸出去的。
返回列表