STM32物联网项目:使用IIC通信采集SHT30温湿度数据

SHT30温湿度采集(IIC通信)

SHT30数字温湿度传感器

SHT3x湿度传感器系列包括低成本版本SHT30、标准版本SHT31,以及高端版本SHT35。

SHT3x湿度传感器系列结合了多种功能和各种接口(I2C、模拟电压输出),应用友好,工作电压范围宽(2.15至5.5 V),适合各类应用。

SHT3x建立在全新和优化的CMOSens® 芯片之上,进一步提高了产品可靠性和精度规格。SHT3x提供了一系列新功能,如增强信号处理、两个独特和用户可选I2C地址、一个可编程温湿度极限的报警模式,以及高达1 MHz的通信速度。

DFN封装的占位面积为2.5 × 2.5 mm2,高度为0.9 mm。这有助于SHT3x集成到多种应用。此外,2.15至5.5 V的宽电源电压范围和多种可选接口可保证与不同集成要求的兼容性。总之,SHT3x湿度传感器系列融入了Sensirion作为行业领导者15年的经验和心血,是一款超高性价比的产品。

特点

完全校准,线性化,和温度补偿数字输出

宽电源电压范围,从2.4到5.5 V

IIC接口通信速度1 MHz和两个用户可选择的地址

典型精度2%RH(湿度)和0.3°C(温度)

非常快速启动和测量时间

小8针DFN包(封装)

32引脚既作输入又作输出

在51单片机中,引脚都叫通用输入输出引脚,是具有双向功能的,32单片机的引脚也可以

开漏输出框图

CPU往位设置寄存器写入1,则高电平1来到输出控制电路这里,N-MOS管不会导通,此时IO端口的电平状态由外部的上拉或者下拉决定,可能为高电平也可能为低电平;同时IO端口的状态可以被读取,通过虚线的电路被CPU读取,此时读取的并不一定是CPU输出的高电平1,而是外部的状态

CPU如果往位设置寄存器写入0,则低电平0来到输出控制寄存器,N-MOS管导通,IO端口与VSS导通,而VSS是电路公共地端电压,一般接GND,所以IO端口就输出低电平0;同时CPU读取时端口状态就是自己输出的低电平0

所以IO引脚配置为开漏输出就既可以作输出又可以作输入了,并不需要再配置改变IO口的工作模式

SHT30通信引脚配置

因为SHT30是IIC通信的,SCL与SDA均配置为开漏输出,总线上均有上拉电阻。因此,SDA作为输出的同时,也可以作为输入使用。与51内核的准双向口一样,作为输入使用时,需要先输出高电平,关闭内部的NMOS管。

CubeMX配置

因为IIC通信的SCL引脚接到了PG11,SDA接到了PG12,所以需要配置这两个引脚为开漏输出

PG11和PG12都配置为开漏输出,并输出高电平,因为是通信,输出速度可以选高速

其他配置如定时器6,数码管,串口,LED灯按照之前实验中配置方法就行,系统时钟72MHz

程序

IIC.c

根据IIC通信时序图编写函数,本次使用软件模拟IIC通信的方式,对IIC通信加深理解

IIC通信介绍可以参考:http://t.csdn.cn/xvYI5

/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/
#define SET_SCL HAL_GPIO_WritePin(SCL_GPIO_Port,SCL_Pin,GPIO_PIN_SET)
#define CLR_SCL HAL_GPIO_WritePin(SCL_GPIO_Port,SCL_Pin,GPIO_PIN_RESET)

#define SET_SDA HAL_GPIO_WritePin(SDA_GPIO_Port,SDA_Pin,GPIO_PIN_SET)
#define CLR_SDA HAL_GPIO_WritePin(SDA_GPIO_Port,SDA_Pin,GPIO_PIN_RESET)

#define READ_SDA HAL_GPIO_ReadPin(SDA_GPIO_Port,SDA_Pin)

/* Private function prototypes------------------------------------------------*/
static void IIC_Init(void);
static void IIC_Start(void);
static void IIC_Stop(void);
static void Write_Byte(uint8_t );
static uint8_t Recive_Byte(void);
static void SendACK(ACK_Value_t );
static ACK_Value_t ReciveACK(void);
static void IIC_Delay_us(uint8_t );
/* Private variables----------------------------------------------------------*/

