软件I2C读写AT24C02

一、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();
}

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值