STM32 GPIO模拟i2c通信实现sht20的温湿度采样 并以JSON格式上报(串口调试助手为例)

文章详细介绍了如何使用STM32L431RCT6微控制器通过I2C协议与SHT20温湿度传感器进行通信。首先解释了I2C协议的基本原理,包括起始和停止信号、从设备地址及数据传输过程。然后,讨论了STM32L431RCT6的I2C引脚分配以及SHT20的传感器引脚配置。接着,提供了SHT20驱动代码的编写步骤,包括发送和接收数据的函数实现。最后,提到了使用GPIO模拟I2C通信的驱动代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、先了解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中的代码,找到下列相应位置,添加如图代码即可

 

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

 将程序烧录至开发板

 

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

嗯,用户让我分析基于STM32F103C8T6的智能粉尘检测系统所涉及的嵌入式知识点,并拆解原理。首先,我需要理解整个系统的组成部分。系统主控是STM32,传感器包括温湿度和粉尘浓度,数据通过WiFi上传云端。然后要拆解每个部分的原理和涉及的嵌入式技术。 首先,主控单元STM32F103C8T6,属于Cortex-M3内核,需要了解其外设使用,比如ADC、GPIO、定时器等。可能涉及到时钟配置、中断处理、低功耗模式等知识点。还有固件开发流程,比如使用HAL库或标准外设库,以及开发环境如Keil或STM32CubeIDE。 接下来是传感器部分。温湿度传感器可能是DHT11或SHT系列,通信接口可能是单总线或I2C。需要理解传感器数据读取的协议,比如时序要求、数据格式解析。而粉尘传感器,比如GP2Y1010AU0F,可能需要模拟信号处理,使用ADC采集电压,再转换成浓度值。这里涉及到模拟电路设计,比如RC滤波,以及软件上的ADC配置和校准。 然后是WiFi模块,常见的有ESP8266或ESP32,通过AT指令或SPI/SDIO与STM32通信。需要理解串口通信协议,AT指令的发送和响应处理,数据封装成TCP/IP包,连接云端服务器的过程,比如MQTT或HTTP协议。可能还需要处理网络异常,重连机制,数据缓存等。 云端通信部分,数据上传可能使用MQTT、HTTP或者TCP自定义协议。需要了解如何与云平台(如阿里云、AWS IoT)对接,数据格式JSON),以及数据加密(TLS/SSL)。这部分可能涉及嵌入式系统中的安全机制,如加密算法实现,证书管理。 系统整体设计要考虑实时性,多任务处理。可能使用RTOS,如FreeRTOS,进行任务调度,管理传感器采集、数据处理、通信等任务。任务间通信机制,如队列、信号量,确保数据同步和资源共享。 电源管理也是重点,尤其是电池供电的情况。需要低功耗设计,比如STM32的睡眠模式,外设的电源控制,传感器间歇工作,数据上传的间隔优化,以延长续航。 数据处理方面,可能需要对传感器数据进行滤波(如均值、中值滤波),校准(线性或非线性补偿),阈值报警(比如浓度超标触发警报)。这些算法在嵌入式中的实现需要考虑计算效率和资源占用。 硬件设计部分,包括PCB布局,传感器接口的电气特性,抗干扰措施,比如去耦电容、屏蔽罩。可能还需要考虑工业环境中的EMC问题,确保稳定运行。 调试与测试涉及日志输出(通过UART或SWD),单元测试(传感器模块、通信模块),系统集成测试,以及现场调试工具的使用,如逻辑分析仪、示波器,确保各模块协同工作。 用户可能还希望了解如何保证系统的可靠性和实时性,如何处理传感器误差,以及如何优化网络通信的稳定性。需要覆盖嵌入式系统的各个层面,从硬件到软件,再到通信协议和云端交互。 最后,要确保回答结构清晰,分点讲解每个模块的原理和对应的嵌入式技术,帮助用户逐步理解整个系统的工作流程和关键技术点。 好的!我将从**嵌入式系统设计的角度**,拆解基于STM32F103C8T6的智能粉尘检测系统的核心原理和关键知识点,分模块进行说明: --- ### 一、系统整体架构 ``` [STM32主控] → [传感器数据采集] → [数据处理] → [WiFi传输] → [云端存储] ↑ ↑ ↑ 温湿度传感器 粉尘传感器 本地报警/显示 ``` **核心逻辑**:STM32通过外设接口获取传感器数据,处理后通过WiFi模块封装成网络数据包,上传至云端。 --- ### 二、关键嵌入式技术分解 #### 1. **STM32外设驱动** - **ADC模块**(模拟信号采集) - 原理:粉尘传感器(如GP2Y1010AU0F)输出模拟电压信号,STM32的12位ADC将其转换为数字量 - 代码关键点:ADC校准、采样率设置、DMA传输优化 ```c HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&adc_value, 1); // 使用DMA提高采集效率 ``` - **GPIO与定时器** - 温湿度传感器(如DHT11)需严格时序控制 - 代码关键点:微秒级延时(TIM定时器实现)、中断响应 #### 2. **通信协议解析** - **UART通信** - WiFi模块(如ESP8266)通过AT指令控制 - 协议解析:状态机实现AT指令响应处理 ```c if(strstr(recv_buf, "OK")) { /* 指令成功响应处理 */ } ``` - **SPI/I2C接口** - 部分高精度传感器(如SHT30温湿度)使用I2C通信 - 代码关键点:I2C地址配置、CRC校验 #### 3. **实时操作系统(RTOS)** - **任务调度** - 使用FreeRTOS创建多任务: - 任务1:传感器数据采集(高优先级) - 任务2:网络通信(中优先级) - 任务3:本地显示/报警(低优先级) ```c xTaskCreate(sensor_task, "SENSOR", 128, NULL, 3, NULL); ``` - **同步机制** - 使用信号量(Semaphore)保护共享资源(如传感器数据缓冲区) #### 4. **低功耗设计** - **电源管理** - 进入Stop模式:在数据上传间隔期关闭非必要外设 ```c HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); ``` - **动态频率调节** - 根据任务需求调整系统时钟(HSI/HSE切换) #### 5. **数据处理算法** - **传感器校准** - 粉尘浓度计算公式(需实验标定): $$ C = k \cdot V_{ADC} + b $$ - 温湿度补偿:通过查表法修正粉尘浓度值 - **滑动平均滤波** ```c #define FILTER_LEN 5 uint16_t filter_buf[FILTER_LEN]; uint16_t adc_filter() { static uint8_t idx = 0; filter_buf[idx++] = ADC_Value; if(idx >= FILTER_LEN) idx = 0; return (filter_buf[0]+...+filter_buf[4])/FILTER_LEN; } ``` #### 6. **网络协议栈** - **TCP/IP协议封装** - ESP8266透传模式下直接发送HTTP POST请求: ``` POST /api/data HTTP/1.1 Host: cloud.example.com Content-Type: application/json {"dust": 25.6, "temp": 28.5, "humidity": 45} ``` - **断网重连机制** - 心跳包检测(每30秒发送ping指令) --- ### 三、工业场景特殊处理 1. **抗干扰设计** - 传感器信号线增加RC滤波电路 - PCB布局隔离数字/模拟地 2. **异常处理** - 看门狗(IWDG)防止程序跑飞 ```c HAL_IWDG_Refresh(&hiwdg); // 主循环中喂狗 ``` 3. **数据安全** - AES加密敏感数据后再上传 --- ### 四、开发工具链 1. **硬件调试**: - 逻辑分析仪抓取传感器时序 - J-Link通过SWD接口调试STM32 2. **软件工具**: - STM32CubeMX配置外设时钟树 - Wireshark抓包分析网络通信 --- ### 五、扩展优化方向 1. **边缘计算**:在本地实现浓度超标预警算法 2. **OTA升级**:通过WiFi更新固件 3. **多节点组网**:LoRa扩展覆盖范围 在我面试时,我改如何介绍这个项目
03-24
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值