目录
一.背景
二.时序
1.基本时序
高位先行,数据线默认高电平。

起始信号:SCL时钟信号线为高电平时,SDA数据信号线由高变低出现下降沿
数据传输:
SCL时钟信号线为高电平时,SDA数据信号线电平保持稳定,此时接收方采样数据
SCL时钟信号线为低电平时,SDA数据信号线电平可以改变,此时发送方发送数据
应答信号:
第9个时钟周期,接收方需要向发送方回复应答信号(ACK低电平NACK高电平),每发一个字节,接收方回复应答信号。接收方不一定一直是主机,发送方也不一定一直是从机。发送方发送一个字节结束后,把电平拉高(线与特性),释放总线,交给接收方,若接收方拉低电平,则表示回复ACK,若接收方拉高电平,则表示回复NACK
发送一个字节后可接着发,不用再发起始信号
结束信号:
SCL时钟信号线为高电平时,SDA数据信号线由低变高出现上升沿。
由于结束信号是上升沿触发,所以当接收方拉高电平(NACK)之后要发送结束信号,则应该先拉低,再拉高电平。(若是低电平,即回复ACK,则直接拉高)
最后,注意所有起始和结束信号都是由主机发起,包括时钟信号
起始信号后第一个字节:设备地址(高7位)+数据流向位(低1位)
0x10
0001 0001 理论128个从机,实际会空出一个,只有127个从机
0:主机发送数据给从机(写)
1:从机发送数据给主机(读)
2.I2c读写时序

I2c写时序:
1.主机发送起始信号+设备地址+写位,等待从机ACK应答
2.主机发送寄存器地址,等待从机ACK应答
3.主机向从机发送数据,等待从机ACK应答
4.主机发送停止信号

I2c读时序:
1.主机发送起始信号+设备地址+写位,等待从机ACK应答
2.主机发送寄存器地址,等待从机ACK应答
3.主机重发起始信号+设备地址+读位,等待从机ACK应答
4.主机接收从机数据,主机回复ACK应答
5.当主机接收完最后一个字节数据,向从机回复NACK应答
6.主机发送停止信号
读写位和设备地址连着,所以要改变读写位就要重发设备地址,而设备地址通常为起始信号后的第一个字节,所以重发起始信号
三.AT24C02
AT24C02 是一款 2Kbit(256 字节)的串行 EEPROM(电可擦除可编程只读存储器)芯片。它通过 I2C 总线接口与主控芯片(如单片机)通信,常用于存储需要掉电保存的小量数据,例如配置参数或用户设置。
📊 核心参数速览
· 存储容量:2Kbit,即 256 字节(256 x 8bit)。
· 接口类型:I2C 两线串行接口(SCL, SDA)。
· 工作电压:1.8V ~ 5.5V,宽电压范围,兼容 3.3V (使用的imx6ull)和 5V 系统。
· 通信速率:100KHZ(1.8V,2.5V,2.7V);400kHz(5V)。
· 支持页写:一页8B。
· 擦写寿命:100 万次。
· 数据保存:100 年。

黑色芯片如下结构


A0,A1,A2默认接GND,即默认低电平。设备地址0101 0000,即0x50
四.I2c寄存器

IADR

当imx6ull芯片作为从机时,应在该寄存器传输地址,本篇作为主机使用。
IFDR


配置分频,由于采用ipg时钟,在上篇中有所提及,应为66MHZ,而本次应设置为100KHZ,计算没有具体值,采用相近的分频因子640

I2CR


TXAK:
当主机为发送模式,此位置0,表示检测从机发送的ACK,置1表示检测从机发送的NACK
当主机为接收模式,此位置0,表示主机回复给从机的ACK,置1表示主机回复给从机的NACK
I2SR

ICF:只判断确认位之前的数据是否传输
IBB:判断总线是否占用,0空闲,1占用,发送停止位之后清零
IAL:总线仲裁,置0表示不丢失总线仲裁,用于多主机情况下,置1表示仲裁丢失,没有抢到总线控制权,本次不需要,配0即可。
IIF:中断标记位,判断包括确认位之前的数据是否接收,即使GIC失能,但外设中断位也会产生,只是到不了内核。接收或发送数据之前清零,接收ACK或NACK之后置1,然后要再发送数据的话再清零。主机作为接收方时标记接收应答,置1,作为发送方时标记发送应答,置1
I2DR

五.实现
1.复用功能

引脚43的UART4 TXD被复用为I2C1的时钟线,引脚42的UART4 RXD 被复用为I2C1的数据线
// 配置引脚复用功能
IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1);
IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1);
2.电气属性

由于I2c配置了上拉电阻,所以此处应修改PUS位为11,选择22k的上拉电阻与芯片电阻并联,同时PUE为选择拉,(保持是电压输入几伏就保持,不改变)PKE位选择失能,所以12-15位为1111,配置电气属性为0xF0B0
// 配置引脚电气属性
IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0xF0B0);
IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0xF0B0);
3.读写时序实现
//与上面的复用功能和电气属性皆为初始化部分
I2C1->I2CR &= ~(1 << 7); // I2C1失能
I2C1->IFDR = 0x15; // 640分频
I2C1->I2CR |= (1 << 7); // I2C1使能

