前置知识:
- I2C具有2根通讯线,SCL时钟线,SDA数据线;
- 开漏输出和上拉电阻,使之闲时高电平,开漏后才低电平;
- 一主多从(One Master N Slave)模式,程序控制主机,从机不控制;
- 主机拿捏着SCL时钟线,即程序时刻按需变换SCL高低电平;
- SDA由主机和从机共同操控,程序置SDA高电平 = 主机释放控制权;
时序图总览:
- Start:开始;
- SendByte:主机发送;
- ReceiveAck:从机应答;(虚线)
- ReceiveByte:从机发送;(虚线)
- SendAck:主机应答;
- Stop:停止;
拆分解析:
Start,开始的时序:在SCL=1的状态,SDA由1→0;
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void I2C_Start(void)
{
I2C_SDA=1; //复合格式,第二次Start在从机应答之后调用,不确定SDA高低电平
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
SendByte,主机发送的时序:SCL拉高前,SDA跳变完成并保持住,等SCL开漏低电平后再动作;
/**
* @brief I2C主机发送字节(TO从机)
* @param 8位字节
* @retval 无
*/
void I2C_SendByte(unsigned char byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
I2C_SDA=byte&(0x80>>i);
I2C_SCL=1;
I2C_SCL=0;
}
}
ReceiveAck,从机应答:主机要先释放数据控制权SDA=1,然后从机控制SDA了,主机控制时钟线高低中间,读取SDA中从机发的数据;
/**
* @brief I2C主机接收应答(From从机)
* @param 无
* @retval 1bit位
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char bitAck;
I2C_SDA=1; //主机释放数据控制权
I2C_SCL=1;
bitAck=I2C_SDA;
I2C_SCL=0;
return bitAck;
}
ReceiveByte,从机发送:主机要先释放数据控制权SDA=1,然后从机控制SDA了,通过中间变量byte一位一位存储bit,最后返回一字节;
/**
* @brief I2C主机接收字节(From从机)
* @param 8位字节
* @retval 无
*/
char I2C_ReceiveByte(void)
{
unsigned char byte=0x00,i;
I2C_SDA=1; //主机释放SDA;
for(i=0;i<8;i++)
{
I2C_SCL=1;
if(I2C_SDA){byte|=(0x80>>i);}
I2C_SCL=0;
}
return byte;
}
SendAck,主机应答:主机拿到SDA的控制权,再控制SCL时钟线高→低;
/**
* @brief I2C主机发送应答(TO从机)
* @param 1bit位
* @retval 无
*/
void I2C_SendAck(unsigned char bitAck)
{
I2C_SDA=bitAck;
I2C_SCL=1;
I2C_SCL=0;
}
Stop,停止:在SCL=1期间,SDA发生一个由低到高的跳变;
/**
* @brief I2C结束
* @param 无
* @retval 无
*/
void I2C_Stop(void)
{
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
拼图汇总:
将以上拼图模块,按照标准,顺序执行就能实现主机对从机的【写入】和【读取】。
以AT24C02这种E2PROM为例:
数据的写入:
void AT24C20_WriteByte(unsigned char addr,byte)
{
char Ack;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS); //输入从机地址找到目标从机
Ack=I2C_ReceiveAck();
I2C_SendByte(addr); //输入从机的字节地址
Ack=I2C_ReceiveAck();
I2C_SendByte(byte); //给该从机的该字节地址写入什么
Ack=I2C_ReceiveAck();
I2C_Stop();
}
数据的读出:
unsigned char AT24C02_ReadByte(unsigned char addr)
{
unsigned char byte;
unsigned char bitAck=0;
unsigned char Ack;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS); //输入从机地址找到目标从机
Ack=I2C_ReceiveAck();
I2C_SendByte(addr); //输入从机的字节地址
Ack=I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01); //输入从机地址找到目标从机,并置读
Ack=I2C_ReceiveAck();
byte=I2C_ReceiveByte(); //从机发送字节,主机收
I2C_SendAck(bitAck);
I2C_Stop();
return byte;
}