STM32 I2C通信实现SHT20温湿度采样并在OLED显示屏显示

一、开发板实例:STM32L431RCT6

​ 开发板采用意法半导体(ST)公司的低功耗 Arm® Cortex®-M4 处理器STM32L431RCT6,其工作主频高达80MHz,并提供256KB Flash, 64KB SRAM存储空间。

二、SHT20原理

技术参数:

  • 工作电压:2.1~3.6VDC(请勿使用5V供电!)
  • 信号输出:I2C数字输出
  • 温度测量范围:-40~125°C 温度测量精度±0.3 °C
  • 湿度测量范围:0~100%RH 湿度测量精度±3%
  • 能耗:3.2uW(8位测量,1次/秒)

​ SHT20在采样时有两种工作模式:hold master模式和no hold master模式,具体采用哪种模式由命令决定。

​ 其中,hold master模式(温度命令为0xE3,湿度命令为0xE5)将会使用Clock Stretching机制来与MCU通信,工作时序如下:

​ 而no hold master模式(温度命令为0xF3,湿度命令为0xF5),工作时序如下:

三、采样

1、基于HAL库实现I2C采样

​ 首先配置SHT20连接的PB6和PB7为I2C模式,Ctrl+S保存代码,会自动生成I2C总线初始化代码。

2、GPIO模拟I2C实现采样

​一般CPU上的I2C总线个数有限制,很多引脚容易冲突导致没有I2C接口使用,此时我们可以直接拿两个GPIO口来模拟I2C总线。

3、代码实现

GPIO模拟I2C头文件gpio_i2c_sht20.h

/*
 * gpio_i2c_sht20.h
 *
 *  Created on: Aug 18, 2023
 *      Author: asus
 */

#ifndef INC_GPIO_I2C_SHT20_H_
#define INC_GPIO_I2C_SHT20_H_

enum{
	NO_ERROR = 0x00, // no error
	PARM_ERROR = 0x01, // parameter out of range error
	ACK_ERROR = 0x02, // no acknowledgment error
	CHECKSUM_ERROR = 0x04, // checksum mismatch error
	TIMEOUT_ERROR = 0x08, // timeout error
	BUS_ERROR = 0x10, // bus busy
};

enum{
	ACK_NONE,
	ACK,
	NAK,
};

extern int I2C_Master_Receive(uint8_t addr, uint8_t *buf, int len);
extern int I2C_Master_Transmit(uint8_t addr, uint8_t *data, int bytes);

#endif /* INC_GPIO_I2C_SHT20_H_ */

GPIO模拟I2C源文件gpio_i2c_sht20.c

/*
 * gpio_i2c_sht20.c
 *
 *  Created on: Aug 18, 2023
 *      Author: asus
 */
#include <stdio.h>
#include "stm32l4xx_hal.h"
#include "tim.h"
#include "gpio.h"
#include "gpio_i2c_sht20.h"

#define I2C_CLK_STRETCH_TIMEOUT 50

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


typedef struct i2c_gpio_s
{
	GPIO_TypeDef *group;
	uint16_t scl;
	uint16_t sda;
}i2c_gpio_t;

static i2c_gpio_t i2c_pins = {GPIOB,GPIO_PIN_6,GPIO_PIN_7};

#define SDA_IN() do{GPIO_InitTypeDef GPIO_InitStruct = {0};\
					GPIO_InitStruct.Pin = i2c_pins.sda;\
					GPIO_InitStruct.Mode = GPIO_MODE_INPUT;\
					GPIO_InitStruct.Pull = GPIO_PULLUP;\
					GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;\
					HAL_GPIO_Init(i2c_pins.group,&GPIO_InitStruct);\
					}while(0)

#define SDA_OUT() do{GPIO_InitTypeDef GPIO_InitStruct = {0};\
					 GPIO_InitStruct.Pin = i2c_pins.sda;\
					 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;\
					 GPIO_InitStruct.Pull = GPIO_PULLUP;\
					 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;\
					 HAL_GPIO_Init(i2c_pins.group,&GPIO_InitStruct);\
					 }while(0)

