IIC (Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送, 高速 IIC 总线一般可达 400kbps 以上。
在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;STM32 标准库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英文来交流。
下面我们分别对 I2C 协议的物理层及协议层进行讲解

它的物理层有如下特点:
I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
开始信号: SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。
结束信号: SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。 CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号, CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。 IIC 总线时序图如下图所示:

目前大部分 MCU 都带有 IIC 总线接口, STM32 也不例外。但是这里我们依次使用 STM32 的硬件 IIC和软件IIC 来讲解,
我们先看看I2C通信过程的基本结构,它的通讯过程:


这些图表示的是主机和从机通讯时, SDA 线的数据包序列。
其中 S 表示由主机的 I2C 接口产生的传输起始信号 (S),这时连接到 I2C 总线上的所有从机都会接收到这个信号。
起始信号产生后,所有从机就开始等待主机紧接下来广播的从机地址信号。在 I2C 总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据 I2C 协议,这个从机地址可以是7 位或 10 位。
在地址位之后,是传输方向的选择位,该位为 0 时,表示后面的数据传输方向是由主机传输至从 机,即主机向从机写数据。该位为 1 时,则相反,即主机由从机读数据。
从机接收到匹配的地址后,主机或从机会返回一个应答 (ACK) 或非应答 (NACK) 信号,只有接收到应答信号后,主机才能继续发送或接收数据。
写数据
若配置的方向传输位为“写数据”方向,即第一幅图的情况,广播完地址,接收到应答信号后,主机开始正式向从机传输数据 (DATA),数据包的大小为 8 位,主机每发送完一个字节数据,都要等待从机的应答信号 (ACK),重复这个过程,可以向从机传输 N 个数据,这个 N 没有大小限制。当数据传输结束时,主机向从机发送一个停止传输信号 §,表示不再传输数据。
读数据
若配置的方向传输位为“读数据”方向,即第二幅图的情况,广播完地址,接收到应答信号后,从机开始向主机返回数据 (DATA),数据包大小也为 8 位,从机每发送完一个数据,都会等待主机的应答信号 (ACK),重复这个过程,可以返回 N 个数据,这个 N 也没有大小限制。当主机希停止接收数据时,就向从机返回一个非应答信号 (NACK),则从机自动停止数据传输。
读和写数据
除了基本的读写, I2C 通讯更常用的是复合格式,即第三幅图的情况,该传输过程有两次起始信号 (S)。一般在第一次传输中,主机通过 从机地址信号寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址 (注意区分它与从机地址信号的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。
以上通讯流程中包含的各个信号分解如下:
之前提到的起始 (S) 和停止 § 信号是两种特殊的状态 。当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。

I2C 使用 SDA 信号线来传输数据,使用 SCL 信号线进行数据同步 。 SDA 数据线在 SCL 的每个时钟周期传输一位数据。传输时, SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL 为低电平时, SDA的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备

每次数据传输都以字节为单位,每次传输的字节数不受限制。
I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址
(SLAVE_ADDRESS) 来查找从机。 I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位 (R/),第 8位或第 11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写
数据。

读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,
写数据方向时, SDA 由主机控制,从机接收信号。
I2C 的数据和地址传输都带响应。响应包括“应答 (ACK)”和“非应答 (NACK)”两种信号。作为数据接收端时,当设备 (无论主从机) 接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答 (ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答 (NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。

传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制SDA,若 SDA 为高电平,表示非应答信号 (NACK),低电平表示应答信号 (ACK)。
如果我们直接控制 STM32 的两个 GPIO 引脚,分别用作 SCL 及 SDA,按照上述信号的时序要求,可以像控制 LED 灯那样控制引脚的输出 (若是接收数据时则读取 SDA 电平),就可以实现 I2C 通讯。同样,假如我们按照 USART 的要求去控制引脚,也能实现 USART 通讯。所以只要遵守协议,就是标准的通讯,不管如何实现它,都能按通讯标准交互。
由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。
模拟协议简单的说就是自定义GPIO引脚状态,通过调电平模仿时序。
相对地,还有“硬件协议”方式, STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来, CPU 只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理 I2C 协议的方式减轻了CPU 的工作,且使软件设计更加简单。
STM32 的 I2C 外设可用作通讯的主机及从机,支持 100Kbit/s 和 400Kbit/s 的速率,支持 7 位、 10位设备地址,支持 DMA 数据传输,并具有数据校验功能。

I2C 的所有硬件架构都是根据图中左侧 SCL 线和 SDA 线展开的 (其中的 SMBA 线用于 SMBUS 的警告信号, I2C 通讯没有使用)。 STM32 芯片有多个 I2C 外设,它们的 I2C 通讯信号引出到不同的GPIO 引脚上,使用时必须配置到这些指定的引脚。关于 GPIO 引脚的复用功能,以规格书为准。
| 引脚 | I2C1 | I2C2 |
|---|---|---|
| SCL | PB6 / PB8(重映射) | PB10 |
| SDA | PB7 / PB9(重映射) | PB11 |
SCL 线的时钟信号,由 I2C 接口根据时钟控制寄存器 (CCR) 控制,控制的参数主要为时钟频率。
配置 I2C 的 CCR 寄存器可修改通讯速率相关的参数:
计算结果得出 CCR 为 30,向该寄存器位写入此值则可以控制 IIC 的通讯速率为 400KHz,其实即使配置出来的 SCL 时钟不完全等于标准的 400KHz, IIC 通讯的正确性也不会受到影响,因为所有数据通讯都是由 SCL 协调的,只要它的时钟频率不远高于标准即可。
I2C 的 SDA 信号主要连接到数据移位寄存器上,数据移位寄存器的数据来源及目标是数据寄存器 (DR)、地址寄存器 (OAR)、 PEC 寄存器以及 SDA 数据线。
当向外发送数据的时候,数据移位寄存器以“数据寄存器”为数据源,把数据一位一位地通过 SDA 信号线发送出去;
当从外部接收数据的时候,数据移位寄存器把 SDA 信号线采样到的数据一位一位地存储到“数据寄存器”中。
若使能了数据校验,接收到的数据会经过 PCE 计算器运算,运算结果存储在“PEC 寄存器”中。
当 STM32 的 I2C 工作在从机模式的时候,接收到设备地址信号时,数据移位寄存器会把接收到的地址与 STM32 的自身的I2C 地址寄存器的值作比较,以便响应主机的寻址。 STM32 的自身 I2C 地址可通过修改“自身地址寄存器”修改,支持同时使用两个 I2C 设备地址,两个地址分别存储在 OAR1 和 OAR2 中。
整体控制逻辑负责协调整个 I2C 外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR1/CR2)”的参数而改变。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器 (SR1 和 SR2)”,我们只要读取这些寄存器相关的寄存器位,就可以了解 I2C 的工作状态。除此之外,控制逻辑还根据要求,负责控制产生 I2C 中断信号、 DMA 请求及各种 I2C 的通讯信号(起始、停止、响应信号等)
使用 I2C 外设通讯时,在通讯的不同阶段它会对“状态寄存器 (SR1 及 SR2)”的不同数据位写入参数,我们通过读取这些寄存器标志来了解通讯状态。
主发送器流程,即作为 I2C 通讯的主机端时,向外发送数据时的过程。

主发送器发送流程及事件说明如下:
“EV5”,并会对 SR1 寄存器的“SB”位置 1,表示起始信号已经发送;“EV6”及“EV8”,这时SR1 寄存器的“ADDR”位及“TXE”位被置1,ADDR 为 1 表示地址已经发送, TXE 为 1 表示数据寄存器为空;正常执行并对 ADDR 位清零后,我们往 I2C 的“数据寄存器 DR”写入要发送的数据,这时 TXE 位会被重置 0,表示数据寄存器非空, I2C 外设通过 SDA 信号线一位位把数据发送出去后,又会产生“EV8”事件,即 TXE 位被置 1,重复这个过程,就可以发送多个字节数据了; EV8_2 事件,SR1 的 TXE 位及 BTF 位都被置 1,表示通讯结束。假如我们使能了 I2C 中断,以上所有事件产生时,都会产生 I2C 中断信号,进入同一个中断服务函数,到 I2C 中断服务程序后,再通过检查寄存器位来判断是哪一个事件。主接收器过程,即作为 I2C 通讯的主机端时,从外部接收数据的过程,

主接收器接收流程及事件说明如下:
“EV5”,并会对 SR1 寄存器的“SB”位置 1,表示起始信号已经发送;“EV6”这时 SR1 寄存器的“ADDR”位被置 1,表示地址已经发送。从机端接收到地址后,开始向主机端发送数据。当主机接收到这些数据后,会产生“EV7”事件, SR1 寄存器的 RXNE 被置 1,表示接收数据寄存器非空,我们读取该寄存器后,可对数据寄存器清空,以便接收下一次数据。此时我们可以控制 I2C 发送应答信号 (ACK) 或非应答信号(NACK),若应答,则重复以上步骤接收数据,若非应答,则停止传输;跟其它外设一样, STM32 标准库提供了 I2C 初始化结构体及初始化函数来配置 I2C 外设。初始化结构体及函数定义在库文件“stm32f10x_i2c.h”及“stm32f10x_i2c.c”中。
typedef struct {
uint32_t I2C_ClockSpeed; /*!< 设置 SCL 时钟频率,此值要低于 400000*/
uint16_t I2C_Mode; /*!< 指定工作模式,可选 I2C 模式及 SMBUS 模式 */
uint16_t I2C_DutyCycle; /* 指定时钟占空比,可选 low/high = 2:1 及 16:9 模式
*/
uint16_t I2C_OwnAddress1; /*!< 指定自身的 I2C 设备地址 */
uint16_t I2C_Ack; /*!< 使能或关闭响应 (一般都要使能) */
uint16_t I2C_AcknowledgedAddress; /*!< 指定地址的长度,可为 7 位及 10 位 */
} I2C_InitTypeDef;
这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 标准库中定义的宏:
I2C 的传输速率,在调用初始化函数时,函数会根据我们输入的数值经过运算后把时钟因子写入到 I2C 的时钟控制寄存器 CCR。而我们写入的这个参数值不得高于 400KHz。实际上由于 CCR 寄存器不能写入小数类型的时钟因子,影响到 SCL 的实际频率可能会低于本成员设置的参数值,这时除了通讯稍慢一点以外,不会对 I2C 的标准通讯造成其它影响。直接设置 I2C_Mode_I2C 即可。差别并不大,一般要求都不会如此严格,这里随便选就可以。STM32 的 I2C 设备自己的地址,每个连接到 I2C 总线上的设备都要有一个自己的地址,作为主机也不例外。地址可设置为 7 位或 10 位 (受下面 I2C_AcknowledgeAddress 成员决定),只要该地址是 I2C 总线上唯一的即可。STM32 的 I2C 外设可同时使用两个地址,即同时对两个地址作出响应,这个结构成员I2C_OwnAddress1 配置的是默认的、 OAR1 寄存器存储的地址,若需要设置第二个地址寄存器OAR2,可使用I2C_OwnAddress2Config 函数来配置, OAR2 不支持 10 位地址,只有 7 位。设置为使能则可以发送响应信号。本实验配置为允许应答 (I2C_Ack_Enable) ,这是绝大多数遵循 I2C 标准的设备的通讯要求,改为禁止应答(I2C_Ack_Disable) 往往会导致通讯错误。寻址模式是 7 位还是 10 位地址。这需要根据实际连接到 I2C 总线上设备的地址进行选择,这个成员的配置也影响到 I2C_OwnAddress1 成员,只有这里设置成 10 位模式时,I2C_OwnAddress1 才支持 10 位地址。配置完这些结构体成员值,调用库函数 I2C_Init 即可把结构体的配置写入到寄存器中。配置通讯使用的目标引脚为开漏模式;
使能 I2C 外设的时钟;
配置 I2C 外设的模式、地址、速率等参数并使能 I2C 外设;
编写基本 I2C 按字节收发的函数;
#include "iic.h"
#include "stm32f10x_i2c.h"
#include
#include "stdio.h"
static __IO uint32_t I2CTimeout = I2CT_LONG_TIMEOUT;
static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode);
//iic初始化
void I2C_Configuration(void)
{
I2C_InitTypeDef I2C_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
I2C_APBxClock_FUN(I2C_CLK,ENABLE);
I2C_GPIO_APBxClock_FUN(I2C_GPIO_CLK,ENABLE);
/*STM32F103C8T6芯片的硬件I2C: PB6 -- SCL; PB7 -- SDA */
GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//I2C必须开漏输出
GPIO_Init(I2C_PORT, &GPIO_InitStructure);
I2C_DeInit(I2Cx);//使用I2C1
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = I2Cx_OWN_ADDRESS;//主机的I2C地址,随便写的
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;//400K
I2C_Cmd(I2Cx, ENABLE);
I2C_Init(I2Cx, &I2C_InitStructure);
}
void I2C_WriteByte(uint8_t addr,uint8_t data)
{
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1, ENABLE);//开启I2C1
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));/*EV5,主模式*/
I2C_Send7bitAddress(I2C1, I2C_ADDRESS, I2C_Direction_Transmitter);//器件地址 -- 默认0x78
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, addr);//寄存器地址
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_SendData(I2C1, data);//发送数据
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C1, ENABLE);//关闭I2C1总线
}
/**
* @brief 写一个字节到I2C设备中
* @param
* @arg pBuffer:缓冲区指针
* @arg Cmd_or_Data:写地址(数据、命令)
* @retval 正常返回1,异常返回0
*/
uint8_t Write_Byte(u8 Cmd_or_Data,u8 pBuffer)
{
//总线忙标志位
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
/* 发送参数条件 */
I2C_GenerateSTART(I2Cx, ENABLE);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* EV5,主模式*/
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);
}
/* 器件地址 -- 默认0x78*/
I2C_Send7bitAddress(I2Cx, I2C_ADDRESS, I2C_Direction_Transmitter);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 测试EV6并清除它*/
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if((I2CTimeout--) == 0)
return I2C_TIMEOUT_UserCallback(1);
}
/* 寄存器地址*/
I2C_SendData(I2Cx, Cmd_or_Data);
//发送命令还是发送数据,命令0x00,数据0x40
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* 测试EV8并清除它 */
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
}
/* 发送数据*/
I2C_SendData(I2Cx, pBuffer);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/*测试EV8并清除它*/
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
}
/* 关闭I2C1总线 */
I2C_GenerateSTOP(I2Cx, ENABLE);
return 1; //正常返回1
}
void WriteCmd(unsigned char I2C_Command)//写命令
{
Write_Byte(0x00, I2C_Command);
}
void WriteDat(unsigned char I2C_Data)//写数据
{
Write_Byte(0x40, I2C_Data);
}
static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode)
{
ERROR("I2C 等待超时!errorCode = %d",errorCode);
return 0;
}
#ifndef _IIC_H
#define _IIC_H
#include "stm32f10x.h"
/**************************I2C参数定义,I2C1或I2C2********************************/
#define I2Cx I2C1
#define I2C_APBxClock_FUN RCC_APB1PeriphClockCmd
#define I2C_CLK RCC_APB1Periph_I2C1
#define I2C_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define I2C_GPIO_CLK RCC_APB2Periph_GPIOB
#define I2C_PORT GPIOB
#define I2C_SCL_PIN GPIO_Pin_6
#define I2C_SDA_PIN GPIO_Pin_7
/* STM32 I2C 快速模式 */
#define I2C_Speed 400000 //*
/* 这个地址只要与STM32外挂的I2C器件地址不一样即可 */
#define I2Cx_OWN_ADDRESS 0X30
#define I2C_ADDRESS 0X78
/*等待超时时间*/
#define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))
/*信息输出*/
#define DEBUG_ON 1
#define INFO(fmt,arg...) printf("<<--INFO->> "fmt"\n",##arg)
#define ERROR(fmt,arg...) printf("<<--ERROR->> "fmt"\n",##arg)
#define DEBUG(fmt,arg...) do{\
if(DEBUG_ON)\
printf("<<--DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
void I2C_Configuration(void);
uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode);
uint8_t Write_Byte(u8 Cmd_or_Data,u8 pBuffer);
void WriteCmd(unsigned char I2C_Command);//写命令
void WriteDat(unsigned char I2C_Data);//写数据
#endif
切记尽量避免使用PB3、PB4,具体看stm32f103c8t6使用PB3和PB4做普通GPIO使用时发现异常
#include "iic.h"
#include "delay.h"
//初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); /* 打开GPIO时钟 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;/* 开漏输出 */
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
IIC_SCL=1;
IIC_SDA=1;
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
Delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
Delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
Delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
Delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;
Delay_us(1);
IIC_SCL=1;
Delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
Delay_us(2);
IIC_SCL=1;
Delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
Delay_us(2);
IIC_SCL=1;
Delay_us(2);
IIC_SCL=0;
}
//IIC发送一个字节
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0; t<8; t++)
{
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
Delay_us(2);
IIC_SCL=1;
Delay_us(2);
IIC_SCL=0;
Delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (ack)
IIC_Ack();//发送ACK
else
IIC_NAck(); //发送NACK
return receive;
}
//IIC连续写
void IIC_WriteBytes(u8 WriteAddr,u8* data,u8 dataLength)
{
u8 i;
IIC_Start();
IIC_Send_Byte(WriteAddr); //发送写命令
IIC_Wait_Ack();
for(i=0; i<dataLength; i++)
{
IIC_Send_Byte(data[i]);
IIC_Wait_Ack();
}
IIC_Stop();//产生一个停止条件
Delay_ms(10);
}
//IIC连续读
void IIC_ReadBytes(u8 deviceAddr, u8 writeAddr,u8* data,u8 dataLength)
{
u8 i;
IIC_Start();
IIC_Send_Byte(deviceAddr); //发送读命令
IIC_Wait_Ack();
IIC_Send_Byte(writeAddr);
IIC_Wait_Ack();
IIC_Send_Byte(deviceAddr|0X01);//进入接收模式
IIC_Wait_Ack();
for(i=0; i<dataLength-1; i++)
{
data[i] = IIC_Read_Byte(1); //发送应答信号
}
data[dataLength-1] = IIC_Read_Byte(0);//停止读发送不应答
IIC_Stop();//产生一个停止条件
Delay_ms(10);
}
void IIC_Read_One_Byte(u8 daddr,u8 addr,u8* data)
{
IIC_Start();
IIC_Send_Byte(daddr); //发送读命令
IIC_Wait_Ack();
IIC_Send_Byte(addr);//发送地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(daddr|0X01);//进入接收模式
IIC_Wait_Ack();
*data = IIC_Read_Byte(0);
IIC_Stop();//产生一个停止条件
}
void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data)
{
IIC_Start();
IIC_Send_Byte(daddr); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(addr);//发送地址
IIC_Wait_Ack();
IIC_Send_Byte(data); //发送字节
IIC_Wait_Ack();
IIC_Stop();//产生一个停止条件
Delay_ms(10);
}
#ifndef _IIC_H
#define _IIC_H
#include "sys.h"
//IO方向设置
/*
CRL(引脚的0-7脚) CRH(引脚的8-16脚)
0X0FFFFFFF=0000 1111 1111 1111 1111 1111 1111 1111 即为第7引脚-〉PB7
(u32)8 为控制模式 ,转为二进制:1000意思就是Input模式, Input pull-up 即输出模式!!!(i2c一般固定是8)
<<28 左移28位 即将上面 0X0FFFFFFF=0000 1111 1111 1111 1111 1111 1111 1111 左移28 直到0000 位.
(u32)3 3换成2进制是0011, 结合上面的就是00就是outpu的push-pull, 11表示速度是50MHz
*/
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//IO操作函数
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
void IIC_Read_One_Byte(u8 daddr,u8 addr,u8* data);
void IIC_WriteBytes(u8 WriteAddr,u8* data,u8 dataLength);
void IIC_ReadBytes(u8 deviceAddr, u8 writeAddr,u8* data,u8 dataLength);
#endif
链接:https://pan.baidu.com/s/1uxNszZKhz7FEMclhsbfUbw 提取码:0000