news 2026/5/28 21:21:13

AS32X601的I2C模块操作EEPROM详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AS32X601的I2C模块操作EEPROM详解

国科安芯推出的AS32X601系列MCU芯片内置的I2C模块提供了符合工业标准的两线串行制接口,可用于MCU和外部I2C设备的通讯。I2C总线使用两条串行线:串行数据线SDA和串行时钟线SCL。 I2C接口模块实现了I2C协议的标准模式和快速模式,支持多主机I2C总线架构。其标准模式为100K,快速模式400K。而EEPROM,作为一种支持字节级单独擦写、数据掉电不丢失的存储器,其存储容量(从几字节到数百千字节)恰好满足了大量嵌入式应用对中小规模非易失性数据存储的需求。将EEPROM与并行地址/数据总线相连的传统方式会占用大量I/O口,在引脚资源紧张的微控制器(如众多8位、32位MCU)上显得笨重且不经济,因此,AS32X601系列开发板搭载了一块24C02 eeprom。本文旨在系统阐述I2C EEPROM的工作原理与核心操作流程。内容将涵盖I2C通信的基本框架,EEPROM的器件寻址方式,以及针对字节写入等关键流程

一、硬件设计

二、I2C时序

①Start开始信号、Stop停止信号:

这两个信号由主机产生,不属于数据域交互:

在SCL的高电平时,主机将SDA的电平由 高–>低是Start信号(下降沿);

在SCL的高电平时,主机将SDA的电平由 低–>高是Stop信号(上升沿);

②7位寻址

AS32X601的I2C只支持7位寻址模式,配置过程中从机地址需要左移1位才为实际地址。

③数据方向

0写/1读

④应答ACK、非应答NACK

在SCL的一个时钟周期内,从机在SCL的高电平时,将SDA的电平由高拉低(或者继续保持低电平状态) 则是ACK信号;

从机在SCL的高电平时,如果SDA的电平一直是 高电平 则是NACK信号;

三、时钟

I2C0、I2C1时钟来自APB0,I2C2、I2C3时钟来自ABP1。具体配置可见I2C_CTLR寄存器。

四、I2C初始化

1.配置I2Cx需要的GPIO为复用功能。

2.通过配置I2C_INITSTRUCT初始化I2Cx,包括时钟分频,从机地址,ACK,高低电平时间等

3.按需求配置中断,并配置IRQ_HANDLER;

4.调用收发接口,并处理数据

五、如何操作EEPROM

5.1按字节写入函数

FlagStatus I2C_MEEPROMWriteByte(I2C_TypeDef* I2Cx, uint8_t addr, uint16_t reg, uint16_t data, uint32_t timeout)

{

unsignedintnum;

/*等待总线释放*/

while(!I2C_CheckStatus(I2Cx, I2C_BUS_IDLE))

{

I2C_StartClear(I2Cx);

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

if((timeout--) == 0)

{

returnRESET;

}

delay_ms(1);

}

I2C_GenerateStart(I2Cx);

/*等待启动信号完成*/

while(!I2C_CheckStatus(I2Cx, MASTER_START_READY))

{

if((timeout--) == 0)

{

returnRESET;

}

delay_ms(1);

}

I2C_Send7bitAddress(I2Cx, addr, I2C_WRITE);

I2C_StartClear(I2Cx);

I2C_ClearITPendingBit(I2Cx);

/*等待从机接收完成地址并发送ack*/

while(!I2C_CheckStatus(I2Cx, MSEND_WADDR_ACK))

{

if((timeout--) == RESET)

{

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

returnRESET;

}

delay_ms(1);

}

I2C_SendData(I2Cx, (uint8_t)(reg >> 0));

I2C_ClearITPendingBit(I2Cx);

/*等待从机接收完成数据并发送ack*/

while(!I2C_CheckStatus(I2Cx, MSEND_DATA_ACK))

{

if((timeout--) == 0)

{

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

returnRESET;

}

delay_ms(1);

}

I2C_SendData(I2Cx, data);

I2C_ClearITPendingBit(I2Cx);

/*等待从机接收完成数据并发送ack*/

while(!I2C_CheckStatus(I2Cx, MSEND_DATA_ACK))

{

if((timeout--) == 0)

{

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

returnRESET;

}

delay_ms(1);

}

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

return1;

}