#define SCL_OUT() do{GPIO_InitTypeDef GPIO_InitStruct = {0};\
					 GPIO_InitStruct.Pin = i2c_pins.scl;\
					 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;\
					 GPIO_InitStruct.Pull = GPIO_PULLUP;\
					 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;\
					 HAL_GPIO_Init(i2c_pins.group,&GPIO_InitStruct);\
					 }while(0)

#define SCL_H() HAL_GPIO_WritePin(i2c_pins.group,i2c_pins.scl,GPIO_PIN_SET)
#define SCL_L() HAL_GPIO_WritePin(i2c_pins.group,i2c_pins.scl,GPIO_PIN_RESET)
#define SDA_H() HAL_GPIO_WritePin(i2c_pins.group,i2c_pins.sda,GPIO_PIN_SET)
#define SDA_L() HAL_GPIO_WritePin(i2c_pins.group,i2c_pins.sda,GPIO_PIN_RESET)

#define READ_SDA() HAL_GPIO_ReadPin(i2c_pins.group,i2c_pins.sda)
#define READ_SCL() HAL_GPIO_ReadPin(i2c_pins.group,i2c_pins.scl)

//使用时钟延伸
static inline uint8_t I2c_WaitWhileClockStretching(uint16_t timeout)
{
	while(timeout-- > 0)
	{
		if(READ_SCL())
			break;
		delay_us(1);
	}
	return timeout ? NO_ERROR :BUS_ERROR;
}

//起始信号
uint8_t I2c_StartCondition()
{
	uint8_t rv = NO_ERROR;
	SDA_OUT();
	SCL_OUT();

	SDA_H();
	delay_us(1);
	SCL_H();
	delay_us(1);

#ifdef I2C_CLK_STRETCH_TIMEOUT
	rv = I2c_WaitWhileClockStretching(I2C_CLK_STRETCH_TIMEOUT);
	if(rv)
	{
		i2c_print("ERROR:%s() I2C bus busy\n",__func__);
		return rv;
	}
#endif

	SDA_L();//先于SCL先拉低
	delay_us(2);
	SCL_L();//拉低SCL便于之后的操作
	delay_us(2);
	return rv;
}

//终止信号
uint8_t I2c_StopCondition(void)
{
	uint8_t rv = NO_ERROR;
	SDA_OUT();

	SCL_L();
	SDA_L();
	delay_us(2);
	SCL_H();
	delay_us(2);

#ifdef I2C_CLK_STRETCH_TIMEOUT
	rv = I2c_WaitWhileClockStretching(I2C_CLK_STRETCH_TIMEOUT);
	if(rv)
	{
		i2c_print("ERROR:%s() I2C bus busy\n",__func__);
	}
#endif

	SDA_H();
	delay_us(2);
	return rv;
}

//写一个字节
uint8_t I2c_WriteByte(uint8_t byte)
{
	uint8_t rv = NO_ERROR;
	uint8_t mask;
	SDA_OUT();
	SCL_L();
	for(mask=0x80;mask>0;mask>>=1)
	{
		if((mask & byte) == 0)
		{
			SDA_L();
		}
		else
		{
			SDA_H();
		}
		delay_us(5);
		SCL_H();
		delay_us(5);

#ifdef I2C_CLK_STRETCH_TIMEOUT
		rv = I2c_WaitWhileClockStretching(I2C_CLK_STRETCH_TIMEOUT);
		if(rv)
		{
			i2c_print("ERROR:%s() I2C bus busy\n",__func__);
			goto OUT;
		}
#endif

		SCL_L();
		delay_us(5);
	}
	SDA_IN();
	SCL_H();
	delay_us(5);

#ifdef I2C_CLK_STRETCH_TIMEOUT
	rv = I2c_WaitWhileClockStretching(I2C_CLK_STRETCH_TIMEOUT);
	if(rv)
	{
		i2c_print("ERROR:%s() I2C bus busy\n",__func__);
		goto OUT;
	}
#endif

	if(READ_SDA())
		rv = ACK_ERROR;

OUT:
	SCL_L();
	delay_us(20);
	return rv;
}

