一、I2C介绍
1.1 I2C简介
I2C是一种双向,两线串口通讯接口,两线分别是串行数据线SDA和串行时钟线SCL。两根线都必须通过一个外部上拉电阻接到电源VCC,典型的接线配置如下图所示:
上图中Rp的值一般都是取10K,也有取4.7K的,具体可根据实际情况修改。SCL和SDA均要配置为开漏输出模式。
1.2 I2C的基本时序单元
1.2.1 起始信号和结束信号
数据和时钟线都为高则总线处在空闲状态。
起始信号:SCL高电平期间,SDA从高电平变为低电平
结束信号:SCL高电平期间,SDA从低电平变为高电平,时序图如图2所示。
1.2.2 位传输
每个时钟脉冲传输一位数据。SCL为高时SDA必须保持稳定,这句话贯穿了整个I2C的时序,对写程序时大有帮助。位传输参见下图3。
1.2.3 发送一个字节
SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
如下图所示,只有当主机拉低SCL,主机才会把数据放在SDA线上,完了主机再释放SCL,从机在SCL高电平期间把SDA上的数据读走,这样重复8次就成功读走了一个字节数据。
1.2.4 接收一个字节
SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节。主机在接收之前,需要先释放SDA,才能将SDA的控制权交给从机。不然主机会对从机发送的数据造成干扰。
1.2.5 应答信号
1)发送应答:主机在接收完一个字节之后,在下一个时钟(第九个时钟)发送一位数据,数据0表示应答,数据1表示非应答;
2)接收应答:主机在发送完一个字节之后,在下一个时钟(第九个时钟)接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
由上图可以看出,将SDA拉低表示应答,将SDA释放表示非应答。
1.3 I2C的功能时序单元
将1.2的基本时序单元按规则组合在一起就组成了我们的功能时序单元
1.3.1字节写
由上图简单说一下单字节写的流程:1)起始信号;2)发送从机的器件地址;3)接收应答;4)发送要写入的从机寄存器地址;5)接收应答;6)发送要写入寄存器的数据;7)接收应答;8)结束信号。这样就完成了一个字节的写入。
1.3.2 页写
页写的开始部分和字节写的结束信号之前是一样的,只是页写一直在重复最后的写字节和接收应答,写完数据了最后跟一个结束信号就完成了(每写入一个数据,寄存器的地址是自动加一的,不需要我们写程序操作)。查AT24C02的手册可以知道,AT24C02每次写入最多需要5ms的时间才能完成,即上一次的写入完成和下一次的写入开始最好能间隔5ms及以上,来保证数据能正确写入到AT24C02寄存器中。所以在页写中,我们可以给每次写完后加一个5ms的延时。 AT24C02一页是八个字节,
1.3.3 指定地址读
指定地址读要两次使用起始信号,第一次写入的器件地址就是器件地址(最低位为0,表示写),第二次起始信号写入的器件地址是器件地址 | 0x01(最低位为1,表示读),读了数据以后,主机需要发送一个非应答信号,然后结束。
1.3.4 顺序读
同理,顺序读的前面部分也和指定地址读的前面部分是一样的,假如主机要读取n个数据,则前面n-1个数据每读完一个数据后,主机都要发送应答信号(拉低SDA),这样才能继续读数据,当第n个数据读到后,则主机发送非应答信号(拉高SDA),再跟一个结束信号就可以了。
二、AT24C02介绍
1.基本介绍
1.1 AT24C02简介
AT24C02A/04A/08A/16A提供2048/4096/8192/16384位串行电可擦除和可编程只读存储器(EEPROM),每个字被组织为256/512/1024/2048位。该设备被优化用于许多工业和商业应用中,其中低功率和低压操作是必不可少的。AT24C02A/04A/08A/16A可提供节省空间的8线PDIP、8线JEDEC SOIC、8线MAP和8线TSSOP包,并可通过2线串行接口访问。其结构图如下:
1.2最大额定参数
1.3引脚说明(SOP-8封装)
1.4器件寻址
如下图所示,AT24C02的A0-A2引脚分别是其器件地址的第1到3为,所以若A0-A2均接地,则写入时AT24C02的器件地址为0xA0(写入时最低位为0),读取时的器件地址为0xA1(读取时最低位为1)。
三、程序
软件SPI使用普通IO口即可,如下图我使用的是PB6和PB7口作为时钟线和数据线,使用CubeMX配置一下初始化和GPIO即可开始写程序了,时钟线和数据线均设置为开漏输出模式,因为SDA还需要用到输入模式。程序分为I2C_Driver.c和AT24C02.c。
1.基本时序单元程序(I2C_Driver.c)
1.1 读写时钟线和数据线
void MyI2C_W_SCL(uint8_t BitValue)
{
HAL_GPIO_WritePin(GPIOB, SCL_Pin, BitValue?GPIO_PIN_SET:GPIO_PIN_RESET);
Delay_us(10);//I2C最大速度为400KHz,保险起见加一个10us延时
}
void MyI2C_W_SDA(uint8_t BitValue)
{
HAL_GPIO_WritePin(GPIOB, SDA_Pin, BitValue?GPIO_PIN_SET:GPIO_PIN_RESET);
Delay_us(10);//I2C最大速度为400KHz,保险起见加一个10us延时
}
//SCL和SDA配置为开漏输出,此模式下也可以输入:输入时,先输出1,再直接读取输入数据寄存器就行了
uint8_t MyI2C_R_SDA(void)
{
uint8_t data = 0x00;
data = HAL_GPIO_ReadPin(GPIOB, SDA_Pin);
Delay_us(10);
return data;
}
1.2 起始和结束信号
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
1.3 写一个字节和读一个字节
void MyI2C_WriteByte(uint8_t pdata)
{
uint8_t i;
for(i=0;i<8;i++)
{
MyI2C_W_SDA(pdata & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
uint8_t MyI2C_ReadByte(void)
{
uint8_t i,data=0x00;
MyI2C_W_SDA(1);//主机释放SDA也是将SDA的控制权交给从机
for(i=0;i<8;i++)
{
data <<= 1;
MyI2C_W_SCL(1);
data |= MyI2C_R_SDA();
MyI2C_W_SCL(0);
}
return data;
}
1.4 主机接收(从机发送)应答和主机发送应答
uint8_t Slave_Ack(void)
{
uint8_t Ack = 0;
//函数进来时,SCL低电平,主机释放SDA,同时从机把应答位放在SDA上
MyI2C_W_SDA(1);
//SCL高电平,主机读取应答位
MyI2C_W_SCL(1);
Ack = MyI2C_R_SDA();
//SCL低电平进入下一个时序单元
MyI2C_W_SCL(0);
return Ack;
}
void Master_Ack(uint8_t AckBit)
{
//函数进来时,SCL低电平,主机把AckBit放在SDA上
MyI2C_W_SDA(AckBit);
//SCL高电平,从机读取应答
MyI2C_W_SCL(1);
//SCL低电平,进入下一个时序单元
MyI2C_W_SCL(0);
}
2.功能时序单元程序(AT24C02.c)
2.1 发送单字节
#define AT24C02_ADDRESS 0xa0
void AT24C02_WriteByte(uint8_t Address,uint8_t data)
{
MyI2C_Start();
MyI2C_WriteByte(AT24C02_ADDRESS);
Slave_Ack();
MyI2C_WriteByte(Address);
Slave_Ack();
MyI2C_WriteByte(data);
Slave_Ack();
MyI2C_Stop();
}
2.2 指定地址读单字节
uint8_t AT24C02_ReadByte(uint8_t Address)
{
uint8_t ReadData = 0x00;
MyI2C_Start();
MyI2C_WriteByte(AT24C02_ADDRESS);
Slave_Ack();
MyI2C_WriteByte(Address);
Slave_Ack();
MyI2C_Start();
MyI2C_WriteByte(AT24C02_ADDRESS+1);
Slave_Ack();
ReadData = MyI2C_ReadByte();
Master_Ack(1);
MyI2C_Stop();
return ReadData;
}
2.3 多数据连续写(这个函数最多写8个数据)
我这个只是页写的函数,大家也可以封装一个写任意个数据的函数
void AT24C02_WriteManyByte(uint8_t Address,uint8_t *WriteData,uint8_t Count)
{
uint8_t i;
MyI2C_Start();
MyI2C_WriteByte(AT24C02_ADDRESS);
Slave_Ack();
MyI2C_WriteByte(Address);
Slave_Ack();
for(i=0;i<Count;i++)
{
MyI2C_WriteByte(*(WriteData+i));
Slave_Ack();
Delay_ms(5);//AT24C02写字节的最大周期为5ms
}
MyI2C_Stop();
}
2.4 顺序读
下图前边有点不完整,要参考前面的单字节读的时序图
void AT24C02_ReadManyByte(uint8_t Address, uint8_t *pData,uint8_t Count)
{
uint8_t i;
MyI2C_Start();
MyI2C_WriteByte(AT24C02_ADDRESS);
Slave_Ack();
MyI2C_WriteByte(Address);
Slave_Ack();
MyI2C_Start();
MyI2C_WriteByte(AT24C02_ADDRESS+1);
Slave_Ack();
for(i=0;i<Count-1;i++)
{
*(pData+i) = MyI2C_ReadByte();
Master_Ack(0);
}
*(pData+Count-1) = MyI2C_ReadByte();
Master_Ack(1);
MyI2C_Stop();
}