代码执行流程详细解释如下:

等待总线空闲:函数首先进入一个循环,反复检查I2C总线是否处于空闲(I2C_BUS_IDLE)状态。如果总线被占用(忙状态),它会尝试通过调用I2C_StartClear和I2C_GenerateStop来清除可能的异常状态并发送停止信号,试图释放总线。每次循环都会递减超时计数器timeout并延迟1毫秒。如果timeout减到0,函数会返回RESET。这个步骤确保了本次传输开始时总线是可用的。

发起起始条件:确认总线空闲后,函数调用I2C_GenerateStart在I2C总线上产生一个起始条件(Start Condition),这标志着一次传输序列的开始。

等待起始条件完成:紧接着,函数进入另一个循环,等待起始条件成功发出的状态(MASTER_START_READY)。同样,这里也有超时检查和1ms延迟,防止程序死锁。超时则返回失败。

发送从机地址(写模式):起始条件成功后,函数调用I2C_Send7bitAddress,将参数addr(EEPROM的7位设备地址)和写操作位(I2C_WRITE,通常值为0)组合成一个8位字节发送出去。随后清除相关状态和中断标志。

等待从机地址应答:函数循环等待从设备(EEPROM)对收到地址的应答信号(MSEND_WADDR_ACK)。如果EEPROM存在于总线上并识别出自己的地址,它会拉低SDA线作为应答(ACK)。函数检测到这个状态才能继续。此处有一个代码瑕疵:超时判断写成了(timeout--) == RESET,虽然RESET很可能定义为0,但不如其他地方的== 0直观统一。超时或失败会发送停止条件并返回失败。

发送EEPROM内部存储地址(存在严重错误):地址应答后,函数准备发送要写入的EEPROM内部单元地址reg。这是一个关键错误。对于16位地址的EEPROM(如reg是uint16_t),需要发送两个字节:先发送高8位,再发送低8位。但代码中I2C_SendData(I2Cx, (uint8_t)(reg >> 0))的reg >> 0等于reg本身,所以它只发送了reg的低8位,完全遗漏了高8位。这会导致写入到错误的EEPROM位置。

等待内部地址字节应答:发送(不完整的)地址字节后,循环等待EEPROM对此数据字节的应答(MSEND_DATA_ACK)。有超时处理。

发送要写入的数据:收到地址字节应答后,调用I2C_SendData(I2Cx, data)发送数据。这里有一个潜在问题:参数data是uint16_t类型,但函数被命名为WriteByte,且I2C_SendData通常发送一个字节。这里发生了隐式截断,只有data的低8位被发送出去。函数意图和参数类型不匹配。

等待数据字节应答:再次循环等待EEPROM对收到数据字节的应答。有超时处理。

结束传输:数据成功发送并得到应答后,函数调用I2C_GenerateStop产生停止条件(Stop Condition),结束本次I2C通信。然后清除中断标志。

5.2读函数

FlagStatus I2C_MEEPROMRead(I2C_TypeDef* I2Cx, uint8_t addr, uint16_t reg, uint8_t* pData, uint32_t Size, uint32_t timeout)

