一、概述
- IIC即Inter-Intergrated Circuit(集成电路总线),I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。
- 时钟线必须由主机(通常为微控制器)控制,主机产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。I2C总线上有主机(MCU)和从机(片外外设,如AT24C02)之分,可以有多个主机和多个从机。从机永远不会主动给主机发送数据。
- 每个接到I2C总线的设备都有一个唯一的地址,以便于主机寻访。
- IIC的通信速度分为三种:标准模式传输速率:100KHZ,快速模式:400KHZ,高速模式:3.4MHZ
二、信号概念
I2C通信,存在几种信号
- 起始信号(条件):通知从机做好通信的准备。
- 应答信号:有应答和无应答。有应答是低电平,无应答是高电平。
- 停止信号(条件):告诉从机通信已经结束。
三、关键时序图
数据位的传输:主机在时钟的上升沿时接收数据,下降沿时发送数据,一次数据的传输就是8位,高位先发
数据的有效性: 由图可知:时钟线为高电平时,数据线上的数据才稳定;时钟线为低电平时,数据线上的数据改变
起始和停止条件: 1. 起始条件:时钟线处于高电平期间,数据线产生了一个下降沿 2. 停止条件:时钟线处于高电平期间,数据线产生了一个上升沿
应答信号:判断刚刚接收到的数据有没有正常响应,应答位一定是接收方发出来的,发送和接收的时序和普通的一个位传输没有区别。发生在第9个时钟周期。 3. 主机发送一个应答位:在第9个时钟周期,时钟低电平时,数据引脚输出低电平时是应答,高电平时是无应答。
- 主机接收一个应答位:在第9个时钟周期,时钟从低电平到高电平变化时,读取SDA引脚的电平,数据引脚位低电平是时应答,高电平时是无应答。
一个字节的发送和接收: 发送一个字节:和发送一个应答位差不多,for循环8次即可,先发送高位``。 接收一个字节:和接收一个应答位差不多 时钟和数据转换:SDA引脚通常使用外部设备拉高。SDA引脚上的数据可能仅在SCL低时段发生变化。SCL高期间的数据变化将指示定义的起始或停止条件。
I2C的通信步骤:
- 起始条件
- 发送8位的数据(发送器件地址+写/读方向)
- 等待应答位
- 继续发送相应的字节数据
- 停止条件
如何区分是和哪个从机进行通信? 通过IIC器件自带的地址:一般是7bit的硬件地址,加上1bit的读写位 如果是主机发送:器件地址+写方向(0) 如果是主机接收:器件地址+读方向(1)
四、代码实现
主要看时序图编写
模拟I2C初始化, 使用的引脚:PB8–SCL , PB9–SDA 主机通过时钟引脚输出高低电平,故配置为推挽输出 主机有时通过数据引脚输出数据,有时需要从数据引脚中读取数据,故先配置为推挽输出
void i2c_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin = SCL_PIN|SDA_PIN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIO_PORT, &GPIO_InitStructure);
SCL_W = 1;
SDA_W = 1;
}
数据线输入/输出配置函数 当需要读取SDA引脚上的电平时,设置为输入模式:GPIO_Mode_IN 当需要从SDA引脚上输出数据时,设置为输出模式:GPIO_Mode_OUT
void sda_pin_mode(GPIOMode_TypeDef pin_mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = pin_mode;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin = SDA_PIN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIO_PORT, &GPIO_InitStructure);
}
void i2c_start(u8 delay)
{
sda_pin_mode(GPIO_Mode_OUT);
SCL_W = 1;
SDA_W = 1;
delay_us(delay);
SDA_W = 0;
delay_us(delay);
SCL_W = 0;
delay_us(delay);
}
起始条件和结束条件的时序图
void i2c_stop(u8 delay)
{
sda_pin_mode(GPIO_Mode_OUT);
SCL_W = 1;
SDA_W = 0;
delay_us(delay);
SDA_W = 1;
delay_us(delay);
}
void i2c_send_ack(u8 ack,u8 delay)
{
sda_pin_mode(GPIO_Mode_OUT);
SCL_W=0;
if(ack)
{
SDA_W = 1;
}
else
{
SDA_W = 0;
}
delay_us(delay);
SCL_W=1;
delay_us(delay);
SCL_W=0;
delay_us(delay);
}
发送应答位和接收应答位的时序图
u8 i2c_recv_ack(u8 delay)
{
u8 ack = 0;
sda_pin_mode(GPIO_Mode_IN);
SCL_W = 1;
delay_us(delay);
if(SDA_R) ack = 1;
else ack = 0;
SCL_W = 0;
delay_us(delay);
return ack;
}
发送一个字节:和发送一个应答位差不多,for循环8次即可,先发送高位``。
u8 i2c_send_byte(u8 byte,u8 delay)
{
u8 ack;
int i = 0;
sda_pin_mode(GPIO_Mode_OUT);
for(i=7;i>=0;i--)
{
SCL_W = 0;
if(byte & (1<<i))
{
SDA_W = 1;
}
else
{
SDA_W = 0;
}
delay_us(5);
SCL_W = 1;
delay_us(5);
SCL_W = 0;
delay_us(5);
}
ack = i2c_recv_ack(delay);
return ack;
}
u8 i2c_recv_byte(u8 ack,u8 delay)
{
u8 d = 0;
int i;
sda_pin_mode(GPIO_Mode_IN);
for(i=7; i>=0; i--)
{
SCL_W = 1;
delay_us(delay);
if(SDA_R)
d|=1<<i;
SCL_W = 0;
delay_us(delay);
}
i2c_send_ack(ack,delay);
return d;
}
|