I2C协议
1、I2C简介
1.1、I2C总线
I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。
1.2、工作原理编辑
SDA(串行数据线)和SCL(串行时钟线)都是双向I/O线,接口电路为开漏输出,需通过上拉电阻接电源VCC。当总线空闲时,两根线都是高电平,连接总线的外同器件都是CMOS器件,输出级也是开漏电路.在总线上消耗的电流很小,因此,总线上扩展的器件数量主要由电容负载来决定,因为每个器件的总线接口都有一定的等效电容.而线路中电容会影响总线传输速度.当电容过大时,有可能造成传输错误.所以,其负载能力为400pF,因此可以估算出总线允许长度和所接器件数量。
主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。
1.3、特点
I2C总线特点可以概括如下:
(1)、在硬件上,I2C总线只需要一根数据线和一根时钟线两根线,总线接口已经集成在芯片内部,不需要特殊的接口电路,而且片上接口电路的滤波器可以滤去总线数据上的毛刺.因此I2C总线简化了硬件电路PCB布线,降低了系统成本,提高了系统可靠性。因为I2C芯片除了这两根线和少量中断线,与系统再没有连接的线,用户常用IC可以很容易形成标准化和模块化,便于重复利用。
(2)、I2C总线是一个真正的多主机总线,如果两个或多个主机同时初始化数据传输,可以通过冲突检测和仲裁防止数据破坏,每个连接到总线上的器件都有唯一的地址,任何设备既可以作为主机也可以作为从机,但同一时刻只允许有一个主机,而且每一个设备都对应着唯一的地址。数据传输和地址设定由软件设定,非常灵活。总线上的器件增加和删除不影响其他器件正常工作。在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。
(3)、I2C总线可以通过外部连线进行在线检测,便于系统故障诊断和调试,故障可以立即被寻址,软件也利于标准化和模块化,缩短开发时间。
(4)、 I2C总线上可挂接的设备数量受总线的最大电容400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址位的限制。串行的8位双向数据传输位速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Mbit/s。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。I2C总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输。
(5)、总线具有极低的电流消耗.抗高噪声干扰,增加总线驱动器可以使总线电容扩大10倍,传输距离达到15m;兼容不同电压等级的器件,工作温度范围宽。
1.4、数据传输
(1)、字节格式
发送到SDA 线上的每个字节必须为8 位,每次传输可以发送的字节数量不受限制。每个字节后必须跟一个响应位。首先传输的是数据的最高位(MSB),如果从机要完成一些其他功能后(例如一个内部中断服务程序)才能接收或发送下一个完整的数据字节,可以使时钟线SCL 保持低电平,迫使主机进入等待状态,当从机准备好接收下一个数据字节并释放时钟线SCL 后数据传输继续。
(2)、应答响应
数据传输必须带响应,相关的响应时钟脉冲由主机产生。在响应的时钟脉冲期间发送器释放SDA 线(高)。
在响应的时钟脉冲期间,接收器必须将SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平。
通常被寻址的接收器在接收到的每个字节后,除了用CBUS 地址开头的数据,必须产生一个响应。当从机不能响应从机地址时(例如它正在执行一些实时函数不能接收或发送),从机必须使数据线保持高电平,主机然后产生一个停止条件终止传输或者产生重复起始条件开始新的传输。
如果从机接收器响应了从机地址,但是在传输了一段时间后不能接收更多数据字节,主机必须再一次终止传输。这个情况用从机在第一个字节后没有产生响应来表示。从机使数据线保持高电平,主机产生一个停止或重复起始条件。
如果传输中有主机接收器,它必须通过在从机发出的最后一个字节时产生一个响应,向从机发送器通知数据结束。从机发送器必须释放数据线,允许主机产生一个停止或重复起始条件。
I2C总线数据传输和应答如下图:
(3)、时钟同步
所有主机在SCL线上产生它们自己的时钟来传输I2C总线上的报文。数据只在时钟的高电平周期有效,因此需要一个确定的时钟进行逐位仲裁。
时钟同步通过线与连接I2C 接口到SCL 线来执行。这就是说SCL 线的高到低切换会使器件开始数它们的低电平周期,而且一旦器件的时钟变低电平,它会使SCL 线保持这种状态直到到达时钟的高电平。但是如果另一个时钟仍处于低电平周期,这个时钟的低到高切换不会改变SCL 线的状态。因此SCL 线被有最长低电平周期的器件保持低电平。此时低电平周期短的器件会进入高电平的等待状态。
当所有有关的器件数完了它们的低电平周期后,时钟线被释放并变成高电平。之后,器件时钟和SCL线的状态没有差别,而且所有器件会开始数它们的高电平周期。首先完成高电平周期的器件会再次将SCL线拉低。
这样产生的同步SCL 时钟的低电平周期由低电平时钟周期最长的器件决定,而高电平周期由高电平时钟周期最短的器件决定。
1.5、传输模式
(1)、快速模式
快速模式器件可以在400kbit/s 下接收和发送。最小要求是:它们可以和400kbit/s 传输同步,可以延长SCL 信号的低电平周期来减慢传输。快速模式器件都向下兼容,可以和标准模式器件在0~100kbit/s 的I2C 总线系统通讯。但是,由于标准模式器件不向上兼容,所以不能在快速模式I2C 总线系统中工作。快速模式I2C 总线规范与标准模式相比有以下特征:
A、最大位速率增加到400kbit/s;
B、调整了串行数据(SDA) 和串行时钟(SCL )信号的时序;
C、快速模式器件的输入有抑制毛刺的功能,SDA 和SCL输入有施密特触发器;
D、快速模式器件的输出缓冲器对SDA 和SCL 信号的下降沿有斜率控制功能;
E、如果快速模式器件的电源电压被关断,SDA 和SCL 的I/O 管脚必须悬空,不能阻塞总线;
F、连接到总线的外部上拉器件必须调整以适应快速模式I2C 总线更短的最大允许上升时间。对于负载最大是200pF 的总线,每条总线的上拉器件可以是一个电阻,对于负载在200pF~400pF 之间的总线,上拉器件可以是一个电流源(最大值3mA )或者是一个开关电阻电路。
(2)、高速模式
高速模式(Hs 模式)器件对I2C 总线的传输速度有巨大的突破。Hs 模式器件可以在高达3.4Mbit/s 的位速率下传输信息,而且保持完全向下兼容快速模式或标准模式(F/S 模式)器件,它们可以在一个速度混合的总线系统中双向通讯。
Hs 模式传输除了不执行仲裁和时钟同步外,与F/S 模式系统有相同的串行总线协议和数据格式。
高速模式下I2C 总线规范如下:
A、Hs 模式主机器件有一个SDAH 信号的开漏输出缓冲器和一个在SCLH 输出的开漏极下拉和电流源上拉电路。这个电流源电路缩短了SCLH 信号的上升时间,任何时候在Hs 模式,只有一个主机的电流源有效;
B、在多主机系统的Hs 模式中,不执行仲裁和时钟同步,以加速位处理能力。仲裁过程一般在前面用F/S 模式传输主机码后结束;
C、Hs 模式主机器件以高电平和低电平是1:2 的比率产生一个串行时钟信号。解除了建立和保持时间的时序要求;
D、可以选择Hs 模式器件有内建的电桥。在Hs 模式传输中,Hs 模式器件的高速数据(SDAH)和高速串行时钟(SCLH )线通过这个电桥与F/S 模式器件的SDA 和SCL 线分隔开来。减轻了SDAH 和SCLH 线的电容负载,使上升和下降时间更快;
E、Hs 模式从机器件与F/S 从机器件的唯一差别是它们工作的速度。Hs 模式从机在SCLH 和SDAH输出有开漏输出的缓冲器。SCLH 管脚可选的下拉晶体管可以用于拉长SCLH 信号的低电平,但只允许在Hs 模式传输的响应位后进行;
F、Hs 模式器件的输出可以抑制毛刺,而且SDAH 和SCLH 输出有一个施密特触发器;
G、Hs 模式器件的输出缓冲器对SDAH 和SCLH 信号的下降沿有斜率控制功能。
1.6、I2C线路图
2、I2C 通讯详解
I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、 CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。下面我们分别对 I2C 协议的物理层及协议层进行讲解。
2.1、I2C 物理层
I2C 通讯设备之间的常用连接方式见下图:
它的物理层有如下特点:
(1)、它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2)、一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3)、每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
(4)、总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
(5)、 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
(6)、具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
(7)、连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。
2.2、 协议层
I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。
(1)、I2C 基本读写过程
先看看 I2C 通讯过程的基本结构,它的通讯过程见下图:
I2C 通讯复合格式
这些图表示的是主机和从机通讯时, SDA 线的数据包序列。
A、S 表示由主机的 I2C 接口产生的传输起始信号(S),这时连接到 I2C 总线上的所有从机都会接收到这个信号。
B、起始信号产生后,所有从机就开始等待主机紧接下来广播 的从机地址信号(SLAVE_ADDRESS)。 在 I2C 总线上,每个设备的地址都是唯一的, 当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据 I2C 协议,这个从机地址可以是 7 位或 10 位。
C、在地址位之后,是传输方向的选择位,该位为 0(W) 时,表示后面的数据传输方向是由主机传输至从机,即主机往从机中写数据。该位为 1(R) 时,则相反,即主机由从机读数据。
D、从机接收到匹配的地址后,从机会返回一个应答(ACK)或非应答(NACK)信号,只有接收到应答信号后,主机才能继续发送或接收数据。
E、若配置的方向传输位为“写数据”方向, 广播完地址,接收到应答信号后, 主机开始正式向从机传输数据(DATA),数据包的大小为 8 位,主机每发送完一个字节数据,都要等待从机的应答信号(ACK),重复这个过程,可以向从机传输 N 个数据,这个 N 没有大小限制。当数据传输结束时,主机向从机发送一个停止传输信号(P),表示不再传输数据。
F、若配置的方向传输位为“读数据”方向, 广播完地址,接收到应答信号后, 从机开始向主机返回数据(DATA),数据包大小也为 8 位,从机每发送完一个数据,都会等待主机的应答信号(ACK),重复这个过程,可以返回 N 个数据,这个 N 也没有大小限制。当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。
G、除了基本的读写, I2C 通讯更常用的是复合格式,该传输过程有两次起始信号(S)。一般在第一次传输中,主机通过 SLAVE_ADDRESS 寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与 SLAVE_ADDRESS 的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。
(2)、通讯的起始和停止信号
前文中提到的起始(S)和停止(P)信号是两种特殊的状态,见下图。当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。
I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生(意味着从设备不可以主动通信?所有的通信都是主设备发起的,主设备可以发出询问的command,然后等待从设备的通信)。
起始和结束信号产生条件:总线在空闲状态时,SCL和SDA都保持着高电平。当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个停止条件。
在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。起始和结束如图所示:
在了解起始条件和停止条件后,我们再来看看在这个过程中数据的传输是如何进行的。前面我们已经提到过,数据传输以字节为单位。主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位, 此时才认为一个字节真正的被传输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个否定应答位。数据传输的过程如图所示:
在前面我们还提到过,I2C总线上的每一个设备都对应一个唯一的地址,主从设备之间的数据传输是建立在地址的基础上,也就是说,主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。向指定设备发送数据的格式如图所示:(每一最小包数据由9bit组成,8bit内容+1bit ACK, 如果是地址数据,则8bit包含1bit方向)
(3)、数据有效性
I2C 使用 SDA 信号线来传输数据,使用 SCL 信号线进行数据同步。见图 23-6。 SDA数据线在 SCL 的每个时钟周期传输一位数据。传输时, SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低电平时, SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。
每次数据传输都以字节为单位,每次传输的字节数不受限制。
(4)、地址及数据方向
I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。 I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),第 8 位或第 11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。见下图:
读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时, SDA 由主机控制, 从机接收信号。
(5)、响应
I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。见下图:
传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时, SDA 由主机控制, 从机接收信号。
3、I2C总线操作
对I2C总线的操作实际就是主从设备之间的读写操作。大致可分为以下三种操作情况:
(1)、主设备往从设备中写数据。数据传输格式如下:
(2)、主设备从从设备中读数据。数据传输格式如下:
(3)、主设备往从设备中写数据,然后重启起始条件,紧接着从从设备中读取数据;或者是主设备从从设备中读数据,然后重启起始条件,紧接着主设备往从设备中写数据。数据传输格式如下:
第三种操作在单个主设备系统中,重复的开启起始条件机制要比用STOP终止传输后又再次开启总线更有效率。
4、软件I2C程序
/** * @brief 配置I2C总线的GPIO * @param 无 * @retval 无 */ static void I2C_1_GPIO_Config(void) { I2C_1_GPIO_ClockCmd(I2C_1_GPIO_CLK, ENABLE); //打开GPIO时钟 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Pin = I2C_1_SCL_GPIO_PIN ; GPIO_Init(I2C_1_SCL_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = I2C_1_SDA_GPIO_PIN; GPIO_Init(I2C_1_SDA_GPIO_PORT, &GPIO_InitStructure); } /** * @brief I2C初始化 * @param 无 * @retval 无 */ void I2C_1_Config_Init(void) { I2C_1_GPIO_Config(); I2C_1_Stop(); //给一个停止信号, 复位I2C总线上的所有设备到待机模式 } /** * @brief 配置I2C的SDA线为输入 * @param 无 * @retval 无 */ static void I2C_1_SDA_INTPUT(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; //输入 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //浮空输入 GPIO_InitStructure.GPIO_Pin = I2C_1_SDA_GPIO_PIN; GPIO_Init(I2C_1_SDA_GPIO_PORT, &GPIO_InitStructure); } /** * @brief 配置I2C的SDA线为输出 * @param 无 * @retval 无 */ static void I2C_1_SDA_OUTPUT(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //开漏输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Pin = I2C_1_SDA_GPIO_PIN; GPIO_Init(I2C_1_SDA_GPIO_PORT, &GPIO_InitStructure); } /** * @brief I2C总线位延迟,最快400KHz * @param 无 * @retval 无 */ static void I2C_1_Delay(void) { for(uint8_t i = 0; i < 30; i++); //上面的时间是通过逻辑分析仪测试得到的 //工作条件:CPU主频72MHz ,MDK编译环境,1级优化 //循环次数为10时,SCL频率 = 205KHz //循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us //循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us //工作条件:CPU主频180MHz ,MDK编译环境,1级优化 //循环次数为20~250时都能通讯正常 } /** * @brief CPU发起I2C总线启动信号 * @param 无 * @retval 无 */ void I2C_1_Start(void) { //当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 I2C_1_SDA_OUTPUT(); I2C_1_SDA_H(); I2C_1_SCL_H(); I2C_1_Delay(); I2C_1_SDA_L(); I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); } /** * @brief CPU发起I2C总线停止信号 * @param 无 * @retval 无 */ void I2C_1_Stop(void) { //当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); I2C_1_SDA_L(); I2C_1_Delay(); I2C_1_SCL_H(); I2C_1_Delay(); I2C_1_SDA_H(); I2C_1_Delay(); } /** * @brief CPU产生一个时钟,并读取器件的ACK应答信号 * @param 无 * @retval 返回0表示正确应答,1表示无器件响应 */ uint8_t I2C_1_Wait_Ack(void) { uint8_t re; //应答信号 0:应答 1:非应答 I2C_1_SCL_L(); I2C_1_SDA_H(); //CPU释放SDA总线 I2C_1_Delay(); I2C_1_SCL_H(); //CPU驱动SCL = 1, 此时器件会返回ACK应答 I2C_1_Delay(); I2C_1_SDA_INTPUT(); while(I2C_1_SDA_READ()) { re++; if(re>250) { I2C_1_Stop(); return 1; } } I2C_1_SCL_L(); I2C_1_Delay(); return 0; } /** * @brief CPU产生一个ACK应答信号,SDA低电平 * @param 无 * @retval 无 */ void I2C_1_Ack(void) { I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); I2C_1_SDA_L(); //CPU驱动SDA = 0 I2C_1_Delay(); I2C_1_SCL_H(); //CPU产生1个时钟 I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); I2C_1_SDA_H(); //CPU释放SDA总线 I2C_1_Delay(); } /** * @brief CPU产生1个NACK非应答信号,SDA高电平 * @param 无 * @retval 无 */ void I2C_1_NAck(void) { I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); I2C_1_SDA_H(); //CPU驱动SDA = 1 I2C_1_Delay(); I2C_1_SCL_H(); //CPU产生1个时钟 I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); I2C_1_SDA_H(); //CPU释放SDA总线 I2C_1_Delay(); } /** * @brief CPU向I2C总线设备写8bit数据 * @param Data :要发送的数据 * @retval 无 */ void I2C_1_SendData(uint8_t Data) { uint8_t temp = 0; I2C_1_SDA_OUTPUT(); I2C_1_SCL_L(); for(uint8_t i = 0; i < 8; i++) //先发送字节的高位bit7 { temp = (Data & 0x80) >> 7; if(temp == 0x01) //判断当前位是否为高电平 { I2C_1_SDA_H(); } else { I2C_1_SDA_L(); } I2C_1_Delay(); I2C_1_SCL_H(); //SCL处于高电平时SDA数据有效 I2C_1_Delay(); I2C_1_SCL_L(); //SCL处于低电平时SDA进行高低电平切换 Data <<= 1; if (i == 7) //最后一位数据发送完 { I2C_1_SDA_H(); //CPU释放SDA总线 } } } /** * @brief CPU从I2C总线设备读取8bit数据 * @param Ack:0:产生应答信号 1:产生非应答信号 * @retval 读到的数据 */ uint8_t I2C_1_ReceiveData(void) { uint8_t Data=0; I2C_1_SDA_INTPUT(); I2C_1_SCL_L(); for(uint8_t i = 0; i < 8; i++) //读到第1个bit为数据的bit7 { Data = Data << 1; //移位放在读SDA数据前面,放后面的话读完数据后会向左移一位,数据出错 I2C_1_SCL_H(); //SCL处于高电平时读取SDA数据 I2C_1_Delay(); if(I2C_1_SDA_READ()) //读SDA口的状态,读数据 { Data++; } I2C_1_Delay(); I2C_1_SCL_L(); I2C_1_Delay(); } return Data; } /** * @brief 检测I2C总线设备,CPU向I2C发送设备地址,然后读取设备应答来判断该设备是否存在 * @param Address:设备的I2C总线地址 * @retval 返回值 0 表示有应答, 返回1表示未探测到 */ uint8_t I2C_1_CheckDevice(uint8_t Address) { uint8_t Ack; I2C_1_Start(); //发送启动信号 I2C_1_SendData(Address | I2C_1_WR); //发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 Ack = I2C_1_Wait_Ack(); //检测设备的ACK应答 ,0表示应答,1表示非应答 I2C_1_Stop(); //发送停止信号 return Ack; }