12. I2C协议
12.1 I2C协议介绍
IIC(Inter-Integrated Circuit)协议也称为I2C总线,是一种串行通信协议,通常用于连接低速外设。它由Philips(现在的NXP Semiconductors)公司于1980年代初开发,现在已经成为一个标准。IIC总线只需要两条数据线,分别是串行数据线(SDA)和串行时钟线(SCL),这使得它成为一种非常简单的接口。它适用基于芯片的通信,例如连接传感器、存储器或数字信号处理器等。
在IIC协议中,总线上有一个主设备和多个从设备。主设备掌控着总线上的通信过程,负责发起、控制、停止通信。而从设备则需要等待主设备的请求,接收或发送数据。主设备和从设备之间的数据交换采用帧格式,每个帧通常包含地址、数据和控制信息。主设备根据从设备的地址来选中要通信的设备,从设备则根据控制信息进行相应的操作。IIC协议可以支持多个从设备连接到同一个主设备,为系统设计提供了更大的灵活性。
12.2 I2C的硬件实现
I2C总线通常使用两种电压电平,即高电平(VH)和低电平(VL)。高电平为2.5V至5.5V,低电平为0V至0.3V;这些电压电平范围是根据I2C规范确定的。I2C总线有不同的传输速率可选,包括标准模式(100 kbps)、快速模式(400 kbps)以及高速模式。传输速率的选择取决于应用的需求和设备的支持能力。为避免信号冲突,微处理器(MCU)必须只能驱动SDA和 SCL在低电平,即开漏输出。设置为开漏模式主要是为了保护器件和防止干扰。
- 防止干扰:多个器件共享同一条数据线(SDA)和同一条时钟线(SCL),如果采用推挽输出模式,多个器件的输出将会叠加在数据线上,造成信号干扰,严重时会损坏器件或导致通信错误。而采用开漏输出模式,则各个器件的输出只有拉低数据线的部分,不会干扰彼此,从而提高了总线的可靠性和抗干扰能力。
- 防止短路:在开漏输出模式下,由于器件的输出只有拉低数据线的部分,如果两个或多个器件同时输出,也不会造成短路。而如果采用推挽输出模式,两个或多个器件同时输出时,可能会形成短路。比如主设备输出高电平,从设备输出低电平。
- 因设置为开漏模式,需要连接一个外部的上拉电阻(例如:10k)将信号提拉至高电平。故I2C总线中的SDA(数据线)和SCL(时钟线)通常都连接了上拉电阻,以确保逻辑高电平的稳定性。上拉电阻的阻值通常在2.2kΩ至10kΩ之间,具体取决于总线的电容负载和通信距离。
I2C总线的最大线缆长度和传输容量受到一定限制。在标准模式下,最大线缆长度大约在1米左右,而在快速模式下,最大线缆长度约为0.3米。此外,线缆上的总线容量也会对传输速率产生影响。
12.3 I2C数据传输
IIC只有两根通信线,因此它数据传输是基于时钟信号的。各个设备在时钟信号的控制下进行数据的收发操作。下面是IIC总线的几个重要的时序:
起始信号:SCL在高电平的状态下,SDA的电平由高转低,表示开始一次通信。
void IIC_Start(void)
{
SDA_OUT();//设置SDA为输出模式
SDA(1);
SCL(1);
delay_us(5);
SDA(0);
delay_us(5);
SCL(0);
delay_us(5);
}
2
3
4
5
6
7
8
9
10
11
停止信号:SCL在高电平的状态下,SDA的电平由低转高,表示结束这次通信。主设备在发送停止信号后不能再向从设备发送任何数据,除非再次发送起始信号。
void IIC_Stop(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SCL(1);
delay_us(5);
SDA(1);
delay_us(5);
}
2
3
4
5
6
7
8
9
10
数据传输:主设备和从设备进行数据的传输,可以是一个或多个字节的数据,发送和接收都是基于地址选择的。
//发送一个字节
void IIC_Send_Byte(uint8_t dat)
{
int i = 0;
SDA_OUT();
SCL(0);
for( i = 0; i < 8; i++ )
{
SDA( (dat & 0x80) >> 7 );
delay_us(1);
SCL(1);
delay_us(5);
SCL(0);
delay_us(5);
dat<<=1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//接收一个字节
unsigned char IIC_Read_Byte(void)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
SCL(0);
delay_us(5);
SCL(1);
delay_us(5);
receive<<=1;
if( SDA_GET() )
{
receive |= 1;
}
}
SCL(0);
return receive;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
I2C还提供了一种称为“ACK/NACK”(应答/非应答)的确认机制。如果一个设备接收到数据,它将通过在SDA线上拉低电平来发送一个应答信号以通知发送方数据已被接收。相反,如果数据被损坏或未接收,接收设备将发送非应答信号。(在SDA上保持高电平)。
void IIC_Send_Ack(void)
{
SDA_OUT();
SCL(0);
SDA(1);
SDA(0);
SCL(1);
delay_us(5);
SCL(0);
SDA(1);
}
2
3
4
5
6
7
8
9
10
11
void IIC_Send_Nack(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SDA(1);
SCL(1);
delay_us(5);
SCL(0);
SDA(0);
}
2
3
4
5
6
7
8
9
10
11
在IIC总线中,时钟线由主设备控制,每个数据位在时钟边沿更新,传输的最高速率取决于总线上最慢的设备。一般来讲,IIC总线的通信速率比较慢,通常在几百kbps的范围内。如果需要更高的传输速率,可以采用其他通信协议,如SPI协议、CAN协议等。
12.4 通信流程
I2C通信流程按照以下步骤进行:
- 主控向总线发送开始信号。
- 主控将要通信的设备地址和读写位(R/W)发送到总线上。
- 设备接收到地址后发送应答信号,主控接收到应答信号后发送数据或继续发送地址。
- 设备接收到数据后发送应答信号,主控接收到应答信号后可以继续发送数据或者停止通信。
- 主控向总线发送停止信号。
12.5. IIC基本参数
速率: I2C总线有标准模式(100 kbit/s)和快速模式(400 kbit/s)两种传输模式,还有更快的扩展模式和高速模式可供选择。
器件地址: 每个设备都有唯一的7位或10位地址,可以通过地址选择来确定与谁进行通信。
总线状态: I2C总线有五种状态,分别是空闲状态、起始信号、结束信号、响应信号、数据传输。
数据格式: I2C总线有两种数据格式,标准格式和快速格式。标准格式是8位数据字节加上1位ack/nack(应答/非应答)位,快速格式允许两个字节同时传输。
由于SCL和SDA线是双向的,它们也可能会由于外部原因(比如线路中的电容等)出现电平误差,而从而导致通信出错。因此,在IIC总线中,通常使用上拉电阻来保证信号线在空闲状态下的电平为高电平。
12.6. 硬件I2C
MSPM0L系列的I2C支持主从模式,有7位地址位可以设置,支持 100kbps、400kbps、1Mbps 的 I2C 标准传输速率,并支持 SMBUS。 无论是主机或者从机,发送和接收都有独立的8个字节FIFO。MSPM0 I2C 具有 8 字节 FIFO,对于控制器和目标模式会生成独立的中断,并支持 DMA。
12.7 软件I2C与硬件I2C
I2C协议可以通过软件实现或者硬件实现。这两种方式的区别在于实现的方法和所需的硬件资源。
12.7.1 软件I2C
软件I2C是指通过在程序中编写代码来实现I2C通信协议。它利用通用输入输出(GPIO)引脚来模拟I2C的数据线(SDA)和时钟线(SCL),通过软件控制引脚的电平变化来传输数据和生成时序信号。与硬件I2C相比,软件I2C的优势在于不需要特定的硬件支持,可以在任何支持GPIO功能的微控制器上实现。它利用了微控制器的通用IO引脚来实现I2C通信协议。
软件I2C的实现通过编程方式来模拟I2C的主机和从机设备。通过逐位地读取和写入GPIO引脚的状态,并根据I2C协议的时序要求进行相应的操作,实现数据的传输和通信。软件I2C的灵活性较高,可以根据应用需求进行定制和扩展。它可以处理多个从机设备,并支持多主机环境。因此,软件I2C广泛应用于资源受限的MCU系统,特别是那些需要与多个外部设备进行通信的应用。
尽管软件I2C的性能相对于硬件I2C较低,但在一些低速通信和简单通信需求的场景下,软件I2C是一种经济实用的解决方案。
12.7.2 硬件I2C
硬件I2C是指通过专门的硬件模块来处理I2C通信协议。大多数现代微控制器和一些外部设备已经集成了硬件I2C模块,这些硬件模块负责处理I2C通信的细节,包括生成正确的时序信号、自动处理信号冲突、数据传输和错误检测等。可以直接使用硬件引脚连接,无需编写时序的代码。
使用硬件I2C通常相对简单,开发者无需编写复杂的代码来处理通信协议的细节。硬件模块可以直接与外部设备连接,通过专用的引脚进行数据和时钟传输,从而实现高效且可靠的通信。
在选择软件I2C还是硬件I2C时,需要考虑应用需求和硬件资源。软件I2C适用于资源受限的系统,可以在任何支持GPIO的微控制器上实现,但相对性能较低。硬件I2C通常性能更好,但需要硬件支持,并且可能占据一些特定的引脚资源。
12.8 IIC优缺点
12.8.1 优点
双向传输: I2C总线支持双向传输,可以通过SDA线同时传输主设备和从设备之间的数据,节约了总线的资源。
系统集成: I2C总线可以快速集成到芯片中,减少系统实现的逻辑复杂性,提高了设计效率。
多设备共享: I2C总线可以通过地址传输实现多个设备与主控器的通信,使得多个设备可以共享总线,并直接交互。
高可靠性: I2C总线使用逻辑层次的代替电气信号来表示数据传输,具有更高的传输可靠性。
12.8.2 缺点
带宽不高: I2C总线的传输速度限制在400 kbps,相比较于SPI总线和CAN总线,带宽相对较低。
时序要求严格: I2C总线传输数据需要严格遵循时序要求,特别是在高速传输过程,时序容易受到干扰,造成通信失败。
最长电缆长度有限: 虽然I2C总线可以通过中继器扩展总线长度,但是由于信号线受到干扰,信号衰减和时序要求等问题,电缆最长长度一般限制在1~2米之间。
总之,I2C总线具有双向传输、系统集成、多设备共享等优点,但传输速度相对较低,时序要求严格且最长电缆长度有限等缺点。
12.9 I2C应用
I2C总线是应用最广泛的通信接口之一,以下是几个常见的应用例子:
- 温度计传感器:常见的温度计传感器,如SHT31、LM75等,都采用I2C接口,其通过I2C总线将温度数据传输到主控器进行处理。
- LED驱动器:LED驱动器,如PCA9685,常用于控制LED灯的亮度和颜色,其通过I2C总线和主控器通信,可实现快速和精确定时。
- OLED显示屏:OLED显示屏通过I2C总线与主控器通信,可实现高清晰度的图形显示,应用于像表盘、智能手表、电子血压计等低功耗设备之中。
- 触摸屏控制器:常见的15寸及以下触摸屏控制器,如STMPE610,都采用I2C接口,这些控制器可提供触摸检测和X/Y坐标的读取等功能。
- 电流电压采集:电流或电压采集芯片,如INA219,可通过I2C总线和主控器通信,实现精确高速的电流电压数据采集,应用于电源管理和工业自动化等领域。
上述只是常见的应用例子之一,I2C总线在许多领域都有广泛的应用,具有性价比高、易于移植等优点。
12.10 I2C实验介绍
以SHT20温湿度传感器作为实验案例。通过软件I2C的方式与其进行通信,获取周围环境的温度与湿度状况。
12.10.1 模块来源
12.10.2 SHT20介绍
SHT20是一种数字式温湿度传感器,它采用电容式测量技术,具有高准确度和稳定性,并采用标准的I2C数字接口进行通信。SHT20的测量范围涵盖了温度-40到+125°C和相对湿度0到100%RH。它广泛应用于空气质量监测、气象监测、恒温恒湿控制、食品贮藏等领域。
SHT20温湿度传感器的相关参数,见下图:
12.10.3. 规格参数
工作电压: 2.1~3.6V
工作电流: 0.1~1000uA
温度精度: ±0.3℃
温度范围: -40~125℃
湿度范围: 0~100 %RH
湿度精度: ±3%RH
输出方式: IIC
管脚数量: 4 Pin
12.10.4 实验硬件连接
SHT20 | 开发板 |
---|---|
SCL | PA0 |
SDA | PA1 |
GND | GND |
VCC | 3V3 |
12.11 I2C的参数配置
这里使用我们提供的模板工程进行介绍。
盘链接:https://pan.baidu.com/s/1injyO0DmzHxG5S-hjslhBg?pwd=LCKF
提取码:LCKF
将模板工程放在SDK的根目录下解压,我将其重新命名为10_i2c文件夹。
12.11.1 打开SYSCONFIG配置工具
接着打开该工程。在Keil的主界面打开empty.syscfg文件,在empty.syscfg文件打开的情况下,再打开SYSCONFIG的GUI界面。
12.11.2 引脚参数的配置
因为引脚的配置有在LED章节进行说明,这里就直接将相关参数的配置贴出。
- 点击 ADD 添加GPIO组的配置
- 配置I2C组的引脚参数
将以上配置保存,然后到Keil中编译更新。
更新完成之后,我们所有设定的引脚和功能就会在 ti_msp_dl_config.h 中定义。因为这个文件在该模板工程中包含进了 board.h 中,所以我们后续使用其他文件只需要引用 board.h 即可。
12.11.3 I2C协议的使用
我们在工程模板中新建两个文件,分别是 bsp_sht20.c 和 bsp_sht20.h。将它们保存在工程的BSP文件夹下。
更新头文件路径。
在使用软件实现I2C通信时,需要选择合适的引脚来作为数据线(SDA)和时钟线(SCL)。通常情况下,可以选择任何可编程的通用输入输出(GPIO)引脚作为软件I2C的引脚。对于软件I2C,需要至少两个引脚用于数据线(SDA)和时钟线(SCL),并确保这些引脚能够满足I2C通信协议的时序要求。以下是一般的引脚说明:
- 数据线(SDA):用于传输数据的引脚。在软件I2C中,需要将该引脚设置为输出模式(用于主设备发送数据)与输入模式(用于主设备接收数据)。在通信期间,需要通过控制数据线的电平变化来实现数据的传输。
- 时钟线(SCL):用于控制数据传输的时钟信号的引脚。在软件I2C中,需要将该引脚设置为输出模式,通过控制时钟线的电平变化来生成时钟脉冲,以控制数据线的传输。 需要注意的是,选取合适的引脚时要考虑以下几个方面:
- 支持输入/输出配置:引脚需要支持在软件中配置为输入或输出模式,并能够通过程序动态地进行切换。
- 硬件限制和冲突:确保选取的引脚没有被分配给其他硬件功能或外设,以避免冲突。
- 电气特性:引脚的电气特性应满足I2C总线的标准要求,例如正确的电平和驱动能力。
需要注意的是,软件I2C的实现需要更多的程序代码和计算,相对于硬件I2C,软件I2C在处理器效能和时序控制方面更加敏感。因此,在选择引脚时,还需要考虑处理器的性能和可编程性能。 为了保证代码的可维护性和可移植性,这里将相关的功能进行的宏定义。
SDA引脚和SCL引脚的宏定义如下:
bsp_sht20.h文件
#ifndef __BSP_SHT20_H__
#define __BSP_SHT20_H__
#include "board.h"
//设置SDA输出模式
#define SDA_OUT() {\
DL_GPIO_initDigitalOutput(I2C_SDA_IOMUX);\
DL_GPIO_setPins(I2C_PORT, I2C_SDA_PIN);\
DL_GPIO_enableOutput(I2C_PORT, I2C_SDA_PIN);\
}
//设置SDA输入模式
#define SDA_IN() { DL_GPIO_initDigitalInput(I2C_SDA_IOMUX); }
//获取SDA引脚的电平变化
#define SDA_GET() ( ( ( DL_GPIO_readPins(I2C_PORT,I2C_SDA_PIN) & I2C_SDA_PIN ) > 0 ) ? 1 : 0 )
//SDA与SCL输出
#define SDA(x) ( (x) ? (DL_GPIO_setPins(I2C_PORT,I2C_SDA_PIN)) : (DL_GPIO_clearPins(I2C_PORT,I2C_SDA_PIN)) )
#define SCL(x) ( (x) ? (DL_GPIO_setPins(I2C_PORT,I2C_SCL_PIN)) : (DL_GPIO_clearPins(I2C_PORT,I2C_SCL_PIN)) )
float SHT20_Read(unsigned char regaddr);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
接下来就是配置I2C的时序部分,关于I2C的时序介绍,已在章节【I2C数据传输】进行说明,这里不再进行叙述。
在bsp_sht20.c文件中补充以下代码:
#include "bsp_sht20.h"
/******************************************************************
* 函 数 名 称:IIC_Start
* 函 数 说 明:IIC起始信号
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void IIC_Start(void)
{
SDA_OUT();
SCL(0);
SDA(1);
SCL(1);
delay_us(5);
SDA(0);
delay_us(5);
SCL(0);
delay_us(5);
}
/******************************************************************
* 函 数 名 称:IIC_Stop
* 函 数 说 明:IIC停止信号
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void IIC_Stop(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SCL(1);
delay_us(5);
SDA(1);
delay_us(5);
}
/******************************************************************
* 函 数 名 称:IIC_Send_Ack
* 函 数 说 明:主机发送应答
* 函 数 形 参:0应答 1非应答
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void IIC_Send_Ack(uint8_t ack)
{
SDA_OUT();
SCL(0);
SDA(0);
delay_us(5);
if(!ack) SDA(0);
else SDA(1);
SCL(1);
delay_us(5);
SCL(0);
SDA(1);
}
/******************************************************************
* 函 数 名 称:IIC_Wait_Ack
* 函 数 说 明:等待从机应答
* 函 数 形 参:无
* 函 数 返 回:1=无应答 0=有应答
* 作 者:LC
* 备 注:无
******************************************************************/
uint8_t IIC_Wait_Ack(void)
{
char ack = 0;
unsigned char ack_flag = 10;
SDA_IN();
SDA(1);
delay_us(5);
SCL(1);
delay_us(5);
while( (SDA_GET()==1) && ( ack_flag ) )
{
ack_flag--;
delay_us(5);
}
if( ack_flag <= 0 )
{
IIC_Stop();
return 1;
}
else
{
SCL(0);
SDA_OUT();
}
return ack;
}
/******************************************************************
* 函 数 名 称:IIC_Write
* 函 数 说 明:IIC写一个字节
* 函 数 形 参:dat写入的数据
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void IIC_Write(uint8_t data)
{
int i = 0;
SDA_OUT();
SCL(0);//拉低时钟开始数据传输
for( i = 0; i < 8; i++ )
{
SDA( (data & 0x80) >> 7 );
delay_us(2);
data<<=1;
delay_us(6);
SCL(1);
delay_us(4);
SCL(0);
delay_us(4);
}
}
/******************************************************************
* 函 数 名 称:IIC_Read
* 函 数 说 明:IIC读1个字节
* 函 数 形 参:无
* 函 数 返 回:读出的1个字节数据
* 作 者:LC
* 备 注:无
******************************************************************/
uint8_t IIC_Read(void)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
SCL(0);
delay_us(5);
SCL(1);
delay_us(5);
receive<<=1;
if( SDA_GET() )
{
receive|=1;
}
delay_us(5);
}
return receive;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
12.11.4 SHT20的开发
我们要实现的是通过案例SHT20温湿度传感器读取周围环境的温湿度情况。要进行I2C通信,需要知道其器件地址。并且了解如何与其进行通信。
SHT20温湿度传感器的I2C地址为0x80。I2C地址是一个用于在总线上识别设备的7位地址。对于SHT20传感器,其地址的最高7位已经预设为固定值0b1000_000(0b代表二进制)。最低一位用于标识读操作或写操作,读操作为1,写操作为0。因此,SHT20的I2C地址在写操作时可以表示为0b1000_0000(0x80),读操作时表示为0b1000_0001(0x81)。
测量命令
SHT20有两种测量模式可以选择,分别是主机模式与非主机模式。在主机模式下,在测量的过程中,SCL线被封锁(由传感器进行控制),我们作为主机无法操控I2C总线;在非主机模式下,当传感器在执行测量任务时,SCL线仍然保持开放状态,可进行其他通讯。非主机模式允许传感器进行测量时在总线上处理其他I2C总线通讯任务。
而在两种模式下,触发测量的命令也不相同,例如在主机模式下测量温度命令为0b1110_0011,即0xe3;在非主机模式下测量温度命令为0b1111_0011,即0xf3。
本案例使用的是非主机模式,故后续的测量温度和湿度命令为0b1111_0011(0xf3)和0b1111_0101(0xf5)。
通信案例
在SHT20的数据手册中,有相关使用流程,因为本案例是使用非主机模式,故只说明非主机模式下的使用流程,关于主机模式的使用流程,可以查阅SHT20的数据手册。
在非主机模式下,MCU需要对传感器状态进行查询。此过程通过发送一个I2C起始信号,告诉总线开始通信。然后发送器件地址+写操作(0x80,对应图2-3-3中的bit1~bit8),等待传感器应答。如果传感器没有应答,请注意设备的实物连线情况是否正确。待传感器应答之后发送对应的命令(对应图2-3-3中的bit10~bit17),具体命令见 【图 命令集说明】 。
之后紧接着是如下图(非主机模式通信流程)所示的IC首字节(0b1000_0001)来完成。如果内部处理工作完成,单片机查询到传感器发出的确认信号后,相关数据就可以通过MCU进行读取。如果测量处理工作没有完成,传感器无确认位(ACK)输出,此时必须重新发送启动传输时序。
无论哪种传输模式,由于测量的最大分辨率为14位,第二个字节SDA上的后两位LSBs( bit43和44)用来传输相关的状态信息。两个LSBs 中的bit1表明测量的类型('0':温度;‘1':湿度)。bit0位当前没有赋值即无意义。
读取温湿度配置
根据数据手册中的通信案例,我们可以验证实现读取温湿度功能。我们以读取非主机模式的温度为例。
- 根据【图 非主机模式通信流程】所示,在开始通信之前,我们需要发送起始信号S和器件地址+写(图中的bit1~bit8),发送完成之后,等待从机应答。
IIC_Start();//发送起始信号
IIC_Send_Byte(0X80);//发送器件地址加写
//如果返回非应答,则串口提示失败-1
if( IIC_Wait_Ack() == 1 ) printf("receive fail -1\r\n");
2
3
4
- 发送对应的命令(对应【图 非主机模式通信流程】中的bit10~bit17),具体命令见【图 命令集说明】,本案例发送的是测量温度命令,对应代码中的0xf3。发送完成后,等待从机应答,确定发送成功。
IIC_Send_Byte(0Xf3);//发送非主机模式读取温度命令
//如果返回非应答,则串口提示失败-2
if( IIC_Wait_Ack() == 1 )
{
printf("receive fail -2\r\n");
}
2
3
4
5
6
- 重新发送起始信号与器件地址加读0X81(对应图中的bit19~bit26),告诉传感器要开始读取温度数据。传感器接收到后,就开始采集工作。如果传感器内部处理工作完成,单片机查询到传感器发出的确认信号ACK(bit27)后,相关数据就可以通过MCU进行读取。如果测量处理工作没有完成,传感器输出非应答位NACK(bit27),此时必须重新发送启动传输时序。
//发送起始信号与器件地址+读
do
{
delay_us(10);//等待传感器采集
IIC_Start(); //起始信号
IIC_Send_Byte(0X81);//器件地址+读
} //如果返回非应答信号,则重复发送起始信号和器件地址+读
while( IIC_Wait_Ack() == 1 ); //等待应答
2
3
4
5
6
7
8
- 接收传感器输出的数据高8位和低8位。代码中的变量data_msb、data_lsb和check分别代表数据高8位、数据低8位、校验数据。
data_msb = IIC_Read_Byte();//读取第一个字节
IIC_Send_Ack(); //发送应答
data_lsb = IIC_Read_Byte();//读取第二个字节
IIC_Send_Ack(); //发送应答
check = IIC_Read_Byte(); //读取第三个字节
IIC_Send_Nack(); //发送非应答
IIC_Stop(); //发送停止信号
2
3
4
5
6
7
- 将接收到的数据进行整合为16位数据,并且根据数据手册的要求,将最后两位的状态位清零。即低8位的最后两位状态位清零。Dat为16位的变量。
//数据整合
dat = data_msb<<8; //整合高8位
dat = dat | data_lsb;//整合低8位
dat &= ~(0x03); //将最后两位状态位清除
2
3
4
- 数据换算。根据数据手册换算出实际的温度数据。
相对湿度的转换公式为:
温度的转换公式为:
其中的 S(T)和S(RH )表示的是传感器输出的高低位整合后的16位数据。
//数据换算
//temp为浮点型变量,2的16次方为65536
temp = ( dat / 65536.0 ) * 175.72 - 46.85;
2
3
将以上步骤的代码进行整合封装,并添加获取湿度部分,得到以下函数。
在bsp_sht20.c文件中补充以下代码:
/******************************************************************
* 函 数 名 称:SHT20_Read
* 函 数 说 明:测量温湿度
* 函 数 形 参:regaddr寄存器地址 regaddr=0xf3测量温度 =0xf5测量湿度
* 函 数 返 回:regaddr=0xf3时返回温度,regaddr=0xf5时返回湿度
* 作 者:LC
* 备 注:无
******************************************************************/
float SHT20_Read(uint8_t regaddr)
{
unsigned char data_H = 0;
unsigned char data_L = 0;
float temp = 0;
IIC_Start();
IIC_Write(0x80|0);
if( IIC_Wait_Ack() == 1 ) printf("error -1\r\n");
IIC_Write(regaddr);
if( IIC_Wait_Ack() == 1 ) printf("error -2\r\n");
do{
delay_us(10);
IIC_Start();
IIC_Write(0x80|1);
}while( IIC_Wait_Ack() == 1 );
delay_us(20);
data_H = IIC_Read();
IIC_Send_Ack(0);
data_L = IIC_Read();
IIC_Send_Ack(1);
IIC_Stop();
if( regaddr == 0xf3 )
{
temp = ((data_H<<8)|data_L) / 65536.0 * 175.72 - 46.85;
}
if( regaddr == 0xf5 )
{
temp = ((data_H<<8)|data_L) / 65536.0 * 125.0 - 6;
}
return temp;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
12.12 I2C读取温湿度传感器实验
在empty.c(要main函数的文件)中编写如下代码:
#include "board.h"
#include <stdio.h>
#include "bsp_sht20.h"
#define T_ADDR 0xf3 // 温度
#define PH_ADDR 0xf5 // 湿度
int main(void)
{
//开发板初始化
board_init();
printf("SHT20 Start!!\r\n");
while(1)
{
uint32_t TEMP = SHT20_Read(T_ADDR) * 100;
uint32_t PH = SHT20_Read(PH_ADDR) * 100;
printf("温度 = %d.%02d ℃\r\n", TEMP/100,TEMP%100);
printf("湿度 = %d.%02d %%RH\r\n", PH/100,PH%100);
printf("\n");
delay_ms(1000);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
实验结果: