I2C协议介绍以及HAL库实现I2C对SHT30温湿度采样


1- I2C协议介绍

(1)I2C协议简介

I2C总线是Philips公司在八十年代初推出的一种串行、半双工的总线,主要用于近距离、低速的芯片之间的通信。
12C Bus(IIC, Inter-Integrated Circuit Bus)是由一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步,利用上拉电阻将它们拉成高电平(表示总线空闲),其典型的电压准位为+3.3V或+5V,具有电路简单、连接线少、控制简单、通信速率高等优点。

在这里插入图片描述

I2C总线是一种主从结构(Master/Slave)总线, I2C总线上的每一个设备都可以作为主设备或者从设备,但一个总线上一般只有一个主设备,可以带多个从设备。其中主设备用来产生允许传输的时钟信号,并初始化总线的数据传输,所以主设备通常是CPU,而从设备只能被动响应主设备发起的通信请求,所以各种12C接口芯片将作为从设备使用。

(2)I2C从设备地址

因为一个12C总线上可以有多个从设备,这样主设备需要通过地址来确定与哪个器件进行通信。I2C总线上每个从设备都有一个唯一的7bit地址物理识别,这个地址固化在芯片内部,并可以从芯片datasheet上找到。因为12C地址全0为广播地址,所以12C总线理论上最多能带2^7-1=127个从设备。

其中I2C的从器件地址(我只用到了7位的)的组成如下:

1byte = 7bit地址 + 1bit读写标志

:1bit读写标志中,0-发送数据(写),1-请求数据(读)

有些时候一个总线上可能需要挂多个同一芯片,这样有些芯片还需要引出一个或几个引脚,由开发板设计电路来决定其具体地址,从而让不同芯片具有不同的7bit物理地址。如下图:

如果ADDR连VSS,则其7bit地址为0x44
如果ADDR连VDD,则其7bit地址为0x45

在这里插入图片描述


2- I2C通信时序

在12C总线上传送的每一位数据都由一个同步时钟脉冲相对应,即在SCL串行时钟的配合下,数据在SDA上从高位向低位依次串行传送每一位的数据。下面是12C通信的时序图:
在这里插入图片描述

(1)起始位

12C总线在空闲时SDA和SCL都处于高电平状态(由上拉电阻拉成高电平),当主设备要开始一次12C通信时就发送一个START(S)信号,这个起始位就可以告诉所有12C从机, “我”要开始进行12C通信了;当要结束一次12C通信时,则发送一个STOP§信号结束本次通信。

START(S):当SCL保持高电平时候,SDA出现下降沿,产生一个起始位,注意SCL一定要在高电平。
STOP( P ):当SCL保持高电平时候,SDA出现上升沿,产生一个停止位,注意SCL一定要在高电平。
在这里插入图片描述

(2)读写地址

主机在发送START信号之后,第2个时序应该立刻给出要通信的目标从机物理地址。此外,I2C总线是一种能够实现半双工通信的同步串行通信协议,站在主设备的角度来看应该具有读/写从设备的功能。

这时候12C的读写地址除了7bit物理地址以外,还有1bit用来标识读/写方向位。这样12C的从设备读写地址通常是一个字节,其中高7bit是上面描述的物理地址,最低位用来表示读写方向(0为写操作, 1为读操作)

在这里插入图片描述

(3)I2C应答信号

主机往12C总线上传输器件地址,所有的从机接收到这个地址后与自己的地址相比较若相同则发出一个应答ACK(Acknowledge)信号,主机收到这个应答信号后通讯连接建立成功,若未收到应答信号则表示寻址失败。

在这里插入图片描述

此外,主/从机在之后的数据通信中,数据接收方(可能是主机也可能是从机)收到传输的一个字节数据后,需要给出响应,此时处在第九个时钟,发送端释放SDA线控制权,将SDA电平拉高,由接收方控制。

  • 若希望继续,则给出“应答(ACK, Acknowledge)”信号,即SDA为低电平
  • 若不希望继续,则给出“非应答(NACK,Not Acknowledge) ”信号,即SDA为高电平

(4)数据位发送与接收

主机在收到从机的应答信号之后,开始给从机发送数据。SDA数据线上的每个字节必须是8位,每次传输的字节数量没有限制,每个字节发送完成之后,从机必须跟一个应答信号。

在这里插入图片描述

12C总线通信时数据位传输采用MSB(最高位优先)方式发送,其中高电平表示数据位1,低电平表示数据位0。
当传输的数据位需要改变时(如上一个位发送的是1,下一个位要发送0),必须发生在SCL为低电平期间。另外在传输过程中, SDA上的数据位在SCL高电平期间必须保持稳定不变。

假设SCL在高电平,想一下是不是就会触发起始位或者终止位。想一想起始信号与停止信号是怎么发送的就会明白为什么SCl一定要在高电平才能改变SDA。


3- I2C协议主机收发数据流程

(1)主机发送数据

  • 主机在检测到总线为“空闲状态”(即 SDA、SCL 线均为高电平)时,发送一个启动信号“S”,开始一次通信的开始;
  • 主机接着发送一个从设备地址,它由7bit物理地址和1bit读写控制位R/w组成(此时R/W=0);
  • 相对应的从机收到命令字节后向主机回馈应答信号 ACK(ACK=0);
  • 主机收到从机的应答信号后开始发送第一个字节的数据;
  • 从机收到数据后返回一个应答信号 ACK;
  • 主机收到应答信号后再发送下一个数据字节;
  • 当主机发送最后一个数据字节并收到从机的 ACK 后,通过向从机发送一个停止信号P结束本次通信并释放总线。从机收到P信号后也退出与主机之间的通信。
    在这里插入图片描述

注意:

  • 主机通过发送地址码与对应的从机建立了通信关系,而挂接在总线上的其它从机虽然同时也收到了地址码,但因为与其自身的地址不相符合,因此提前退出与主机的通信;
  • 主机的一次发送通信,其发送的数据数量不受限制。主机是通过 P 信号通知发送的结束,从机收到 P 信号后退出本次通信;
  • 主机的每一次发送后都是通过从机的 ACK 信号了解从机的接收状况,如果应答错误则重发。

(2)主机接收数据

  • 主机发送起始信号后,接着发送地址字节(其中R/W=1);
  • 对应的从机收到地址字节后,返回一个应答信号并向主机发送数据;
  • 主机收到数据后向从机反馈一个应答信号ACK;
  • 从机收到应答信号后再向主机发送下一个数据;
  • 当主机完成接收数据后,向从机发送一个NAK,从机收到非应答信号后便停止发送;
  • 主机发送非应答信号后,再发送一个停止信号,释放总线结束通信。在这里插入图片描述

注意:

  • 主机所接收数据的数量是由主机自身决定,当发送“非应答信号NAK”时从机便结束传送并释放总线。
  • 非应答信号的两个作用:前一个数据接收成功,停止从机的再次发送。

4- SHT30传感器介绍

(1)SHT30简介

SHT30数字温湿度传感器采用业内知名的瑞士Sensirion公司推出的新一代SHT30温湿度传感器芯片,它能够提供极高的可靠性和出色的长期稳定性,具有功耗低、反应快、抗干扰能力强等优点。IIC通讯,兼容3.3V/5V,可以非常容易的集成到智能楼宇、天气站、仓库存储、养殖、孵化等应用场景中,其中小米的温湿度传感器使用的也是SHT30。

在这里插入图片描述

  • 高精度,内部自动校准,数字输出
  • 低功耗、响应速度快、抗干扰能力强
  • 兼容3.3V/5V控制器

(2)SHT30工作原理

SHT30 芯片有八个引脚,利用12C进行数据传输,具有两个可选地址,宽电源电压从2.4V到5.5V。下面是引脚说明:
在这里插入图片描述
下面是小熊座NB-loT开发板上SHT30传感器的原理图,其中12C接口连到了MCU的PB13和PB14两个引脚上,这两个引脚可以设置为12C2模式工作。
在这里插入图片描述