//读一个字节
uint8_t I2c_ReadByte(uint8_t *byte,uint8_t ack)
{
	uint8_t rv = NO_ERROR;
	uint8_t mask;
	*byte = 0x00;
	SDA_IN();
	for(mask = 0x80;mask > 0;mask >>= 1)
	{
		SCL_H();
		delay_us(1);

#ifdef I2C_CLK_STRETCH_TIMEOUT
		rv = I2c_WaitWhileClockStretching(I2C_CLK_STRETCH_TIMEOUT);
		if(rv)
		{
			i2c_print("ERROR:%s() I2C bus busy\n",__func__);
			goto OUT;
		}
#endif

		if(READ_SDA())
			*byte |= mask;
		SCL_L();
		delay_us(2);
	}

	if(ack == ACK)
	{
		SDA_OUT();
		SDA_L();
	}
	else if(ack ==NAK)
	{
		SDA_OUT();
		SDA_H();
	}
	delay_us(1);
	SCL_H();
	delay_us(2);

#ifdef I2C_CLK_STRETCH_TIMEOUT
	rv = I2c_WaitWhileClockStretching(I2C_CLK_STRETCH_TIMEOUT);
	if(rv)
	{
		i2c_print("ERROR:%s() I2C bus busy\n",__func__);
	}
#endif

OUT:
	SCL_L();
	delay_us(2);
	return rv;
}


uint8_t I2c_SendAddress(uint8_t addr)
{
	return I2c_WriteByte(addr);
}

//接收从机发来的信号
int I2C_Master_Receive(uint8_t addr,uint8_t *buf,int len)
{
	int i;
	int rv = NO_ERROR;
	uint8_t byte;

	I2c_StartCondition();
	rv = I2c_WriteByte(addr);
	if(rv)
	{
		i2c_print("Send I2C read address failure,rv=%d\n",rv);
		goto OUT;
	}
#ifdef I2C_CLK_STRETCH_TIMEOUT
	rv = I2c_WaitWhileClockStretching(I2C_CLK_STRETCH_TIMEOUT);
	if(rv)
	{
		i2c_print("ERROR:%s() I2C wait clock stretching failure,rv=%d\n",__func__,rv);
		return rv;
	}
#endif

	for(i=0;i<len;i++)
	{
		if(!I2c_ReadByte(&byte,ACK))
			buf[i] = byte;
		else
			goto OUT;
	}

OUT:
	I2c_StopCondition();
	return rv;
}

//发送命令
int I2C_Master_Transmit(uint8_t addr, uint8_t *data, int bytes)
{
	int i;
	int rv = NO_ERROR;

	if(!data)
	{
		return PARM_ERROR;
	}

	i2c_print("I2C Mastr start transimit [%d] bytes data to addr [0x%02x]\n", bytes, addr);
	I2c_StartCondition();

	rv = I2c_SendAddress(addr);
	if(rv)
	{
		goto OUT;
	}

	for(i=0;i<bytes;i++)
	{
		if(NO_ERROR != (rv=I2c_WriteByte(data[i])))
			break;
	}

OUT:
	I2c_StopCondition();
	return rv;
}

SHT20的驱动源文件sht20.c

/*
 * sht20.c
 *
 *  Created on: Jul 21, 2023
 *      Author: asus
 */


#include <stdio.h>
#include "stm32l4xx_hal.h"
#include "sht20.h"
#include "tim.h"


/*通过该宏控制是使用HAL库里的I2C接口还是CPIO模拟串口的接口*/
#define CONFIG_GPIO_I2C

#ifdef CONFIG_GPIO_I2C
#include "gpio_i2c_sht20.h"
#else
#include "i2c.h"
#endif

//#define CONFIG_SHT20_DEBUG //用于调试,打开时由printf,发布产品时注释掉,printf语句会被替换成do{}while(0)

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