{

uint32_t num = 0x00;

/*等待总线释放*/

while(!I2C_CheckStatus(I2Cx, I2C_BUS_IDLE))

{

I2C_StartClear(I2Cx);

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

if((timeout--) == 0)

{

returnRESET;

}

delay_ms(1);

}

I2C_AcknowledgeConfig(I2Cx, I2C_IICAA_ACK);

I2C_GenerateStart(I2Cx);

/*等待启动信号完成*/

while(!I2C_CheckStatus(I2Cx, MASTER_START_READY))

{

if((timeout--) == 0)

{

I2C_StartClear(I2Cx);

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

returnRESET;

}

delay_ms(1);

}

I2C_Send7bitAddress(I2Cx, addr, I2C_WRITE);

I2C_StartClear(I2Cx);

I2C_ClearITPendingBit(I2Cx);

/*等待从机接收完成地址并发送ack*/

while(!I2C_CheckStatus(I2Cx, MSEND_WADDR_ACK))

{

if((timeout--) == 0)

{

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

returnRESET;

}

delay_ms(1);

}

I2C_SendData(I2Cx, (uint8_t)(reg >> 8));

I2C_ClearITPendingBit(I2Cx);

/*等待从机接收完成数据并发送ack*/

while(!I2C_CheckStatus(I2Cx, MSEND_DATA_ACK))

{

if((timeout--) == 0)

{

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

return0;

}

delay_ms(1);

}

I2C_SendData(I2Cx, (uint8_t)(reg >> 0));

I2C_ClearITPendingBit(I2Cx);

/*等待从机接收完成数据并发送ack*/

while(!I2C_CheckStatus(I2Cx, MSEND_DATA_ACK))

{

if((timeout--) == 0)

{

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

returnRESET;

}

delay_ms(1);

}

I2C_GenerateStart(I2Cx);

I2C_ClearITPendingBit(I2Cx);

/*等待从机接收完成数据并发送ack*/

while(!I2C_CheckStatus(I2Cx, MASTER_START_REPEAT))

{

if((timeout--) == 0)

{

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

returnRESET;

}

delay_ms(1);

}

I2C_Send7bitAddress(I2Cx, addr, I2C_READ);

I2C_ClearITPendingBit(I2Cx);

/*等待从机接收完成地址并发送ack*/

while(!I2C_CheckStatus(I2Cx, MSEND_RADDR_ACK))

{

if((timeout--) == 0)

{

I2C_StartClear(I2Cx);

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

returnRESET;

}

delay_ms(1);

}

for(num = 0; num < Size; num++)

{

if(num == (Size - 1))

{

/* IIC sends NACK */

I2C_AcknowledgeConfig(I2Cx, I2C_IICAA_NACK);

}

else

{

I2C_AcknowledgeConfig(I2Cx, I2C_IICAA_ACK);

}

I2C_StartClear(I2Cx);

I2C_ClearITPendingBit(I2Cx);

/* Wait for the slave to send the completed data, and the host will send an ack */

while(!(I2C_CheckStatus(I2Cx, MREAD_DATA_ACK) || I2C_CheckStatus(I2Cx, MREAD_DATA_NACK)))

{

if((Timeout--) == 0)

{

I2C_StartClear(I2Cx);

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

returnRESET;

}

delay_ms(1);

}

*pData++ = I2C_ReceiveData(I2Cx);

}

I2C_StartClear(I2Cx);

I2C_GenerateStop(I2Cx);

I2C_ClearITPendingBit(I2Cx);

returnSET;

}

代码执行流程详细解释如下:

函数参数说明:

I2Cx: I2C外设指针

addr: EEPROM设备地址(7位)

reg: EEPROM内部起始地址(16位)

pData: 指向接收数据缓冲区的指针

Size: 要读取的字节数

timeout: 超时计数值(注意:函数内部有一处拼写错误写成了Timeout)

代码执行流程详细解释:

等待总线空闲:函数首先检查I2C总线是否空闲(I2C_BUS_IDLE)。如果总线忙,执行清理操作(I2C_StartClear)并发送停止信号(I2C_GenerateStop),尝试释放总线。每次循环都递减超时计数器并延迟1ms,超时则返回RESET。

配置应答:调用I2C_AcknowledgeConfig(I2Cx, I2C_IICAA_ACK)使能主设备的数据应答功能,这是为后续接收数据做准备。

发起起始条件:生成起始条件(I2C_GenerateStart)开始传输,并等待起始条件成功(MASTER_START_READY)。超时则清理总线并返回失败。

发送设备地址(写模式):发送EEPROM的7位地址和写方向位(I2C_WRITE),因为EEPROM读取操作需要先发送要读取的内部地址,这相当于一个"伪写"操作。清除相关状态后,等待EEPROM应答地址(MSEND_WADDR_ACK)。

发送重复起始条件:为了从写操作切换到读操作,需要发送一个重复起始条件(Repeated Start)。调用I2C_GenerateStart,然后等待重复起始条件完成(MASTER_START_REPEAT)。这是I2C协议中在不释放总线的情况下改变数据传输方向的标准做法。