5- HAL库中I2C发送接收数据函数

开始实现HAL库实现I2C对SHT30温湿度采样我们首先了解一下HAL库中的两个函数。

(1)HAL_I2C_Master_Transmit()

(1)函数原型:

HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

(2)函数功能:IIC发送数据,主机需要将数据通过IIC发送过去
(3)参数介绍:

  • *hi2c 设置使用的是那个IIC
  • DevAddress 写入的地址,设置写入数据的地址
  • *pData 需要写入的数据
  • Size 要发送的字节数
  • Timeout 最大传输时间,超过传输时间将自动退出传输函数

(4) 使用到的函数参数讲解(举例子):

HAL_I2C_Master_Transmit(&hi2c2, SHT30_ADDR_WR, (uint8_t*)buf, 2, 0xFFFF);
  • &hi2c2:我们使用的是:hi2c2,传地址&hi2c2
  • SHT30_ADDR_WR:我们宏定义了写的地址,传写的地址c #define SHT30_ADDR_WR (SHT30_ADDR<<1)
  • (uint8_t*)buf:我们将需要传的数据保存在buf中
  • 2:传2个字节,16个位
  • 0xFFFF超时:oxFFFF(4 294 967 295也就是无符号整型所能表示的最大值)

(2)HAL_I2C_Master_Receive()

(1)函数原型:

HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

(2)函数功能:IIC接收数据,从机发送给主机,主机需要将数据通过IIC接收
(3)函数参数和HAL_I2C_Master_Transmit()大概是一样的,只是取到的数据保存在pData中。
(4)使用到的函数参数讲解(举例子):

HAL_I2C_Master_Receive(&hi2c2, SHT30_ADDR_RD, buf, SHT30_DATA_SIZE, 0xFFFF);
  • &hi2c2:我们使用的是:hi2c2,传地址&hi2c2
  • SHT30_ADDR_RD:我们宏定义了读的地址,传读的地址c #define SHT30_ADDR_RD ((SHT30_ADDR<<1) | 0x01)
  • buf:我们将获取到的数据保存在buf中
  • SHT30_DATA_SIZE:宏定义,6个字节c #define SHT30_DATA_SIZE 6
  • 0xFFFF超时:oxFFFF(4 294 967 295也就是无符号整型所能表示的最大值)

6- SHT30温湿度采样

(1)配置

先配置SHT30连接的I2C管脚PB13 和PB14为I2C模式,此时因为I2C功能并没有使能,管脚状态为黄色。接下来再在Connectivity里选择12C2并配置为I2C模式,这是12C功能配置完成。配置好之后按Ctrl+S将会自动生成I2C总线初始化代码。
在这里插入图片描述

(2)创建sht30.c

/*
 * sht30.c
 *
 *  Created on: Nov 3, 2022
 *      Author: Administrator
 */

#include <stdio.h>
#include "stm32l4xx_hal.h"
#include "i2c.h"
#include "sht30.h"

#define CONFIG_SHT30_DEBUG

#ifdef CONGIF_SHT30_DEBUG
#define sht30_print(format, args...) printf(format, ##args)
#else
#define sht30_print(format, args...) do{} while(0)
#endif

static int sht30_send_cmd(SHT30_CMD cmd)
{
	uint8_t buf[2];

	buf[0] = cmd >> 8;
	buf[1] = cmd & 0xFF;

	return HAL_I2C_Master_Transmit(&hi2c2, SHT30_ADDR_WR, (uint8_t*)buf, 2, 0xFFFF);
}

static void sht30_soft_reset(void)
{
	sht30_send_cmd(SOFT_RESET_CMD);
	HAL_Delay(1);
}

