一、IIC总线概述
1、IIC总线介绍
I2C (Inter-Integrated Circuit)总线产生于在80年代, 由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备, 最初为音频和视频设备开发。I2C总线两线制包括:串行数据SDA(Serial Data)、串行时钟SCL(Serial Clock)。时钟线必须由主机(通常为微控制器)控制,主机产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。I2C总线上有主机(MCU)和从机(片外外设,如AT24C02)之分,可以有多个主机和多个从机。从机永远不会主动给主机发送数据。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。
USART : 异步串行全双工
SPI: 同步串行全双工
IIC:同步串行半双工
挂载在同一根IIC总线上可以有多个设备,有主机和从机之分
2、IIC总线物理拓扑图
3、IIC总线主从设备通信
SPI如何解决从多个从机中选择一个通信:片选线
IIC如何解决从多个从机中选择一个通信: 器件地址
首先向外界发出一个器件地址
所有挂载在这个总线上的设备都可以接收到这个地址
设备获取到这个器件地址后和本身的地址作比较
匹配的设备就会接下来接收主机发出的数据
其他设备重新处于休眠状态
器件地址:一般是7位的硬件地址 + 1位的读写位
IIC通信规定一次发送的数据是8位的
针对主机而言:
写(向外界发送数据):0
读(从外界接收数据):1
4、IIC总线与UART比较
通讯协议 | UART | IIC | SPI |
特征 | 异步串行全双工 | 同步串行半双工 | 同步串行全双工 |
接口 | TX、RX | SDA、SCL | MOSI、MISO、CS、SCK |
速度 | 波特率很多 | 100Khz、400Khz、3.5Mhz | Mhz级以上 |
数据帧格式 | 起始位+数据位+校验位+停止位 | 起始条件+位传输+应答+停止条件 | 四种MODE0-3 |
主从设备通讯 | 没有主从之分 | 有主从之分 | 有主从之分 |
总线结构 | 一对一 | 一对多 | 一对多 |
发送数据顺序 | 先发低位再发高位 | 先发高位再发低位 | 先发高位再发低位 SPI控制器可以选择先低后高还是先高后低 |
二、IIC总线数据
1、IIC数据帧的格式
起始条件(告诉接收方要开始发送数据了):在时钟线高电平期间,数据线产生下降沿
数据的传输:时钟线低电平期间发送数据,高电平期间接收数据,需要给一定的时间让数据可以发送和接收
高位先发,8位数据的发送
应答位:1个位的数据,代表当前有没有受到数据,是接收方回发给发送方的
(0)应答:代表接收到数据并且可以继续发送
(1)非应答:代表当前不能再接受数据了
停止条件(代表一次通信结束):在时钟线高电平期间,数据线产生上升沿
2、标准IIC时序(100khz)
1、起始条件
SDA = 1;
SCL = 1;
Delay_us(4);//起始条件的建立时间
SDA = 0;
Delay_us(4);//起始条件的保持时间
SCL = 0;//一个完整的时钟周期
2、停止条件
SDA = 0;
SCL = 1;
Delay_us(4);//停止条件的建立时间
SDA = 1;
Delay_us(4);//停止和启动条件之间的总线空闲时间
3、位传输
高位先发,高电平期间接收数据,低电平期间发送数据主机发送数据(发送的是写方向):
for( i = 0 ; i < 8; i++)
{
????????SCL = 0;
????????SDA = 0/1;
????????Delay_us(4);//主机发送数据的时间
????????SCL = 1;
????????Delay_us(4);//从机接收数据的时间
}主机接收数据:(发送的是读方向)
for( i = 0 ; i < 8; i++)
{
????????SCL = 0;
????????Delay_us(4);//从机发送数据的时间
????????SCL = 1;
????????if(SDA)
????????{
????????????????Data |= 1;
????????}
????????Delay_us(4);//主机接受数据的时间
}
4、应答
主机发送一个应答位:
? ? SCL = 0;
? ? SDA = 0/1;
? ? Delay_us(4);//主机发送数据的时间
? ? SCL = 1;
? ? Delay_us(4);//从机接收数据的时间
? ? SCL = 0;
? ? Delay_us(4);//保证周期的完整性
主机接收一个应答位:
? ? SCL = 0;
? ? Delay_us(4);//从机发送数据的时间
? ? SCL = 1;
? ? if(SDA)
? ? {
? ? ? ? Data = 1;
? ? }
? ? Delay_us(4);//主机接受数据的时间
? ? SCL = 0;
? ? Delay_us(4);//保证周期的完整性
?
三、SHT20温湿度芯片
1、硬件:
相对湿度的误差在±3
湿度范围:0 - 100%
温度的误差在±0.3摄氏度
温度范围:-40 - 125摄氏度
管脚说明:
SCL: IIC通信的时钟线
SDA: IIC通信的数据线
SHT20采用IIC的400kHZ的通信速率
测量温度的命令:0xf3
测量湿度的命令:0xf5
器件地址 + 写方向:0x80
器件地址 + 写方向:0x81
2、软件:
通信步骤:
1. 传感器上电,最多等待15ms的时间再去发送相应的命令
2. 发送器件地址 + 方向位以及相应的命令去触发测量
3、时序
起始条件--->发送器件地址 + 写方向(0x80) --> 如果通信正常则回应答位 --> 发送测量温湿度数据的命令-->
起始条件--->不断发送发送器件地址 + 读方向(0x81)直到回了一个应答才停止发送器件地址 + 读方向(0x81)-->如果回了应答就可以接收从机发过来的数据(16位构成的),需要接收两次才完整,先发送过来的数据是16位数据中的高八位----->接受完16位的数据主机发送非应答告诉SHT20不用再回发数据---->停止条件
注意:接收回来的16位数据中最低两位要清零
起始条件 ---->发送器件地址 + 写方向(0x80)---->等待sht20应答 ---->发送软复位指令(0xfe)等待sht20应答 ---->停止条件
写用户寄存器:
起始条件 ---->发送器件地址 + 写方向(0x80)---->等待sht20应答 ---->发送0xe6告诉SHT20要往用户寄存器里写值---->?等待sht20应答 ---->发送需要写到里面的值---->等待sht20应答 ----->停止条件
RH? = -6 + 125 * Srh / 65536
T = -46.85 + 175.72 * St / 65536
初始化GPIO口
PB8 -- SCL -- 推挽输出
PB9 -- SDA
输入输出模式切换
SDA线既可以接收数据也可以发送数据
输入模式的时候不能输出数据
输出模式的时候可以读取数据
开漏输出
开漏输出:只能输出低电平
如果输出高电平就是断开输出电路
#include "iic.h"
/****************************
函数功能:初始化IIC总线的IO口
函数形参:void
函数返回值:void
函数说明:
PB8 -- SCL -- 推挽输出
PB9 -- SDA
1.输入输出模式切换
SDA线既可以接收数据也可以发送数据
输入模式的时候不能输出数据
输出模式的时候可以读取数据
2.开漏输出
开漏输出:只能输出低电平
如果输出高电平就是断开输出电路
****************************/
void Iic_PortInit(void)
{
GPIO_InitTypeDef GPIO_InitStruct;//定义了一个结构体变量
//1. 打开GPIOB的时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
//2. 配置GPIO口功能
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;//配置输出模式
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//配置为推挽
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;//选择8号管脚
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
GPIO_InitStruct.GPIO_Speed = GPIO_Low_Speed;//低速
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;//配置为开漏
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;//选择9号管脚
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/****************************
函数功能:起始条件
函数形参:void
函数返回值:void
函数说明:
在时钟线高电平期间,数据线产生下降沿
****************************/
void IIC_Start(void)
{
SDA_H;
SCL_H;
Delay_Us(4);//起始条件的建立时间
SDA_L;
Delay_Us(4);//起始条件的保持时间
SCL_L;//保证周期完整
}
/****************************
函数功能:停止条件
函数形参:void
函数返回值:void
函数说明:
在时钟线高电平期间,数据线产生上升沿
****************************/
void IIC_Stop(void)
{
SDA_L;
SCL_H;
Delay_Us(4);//停止条件的建立时间
SDA_H;
Delay_Us(4);//停止和启动条件之间的总线空闲时间
}
/****************************
函数功能:发送应答位
函数形参:u8 ack
函数返回值:void
函数说明:
0:代表应答
1:代表非应答
****************************/
void Send_Ack(u8 ack)
{
SCL_L;//发送方准备发送数据
if(ack)
{
SDA_H;
}
else
{
SDA_L;
}
Delay_Us(4);//发送数据所需要的时间
SCL_H;
Delay_Us(4);//接收数据所需要的时间
SCL_L;
Delay_Us(4);//保证一个完整的周期
}
/****************************
函数功能:接收应答位
函数形参:void
函数返回值:u8
函数说明:
0:代表应答
1:代表非应答
****************************/
u8 Receive_Ack(void)
{
u8 ack = 0;
SDA_H;//断开输出电路
SCL_L;//发送方准备发送数据
Delay_Us(4);//发送数据所需要的时间
SCL_H;
if(SDA)
{
ack = 1;
}
Delay_Us(4);//接收数据所需要的时间
SCL_L;
Delay_Us(4);//保证一个完整的周期
return ack;
}
/****************************
函数功能:发送一个字节并接收一个应答位
函数形参:u8 data
函数返回值:u8
函数说明:
0:代表应答
1:代表非应答
****************************/
u8 Send_Byte_Receive_Ack(u8 data)
{
u8 i,ack = 0;
for(i = 0; i < 8; i++)
{
SCL_L;//发送方准备发送数据
//1100 0011
//0100 0000
if(data & 0x80 >> i)
{
SDA_H;
}
else
SDA_L;
Delay_Us(4);//发送数据所需要的时间
SCL_H;
Delay_Us(4);//接收数据所需要的时间
}
ack = Receive_Ack();
return ack;
}
/****************************
函数功能:接收一个字节并发送一个应答位
函数形参:u8 data
函数返回值:u8
函数说明:
0:代表应答
1:代表非应答
****************************/
u8 Receive_Byte_Send_Ack(u8 ack)
{
u8 i = 0;
u8 data = 0;
for(i = 0; i < 8; i++)
{
SCL_L;//发送方准备发送数据
Delay_Us(4);//发送数据所需要的时间
SDA_H;//断开输出电路
SCL_H;
data <<= 1;//空出最低位
if(SDA)
{
data |= 1;
}
Delay_Us(4);//接收数据所需要的时间
}
Send_Ack(ack);
return data;
}
#ifndef __IIC_H_
#define __IIC_H_
#include "stm32f4xx.h"
#include "delay.h"
#define SCL_H GPIO_SetBits(GPIOB,GPIO_Pin_8)
#define SCL_L GPIO_ResetBits(GPIOB,GPIO_Pin_8)
#define SDA_H GPIO_SetBits(GPIOB,GPIO_Pin_9)
#define SDA_L GPIO_ResetBits(GPIOB,GPIO_Pin_9)
#define SDA GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)
void Iic_PortInit(void);
void IIC_Start(void);
void IIC_Stop(void);
u8 Send_Byte_Receive_Ack(u8 data);
u8 Receive_Byte_Send_Ack(u8 ack);
#endif
完整版代码例程在