STM32 GPIO模拟i2c通信实现sht20的温湿度采样 并以JSON格式上报(串口调试助手为例)_sht20 stm32-程序员宅基地

技术标签: stm32  嵌入式硬件  单片机  

一、先了解I2C协议

由时钟线SCL和数据线SDA构成的通信线路,利用上拉电阻将它们拉成高电平(表示总线空闲)     

I2C总线可以有多个从设备,且每个从设备都有一个唯一的7bit地址物理识别,因为I2C地址全0为广播地址,所以I2C总线理论上最多能带2^7-1=127个从设备

(I2C:半双工通信的同步串行通信协议,采用电平信号,数据传输采用大端方式MSB,先发高位数据)

I2C总线通信时序:

I2C协议的起始信号(start):当SCL保持高电平时,SDA出现一个下降沿,产生起始位

I2C协议的停止信号(stop):当SCL保持高电平时,SDA出现一个上升沿,产生停止位

(停止通信后,总线空闲,处于高电平)

主设备向从设备发送从设备地址信号,在收到从设备的应答信号后通讯连接才建立成功

若未收到应答则表示寻址失败。

希望继续,则给出“应答(ACK)”信号,即SDA为低电平

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

(建立通信后才开始传输数据位)

(读写方向位:RW,0为写操作,1为读操作)

主机发送数据流程:

1、主机在检测到总线为空闲时,发送一个启动信号"S",开始一次通信的开始

2、主机接着发送一个从设备地址,它由7bit物理地址和1bit读写控制位R/W组成(此时RW=0)(发送的地址有7位的物理地址和1位的读写方向号)

3、相对应的从机收到命令字节后向主机回馈应答信号ACK(ACK=D)

4、主机收到从机的应答信号后开始发送第一个字节的数据;

5、从机收到数据后返回一个应答信号ACK;

6、主机收到应答信号后再发送下一个数据字节;

7、主机发完最后一个字节并收到ACK后,向从机发送一个停止信号P结束本次通信并释放总线;

8、从机收到p信号后也退出与主机之间的通信;

主机接收数据流程:

1、主机发送启动信号后,接着发送地址字节(其中R/W=1) :

2、对应的从机收到地址字节后,返回一个应答信号并向主机发送数据;

3、主机收到数据后向从机反馈一个应答信号ACK:

4、从机收到应答信号后再向主机发送下一个数据:

5、当主机完成接收数据后,向从机发送一个NAK,从机收到非应答信号后便停止发送;

6、主机发送非应答信号后,再发送一个停止信号,释放总线结束通信

stm32l431rct6的i2c引脚分配(本例我们使用引脚PB6和PB7为例)

 二、了解sht20

stm32l431rct6的温湿度传感器引脚分配

三、开整

记得把串口使能了(这里我使用的是串口1),如下,其他的我相信你们都配好了(ctr+s就可以直接生成代码哦)

1、在 Core/Src 下创建并编写 SHT20 温湿度传感器的驱动源文件 sht20.c

 

 sht20.c代码如下
#include<stdio.h>
#include "stm32l4xx_hal.h"
#include "sht20.h"
#include "tim.h"

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

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

/*采样*/
int SHT20_Sample_data(uint8_t cmd, float *data)
{
	uint8_t 	buff[2];
	float 		sht20_data=0.0;
	int			rv;
    /*主设备向发送从设备地址信号,这里sht20的写地址是0x80,成功则返回1个字节*/
	rv=I2C_Master_Transmit(0x80,&cmd,1); 
	if(0!=rv)
	{
		return -1;
	}
	if(cmd==0xf3)
	{
		HAL_Delay(85);
	}
	else if(cmd==0xf5)
	{
		HAL_Delay(29);
	}
    /*主设备向发送从设备地址信号,这里sht20的读地址是0x81,成功则返回2个字节,分别是温湿度的整数                
     *和小数,并且数据放在buff中*/
	rv=I2C_Master_Receive(0x81,buff,2);
	if(0!=rv)
    {
		return -1;
    }
	sht20_data=buff[0];
	sht20_data=ldexp(sht20_data,8);
	sht20_data+=buff[1]&0xFC;
	if(cmd==0xf3)    //0xf3为读温度的信号
	{
		*data=(-46.85+175.72*sht20_data/65536);//计算温度的公式
	}
	else if(cmd==0xf5)    //0xf5为读湿度的信号
	{
		*data=(-6.0+125.0*sht20_data/65536);//计算湿度的公式
	}
	return *data;

}

2、同理,在 Core/Inc 下创建并编写 SHT20 温湿度传感器的驱动头文件 sht20.h

 sht20.h代码如下:
#ifndef INC_SHT20_H_
#define INC_SHT20_H_

#include "stm32l4xx_hal.h"

#define  add_w  0x80  //地址写
#define  add_r  0x81  //地址读
#define measure_temp 0xf3  //读取温度
#define measure_hum 0xf5   //读取湿度

#define user_code_w 0xe6
#define user_code_r 0xe7

#define RST_code 0xfe	//复位

int SHT20_Sample_data(uint8_t cmd, float *data);
#endif /* INC_SHT20_H_ */