/* Public variables-----------------------------------------------------------*/
IIC_Soft_t IIC_Soft = 
{
    IIC_Init,
    IIC_Start,
    IIC_Stop,
    Write_Byte,
    Recive_Byte,
    SendACK,
    ReciveACK
};

/*
* @name   IIC_Init
* @brief  IIC初始化
* @param  None
* @retval None
*/
static void IIC_Init()
{
    SET_SCL;
    SET_SDA;
}

/*
* @name   IIC_Start
* @brief  起始信号
* @param  None
* @retval None
*/
static void IIC_Start()
{
    //SCL为高电平时,SDA的下降沿为IIC的起始信号
    SET_SCL;
    SET_SDA;
    IIC_Delay_us(1);
    CLR_SDA;
    IIC_Delay_us(10);
    CLR_SCL;
    IIC_Delay_us(1);
}

/*
* @name   IIC_Stop
* @brief  停止信号
* @param  None
* @retval None
*/
static void IIC_Stop()
{  
    //SCL为高电平时,SDA的上升沿为IIC的停止信号
    CLR_SDA;
    SET_SCL;
    IIC_Delay_us(10);
    SET_SDA;
}

/*
* @name   Write_Byte
* @brief  主机发送一个字节
* @param  WR_Byte:要发送的数据
* @retval None
*/
static void Write_Byte(uint8_t WR_Byte)
{
    uint8_t i;
    //传输一个字节
    /*SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
	数据按8位传输,高位在前,利用for循环逐个接收*/
    for(i=0;i<8;i++)
    {
        CLR_SCL;            //拉低SCL
        IIC_Delay_us(1);
        if((WR_Byte&BIT7) == BIT7)	//如果一个字节最高位为1
        {
            SET_SDA;        //往SDA上放1
        }
        else
        {
            CLR_SDA;        //否则往SDA上放0
        }
        IIC_Delay_us(1);
        SET_SCL;            //拉高SCL,从机读取数据
        IIC_Delay_us(10);
        WR_Byte <<= 1;      //左移一位,发送下一位
    }
}

/*
* @name   Recive_Byte
* @brief  主机接收一个字节
* @param  None
* @retval 接收到的数据
*/
static uint8_t Recive_Byte()
{
    uint8_t i,Re_Byte = 0x00;

    //释放SDA,将控制权交给从机,即SDA = 1
    SET_SDA;
    //接收一个字节
    for(i=0;i<8;i++)
    {
        CLR_SCL;    //拉低SCL,从机SDA准备数据
        IIC_Delay_us(10);

        SET_SCL;    //拉高SCL,读取数据
        IIC_Delay_us(10);
        if(READ_SDA == GPIO_PIN_SET)
        {
            Re_Byte|=(BIT7>>i);
        } 
    }
    return Re_Byte;
}

/*
* @name   SendACK
* @brief  主机发送应答
* @param  ACK_Value:ACK:应答,NACK:非应答
* @retval None
*/
static void SendACK(ACK_Value_t ACK_Value)
{
    CLR_SCL;        //拉低SCL,主机准备发送应答信息
    IIC_Delay_us(1);
    //发送应答信息
    if(ACK_Value == ACK)
    {
        CLR_SDA;    //0表示应答       
    }
    else
    {
        SET_SDA;    //1表示非应答
    }
    IIC_Delay_us(1);
    SET_SCL;        //拉高SCL,主机发送应答
    IIC_Delay_us(1);
    //释放SDA数据线
	//SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACk信号
    CLR_SCL;        //拉低SCL
    SET_SDA;        //因为主机发送应答后,从机可能会继续发送数据,所以控制权要给到从机
    IIC_Delay_us(1);
}

/*
* @name   ReciveACK
* @brief  主机接收应答,判断从机是否应答
* @param  None
* @retval 从机的应答
*/
static ACK_Value_t ReciveACK()
{
    uint8_t RecAck;
    CLR_SCL;            //拉低SCL
    SET_SDA;            //主机释放SDA,将控制权交给从机,即SDA = 1
    IIC_Delay_us(1);
    SET_SCL;            //拉高SCL,读取SDA的值
    IIC_Delay_us(10);
    RecAck = READ_SDA;  //读取SDA的值,0表示应答,1表示非应答
    CLR_SCL;
    IIC_Delay_us(1);
    return (ACK_Value_t)RecAck;
}