static int sht30_single_shot_measurement(uint8_t *buf, uint8_t buf_size)
{
	uint16_t cmd = HIGH_ENABLED_CMD;
	uint8_t  rv;

	if( !buf || buf_size < SHT30_DATA_SIZE )
	{
		sht30_print("%s(): Invalid input argument\n", __func__);
		return -1;
	}

	rv = sht30_send_cmd(cmd);
	if( rv )
	{
		sht30_print("ERROR: HST30 send messurement command failure, rv = &d\n", rv);
		sht30_soft_reset();
		return -2;
	}

	rv = HAL_I2C_Master_Receive(&hi2c2, SHT30_ADDR_RD, buf, SHT30_DATA_SIZE, 0xFFFF);
	if(rv)
	{
		sht30_print("ERROR: SHT30 read measurement result failure, rv = %d\n", rv);
		return -3;
	}

	return 0;
}

static uint8_t sht30_crc8(const uint8_t *data, int len)
{
	const uint8_t POLYNOMIAL = 0x31;
	uint8_t       crc = 0xFF;
	int           i,j;

	for (i=0; i<len; ++i)
	{
		crc ^= *data++;

		for (j=0; j<8; j++)
		{
			crc = (crc & 0x80)? (crc << 1) ^ POLYNOMIAL:(crc << 1);
		}
	}
	return crc;
}

int SHT30_SampleData(float *temperature, float *humidity)
{
	uint8_t buf[SHT30_DATA_SIZE];
	int rv;
	uint16_t temp;
	uint16_t humd;
	uint16_t crc;

	if(!temperature || !humidity)
	{
		sht30_print("%s(): Invalid input argument\n", __func__);
		return -1;
	}

	rv = sht30_single_shot_measurement(buf, SHT30_DATA_SIZE);
	if(rv)
	{
		sht30_print("SHT30 Single Short measurement failure, rv=%d\n", rv);
		return -2;
	}

#ifdef CONFIG_SHT30_DEBUG
	{
		int i;

		sht30_print("SHT30 get %d bytes sample data: \n", SHT30_DATA_SIZE);
		for(i=0; i<SHT30_DATA_SIZE; i++)
		{
			sht30_print("0x%02x ", buf[i]);
		}
		sht30_print("\n");
	}
#endif

	crc = sht30_crc8(buf, 2);
	sht30_print("SHT30 temperature Cal_CRC: [%02x] EXP_CRC: [%02x]\n", crc, buf[2]);
	if(crc != buf[2])
	{
		sht30_print("SHT30 measurement temperature got CRC error\n");
		return -3;
	}

	crc = sht30_crc8(&buf[3], 2);
	sht30_print("SHT30 humidity Cal_CRC: [%02x] EXP_CRC: [%02x]\n", crc, buf[5]);
	if(crc != buf[5])
	{
		sht30_print("SHT30 messurement temperature got CRC error\n");
		return -4;
	}

	temp = (buf[0]<<8) | buf[1];
	humd = (buf[3]<<8) | buf[4];

	*temperature = -45 + 175*((float)temp/65535);
	*humidity = 100 * ((float)humd/65535);

	return 0;
}

(3)创建sht30.h

/*
 * sht30.h
 *
 *  Created on: Nov 3, 2022
 *      Author: Administrator
 */

#ifndef INC_SHT30_H_
#define INC_SHT30_H_

#include "stm32l4xx_hal.h"

#define SHT30_ADDR 0x44

#define SHT30_ADDR_WR (SHT30_ADDR<<1)
#define SHT30_ADDR_RD ((SHT30_ADDR<<1) | 0x01)

#define SHT30_DATA_SIZE 6

