二十四、 IIC 实验
以 SHT20 温湿度传感器作为实验案例。通过软件 IIC 的方式与其进行通信,获取周围环境的温度与湿度状况。
24.1. 实验介绍
SHT20 温湿度传感器的相关参数,见下图:
24.1.2. 硬件连接
因 SHT20 温湿度传感器采用的是标准的 IIC 接口,我们可以使用软件 I2C 和硬件 I2C 的方式与其通信。只要是通用输入输出(GPIO)的引脚就可以使用软件 I2C,而硬件 I2C 需要使用到指定引脚才可使用。为了讲解软硬件 I2C 的两个案例,选择了 PB9(I2C0_SDA)和 PB8(I2C0_SCL)作为通信引脚。
如使用软件 I2C 的方式进行通信,需将引脚配置为普通开漏输入输出,并将 I2C 的时序通过控制 GPIO 的高低电平变化的方式进行编写,具体时序的实现方法已在章节【24.1.3. IIC 数据传输】中进行说明。如果使用硬件 I2C 的方式进行通信,需将引脚配置为复用开漏模式,开启指定引脚对应的 I2C 外设。
24.2. 软件 I2C 配置
一般使用软件 I2C 功能,都需要有以下几个步骤。
- 配置引脚
- 配置 I2C 时序
- 确定通信步骤
24.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
24.2.2. 配置 I2C 时序
关于 I2C 的时序部分,已在章节【I2C协议介绍】进行说明,这里不在进行叙述。
//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
24.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
24.3. 软件 I2C 验证
在 main.c 中调用 float SHT20_receive_data(unsigned char num) 函数,采集周围环境的温湿度。
int main(void)
{
systick_config();
//串口0初始化
usart_gpio_config(9600U);
//IIC引脚初始化
iic_gpio_config();
//等待传感器上电初始化完成
delay_1ms(20);
while(1)
{
//采集温度
printf("temp = %f\r\n", SHT20_receive_data(0xf3) );
//采集湿度
printf("humi = %f\r\n", SHT20_receive_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
串口输出结果:
24.4. 硬件 I2C 配置
一般使用硬件 I2C 功能,都需要有以下几个步骤。
- 配置引脚
- 配置硬件 I2C 参数
- 确定通信步骤
24.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。
通过以下函数进行配置:
void i2c_clock_config(uint32_t i2c_periph, uint32_t clkspeed, uint32_t dutycyc);
/* 配置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
24.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
24.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
串口输出结果:
关于这一章节的代码,在资源包/04软件资料/代码例程/里面的I2C。