发送设备地址(读模式):再次发送EEPROM的7位地址,但这次带读方向位(I2C_READ)。等待EEPROM对此读地址的应答(MSEND_RADDR_ACK)。

循环接收数据:这是函数的核心部分,循环接收Size个字节的数据:

在接收倒数第二个字节时(num == (Size - 1)),将主设备的应答配置为不应答(I2C_IICAA_NACK),这是I2C协议规定的:主设备在接收最后一个字节前发送不应答信号,通知从设备停止发送。

对于其他字节,使能应答(I2C_IICAA_ACK)。

等待从设备发送数据完成的状态(MREAD_DATA_ACK或MREAD_DATA_NACK)。这里使用了逻辑或||,表示等待任意一种接收完成状态。

从I2C数据寄存器读取数据(I2C_ReceiveData(I2Cx))并存储到pData指向的缓冲区,然后指针递增。

结束传输:所有数据接收完成后,生成停止条件(I2C_GenerateStop)结束本次I2C通信,清除相关状态。

六、下板验证

我们操作I2C写入0~0x3f数据,结果如下:

操作波形如图:

读取完最后一个数据后发送NACK:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/16 8:20:27

震惊!云服务器代理商性价比排行,这3家让你省下千万预算!

震惊&#xff01;云服务器代理商性价比排行&#xff0c;这3家让你省下千万预算&#xff01;在数字化转型浪潮席卷各行各业的今天&#xff0c;云服务器已成为企业IT架构的基石。然而&#xff0c;面对市场上琳琅满目的云服务商和代理商&#xff0c;如何选择一家兼具高性能、高稳定…

作者头像 李华
网站建设 2026/5/27 20:23:37

搞跨端渲染?你绕不开的HarfBuzz原理

文是HarfBuzz系列的第二篇&#xff1a;在这里插入图片描述本文概述在这里插入图片描述一、关键概念与结构1.1 scriptHarfBuzz 中 script 指的是文字系统的类型&#xff0c;注意不是指语言&#xff0c;不同语言也可能属于同一类书写系统&#xff0c;比如&#xff1a;hb_script 举…

作者头像 李华
网站建设 2026/5/22 11:57:48

DX12-1-DirectX3D初始化

什么是 Direct3D 12?DirectX 12 引入了 Direct3D 的下一个版本&#xff0c;即 DirectX 的核心 3D 图形 API。此版本的 Direct3D 比任何以前的版本更快、更高效。Direct3D 12 可实现更丰富的场景、更多的对象、更复杂的效果&#xff0c;以及充分利用现代 GPU 硬件。若要为 Wind…

作者头像 李华
网站建设 2026/5/24 18:16:53

RFSOC学习记录(六)混频模式分析

混频混频器&#xff08;Mixer&#xff09;是RFSOC通过ip核实现在数字域的频率搬移&#xff0c;主要功能是在不改变采样率的情况下&#xff0c;把信号的频谱中心移动到目标频率附近他主要通过一个数控振荡器&#xff0c;生成复指数信号与本振信号相乘&#xff0c;在ip核设置的过…

作者头像 李华
网站建设 2026/5/15 2:13:57

大模型应用开发面经汇总+学习资料包,助你轻松拿下30K+AI岗位!

本文作者分享了近半年面试阿里、腾讯等20多家大模型相关公司的经验&#xff0c;指出大模型应用面试更注重实践而非高深理论&#xff0c;RAG、微调和Agent是重点考察内容。掌握微调原理并实际操作是提升面试通过率的关键。作者还提供了面试准备建议和不同公司的岗位特点&#xf…

作者头像 李华
网站建设 2026/5/25 0:52:14

Chapter-1 Memory Management (section 1.1-1.5)

操作系统为什么需要内存管理&#xff1f;这应该是一个很经典的问题&#xff0c;内存池 (Memory Pool) 也可以认为是一种内存管理的方式&#xff0c;所以关于内存管理四个字有点像谜底就在谜面上&#xff0c;更多的只是你如何管理的方式。比如 FreeRTOS 中的好几种分配方式&…

作者头像 李华