typedef enum
{
	SOFT_RESET_CMD = 0x30A2,

	HIGH_ENABLED_CMD = 0x2C06,
	MEDIUM_ENABLED_CMD =0x2C0D,
	LOW_ENSABLED_CMD = 0x2C10,
	HIGH_DISABLED_CMD = 0x2400,
	MEDIUM_DISABLED_CMD = 0X240B,
	LOW_DISABLED_CMD = 0x2416,


	HIGH_0_5_CMD = 0x2032,
	MEDIUM_0_5_CMD = 0x2024,
	LOW_0_5_CMD = 0x202F,
	HIGH_1_CMD = 0x2130,
	MEDIUM_1_CMD = 0x2126,
	LOW_1_CMD = 0x212D,
	HIGH_2_CMD = 0x2236,

	MEDIUM_2_CMD = 0x2220,
	LOW_2_CMD = 0x222B,
	HIGH_4_CMD = 0x2334,
	MEDIUM_4_CMD = 0x2322,
	LOW_4_CMD = 0x2329,
	HIGH_10_CMD = 0x2737,
	MEDIUM_10_CMD = 0x2721,
	LOW_10_CMD = 0x272A,
}SHT30_CMD;

extern int SHT30_SampleData(float *temperature, float *humidity);

#endif /* INC_SHT30_H_ */

(4)修改main.c

printf()函数实现的代码前期博客中有,就不多说了。

/* USER CODE BEGIN Includes */
#include "sht30.h"
#include "string.h"
/* USER CODE END Includes */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
static int report_tempRH_json(void);
/* USER CODE END 0 */
  printf ("The sht30 starts to obtain temperature and humidity data.\r\n");
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  report_tempRH_json();
	  HAL_Delay(3000);
  }
  /* USER CODE END 3 */
/* USER CODE BEGIN 4 */
int report_tempRH_json(void)
{
	char      buf[128];
	float     temperature, humidity;

	if(SHT30_SampleData(&temperature, &humidity) < 0)
	{
		printf("ERROR: SHT30 Sample data failure\n");
		return -1;
	}

	memset(buf, 0, sizeof(buf));
	snprintf(buf, sizeof(buf), "{\"Temperature\":\"%.2f\", \"Humidity\":\"%.2f\"}", temperature, humidity);

	HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 0xFFFF);

	return 0;
}
/* USER CODE END 4 */

7- 结果呈现

对着开发板上的温湿度传感器哈气,就会看见变化,说明成功。
在这里插入图片描述


以下是SHT20模拟I2C的no hold master读取温度的程序,使用了STM32HAL库: ```c #include "stm32f1xx_hal.h" #define SHT20_ADDR 0x80 // SHT20地址 #define SHT20_TEMP_NOHOLD_CMD 0xF3 // 温度读取命令 I2C_HandleTypeDef hi2c1; // 初始化I2C总线 void I2C_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } } // 向SHT20发送温度读取命令 void SHT20_SendTempCmd(void) { uint8_t cmd = SHT20_TEMP_NOHOLD_CMD; HAL_I2C_Master_Transmit(&hi2c1, SHT20_ADDR, &cmd, 1, HAL_MAX_DELAY); } // 从SHT20读取温度数据 uint16_t SHT20_ReadTempData(void) { uint8_t data[2]; HAL_I2C_Master_Receive(&hi2c1, SHT20_ADDR, data, 2, HAL_MAX_DELAY); uint16_t temp = ((uint16_t)data[0] << 8) | data[1]; return temp; } // 计算温度值 float SHT20_CalcTemp(uint16_t tempData) { float temp = -46.85 + 175.72 * (float)tempData / 65536.0; return temp; } int main(void) { HAL_Init(); I2C_Init(); while (1) { SHT20_SendTempCmd(); // 发送温度读取命令 HAL_Delay(100); // 等待100ms,让SHT20完成温度转换 uint16_t tempData = SHT20_ReadTempData(); // 读取温度数据 float temp = SHT20_CalcTemp(tempData); // 计算温度值 printf("Temperature: %.2f°C\r\n", temp); // 打印温度值 HAL_Delay(1000); // 延时1秒 } } ``` 注意,SHT20的I2C地址是0x80,而不是0x40。在发送温度读取命令后,需要等待一段时间(通常为100ms),让SHT20完成温度转换。读取到的温度数据是16位的,需要根据SHT20的温度计算公式计算出实际的温度值。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值