int SHT20_SampleData(uint8_t cmd,float *data)
{
	uint8_t buf[2];
	float sht20_data = 0.0;
	int rv;

#ifdef CONFIG_GPIO_I2C
	rv = I2C_Master_Transmit(0x80,&cmd,1); //GPIO模拟I2C采样
#else
	rv = HAL_I2C_Master_Transmit(&hi2c1,SHT20_ADDR_WR,&cmd,1,0xFFFF);//基于HAL库实现I2C采样
#endif

	if(0 != rv)
	{
		return -1;
	}
	if(cmd == 0xF3)
	{
		HAL_Delay(85);
	}
	else if(cmd == 0xF5)
	{
		HAL_Delay(29);
	}

#ifdef CONFIG_GPIO_I2C
	rv = I2C_Master_Receive(0x81,buf,2);
#else
	rv = HAL_I2C_Master_Receive(&hi2c1,SHT20_ADDR_RD,buf,2,0xFFFF);
#endif

	if(0 != rv)
	{
		return -1;
	}
	sht20_data = buf[0];
	sht20_data=ldexp(sht20_data,8);
	sht20_data += buf[1]&0xFC;
	if(cmd == 0xF3)
	{
		*data = (-46.85+175.72*sht20_data/65536);
	}
	else if(cmd == 0xF5)
	{
		*data = (-6+125*sht20_data/65536);
	}
	return *data;
}

SHT20的驱动头文件sht20.h

/*
 * sht20.h
 *
 *  Created on: Jul 21, 2023
 *      Author: asus
 */

#ifndef INC_SHT20_H_
#define INC_SHT20_H_

#include "stm32l4xx_hal.h"

#define SHT20_ADDR 0x40

#define SHT20_ADDR_WR (SHT20_ADDR<<1)	//写地址
#define SHT20_ADDR_RD ((SHT20_ADDR<<1) | 0x01)	//读地址

#define SOFT_RESET_CMD 0xFE	//复位

#define TEMP_CMD 0xE3
#define RH_CMD 0xE5



extern int SHT20_SampleData(uint8_t cmd,float *data);

#endif /* INC_SHT20_H_ */

修改main.c文件

int report_tempRH_json(void)
{
	char buf[128],temp[20],humd[20];
	float temperature,humidity;

	SHT20_SampleData(0xF3,&temperature);//温度
	SHT20_SampleData(0xF5,&humidity);//湿度

	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;
}

四、OLED显示

1、OLED显示屏介绍

​ 开发板上配备的OLED显示屏通过I2C接口连接到单片机的PB6,PB7管脚上。

2、添加OLED驱动文件

修改main.c文件

OLED_Init();//初始化


int report_tempRH_json(void)
{
	char buf[128],temp[20],humd[20];
	float temperature,humidity;


	SHT20_SampleData(0xF3,&temperature);
	SHT20_SampleData(0xF5,&humidity);

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

    /*将温度和湿度转为字符串形式*/
	memset(temp,0,sizeof(temp));
	snprintf(temp,sizeof(temp),"%.2f",temperature);
	memset(humd,0,sizeof(humd));
	snprintf(humd,sizeof(humd),"%.2f",humidity);

    /*调用函数在OLED显示屏上显示*/
//	OLED_ShowString(0,0,"Temperature:",16);//显示英文字符

	OLED_ShowChinese(&Hzk_Temp,0,0,0);//显示汉语
	OLED_ShowChinese(&Hzk_Temp,32,0,1);
	OLED_ShowString(48,0,":",16);

	OLED_ShowString(72,0,temp,16);//显示温度


//	OLED_ShowString(0,2,"Humidity:",16);

	OLED_ShowChinese(&Hzk_Humd,0,2,0);
	OLED_ShowChinese(&Hzk_Humd,32,2,1);
	OLED_ShowString(48,2,":",16);

	OLED_ShowString(72,2,humd,16);


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

	return 0;
}

posted @ 2023-10-19 17:04  梨子Li  阅读(579)  评论(0编辑  收藏  举报