二十四、 IIC实验
以SHT20温湿度传感器作为实验案例。通过软件IIC的方式与其进行通信,获取周围环境的温度与湿度状况。
1. 实验介绍
SHT20是一种数字式温湿度传感器,它采用电容式测量技术,具有高准确度和稳定性,并采用标准的I2C数字接口进行通信。SHT20的测量范围涵盖了温度-40到+125°C和相对湿度0到100%RH。它广泛应用于空气质量监测、气象监测、恒温恒湿控制、食品贮藏等领域。
SHT20温湿度传感器的相关参数,见下图:
1.2. 硬件连接
因SHT20温湿度传感器采用的是标准的IIC接口,我们可以使用软件I2C和硬件I2C的方式与其通信。只要是通用输入输出(GPIO)的引脚就可以使用软件I2C,而硬件I2C需要使用到指定引脚才可使用。为了讲解软硬件I2C的两个案例,选择了PB9(I2C0_SDA)和PB8(I2C0_SCL)作为通信引脚。
如使用软件I2C的方式进行通信,需将引脚配置为普通开漏输入输出,并将I2C的时序通过控制GPIO的高低电平变化的方式进行编写,具体时序的实现方法已在章节【IIC协议介绍】中进行说明。如果使用硬件I2C的方式进行通信,需将引脚配置为复用开漏模式,开启指定引脚对应的I2C外设。
接线图:
2. 软件I2C配置
一般使用软件I2C功能,都需要有以下几个步骤。
- 配置引脚
- 配置I2C时序
- 确定通信步骤
2.1. 配置引脚
在使用软件实现I2C通信时,需要选择合适的引脚来作为数据线(SDA)和时钟线(SCL)。通常情况下,可以选择任何可编程的通用输入输出(GPIO)引脚作为软件I2C的引脚。对于软件I2C,需要至少两个引脚用于数据线(SDA)和时钟线(SCL),并确保这些引脚能够满足I2C通信协议的时序要求。以下是一般的引脚说明:
- 数据线(SDA):用于传输数据的引脚。在软件I2C中,需要将该引脚设置为输出模式(用于主设备发送数据)与输入模式(用于主设备接收数据)。在通信期间,需要通过控制数据线的电平变化来实现数据的传输。
- 时钟线(SCL):用于控制数据传输的时钟信号的引脚。在软件I2C中,需要将该引脚设置为输出模式,通过控制时钟线的电平变化来生成时钟脉冲,以控制数据线的传输。
需要注意的是,选取合适的引脚时要考虑以下几个方面:
- 支持输入/输出配置:引脚需要支持在软件中配置为输入或输出模式,并能够通过程序动态地进行切换。
- 硬件限制和冲突:确保选取的引脚没有被分配给其他硬件功能或外设,以避免冲突。
- 电气特性:引脚的电气特性应满足I2C总线的标准要求,例如正确的电平和驱动能力。 需要注意的是,软件I2C的实现需要更多的程序代码和计算,相对于硬件I2C,软件I2C在处理器效能和时序控制方面更加敏感。因此,在选择引脚时,还需要考虑处理器的性能和可编程性能。
为了保证代码的可维护性和可移植性,这里将相关的功能进行的宏定义。
SDA引脚和SCL引脚的宏定义如下:
//SDA端口移植
#define RCU_SDA RCU_GPIOB
#define PORT_SDA GPIOB
#define GPIO_SDA GPIO_PIN_9
//SCL端口移植
#define RCU_SCL RCU_GPIOB
#define PORT_SCL GPIOB
#define GPIO_SCL GPIO_PIN_8
//设置SDA输出模式
#define SDA_OUT() gpio_mode_set(PORT_SDA,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_SDA)
//设置SDA输入模式
#define SDA_IN() gpio_mode_set(PORT_SDA,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,GPIO_SDA)
//获取SDA引脚的电平变化
#define SDA_GET() gpio_input_bit_get(PORT_SDA,GPIO_SDA)
//SDA与SCL输出
#define SDA(x) gpio_bit_write(PORT_SDA,GPIO_SDA, (x?SET:RESET) )
#define SCL(x) gpio_bit_write(PORT_SCL,GPIO_SCL, (x?SET:RESET) )
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
要操作 GPIO 引脚,必不可少的就是开启时钟、配置模式、配置输出、设置功能等,还是这一系列的操作。
引脚初始化配置如下:
void iic_gpio_config(void)
{
/* 使能时钟 */
rcu_periph_clock_enable(RCU_SCL);
rcu_periph_clock_enable(RCU_SDA);
/* 配置SCL为输出模式 */
gpio_mode_set(PORT_SCL,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_SCL);
/* 配置为开漏输出 50MHZ */
gpio_output_options_set(PORT_SCL,GPIO_OTYPE_OD,GPIO_OSPEED_50MHZ,GPIO_SCL);
/* 配置SDA为输出模式 */
gpio_mode_set(PORT_SDA,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_SDA);
/* 配置为开漏输出 50MHZ */
gpio_output_options_set(PORT_SDA,GPIO_OTYPE_OD,GPIO_OSPEED_50MHZ,GPIO_SDA);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2.2. 配置I2C时序
关于I2C的时序部分,已在章节【IIC数据传输】进行说明,这里不在进行叙述。
//IIC起始信号
void IIC_Start(void)
{
SDA_OUT();//设置SDA为输出模式
SDA(1);
SCL(1);
delay_us(5);
SDA(0);
delay_us(5);
SCL(0);
delay_us(5);
}
//IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SCL(1);
delay_us(5);
SDA(1);
delay_us(5);
}
//IIC发送应答
void IIC_Send_Ack(void)
{
SDA_OUT();
SCL(0);
SDA(1);
SDA(0);
SCL(1);
delay_us(5);
SCL(0);
SDA(1);
}
//IIC发送非应答
void IIC_Send_Nack(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SDA(1);
SCL(1);
delay_us(5);
SCL(0);
SDA(0);
}
//IIC等待应答
//应答返回0 非应答返回1
unsigned char IIC_Wait_Ack(void)
{
char ack = 0;
unsigned char ack_flag = 10;
SCL(0);
SDA(1);
SDA_IN();
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;
}
//发送一个字节
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;
}
}
//接收一个字节
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
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
2.3 确定通信步骤
我们要实现的是通过案例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),具体命令见【图2-3-2 命令集说明】。
之后紧接着是如图2-3-3所示的IC首字节(0b1000_0001)来完成。如果内部处理工作完成,单片机查询到传感器发出的确认信号后,相关数据就可以通过MCU进行读取。如果测量处理工作没有完成,传感器无确认位(ACK)输出,此时必须重新发送启动传输时序。
无论哪种传输模式,由于测量的最大分辨率为14位,第二个字节SDA上的后两位LSBs( bit43和44)用来传输相关的状态信息。两个LSBs 中的bit1表明测量的类型('0':温度;‘1':湿度)。bit0位当前没有赋值即无意义。
读取温湿度配置
根据数据手册中的通信案例,我们可以验证实现读取温湿度功能。我们以读取非主机模式的温度为例。
- 根据 【图2-3-3 非主机模式通信流程】 所示,在开始通信之前,我们需要发送起始信号S和器件地址+写(图中的bit1~bit8),发送完成之后,等待从机应答。
IIC_Start();//发送起始信号
IIC_Send_Byte(0X80);//发送器件地址加写
//如果返回非应答,则串口提示失败-1
if( IIC_Wait_Ack() == 1 ) printf("receive fail -1\r\n");
2
3
4
- 发送对应的命令(对应图2-3-3中的bit10~bit17),具体命令见【图2-3-2 命令集说明】,本案例发送的是测量温度命令,对应代码中的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
将以上步骤的代码进行整合封装,并添加获取湿度部分,得到以下函数。
/**********************************************************
* 函 数 名 称:SHT20_receive_data
* 函 数 功 能:读取SHT20的温湿度数据
* 传 入 参 数:num=0xf3读取温度数据 num=0xf5读取湿度数据
* 函 数 返 回:读取到的环境数据
* 作 者:LC
* 备 注:无
**********************************************************/
float SHT20_receive_data( unsigned char num )
{
uint8_t data_msb = 0;//数据高8位
uint8_t data_lsb = 0;//数据低8位
uint8_t check = 0; //校验位
uint16_t dat = 0; //高低位整合后数据
float temp = 0; //换算的温湿度结果
IIC_Start();//发送起始信号
IIC_Send_Byte(0X80);//发送器件地址加写
if( IIC_Wait_Ack() == 1 ) //如果返回非应答,则串口提示失败-1
{
printf("receive fail -1\r\n");
}
IIC_Send_Byte( num );//发送非主机模式读取命令
if( IIC_Wait_Ack() == 1 ) //如果返回非应答,则串口提示失败-2
{
printf("receive fail -2\r\n");
}
do //重新发送起始信号与器件地址+读
{
delay_us(10);//等待
IIC_Start(); //起始信号
IIC_Send_Byte(0X81);//器件地址+读
}
while( IIC_Wait_Ack() == 1 );//等待应答
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(); //发送停止信号
//数据整合
dat = data_msb<<8;
dat = dat | data_lsb;
dat &= ~(1<<1);
//数据换算
if( num == 0xf3 )//测量温度
{
temp = ( dat / 65536.0 ) * 175.72 - 46.85;
}
if( num == 0xf5)//测量湿度
{
temp = ( dat / 65536.0 ) * 125 - 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
46
47
48
49
50
51
52
53
54
55
56
57
3. 软件I2C验证
在main.c中调用float SHT20_receive_data(unsigned char num) 函数,采集周围环境的温湿度。
int main(void)
{
board_init();
bsp_uart_init();
//IIC引脚初始化
iic_gpio_config();
//等待传感器上电初始化完成
delay_ms(20);
while(1)
{
//采集温度
printf("temp = %f\r\n", SHT20_receive_data(0xf3) );
//采集湿度
printf("humi = %f\r\n", SHT20_receive_data(0xf5) );
delay_ms(500);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
串口输出结果:
4. 硬件I2C配置
一般使用硬件I2C功能,都需要有以下几个步骤。
- 配置引脚
- 配置硬件I2C参数
- 确定通信步骤
4.1. 配置引脚
本案例硬件I2C的引脚与上一章节的软件I2C引脚一致。不过使用硬件I2C引脚需要开启引脚的复用功能。以PB8与PB9为例。
数据手册中写明,PB8与PB9在复用线AF4中有I2C0的复用功能,PB8为I2C0的时钟线SCL,PB9为I2C0的数据线SDA。
/* 使能引脚时钟 */
rcu_periph_clock_enable(RCU_SCL);
rcu_periph_clock_enable(RCU_SDA);
/* 配置SCL引脚复用IIC功能 */
gpio_af_set(PORT_SCL, GPIO_AF_4, GPIO_SCL);
/* 配置SDA引脚复用IIC功能 */
gpio_af_set(PORT_SDA, GPIO_AF_4, GPIO_SDA);
/* 配置SCL引脚 */
gpio_mode_set(PORT_SCL, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_SCL);
gpio_output_options_set(PORT_SCL, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_SCL);
/* 配置SDA引脚 */
gpio_mode_set(PORT_SDA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_SDA);
gpio_output_options_set(PORT_SDA, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_SDA);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
25.4.2. 配置硬件I2C参数
I2C速率
确定通信时钟的速率。在配置参数时,需要设置合适的速率,确保与所连接设备的要求相匹配。通常以Hz为单位表示,如100kHz或400kHz。
通过以下函数进行配置:
/* 配置IIC速率为400KHz */
i2c_clock_config(I2C0, 400000, I2C_DTCY_2);
2
I2C地址
确定I2C设备的寻址模式。可以是7位地址模式(使用7位地址)或10位地址模式(使用10位地址)。根据所连接设备的规格,选择适当的寻址模式。SHT20是标准I2C协议接口,地址格式为7位器件地址+读写位,因此本案例选择7位地址,地址为0x80。
/* 配置I2C地址 */
i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0X80);
2
最终配置参数如下:
/* 使能IIC时钟 */
rcu_periph_clock_enable(RCU_I2C0);
/* IIC复位 */
i2c_deinit(I2C0);
/* 配置IIC速率 */
i2c_clock_config(I2C0, 400000, I2C_DTCY_2);
/* 配置I2C地址 */
i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0X80);
/* 使能I2C */
i2c_enable(I2C0);
/* 使能应答功能 */
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
2
3
4
5
6
7
8
9
10
11
12
4.3. 确定通信步骤
根据SHT20读取温湿度的流程,与章节 【23.2.2 硬件I2C接收流程】 得出如下代码:
/**********************************************************
* 函 数 名 称:iic_hardware_get_data
* 函 数 功 能:读取SHT20的温湿度数据
* 传 入 参 数:num=0xf3读取温度数据 num=0xf5读取湿度数据
* 函 数 返 回:读取到的环境数据
* 作 者:LC
* 备 注:无
**********************************************************/
float iic_hardware_get_data(unsigned int num)
{
unsigned int timeout = 0;
unsigned char data_H = 0;
unsigned char data_L = 0;
unsigned int dat = 0;
//发送起始信号
i2c_start_on_bus(I2C0);
//等待SBSEND标志位置一
while( !i2c_flag_get(I2C0,I2C_FLAG_SBSEND) );
//发送器件地址加写操作
i2c_master_addressing(I2C0, 0x80, I2C_TRANSMITTER);
//等待ADDSEND标志位置一
while( !i2c_flag_get(I2C0,I2C_FLAG_ADDSEND) );
//清除ADDSEND标志位
i2c_flag_clear(I2C0,I2C_FLAG_ADDSEND);
//等待TBE标志位置一
while( !i2c_flag_get(I2C0,I2C_FLAG_TBE) );
//发送数据
i2c_data_transmit (I2C0, num);
//等待BTC标志位置一
while( !i2c_flag_get(I2C0,I2C_FLAG_BTC) );
//发送停止信号
i2c_stop_on_bus(I2C0);
again:
//发送起始信号
i2c_start_on_bus(I2C0);
//等待起始信号发送完成标志位置一
while( !i2c_flag_get(I2C0,I2C_FLAG_SBSEND) );
//发送器件地址加读操作
i2c_master_addressing(I2C0, 0x80, I2C_RECEIVER);
//等待器件地址发送成功标志位置一
while( !i2c_flag_get(I2C0,I2C_FLAG_ADDSEND) )
{
delay_1ms(1);
timeout++;
if( timeout > 10 )//等待超过10次都失败
{
timeout = 0;
goto again;//重新发送起始信号
}
}
//清除地址发送成功标志位
i2c_flag_clear(I2C0,I2C_FLAG_ADDSEND);
//使能应答功能,即接收时自动发送应答信号
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
//等待数据寄存器非空标志位置一
while( !i2c_flag_get(I2C0,I2C_FLAG_RBNE) );
//接收数据
data_H = i2c_data_receive (I2C0);
//关闭应答信号,即接收时自动发送非应答信号
i2c_ack_config(I2C0, I2C_ACK_DISABLE);
//等待数据寄存器非空标志位置一
while( !i2c_flag_get(I2C0,I2C_FLAG_RBNE) );
//接收数据
data_L = i2c_data_receive (I2C0);
//发送停止信号
i2c_stop_on_bus(I2C0);
//数据整合
dat = data_H<<8;
dat = dat | data_L;
dat &= ~(0x03);
//数据换算
if( num == 0xf3 )
{
return ( ( dat / 65536.0 ) * 175.72 - 46.85);
}
if( num == 0xf5 )
{
return ( ( dat / 65536.0 ) *125 - 6);
}
return 0;
}
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
5. 硬件I2C验证
在main.c中调用float iic_hardware_get_data(unsigned char num) 函数,采集周围环境的温湿度。
int main(void)
{
systick_config();
//串口0初始化
usart_gpio_config(9600U);
//硬件IIC引脚初始化
iic_hardware_config();
//等待传感器上电初始化完成
delay_1ms(20);
while(1)
{
//采集温度
printf("temp = %f\r\n", iic_hardware_get_data(0xf3) );
//采集湿度
printf("humi = %f\r\n", iic_hardware_get_data(0xf5) );
delay_1ms(500);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
串口输出结果:
完整代码下载:
代码下载
在下载中心
的入门手册资料百度网盘链接下载 软件资料
-> 代码例程