1> I2C总线是PHLIPS公司在八十年代初推出的一种同步串行的半双工总线,主要用于连接整体电路。
2> I2C总线为两线制,只有两根双向信号线。一根是数据线SDA,另一根是 时钟线SCL。
3> I2C硬件结构简单,接口连接方便,成本较低。因此在各个领域得到了广泛的应用。
传感器:温湿度传感器,环境光接近传感器,心率和脉搏传感器,触摸屏传感器。
EEPROM,DS1302, ADC芯片,
4> I2C总线上需要外接两个上拉电阻,上拉电阻的作用是再空闲态时,保持总线为高电平的状态
1> I2C是具备多主机多从机系统所需的包括总线裁决功能的高性能串行总线。
2> 在同一时刻只能由一个主机一个从机使用I2C总线
3> 每个接到I2C总线上的器件都有唯一的从机地址。
4> 主机与其它器件进行数据传送时总线上发送数据的器件为发送器,总线上接收数据的器件则为接收器。
5> 可以主动发起通信的叫做主机(Master),只能被动收发数据的叫做从机(Slave)。
时序图:随着时钟信号的变化,数据线上的数据的变化的图形就叫做时序图。
1> SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;
2> SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号;
3> 起始和终止信号都是由主机发出,起始信号产生后,总线就处于占用的态;终止信号产生后,总线就处于空闲态。
1> I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,
只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
2> 时钟在低电平期间,发送器向数据线上写入数据,此时允许数据线上的数据变化;
3> 时钟在高电平期间,接收器从数据线上读取数据,此时必须保持数据线上的数据稳定;
4> 一个时钟周期完成一个bit位数据的收发。
1> I2C总线采用了应答的机制,接收器收到1个字节的数据之后,必须给发送器返回应答信号。
2> 每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),在发送低位,
每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
3> 在第九个时钟周期的低电平期间,接收器向数据线上写入数据,
在第9个时钟周期的高电平期间,发送器从数据线上读取数据,
如果读到的是低电平,表示应答信号,如果读到的是高电平,表示非应答信号。
1> I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。
2> 主机在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/W),用“0”表示主机发送数据(W),“1”表示主机接收数据(R)。
3> 总线上的每个从机都将这7位地址码与自己的地址进行比较,如果相同,则认为自己被主机寻址,根据R/W位将自己定为发送器或接收器。
从机只能被动的收发数据,当从机给主机发送数据时,如果主机给从机发送的是应答信号,则从机会认为主机还想接收下一个字节的数据,因此从机会给主机发送下一个字节的数据。如果从机收到的是非应答信号,则从机不会发送下一个字节的数据给主机。
- #include "../include/iic.h"
-
- extern void printf(const char *fmt, ...);
- /*
- * 函数名 : delay_us
- * 函数功能:延时函数
- * 函数参数:无
- * 函数返回值:无
- * */
- void delay_us(void)
- {
- unsigned int i = 2000;
- while (i--)
- ;
- }
- /*
- * 函数名 : i2c_init
- * 函数功能: i2C总线引脚的初始化, 通用输出,推挽输出,输出速度,
- * 函数参数:无
- * 函数返回值:无
- * */
- void i2c_init(void)
- {
- // 使能GPIOF端口的时钟
- RCC->MP_AHB4ENSETR |= (0x1 << 5);
- // 设置PF14,PF15引脚为通用的输出功能
- GPIOF->MODER &= (~(0xF << 28));
- GPIOF->MODER |= (0x5 << 28);
- // 设置PF14, PF15引脚为推挽输出
- GPIOF->OTYPER &= (~(0x3 << 14));
- // 设置PF14, PF15引脚为高速输出
- GPIOF->OSPEEDR |= (0xF << 28);
- // 设置PF14, PF15引脚的禁止上拉和下拉
- GPIOF->PUPDR &= (~(0xF << 28));
- // 空闲状态SDA和SCL拉高
- I2C_SCL_H;
- I2C_SDA_H;
- }
-
- /*
- * 函数名:i2c_start
- * 函数功能:模拟i2c开始信号的时序
- * 函数参数:无
- * 函数返回值:无
- * */
- void i2c_start(void)
- {
- /*
- * 开始信号:时钟在高电平期间,数据线从高到低的变化
- * --------
- * SCL \
- * --------
- * ----
- * SDA \
- * --------
- * */
- SET_SDA_OUT; // 设置SDA为输出模式,确保一定为输出模式
- I2C_SDA_H; // SDA拉高
- I2C_SCL_H; // SCL拉高
- delay_us();
- I2C_SDA_L; // SDA拉低
- delay_us();
- I2C_SCL_L; // SCL拉低,I2C总线就处于占用态
- }
-
- /*
- * 函数名:i2c_stop
- * 函数功能:模拟i2c停止信号的时序
- * 函数参数:无
- * 函数返回值:无
- * */
-
- void i2c_stop(void)
- {
- /*
- * 停止信号 : 时钟在高电平期间,数据线从低到高的变化
- * ----------
- * SCL /
- * --------
- * --- -------
- * SDA X /
- * --- -------
- * 为了确保停止信号是一个上升沿,因此在时钟为低电平期间
- * 将数据线拉低,确保可以产生上升沿。
- * */
- SET_SDA_OUT; // 设置SDA为输出
- I2C_SCL_L; // SCL拉低
- delay_us();
- I2C_SDA_L; // SDA拉低
- delay_us();
- I2C_SCL_H; // SCL拉高
- delay_us();
- I2C_SDA_H; // SDA拉高
-
- }
-
- /*
- * 函数名: i2c_write_byte
- * 函数功能:主机向i2c总线上的从设备写8bits数据
- * 函数参数:dat : 等待发送的字节数据
- * 函数返回值: 无
- * */
-
- void i2c_write_byte(unsigned char dat)
- {
- /*
- * 数据信号:时钟在低电平期间,发送器向数据线上写入数据
- * 时钟在高电平期间,接收器从数据线上读取数据
- * ---- --------
- * SCL \ / \
- * -------- --------
- * -------- ------------------ ---
- * SDA X X
- * -------- ------------------ ---
- * 先发送高位在发送低位
- * */
- unsigned int i;
- SET_SDA_OUT; // SDA为输出
- for (i = 0 ; i < 8; i++) {
- I2C_SCL_L; // SCL拉低
- delay_us();
- if (dat & 0x80) // 判断最高位的值
- I2C_SDA_H; // 向数据线上写入高电平
- else
- I2C_SDA_L; // 向数据线上写入低电平
- delay_us();
- I2C_SCL_H; // SCL拉高
- delay_us();
- delay_us(); // 等待从机将数据线上的数据读走
- dat <<= 1; // 将次高位的数据移动到最高位
- }
- }
-
- /*
- * 函数名:i2c_read_byte
- * 函数功能: 主机从i2c总线上的从设备读8bits数据,
- * 主机发送一个应答或者非应答信号给从机
- * 函数参数: 0 : 应答信号 1 : 非应答信号
- * 函数返回值:读到的有效数据
- *
- * */
- unsigned char i2c_read_byte(unsigned char ack)
- {
- /*
- * 数据信号:时钟在低电平期间,发送器向数据线上写入数据
- * 时钟在高电平期间,接收器从数据线上读取数据
- * ---- --------
- * SCL \ / \
- * -------- --------
- * -------- ------------------ ---
- * SDA X X
- * -------- ------------------ ---
- *
- * 先接收高位, 在接收低位
- * */
- unsigned char dat;
- unsigned int i;
- SET_SDA_IN; // 设置SDA为输入
- for (i = 0; i < 8; i++) {
- I2C_SCL_L; // SCL拉低
- delay_us();
- delay_us(); // 等待从机向数据线上写入数据
- I2C_SCL_H; // SCL拉高
- delay_us();
- dat <<= 1;
- if (I2C_SDA_READ)
- dat |= 1;
- else
- dat |= 0;
-
- delay_us();
- }
-
- if (!ack)
- i2c_ack(); // 发送应答信号
- else
- i2c_nack(); // 发送非应答信号
- return dat;
-
- }
- /*
- 将dat中数据的左移1位写到接收数据的上边。
- 为什么将dat中的数据左移1位,写道接收数据的下边不可以?
- 假设接收的数据为0xFF,
- 先接收数据 在进行左移
- 第1次循环 0000 0001 0000 0010
- 第2次循环 0000 0011 0000 0110
- 第3次循环 0000 0111 0000 1110
- 第4次循环 0000 1111 0001 1110
- 第5次循环 0001 1111 0011 1110
- 第6次循环 0011 1111 0111 1110
- 第7次循环 0111 1111 1111 1110
- 第8次循环 1111 1111 1111 1110
- */
-
- /*
- * 函数名: i2c_wait_ack
- * 函数功能: 主机作为发送器时,等待接收器返回的应答信号
- * 函数参数:无
- * 函数返回值:
- * 0:接收到的应答信号
- * 1:接收到的非应答信号
- * */
- unsigned char i2c_wait_ack(void)
- {
- int ack;
- /*
- * 主机发送一个字节之后,从机给主机返回一个应答信号,主机接收应答信号
- * -----------
- * SCL / M:读 \
- * ----------------- --------
- * --- -------- --------------------
- * SDA X X
- * --- --------------------
- * 主 释 设 从机 主机
- * 机 放 置 向数据 读数据线
- * 总 SDA 线写 上的数据
- * 线 输 数据
- * 入
- * */
- I2C_SCL_L; // SCL拉低
- delay_us();
- I2C_SDA_H; // SDA拉高, 释放总线
- SET_SDA_IN; // 设置SDA为输入
- delay_us();
- delay_us(); // 等待从机向数据线上写入应答信号
- I2C_SCL_H; // SCL拉高
- delay_us();
- if (I2C_SDA_READ)
- ack = 1; // 非应答信号
- else
- ack = 0; // 应答信号
- delay_us();
- I2C_SCL_L;
- return ack;
- }
- /*
- * 函数名: iic_ack
- * 函数功能: 主机作为接收器时,给发送器发送应答信号
- * 函数参数:无
- * 函数返回值:无
- * */
- void i2c_ack(void)
- {
- /* --------
- * SCL / \
- * ------- ------
- * ---
- * SDA X
- * --- -------------
- * 在第九个时钟周期的低电平期间,接收器向数据线写入数据,
- * 在第九个时钟周期的高电平期间,发送器从数据线上读取数据,
- * 如果读到低电平表示应答信号
- * */
- SET_SDA_OUT; // 设置SDA为输出
- I2C_SCL_L; // SCL拉低
- delay_us();
- I2C_SDA_L; // SDA拉低,发送的是应答信号
- delay_us();
- I2C_SCL_H; // SCL拉高
- delay_us();
- delay_us(); // 等待从机接收应答信号
- I2C_SCL_L; // SCL拉低
- }
- /*
- * 函数名: iic_nack
- * 函数功能: 主机作为接收器时,给发送器发送非应答信号
- * 函数参数:无
- * 函数返回值:无
- * */
- void i2c_nack(void)
- {
- /* --------
- * SCL / \
- * ------- ------
- * --- ---------------
- * SDA X
- * ---
- * 在第九个时钟周期的低电平期间,接收器向数据线写入数据,
- * 在第九个时钟周期的高电平期间,发送器从数据线上读取数据,
- * 如果读到高电平表示非应答信号
- * */
- SET_SDA_OUT; // 设置SDA为输出
- I2C_SCL_L; // SCL拉低
- delay_us();
- I2C_SDA_H; // SDA拉高,发送的是非应答信号
- delay_us();
- I2C_SCL_H; // SCL拉高
- delay_us();
- delay_us(); // 等待从机接收非应答信号
- I2C_SCL_L; // SCL拉低
- }