IIC/I2C总线实验

6 篇文章 0 订阅
5 篇文章 0 订阅

1、I2C总线相关概念的介绍

1> I2C总线是PHLIPS公司在八十年代初推出的一种同步串行的半双工总线,主要用于连接整体电路。

2> I2C总线为两线制,只有两根双向信号线。一根是数据线SDA,另一根是 时钟线SCL

3> I2C硬件结构简单,接口连接方便,成本较低。因此在各个领域得到了广泛的应用。

​ 传感器:温湿度传感器,环境光接近传感器,心率和脉搏传感器,触摸屏传感器。

​ EEPROM,DS1302, ADC芯片,

4> I2C总线上需要外接两个上拉电阻,上拉电阻的作用是再空闲态时,保持总线为高电平的状态

2、I2C总线的硬件连接

1> I2C是具备多主机多从机系统所需的包括总线裁决功能的高性能串行总线。

2> 在同一时刻只能由一个主机一个从机使用I2C总线

3> 每个接到I2C总线上的器件都有唯一的从机地址。

4> 主机与其它器件进行数据传送时总线上发送数据的器件为发送器,总线上接收数据的器件则为接收器。

5> 可以主动发起通信的叫做主机(Master),只能被动收发数据的叫做从机(Slave)。

3、I2C总线的时序图分析

时序图:随着时钟信号的变化,数据线上的数据的变化的图形就叫做时序图。

3.1 起始信号和终止信号时序图

1> SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;

2> SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号;

3> 起始和终止信号都是由主机发出,起始信号产生后,总线就处于占用的态;终止信号产生后,总线就处于空闲态。

3.2 数据信号时序图

1> I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,

​ 只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

2> 时钟在低电平期间,发送器向数据线上写入数据,此时允许数据线上的数据变化;

3> 时钟在高电平期间,接收器从数据线上读取数据,此时必须保持数据线上的数据稳定;

4> 一个时钟周期完成一个bit位数据的收发。

3.3 应答信号时序图

1> I2C总线采用了应答的机制,接收器收到1个字节的数据之后,必须给发送器返回应答信号。

2> 每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),在发送低位,

​ 每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

3> 在第九个时钟周期的低电平期间,接收器向数据线上写入数据,

​ 在第9个时钟周期的高电平期间,发送器从数据线上读取数据,

​ 如果读到的是低电平,表示应答信号,如果读到的是高电平,表示非应答信号。

3.4 从机地址

1> I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。

2> 主机在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/W),用“0”表示主机发送数据(W),“1”表示主机接收数据(R)。

3> 总线上的每个从机都将这7位地址码与自己的地址进行比较,如果相同,则认为自己被主机寻址,根据R/W位将自己定为发送器或接收器。

4、IIC总线的硬件连接

5、IIC总线的通信协议

5.1 主机给从机写一个字节的通信协议

5.2 主机给从机发送连续的多个字节的通信协议

5.3 主机从从机读一个字节的通信协议

5.4 主机从从机读连续的多个字节的通信协议

从机只能被动的收发数据,当从机给主机发送数据时,如果主机给从机发送的是应答信号,则从机会认为主机还想接收下一个字节的数据,因此从机会给主机发送下一个字节的数据。如果从机收到的是非应答信号,则从机不会发送下一个字节的数据给主机。

6分析手册(这个略,每个芯片手册都不一样,具体解析都再代码实现中有详细写到)

7代码实现

#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拉低
}

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值