news 2026/5/5 22:16:50

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/5 9:48:04

2025冬至继上

如果把生活类比成可迭代的对象&#xff0c;我的生活又到哪个版本了&#xff1f;还有什么可优化的地方吗&#xff1f;客观的事物不以人的意志为转移&#xff0c;怎么才能使自己的生活贴近事物发展的自然规律&#xff0c;使自己更贴近 按规律办事&#xff1f;古人云&#xff0c;头…

作者头像 李华
网站建设 2026/5/1 12:48:45

5.A.swift 使用指南

家好&#xff0c;我是K哥。一名独立开发者&#xff0c;同时也是Swift开发框架【Aquarius】的作者&#xff0c;悦记和爱寻车app的开发者。Aquarius开发框架旨在帮助独立开发者和中小型团队&#xff0c;完成iOS App的快速实现与迭代。使用框架开发将给你带来简单、高效、易维护的…

作者头像 李华
网站建设 2026/5/3 6:02:40

3个常见的降AI率工具大汇总(含免费降AI额度),AI率降到20以内!

临近毕业&#xff0c;好多学弟学妹都在问&#xff1a;有没有免费的降AI率工具&#xff1f; 一篇论文动不动10000、20000字&#xff0c;查重、查AI率、降重、降AIGC率&#xff0c;再查一次AIGC率。从写好论文到最后通过查重&#xff0c;最起码得好几百。 对学生来说&#xff0…

作者头像 李华
网站建设 2026/5/3 6:09:03

生产事故-Caffeine缓存误用之临下班的救赎

0x01 事故背景2025年7月9日17时有余&#xff0c;笔者正准备结束疲惫的一天&#xff0c;关机走人之时&#xff0c;桌面右下角安静了一天的内部通讯软件图标突然亮起&#xff0c;内心顿感不妙……打开一看&#xff0c;原来是运维小哥找过来了&#xff0c;说是某接口服务连续多次调…

作者头像 李华
网站建设 2026/5/4 17:14:23

大模型RL后训练扩展定律:66组实验揭示的幂律关系与效率饱和现象

中国科学技术大学与上海人工智能实验室联合研究基于Qwen2.5全系列模型(0.5B-72B)进行66组控制变量实验&#xff0c;首次系统探究RL后训练扩展定律。研究发现测试损失与计算量、数据量呈对数线性关系&#xff0c;可总结为幂律公式&#xff1b;同时定量揭示强化学习效率随参数量增…

作者头像 李华
网站建设 2026/5/1 14:19:14

大模型面试经验汇总:22家大厂面试实录+高频考点解析

本文详细记录了作者2023年参加的22家科技公司大模型岗位面试经验&#xff0c;包括面试流程、问题及感悟。文章总结了高频考点&#xff1a;多头注意力机制、框架并行方式、主流模型细节、大模型训练技巧、数据预处理和模型评估等。作者指出大模型领域竞争激烈&#xff0c;岗位对…

作者头像 李华