/*
* @name   IIC_Delay_us
* @brief  IIC延时函数——微秒级,
* @param  us:要延时的时间
* @retval None
*/
static void IIC_Delay_us(uint8_t us)
{
    uint16_t i;
	//通过示波器测量进行校准
    while(us--)
    {
        for(i=0;i<7;i++);
    }
}
/********************************************************
  End Of File
********************************************************/

SHT30.c

根据SHT30的数据手册来编写通信函数

4.5定期数据采集模式的测量命令

在这种模式下,一个发出的测量命令会产生一个数据对流。每个数据对由一个16位温度值和一个16位湿度值(按此顺序)组成。在周期模式下,可以选择不同的测量命令。对应的16位命令如表9所示。它们在可重复性(低、中、高)和数据采集频率(每秒0.5、1、2、4和10次测量值,mps)方面存在差异。在此模式下不能选择时钟拉伸。数据采集频率和可重复性设置会影响传感器的测量持续时间和电流消耗。

根据表格下方的通信时序,主机发送起始信号,然后发送从机地址加写指令命令,等待从机应答,再发送命令的高位(MSB),等待从机应答,再发送命令的低位(LSB)

调用IIC的函数,根据通信时序,完成设置周期测量模式

//设置周期测量模式
IIC_Soft.IIC_Start();
IIC_Soft.Write_Byte(SHT30_ADDR&WRITE_CMD);
IIC_Soft.ReciveACK();
IIC_Soft.Write_Byte(0x27);  //MSB,设置Repeatability为High,mps为10的测量模式
IIC_Soft.ReciveACK();
IIC_Soft.Write_Byte(0x37);	//LSB
IIC_Soft.ReciveACK();

4.6周期模式下测量结果的读数

读数可以通过表10所示的命令启动。如果没有测量数据,I2C读取头用NACK而不是ACK(表10中的位9)响应,通信停止。

空白的数据块是由32发送,黑色的数据块是由SHT30发送

根据表10的通信时序,获取SHT30的温湿度数据,读数据的命令是0xE000,获取的是16位的温度值和16位的湿度值,温度和湿度后都有CRC-8校验

从SHT30读出的16位温度和16位湿度还不是真正的温湿度值,而是要根据上面这个公式来计算得到正确的温湿度值,RH是湿度,T是温度

通信时序代码

在通过公式计算温湿度值时,因为216 – 1是65535,175/65535 = 0.002670328…,结果除不尽,但浮点型float的精度为6~7位有效数字,直接计算的话相当于把小数点后7位之后的数据省略掉了,会造成计算出来的温湿度精度有些损失,所以有了这样的方法:先让公式的数值都*100,这样的话计算出来的温湿度值也会 *100,但这样计算的小数位就会多两位,精度较为准确,在赋给最后结果变量前再 *0.01,将结果变回正常值即可

 	//读取数据
    Timer6.SHT30_Measure_Timeout = 0;
    do
    {
        //如果2秒内没有获取到数据,则break退出
        if(Timer6.SHT30_Measure_Timeout >= TIMER_2s)
        {
            LED.LED_Fun(LED2,LED_OFF);
            break;
        }
        IIC_Soft.IIC_Start();
        IIC_Soft.Write_Byte(SHT30_ADDR&WRITE_CMD);
        IIC_Soft.ReciveACK();
        //0xE000是向SHT30取数据的指令,主机发送该指令后开始读取SHT30的温湿度数据
        IIC_Soft.Write_Byte(0xE0);  
        IIC_Soft.ReciveACK();
        IIC_Soft.Write_Byte(0x00);
        IIC_Soft.ReciveACK();
        //重新发送起始信号,往SHT30发送地址加读取数据指令
        IIC_Soft.IIC_Start();
        IIC_Soft.Write_Byte(SHT30_ADDR|READ_CMD);
    /*判断是否收到从机应答,主机收到NACK时,继续循环发送取数据指令,主机收到ACK时,不满足
    条件,退出,往下执行*/
    } while (IIC_Soft.ReciveACK() == NACK); 
	//接收温湿度数据
    //判断是否是超时退出,如果不是,则有数据,进行接收;如果是则没有数据,不处理
    if(Timer6.SHT30_Measure_Timeout < TIMER_2s)
    {
        temp_array[0] = IIC_Soft.Recive_Byte();IIC_Soft.SendACK(ACK);
        temp_array[1] = IIC_Soft.Recive_Byte();IIC_Soft.SendACK(ACK);
        temp_array[2] = IIC_Soft.Recive_Byte();IIC_Soft.SendACK(ACK);
        temp_array[3] = IIC_Soft.Recive_Byte();IIC_Soft.SendACK(ACK);
        temp_array[4] = IIC_Soft.Recive_Byte();IIC_Soft.SendACK(ACK);
        temp_array[5] = IIC_Soft.Recive_Byte();IIC_Soft.SendACK(NACK);
        IIC_Soft.IIC_Stop();

        //计算温度
        if(CRC_8(temp_array,2) == temp_array[2])                //进行CRC-8校验
        {
            temp_uint = temp_array[0]*256+temp_array[1];        //取出16位的温度值
            temp_float = ((float)temp_uint)*0.267032-4500;    //根据手册公式计算,为了精度,计算数值先*100
            SHT30.fTemperature = temp_float*0.01;             //再除以100,得到正常温度值
        }

        //计算湿度
        if(CRC_8(&temp_array[3],2) == temp_array[5])            //进行CRC-8校验
        {
            temp_uint = temp_array[3]*256+temp_array[4];        //取出16位的湿度值
            temp_float = ((float)temp_uint)*0.152590;           //根据手册公式计算
            SHT30.ucHumidity = (uint8_t)(temp_float*0.01);      //除以100,得到正常湿度值
        }
        LED.LED_Fun(LED1,LED_OFF);	//转换完成LED1熄灭
    }
}

