news 2026/9/13 9:53:39

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/9/13 0:42:02

LeetCode每日一题——K个一组翻转链表

题目描述&#xff1a;给定链表的头节点head&#xff0c;每 k 个节点一组进行翻转&#xff0c;返回操作后的链表。示例&#xff1a;输入&#xff1a;head [1,2,3,4,5], k 2 输出&#xff1a;[2,1,4,3,5]我们可以先处理翻转整个链表的情况ListNode* reverseList(ListNode* head…

作者头像 李华
网站建设 2026/9/13 18:36:08

大模型后训练:中美路径与商业闭环|附56页PDF文件下载

全球主流大模型集中于中美。据Artificial Analysis 数据&#xff0c;美国头部模型厂商包括OpenAI、xAI、Anthropic 与Google&#xff1b;国内DeepSeek、阿里、智谱、Kimi与MiniMax较为领先。由于国内高性能算力受限&#xff0c;在同样强化学习后训练范式下&#xff0c;海外模型…

作者头像 李华
网站建设 2026/9/12 18:40:55

震惊!选对云服务器代理商,这5个关键指标必须知道!

震惊&#xff01;选对云服务器代理商&#xff0c;这5个关键指标必须知道&#xff01;在数字化转型浪潮席卷各行各业的今天&#xff0c;云服务器已成为企业IT架构的基石。然而&#xff0c;面对市场上琳琅满目的云服务商和代理商&#xff0c;企业决策者常常感到困惑&#xff1a;如…

作者头像 李华
网站建设 2026/9/13 2:09:45

2025年度复盘与总结

在过去的一年里&#xff0c;我做对了哪些事情?又做错了哪些事情 ?而这些&#xff0c;又怎样深刻的影响我的人生走向 年后二月&#xff0c;赋闲在家&#xff0c;当时手握好几万资金&#xff0c;没有什么危机感。退伍费的2万加上后面补贴的3万&#xff0c;全部败给了股市。股市…

作者头像 李华
网站建设 2026/9/13 3:00:39

ESA正式授予Sivers波束成形技术开发合同

Sivers Semiconductors 获 ESA 为期 18 个月、价值 90 万美元的合同Sivers Semiconductors 已获得欧洲航天局&#xff08;ESA&#xff09;的一项新开发计划。该合同价值约 90 万美元&#xff0c;为期 18 个月&#xff0c;将支持下一代卫星通信&#xff08;SATCOM&#xff09;波…

作者头像 李华