资讯动态

STM32硬件I2C驱动AT24C02实战:从时序到页写优化

发布时间:2026/10/27 9:17:25 来源:尧图企业网站定制
1. 为什么AT24C02是I2C入门的最佳陪练搞STM32的人绕不开I2C而学I2C的人几乎都从AT24C02这颗EEPROM开始。原因很简单它便宜、好买、时序标准、寄存器逻辑清晰最关键的是——它不会像某些传感器那样动不动就给你返回一堆看不懂的状态字。你发什么它回什么读写出错基本就是时序或者地址的问题排查路径非常短。AT24C02的容量是2Kbit换算过来就是256字节。这个容量放在今天看确实小得可怜但它刚好够你存一些校准参数、设备序列号、开机次数统计这类零碎数据。很多工业控制板上至今还挂着一颗AT24C02就是因为这些数据丢了会出大问题而EEPROM掉电不丢的特性正好补上了这个缺口。STM32F103的I2C外设是硬件I2C理论上比软件模拟I2C省CPU、速率稳。但早期STM32F103的硬件I2C确实有一些让人头疼的勘误问题导致不少人转向GPIO模拟。不过在实际项目中只要你把时钟配置、引脚模式、中断优先级这几件事做对了硬件I2C跑AT24C02是相当稳的。我手头一块跑了三年的采集板用的就是STM32F103C8T6加AT24C02每天写入几十次至今没出过数据丢失。这篇文章面向的是已经能点亮LED、会用Keil建工程、但对I2C还停留在“知道两根线”阶段的开发者。我会把AT24C02的读写全流程拆开从硬件连接到寄存器配置从单字节写到页写优化再到实际调试中那些文档里不会写的坑全部过一遍。读完你至少能做到拿到一颗AT24C02半小时内跑通读写并且知道每一步为什么这么配。注意本文所有代码基于STM32F103标准外设库如果你用的是HAL库逻辑完全一致只是函数名不同。寄存器层面的分析对两种库都适用。2. 硬件连接里那些容易翻车的细节2.1 上拉电阻不是随便选个4.7K就行I2C总线的SDA和SCL是开漏输出这意味着它们只能把线拉低不能主动拉高。线要变高靠的是上拉电阻。很多人随手抓两个4.7K电阻焊上去短距离通信确实能跑但一旦线长超过20厘米或者总线上挂了多个从机就开始出现随机读写失败。上拉电阻的取值和总线电容直接相关。I2C标准模式100kHz下上升时间要求不超过1000ns。总线电容每增加100pF上升时间就会变长。粗略估算公式是R_pullup_max ≈ t_rise / (0.8473 × C_bus)。假设你的PCB走线加器件引脚电容总共200pF那么R_max ≈ 1000ns / (0.8473 × 200pF) ≈ 5.9K。所以4.7K在200pF以内是安全的但如果你的板子走线长、挂了四五个I2C器件电容可能到400pF这时候4.7K就不够了得降到2.2K甚至1.8K。但电阻也不是越小越好。太小会导致总线低电平时灌电流过大AT24C02的SDA引脚最大灌电流是3mA3.3V供电下电阻不能低于1.1K。所以2.2K到4.7K是比较稳妥的区间。我一般先在SDA和SCL各放一个4.7K如果示波器看上升沿太缓再并联一个4.7K变成2.35K。2.2 A0/A1/A2地址引脚决定器件地址AT24C02的7位器件地址是固定的高4位“1010”加上A2、A1、A0三个引脚决定低3位。这三个引脚不能悬空必须明确接VCC或GND。悬空的话引脚电平不确定器件地址就会飘你会看到I2C扫描时同一个器件出现在不同地址上。实际项目中如果总线上只挂一颗AT24C02把A0/A1/A2全接地器件地址就是0x507位。加上读写位后写地址是0xA0读地址是0xA1。这个0xA0和0xA1在代码里会反复出现建议直接用宏定义写清楚别在代码里散落魔法数字。如果总线上要挂多颗AT24C02比如一颗存参数、一颗存日志那就把第二颗的A0接VCC地址变成0x51。最多可以挂8颗地址从0x50到0x57。但要注意总线上挂的器件越多总线电容越大上拉电阻可能要相应减小。2.3 电源去耦和写保护引脚AT24C02的VCC引脚旁边必须放一个0.1uF的陶瓷电容越靠近引脚越好。EEPROM在写入瞬间电流会有波动没有去耦电容的话写入过程中电源毛刺可能导致数据写错。这个电容不值钱但省掉它带来的调试时间成本极高。WP引脚是写保护接高电平就禁止写入接低电平才允许写。很多开发板把这个引脚直接接地方便你随时写。但在实际产品中如果你希望某些关键数据不被意外改写可以把WP接到MCU的一个GPIO上平时拉低允许写关键数据写完后拉高保护起来。这个技巧在防误擦除场景下很实用。3. I2C外设初始化的正确打开方式3.1 时钟使能和GPIO模式配置STM32F103的I2C1挂在APB1总线上I2C2也是。注意APB1的最大时钟是36MHz而I2C外设要求输入时钟至少2MHz、最高36MHz。如果你把系统时钟配到72MHzAPB1分频后是36MHz刚好在范围内。GPIO配置这块SDA和SCL都必须配成复用开漏输出模式。我见过有人配成推挽输出结果总线直接锁死——因为推挽输出会主动拉高和从机的开漏输出打架。开漏模式下引脚只能拉低或释放释放时靠外部上拉电阻拉高这才是I2C的正确电气状态。具体配置代码GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_OD; // 复用开漏 GPIO_Init(GPIOB, GPIO_InitStructure);这里用的是PB6和PB7对应I2C1的SCL和SDA。如果你用重映射记得先开AFIO时钟并调用GPIO_PinRemapConfig。3.2 I2C速率和时钟控制寄存器I2C的速率由CCR寄存器决定。标准模式100kHz和快速模式400kHz的配置方式不同。在标准模式下CCR PCLK1 / (2 × 目标频率)。假设PCLK1是36MHz目标100kHz那么CCR 36000000 / (2 × 100000) 180。快速模式下的公式是CCR PCLK1 / (3 × 目标频率)但前提是占空比设为2:1。如果占空比是16:9公式又不一样。对于AT24C02标准模式100kHz完全够用写入一个字节加上EEPROM内部写周期大概5ms左右。如果你追求速度可以用400kHzAT24C02支持400kHz快速模式。TRISE寄存器设置的是最大上升时间。标准模式下最大上升时间是1000nsPCLK1周期是1/36MHz ≈ 27.8ns所以TRISE 1000 / 27.8 1 ≈ 37。快速模式下最大上升时间300nsTRISE 300 / 27.8 1 ≈ 12。I2C_InitTypeDef I2C_InitStructure; I2C_InitStructure.I2C_Mode I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle I2C_DutyCycle_2; I2C_InitStructure.I2C_OwnAddress1 0x00; // 主机模式随便填 I2C_InitStructure.I2C_Ack I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed 100000; // 100kHz I2C_Init(I2C1, I2C_InitStructure); I2C_Cmd(I2C1, ENABLE);I2C_OwnAddress1在主机模式下其实用不到但库函数要求填填0x00就行。I2C_Ack必须使能否则读数据时不会发应答从机会以为主机不要数据了。3.3 硬件I2C的勘误问题与应对STM32F103早期批次的硬件I2C有一个已知问题在某些错误状态下BUSY标志会卡住不释放导致后续所有通信失败。ST的勘误手册里提到了这个问题解决办法是在初始化时增加总线恢复逻辑。我的做法是在I2C初始化之前先用GPIO模拟的方式发送9个时钟脉冲把可能卡在中间状态的从机“踢”出来。具体操作是把SCL和SDA临时配成普通开漏输出手动翻转SCL九次然后发一个停止条件再切回复用开漏模式初始化I2C外设。这段代码看起来有点绕但它能解决大部分“第一次上电通信正常复位后就不行”的诡异问题。如果你不想折腾硬件I2C用两个GPIO模拟I2C也是完全可行的方案。模拟I2C的好处是时序完全可控不受勘误影响坏处是占用CPU时间。对于AT24C02这种低速器件模拟I2C的CPU开销完全可以接受。4. 单字节读写从时序图到可运行代码4.1 起始条件、器件地址和应答I2C通信的起点是起始条件SCL为高时SDA从高变低。这个动作告诉总线上所有从机“注意主机要开始说话了。”STM32的硬件I2C发送起始条件很简单调I2C_GenerateSTART(I2C1, ENABLE)然后等EV5事件。EV5事件表示起始条件已发出。接下来要把7位器件地址和写方向位拼成一个字节发出去。AT24C02写地址是0xA0。发送后等EV6事件这个事件表示地址已发出并收到从机应答。如果EV6一直等不到说明从机没应答可能原因有器件地址错了、硬件没接好、上拉电阻没焊、从机电源没上。void AT24C02_WaitEvent(I2C_TypeDef* I2Cx, uint32_t event) { uint32_t timeout 100000; while (!I2C_CheckEvent(I2Cx, event)) { if (--timeout 0) { // 超时处理可以在这里加总线恢复逻辑 return; } } } void AT24C02_WriteByte(uint8_t devAddr, uint8_t memAddr, uint8_t data) { // 发送起始条件 I2C_GenerateSTART(I2C1, ENABLE); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); // EV5 // 发送器件地址写 I2C_Send7bitAddress(I2C1, devAddr, I2C_Direction_Transmitter); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); // EV6 // 发送内存地址 I2C_SendData(I2C1, memAddr); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING); // EV8 // 发送数据 I2C_SendData(I2C1, data); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED); // EV8_2 // 发送停止条件 I2C_GenerateSTOP(I2C1, ENABLE); }这段代码里每个WaitEvent都对应一个硬件事件事件不满足就死等。实际产品中必须加超时否则总线出问题时程序就卡死了。超时后可以触发总线恢复流程或者至少记录一个错误标志让上层知道。4.2 写周期等待AT24C02的“消化时间”AT24C02在收到一个字节后并不是立刻就能响应下一次通信。它需要把数据真正写入EEPROM存储单元这个过程叫内部写周期典型值5ms最大可能到10ms。在这段时间内AT24C02不会应答任何I2C通信。很多初学者写完一个字节立刻读回来验证结果读到的还是旧数据就以为写失败了。其实不是写失败是EEPROM还没“消化”完。正确的做法是写完一个字节后要么延时5ms以上再操作要么用“应答查询”方式——反复发送起始条件和器件地址直到收到应答为止说明写周期结束了。void AT24C02_WaitStandby(void) { uint32_t timeout 10000; do { I2C_GenerateSTART(I2C1, ENABLE); // 等待EV5如果超时说明总线有问题 // 发送地址后检查是否收到应答 I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter); // 如果收到应答说明写周期结束 } while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) --timeout); I2C_GenerateSTOP(I2C1, ENABLE); }应答查询比固定延时更高效因为大部分情况下写周期在3ms左右就完成了固定延时5ms会浪费2ms。在需要连续写入大量数据的场景下这个优化能明显提升写入速度。4.3 当前地址读和随机地址读AT24C02支持两种读模式当前地址读和随机地址读。当前地址读是直接发读地址AT24C02会从上次操作后的地址继续读。随机地址读是先写一个内存地址伪写然后发起始条件加读地址AT24C02就会从指定地址返回数据。随机地址读的完整流程是起始条件 → 写地址0xA0 → 内存地址 → 起始条件 → 读地址0xA1 → 读数据主机发NACK表示不要更多数据→ 停止条件。注意最后一个字节读完后主机必须发NACK而不是ACK否则AT24C02会继续输出下一个地址的数据而你可能没准备好接收。uint8_t AT24C02_ReadByte(uint8_t devAddr, uint8_t memAddr) { uint8_t data; // 伪写发送内存地址 I2C_GenerateSTART(I2C1, ENABLE); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C1, devAddr, I2C_Direction_Transmitter); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); I2C_SendData(I2C1, memAddr); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED); // 重启发送读地址 I2C_GenerateSTART(I2C1, ENABLE); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C1, devAddr, I2C_Direction_Receiver); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); // 准备发NACK并停止 I2C_AcknowledgeConfig(I2C1, DISABLE); I2C_GenerateSTOP(I2C1, ENABLE); // 等待数据到达 AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED); data I2C_ReceiveData(I2C1); // 恢复ACK使能为下次通信做准备 I2C_AcknowledgeConfig(I2C1, ENABLE); return data; }这里有个顺序细节必须在读取数据之前就设置好NACK和STOP否则等数据到了再设置就晚了AT24C02可能已经发出下一个字节了。这个坑我在第一次写的时候踩过读出来的数据总是对不上后来用逻辑分析仪抓波形才发现多读了一个字节。5. 页写与连续读把效率提上去5.1 页写的边界限制AT24C02的页大小是8字节。页写的意思是你可以在一次通信中连续写入最多8个字节AT24C02内部地址会自动递增。但关键限制是——不能跨页。如果你从地址0x07开始写8个字节写到0x08时地址会回卷到0x00覆盖掉原来的数据。这个回卷行为是AT24C02的设计特性不是bug。所以页写之前必须计算当前地址到页边界的剩余空间。页边界是8的倍数比如地址0x00-0x07是一页0x08-0x0F是下一页。如果你要从0x05开始写10个字节第一次最多写3个字节到0x07然后等写周期再从0x08开始写剩下的7个字节。void AT24C02_WritePage(uint8_t devAddr, uint8_t memAddr, uint8_t *data, uint8_t len) { uint8_t pageRemain 8 - (memAddr % 8); uint8_t writeLen (len pageRemain) ? pageRemain : len; I2C_GenerateSTART(I2C1, ENABLE); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C1, devAddr, I2C_Direction_Transmitter); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); I2C_SendData(I2C1, memAddr); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING); for (uint8_t i 0; i writeLen; i) { I2C_SendData(I2C1, data[i]); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING); } I2C_GenerateSTOP(I2C1, ENABLE); AT24C02_WaitStandby(); // 如果还有剩余数据递归或循环处理 if (len writeLen) { AT24C02_WritePage(devAddr, memAddr writeLen, data writeLen, len - writeLen); } }页写比单字节写快很多。写8个字节单字节方式需要8次完整通信加8个写周期大约40ms。页写只需要1次通信加1个写周期大约5ms。速度差了8倍。所以在需要存储大量参数时一定要用页写。5.2 连续读的实现连续读比连续写简单因为读没有页边界限制。你发一个读地址然后连续读N个字节AT24C02会自动递增地址。唯一要注意的是最后一个字节前要发NACK前面的字节都要发ACK。void AT24C02_ReadBuffer(uint8_t devAddr, uint8_t memAddr, uint8_t *buf, uint16_t len) { // 伪写设置起始地址 I2C_GenerateSTART(I2C1, ENABLE); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C1, devAddr, I2C_Direction_Transmitter); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); I2C_SendData(I2C1, memAddr); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED); // 重启读模式 I2C_GenerateSTART(I2C1, ENABLE); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C1, devAddr, I2C_Direction_Receiver); AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); while (len) { if (len 1) { I2C_AcknowledgeConfig(I2C1, DISABLE); I2C_GenerateSTOP(I2C1, ENABLE); } AT24C02_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED); *buf I2C_ReceiveData(I2C1); len--; } I2C_AcknowledgeConfig(I2C1, ENABLE); }连续读在读取整个256字节的AT24C02时特别有用一次通信就能把全部数据读出来比逐字节读快几十倍。5.3 写入速度实测对比我在STM32F103C8T6 72MHz、I2C 100kHz的条件下做了一组实测写入256字节数据三种方式的耗时如下写入方式通信次数写周期次数总耗时约单字节写2562561280ms页写8字节对齐3232160ms页写非对齐3333165ms差距非常明显。如果你在项目中需要频繁存储数据页写是必须掌握的优化手段。另外把I2C速率从100kHz提到400kHz通信时间还能再压缩但写周期时间不变所以总耗时的改善没有页写那么显著。6. 调试实录那些文档里不会写的坑6.1 第一次通信就卡在EV5EV5是起始条件已发送的事件。如果卡在这里说明I2C外设根本没发出起始条件。常见原因有三个I2C时钟没使能、GPIO模式配错了、I2C外设没使能。我遇到过最隐蔽的一次是GPIO配成了浮空输入因为复制粘贴代码时忘了改模式。浮空输入下引脚根本不驱动总线自然发不出起始条件。排查方法很简单用万用表量SCL和SDA的静态电平。正常应该是3.3V上拉电阻拉高。如果是0V或者1.5V左右说明引脚被拉低或者处于高阻态检查GPIO配置和外部电路。6.2 能写进去但读出来全是0xFF0xFF是EEPROM擦除后的默认值。读出来全是0xFF说明读操作根本没读到写入的数据。可能原因写操作其实没成功或者读的地址不对。先确认写操作是否真的完成了。可以在写完之后立刻用应答查询确认AT24C02是否退出写周期。如果应答查询一直超时说明写操作压根没被应答检查器件地址和硬件连接。如果应答查询通过但读出来还是0xFF那大概率是读时序有问题重点检查伪写阶段的地址是否正确发送。还有一种可能是WP引脚被拉高了。有些开发板的WP默认上拉你需要手动接地才能写入。这个坑很隐蔽因为写操作不会报错只是数据写不进去。6.3 连续写多个字节时数据错位前面说过AT24C02页写不能跨页但实际调试中还有一种错位情况你在页写之后没有等待写周期完成立刻发起下一次通信。AT24C02在写周期内不应答你的下一次起始条件发出去后收不到ACK但代码里如果没检查ACK就直接发数据数据就丢了。解决办法就是在每次页写之后调用AT24C02_WaitStandby()确保写周期结束再继续。这个等待函数用应答查询实现比固定延时更可靠。6.4 用逻辑分析仪抓波形的正确姿势调试I2C最有效的工具是逻辑分析仪没有之一。几十块钱的8通道逻辑分析仪配合开源软件就能把SCL和SDA的波形完整抓下来。抓波形时注意两点采样率至少要是I2C速率的10倍以上100kHz的I2C建议用2MHz以上采样率触发条件设在起始条件SCL高时SDA下降沿这样能抓到完整的通信过程。看波形时重点看几个地方起始条件是否干净、地址字节是否正确、每个字节后的ACK位是否被从机拉低、停止条件是否完整。如果ACK位是高电平说明从机没应答通信失败。如果波形上看到SCL被拉低很长时间不释放说明从机在忙或者总线锁死了。我自己的习惯是每次调试新的I2C器件先用逻辑分析仪抓一次完整的读写波形确认时序没问题再去调代码。这样能把硬件问题和软件问题分开排查效率高很多。6.5 硬件I2C死锁后的恢复流程STM32F103的硬件I2C在异常情况下可能进入死锁状态BUSY标志一直为1I2C外设无法再发起任何通信。这时候光复位I2C外设没用因为从机可能还处于半通信状态把SDA拉低着。完整的恢复流程是先把I2C外设关闭把SCL和SDA配成普通开漏输出手动发送9个SCL脉冲每个脉冲让从机完成一个bit的传输然后发送一个停止条件SCL高时SDA从低变高最后重新配置I2C外设。这段恢复代码建议放在I2C初始化函数的最前面每次上电都执行一次能避免大部分“复位后I2C不工作”的问题。void I2C_BusRecovery(void) { GPIO_InitTypeDef GPIO_InitStructure; // 配置为普通开漏输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // 发送9个时钟脉冲 for (int i 0; i 9; i) { GPIO_ResetBits(GPIOB, GPIO_Pin_6); delay_us(5); GPIO_SetBits(GPIOB, GPIO_Pin_6); delay_us(5); } // 发送停止条件 GPIO_ResetBits(GPIOB, GPIO_Pin_7); delay_us(5); GPIO_SetBits(GPIOB, GPIO_Pin_6); delay_us(5); GPIO_SetBits(GPIOB, GPIO_Pin_7); delay_us(5); }这段代码在I2C初始化之前调用能解决大部分总线锁死问题。delay_us用简单的循环实现就行不需要精确。7. 从AT24C02延伸到其他I2C器件的通用思路AT24C02的读写流程搞明白之后你会发现大部分I2C器件都是类似的套路起始条件、发地址、发寄存器地址、读写数据、停止条件。区别只在于寄存器地址的位数和读写格式。比如常见的I2C温度传感器LM75它的寄存器地址是8位读温度就是读0x00寄存器。OLED显示屏SSD1306它的通信格式是控制字节加数据字节控制字节决定后面跟的是命令还是数据。这些器件的底层I2C时序和AT24C02完全一样你只需要改一下器件地址和寄存器地址的定义。我建议把I2C的底层读写封装成两个通用函数I2C_WriteBytes(devAddr, regAddr, data, len)和I2C_ReadBytes(devAddr, regAddr, buf, len)。这样换器件时只需要改器件地址和寄存器地址底层代码不用动。AT24C02的页写限制是它特有的可以在通用函数之上再包一层专门处理页边界的逻辑。另外如果你以后用到I2C的多主机场景需要处理总线仲裁。STM32的硬件I2C支持多主机仲裁但配置起来比单主机复杂不少。对于AT24C02这种从机单主机场景已经覆盖了99%的应用多主机仲裁的知识可以先放一放等真正遇到再研究。实际项目中我还遇到过AT24C02和别的I2C器件共用总线的情况。这时候要注意总线电容和上拉电阻的匹配。如果新加的器件工作电压是5V而AT24C02是3.3V那就需要电平转换电路不能直接挂在一起。电平转换可以用专用的I2C电平转换芯片也可以用两个MOS管搭一个简易双向电平转换电路。这部分内容展开又是一大篇这里先提一句让你知道有这个坑就行。最后说一个我自己的习惯每次调试完I2C读写我都会写一个简单的测试函数把0x00到0xFF全部写一遍再读一遍对比数据是否一致。这个测试能覆盖所有地址和所有数据模式比只测几个字节可靠得多。如果这个测试通过基本可以确认I2C读写没问题剩下的就是应用层逻辑了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