CRC-8检验函数

/*
* @name   CRC_8
* @brief  CRC-8校验
* @param  Crc_ptr -> 校验数据首地址
		LEN     -> 校验数据长度
* @retval CRC_Value -> 校验值      
*/
static uint8_t CRC_8(uint8_t *Crc_ptr,uint8_t LEN)
{
	uint8_t CRC_Value = 0xFF;
	uint8_t i = 0,j = 0;

	for(i=0;i<LEN;i++)
	{
		CRC_Value ^= *(Crc_ptr+i);
		for(j=0;j<8;j++)
		{
			if(CRC_Value & 0x80)
				CRC_Value = (CRC_Value << 1) ^ 0x31;
			else
				CRC_Value = (CRC_Value << 1);
		}
	}
	return CRC_Value;
}

System.c

系统运行函数中实现串口打印以及数码管显示温湿度值

/*
* @name   Run
* @brief  系统运行
* @param  None
* @retval None   
*/
static void Run()
{
  float   Temp_float = 0;
  uint16_t Temp_uint  = 0;
  //调用SHT30周期检测函数
  SHT30.Measure_Period_Mode();

  //串口打印温湿度
  printf("温度:%.1f度\r\n",SHT30.fTemperature);
  printf("湿度:%d%%RH\r\n\r\n",SHT30.ucHumidity);

  //判断温度正负
  if(SHT30.fTemperature < 0)
  {
    Temp_float = 0 - SHT30.fTemperature;
    Display.Disp_Other(Disp_NUM_4,0x40,Disp_DP_OFF);
  }
  else
  {
    Temp_float = SHT30.fTemperature;
    Display.Disp_Other(Disp_NUM_4,0x00,Disp_DP_OFF);
  }

  //显示温度
  Temp_uint = (uint16_t)(Temp_float*10); //将温度值乘以10,显示小数点后一位
  Display.Disp_Hex(Disp_NUM_3,Temp_uint/100,Disp_DP_OFF);   //显示十位
  Display.Disp_Hex(Disp_NUM_2,Temp_uint%100/10,Disp_DP_ON);  //显示个位,开启小数点
  Display.Disp_Hex(Disp_NUM_1,Temp_uint%10,Disp_DP_OFF);    //显示小数点后一位

  //显示湿度
  Display.Disp_Hex(Disp_NUM_6,SHT30.ucHumidity/10,Disp_DP_OFF);
  Display.Disp_Hex(Disp_NUM_5,SHT30.ucHumidity%10,Disp_DP_OFF);

  //延时
  HAL_Delay(500);
}

实验效果

串口打印

数码管显示

物联沃分享整理
物联沃-IOTWORD物联网 » STM32物联网项目:使用IIC通信采集SHT30温湿度数据

发表评论