IIC总线系统结构
如图,两根线要拉高
起始信号与停止信号
这些都是主控制器(STM32)发出的信号
void i2c_Start(void)
{
/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
EEPROM_I2C_SDA_1();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SDA_0();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
void i2c_Stop(void)
{
/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
EEPROM_I2C_SDA_0();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SDA_1();
}
应答信号
应答信号是外围设备反馈的,不是控制器发送的!
第九个脉冲周期内回收一个低电平信号则为应答ACK
回收一个高电平信号则为非应答NACK
uint8_t i2c_WaitAck(void)
{
uint8_t re;
EEPROM_I2C_SDA_1(); /* CPU释放SDA总线 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
i2c_Delay();
if (EEPROM_I2C_SDA_READ()) /* CPU读取SDA口线状态 */
{
re = 1;
}
else
{
re = 0;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
return re;
}
void i2c_Ack(void)
{
EEPROM_I2C_SDA_0(); /* CPU驱动SDA = 0 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
EEPROM_I2C_SDA_1(); /* CPU释放SDA总线 */
}
数据有效性
- 时钟信号为高电平期间,数据线上的数据必须保持稳定
发送一个八位的数据
void i2c_SendByte(uint8_t _ucByte)
{
uint8_t i;
for (i = 0; i < 8; i++) 循环八次
{
if (_ucByte & 0x80) 与10000000,即获取第7位数字
{
EEPROM_I2C_SDA_1();
}
else
{
EEPROM_I2C_SDA_0();
}
i2c_Delay();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SCL_0();
if (i == 7)
{
EEPROM_I2C_SDA_1(); 释放总线
}
_ucByte <<= 1; 左移一个bit
i2c_Delay();
}
}
读数据
uint8_t i2c_ReadByte(void)
{
uint8_t i;
uint8_t value;
读到第1个bit为数据的bit7 */
value = 0; //00000000
for (i = 0; i < 8; i++)
{
value <<= 1; value左移一位
EEPROM_I2C_SCL_1();
i2c_Delay();
if (EEPROM_I2C_SDA_READ()) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210226154741732.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NjIxMTk0MA==,size_16,color_FFFFFF,t_70)
{
value++;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
}
return value;
}
EEPROM(24C02)
- 通过IIC接口连接
- A0 A1 A2地址线默认接地
- WP是写保护
向24C02指定地址读一个数据
- 时序图如下
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i;
第1步:启动 */
i2c_Start();
第2步:地址为某某某的EEPROM接旨。高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR); /此处是写指令 */
第3步:等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
第4步:发送EEPROM里面具体某个字节的地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
i2c_SendByte((uint8_t)_usAddress);
第5步:等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
第6步:重新启动I2C总线。前面的代码的目的向EEPROM传送地址,下面开始读取数据 */
i2c_Start();
第7步:地址为某某某的EEPROM进贡。高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_RD); 此处是读指令 */
第8步:发送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
第9步:循环读取数据 */
for (i = 0; i < _usSize; i++)
{
_pReadBuf[i] = i2c_ReadByte(); /* 读1个字节 */
每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
if (i != _usSize - 1)
{
i2c_Ack(); 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */
}
else
{
i2c_NAck(); 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
}
}
发送I2C总线停止信号 */
i2c_Stop();
return 1; /* 执行成功 */
cmd_fail: 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
/* 发送I2C总线停止信号 */
i2c_Stop();
return 0;
}
向EEPROM写若干个数据
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i,m;
uint16_t usAddr;
写串行EEPROM不像读操作可以连续读取很多字节,每次写操作只能在同一个page。
对于24xx02,page size = 8
简单的处理方法为:按字节写操作模式,没写1个字节,都发送地址
为了提高连续写的效率: 本函数采用page wirte操作。
*/
usAddr = _usAddress;
for (i = 0; i < _usSize; i++)
{
当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */
if ((i == 0) || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0)
{
第0步:发停止信号,启动内部写操作 */
i2c_Stop();
/ 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
CLK频率为200KHz时,查询次数为30次左右
*/
for (m = 0; m < 1000; m++)
{
/ 第1步:发起I2C总线启动信号 */
i2c_Start();
/ 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR); / 此处是写 */
/第3步:发送一个时钟,判断器件是否正确应答 */
if (i2c_WaitAck() == 0)
{
break;
}
}
if (m == 1000)
{
goto cmd_fail; /* EEPROM器件写超时 */
}
/ 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
i2c_SendByte((uint8_t)usAddr);
/第5步:等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
}
/ 第6步:开始写入数据 */
i2c_SendByte(_pWriteBuf[i]);
/ 第7步:发送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
usAddr++; /* 地址增1 */
}
/ 命令执行成功,发送I2C总线停止信号 */
i2c_Stop();
return 1;
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
/* 发送I2C总线停止信号 */
i2c_Stop();
return 0;
}