void i2c_write(I2C_Type *base, unsigned char dev_address, unsigned char reg_address, const unsigned char *data, int len)
{
// 通信前 仲裁丢失标志位清零 + 中断标记位清零
base->I2SR &= ~((1 << IIF) | (1 << IAL));
// 通信前,等待数据传输完毕
while ((base->I2SR & (1 << ICF)) == 0);
// 发送起始信号
base->I2CR |= (1 << MSTA);
// 主机模式:发送模式
base->I2CR |= (1 << MTX);
// 后续检测从机ACK信号
base->I2CR &= ~(1 << TXAK);
// 中断标记位清零
base->I2SR &= ~(1 << IIF);
// 发送设备地址 + 写位
base->I2DR = dev_address << 1; // 101 0000 0
// 等待从机ACK
while ((base->I2SR & (1 << IIF)) == 0);
// 中断标记位清零
base->I2SR &= ~(1 << IIF);
// 发送寄存器地址
base->I2DR = reg_address;
// 等待从机ACK
while ((base->I2SR & (1 << IIF)) == 0);
while (len--)
{
// 中断标记位清零
base->I2SR &= ~(1 << IIF);
// 发送数据
base->I2DR = *data++;
// 等待从机ACK
while ((base->I2SR & (1 << IIF)) == 0);
}
// 发送停止信号
base->I2CR &= ~(1 << MSTA);
// 等待总线空闲
while ((base->I2SR & (1 << IBB)) != 0)
{
delay_us(100);
}
}
void i2c_read(I2C_Type *base, unsigned char dev_address, unsigned char reg_address, unsigned char *data, int len)
{
// 通信前 仲裁丢失标志位清零 + 中断标记位清零
base->I2SR &= ~((1 << IIF) | (1 << IAL));
// 通信前,等待数据传输完毕
while ((base->I2SR & (1 << ICF)) == 0);
// 发送起始信号
base->I2CR |= (1 << MSTA);
// 主机模式:发送模式
base->I2CR |= (1 << MTX);
// 后续检测从机ACK信号
base->I2CR &= ~(1 << TXAK);
// 中断标记位清零
base->I2SR &= ~(1 << IIF);
// 发送设备地址 + 写位
base->I2DR = dev_address << 1;
// 等待从机ACK
while ((base->I2SR & (1 << IIF)) == 0);
// 中断标记位清零
base->I2SR &= ~(1 << IIF);
// 发送寄存器地址
base->I2DR = reg_address;
// 等待从机ACK
while ((base->I2SR & (1 << IIF)) == 0);
// 重发起始信号
base->I2CR |= (1 << RSTA);
// 中断标记位清零
base->I2SR &= ~(1 << IIF);
// 重发设备地址 + 读位
base->I2DR = dev_address << 1 | 1;
// 等待从机回复ACK信号
while ((base->I2SR & (1 << IIF)) == 0);
// 主机模式:接收模式
base->I2CR &= ~(1 << MTX);
// 中断标记位清零
base->I2SR &= ~(1 << IIF);
// 代表只需要读取一个字节
if (len == 1)
{
// 代表收到一个字节后,后续回复NACK
base->I2CR |= (1 << TXAK);
}
// 假读,在半双工模式下,发送和接收都用I2DR寄存器,读一次才能启动读数据,data不++,下次读取要覆盖此次读取发送时残留的数据
*data = base->I2DR;
// 读取len个字节数据
while (len-- != 0)
{
// 代表收到一个字节数据,并且主机回复了ACK
while ((base->I2SR & (1 << IIF)) == 0);
// 中断标记位清零
base->I2SR &= ~(1 << IIF);
// 代表数据全部接收完
if (len == 0)
{
// 发送停止信号
base->I2CR &= ~(1 << MSTA);
// ACK,以便下次通信发送数据时发送方检测ACK
base->I2CR &= ~(1 << TXAK);
// 等待总线空闲
while ((base->I2SR & (1 << IBB)) != 0)
{
delay_us(100);
}
}
else if (len == 1)
{
// NACK
base->I2CR |= (1 << TXAK);
}
*data++ = base->I2DR;
}
}
i2c_read(I2C1, 0x50, 0, buffer, 5);

设备地址0x0101 0000,即0x50
寄存器地址 0 是 AT24C02 内部的存储单元地址,也叫字地址(Word Address)。
AT24C02 的存储空间是 256 字节(2K bit),地址范围是 0x00 ~ 0xFF。
-
你传
0,表示要从第 0 个字节开始读写。 -
你传
0x10,表示从第 16 个字节开始。 -
容易混淆的两个“地址”
名称 含义 例子 谁决定的 设备地址 I2C 总线上识别哪个芯片 0x50 芯片型号 + A0/A1/A2 引脚 字地址 EEPROM 内部存储单元的偏移量 0x00 ~ 0xFF 你在代码里自己分配
13



被折叠的 条评论
为什么被折叠?