3、同理,在 Core/Src 下创建并编写 GPIO 模拟 I2C 驱动源文件 gpioi2c.c

gpioi2c.c代码如下:
#include <stdio.h>
#include "stm32l4xx_hal.h"
#include "tim.h" /* delay_us() implement */
#include "gpio.h"
#include "gpioi2c.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

/* GPIO Simulate I2C Bus pins */
typedef struct i2c_gpio_s
{
		GPIO_TypeDef *group;
		uint16_t scl; /* SCL */
		uint16_t sda; /* SDA */
} i2c_gpio_t;

static i2c_gpio_t i2c_pins = { GPIOB, GPIO_PIN_6/*SCL*/, GPIO_PIN_7/*SDA*/ };

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

/* StartCondition(S) */
uint8_t I2c_StartCondition()
{
			uint8_t rv = NO_ERROR;
			SDA_OUT();
			SCL_OUT();
/* StartCondition(S): A high to low transition on the SDA line while SCL is high.
		_______
SCL: 		   |___
		_____
SDA: 		 |_____
*/
			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();
			delay_us(2);
			SCL_L();
			delay_us(2);
			return rv;
}

/* StopCondition(P) */
uint8_t I2c_StopCondition(void)
{
			uint8_t rv = NO_ERROR;
			SDA_OUT();
/* StopCondition(P): A low to high transition on the SDA line while SCL is high.
		 _____
SCL: ___|
			_____
SDA: ______|
*/
			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;
			/* Data line changes must happened when SCL is low */
			SDA_OUT();
			SCL_L();
			/* 1Byte=8bit, MSB send: bit[7]-->bit[0] */
			for(mask=0x80; mask>0; mask>>=1)
			{
				if((mask & byte) == 0)
				{
					SDA_L();
				}
				else
				{
					SDA_H();
				}
				delay_us(5); // data set-up time (t_SU;DAT)
				SCL_H();
				delay_us(5); // SCL high time (t_HIGH)
#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); // data hold time(t_HD;DAT)
			}
			/* clk #9 wait ACK/NAK from slave */
			SDA_IN();
			SCL_H(); // clk #9 for ack
			delay_us(5); // data set-up time (t_SU;DAT)
#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
			 /* High level means NAK */
			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();
		/* 1Byte=8bit, MSB send: bit[7]-->bit[0] */
		for(mask = 0x80; mask > 0; mask >>= 1)
		{
			SCL_H(); // start clock on SCL-line
			delay_us(1); // clock set-up time (t_SU;CLK)
#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; // read bit
			SCL_L();
			delay_us(2); // data hold time(t_HD;DAT)
		}
		/* clk #9 send ACK/NAK to slave */
		if(ack == ACK)
		{
			SDA_OUT();
			SDA_L(); // send Acknowledge if necessary
		}
		else if( ack == NAK )
		{
			SDA_OUT();
			SDA_H(); // send NotAcknowledge if necessary
		}
		delay_us(1); // data set-up time (t_SU;DAT)
		SCL_H(); // clk #9 for ack
		delay_us(2); // SCL high time (t_HIGH)
#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); // wait to see byte package on scope
		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_SendAddress(addr);
			if( rv )
			{
				i2c_print("Send I2C read address failure, rv=%d\n", rv);
				goto OUT;
			}
#ifdef I2C_CLK_STRETCH_TIMEOUT
			/* wait while clock streching */
			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;
}

4、同理,在 Core/Inc 下创建并编写 GPIO 模拟 I2C 头文件 gpioi2c.h

gpioi2c.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, /* Without ACK/NAK Reply */
	ACK, /* Reply with ACK */
	NAK, /* Reply with 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);

5、修改main.c中的代码,找到下列相应位置,添加如图代码即可

 

 编写好之后,点击运行代码

 将程序烧录至开发板

 

串口调试助手连接板子,在板子上按复位键就可以看到打印信息了,如下:

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/2201_75917183/article/details/131644023

智能推荐

什么是内部类?成员内部类、静态内部类、局部内部类和匿名内部类的区别及作用?_成员内部类和局部内部类的区别-程序员宅基地

文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别

分布式系统_分布式系统运维工具-程序员宅基地

文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具

用Exce分析l数据极简入门_exce l趋势分析数据量-程序员宅基地

文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量

宁盾堡垒机双因素认证方案_horizon宁盾双因素配置-程序员宅基地

文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置

谷歌浏览器安装(Win、Linux、离线安装)_chrome linux debian离线安装依赖-程序员宅基地

文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖

烤仔TVの尚书房 | 逃离北上广?不如押宝越南“北上广”-程序员宅基地

文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...

随便推点

java spark的使用和配置_使用java调用spark注册进去的程序-程序员宅基地

文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序

汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用_uds协议栈 源代码-程序员宅基地

文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码

AUTOSAR基础篇之OS(下)_autosar 定义了 5 种多核支持类型-程序员宅基地

文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型

VS报错无法打开自己写的头文件_vs2013打不开自己定义的头文件-程序员宅基地

文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件

【Redis】Redis基础命令集详解_redis命令-程序员宅基地

文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令

URP渲染管线简介-程序员宅基地

文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线

推荐文章

热门文章

相关标签