软件模拟单总线协议出错

Viewed 61

一、描述你遇到的问题

欲通过DHT11进行温湿度采集,在STM32上通过软件模拟单总线协议,测试过,模块和程序都没有问题。遂把程序里用的库函数替换了H3863库函数里功能相同的,结果进行DHT11模块的读取时,不是从机不响应就是读到的数据全都是1。

//STM32代码——DHT11.h:
#ifndef __DHT11_H
#define __DHT11_H
#include "stm32f10x.h"                  // Device header



#define dht11_high GPIO_SetBits(GPIOB, GPIO_Pin_12)
#define dht11_low GPIO_ResetBits(GPIOB, GPIO_Pin_12)
#define Read_Data GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12)

void DHT11_GPIO_Init_OUT(void);
void DHT11_GPIO_Init_IN(void);
void DHT11_Start(void);
unsigned char DHT11_REC_Byte(void);
void DHT11_REC_Data(void);



#endif


#include "stm32f10x.h"                  // Device header
#include "delay.h"
#include "OLED.h"
#include "DHT11.h"
#include "Delay.h"

extern unsigned int rec_data[4];

int main()
{

  OLED_Init();
	while(1)
	{
		Delay_s(1);
		DHT11_REC_Data(); //接收温度和湿度的数据
		
		OLED_ShowChar(1,9,'.');
		OLED_ShowChar(3,9,'.');
	  OLED_ShowNum(1,7,rec_data[2],2);
		OLED_ShowNum(1,10,rec_data[3],1);
		OLED_ShowNum(3,7,rec_data[0],2);
		OLED_ShowNum(3,10,rec_data[1],2);
	}
}


//STM32代码——DHT11.c
#include "stm32f10x.h" // Device header
#include "dht11.h"
#include "Delay.h"
//数据
unsigned int rec_data[4];

//对于stm32来说,是输出
void DH11_GPIO_Init_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOB, &GPIO_InitStructure);

}

//对于stm32来说,是输入
void DH11_GPIO_Init_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOB, &GPIO_InitStructure);

}

//主机发送开始信号
void DHT11_Start(void)
{
DH11_GPIO_Init_OUT(); //输出模式

dht11_high; //先拉高
Delay_us(30);

dht11_low; //拉低电平至少18us
Delay_ms(20);

dht11_high; //拉高电平20~40us
Delay_us(30);

DH11_GPIO_Init_IN(); //输入模式

}

//获取一个字节
char DHT11_Rec_Byte(void)
{
unsigned char i = 0;
unsigned char data;

for(i=0;i<8;i++) //1个数据就是1个字节byte,1个字节byte有8位bit
{
	while( Read_Data == 0); //从1bit开始,低电平变高电平,等待低电平结束
	Delay_us(30); //延迟30us是为了区别数据0和数据1,0只有26~28us
	
	data <<= 1; //左移
	
	if( Read_Data == 1 ) //如果过了30us还是高电平的说就是数据1
	{
		data |= 1; //数据+1
	}
	
	while( Read_Data == 1 ); //高电平变低电平,等待高电平结束
}

return data;

}

//获取数据

void DHT11_REC_Data(void)
{
unsigned int R_H,R_L,T_H,T_L;
unsigned char RH,RL,TH,TL,CHECK;

DHT11_Start(); //主机发送信号
dht11_high; //拉高电平

if( Read_Data == 0 ) //判断DHT11是否响应
{
	while( Read_Data == 0); //低电平变高电平,等待低电平结束
	while( Read_Data == 1); //高电平变低电平,等待高电平结束
	
	R_H = DHT11_Rec_Byte();
	R_L = DHT11_Rec_Byte();
	T_H = DHT11_Rec_Byte();
	T_L = DHT11_Rec_Byte();
	CHECK = DHT11_Rec_Byte(); //接收5个数据
	
	dht11_low; //当最后一bit数据传送完毕后,DHT11拉低总线 50us
	Delay_us(55); //这里延时55us
	dht11_high; //随后总线由上拉电阻拉高进入空闲状态。
	
	if(R_H + R_L + T_H + T_L == CHECK) //和检验位对比,判断校验接收到的数据是否正确
	{
		RH = R_H;
		RL = R_L;
		TH = T_H;
		TL = T_L;
	}
}
rec_data[0] = RH;
rec_data[1] = RL;
rec_data[2] = TH;
rec_data[3] = TL;

}

//H3863代码——DHT11.h
#ifndef __DHT11_H
#define __DHT11_H

#define ONE_WIRE_DQ GPIO_00

#define dht11_high uapi_gpio_set_val(ONE_WIRE_DQ, GPIO_LEVEL_HIGH)
#define dht11_low uapi_gpio_set_val(ONE_WIRE_DQ, GPIO_LEVEL_LOW)
#define Read_Data uapi_gpio_get_output_val(ONE_WIRE_DQ)

void DHT11_GPIO_Init_OUT(void);
void DHT11_GPIO_Init_IN(void);
void DHT11_Start(void);
unsigned char DHT11_REC_Byte(void);
unsigned char DHT11_REC_Data(void);

#endif

//初始化后就干一件事:读数据,读到了就打印出来
	while(1)
	{
		osal_msleep(1000);
		if(DHT11_REC_Data())
        {
            printf("tempreture:%d.%d\r\n",rec_data[2],rec_data[3]); 
            printf("moisture:%d.%d\r\n",rec_data[0],rec_data[1]);
            printf("\r\n\n");
        }//接收温度和湿度的数据
	}

//H3863代码——DHT11.c
#include "../../../../include/driver/pinctrl.h"
#include "../../../../kernel/osal/include/debug/osal_debug.h"
#include "../../../../include/driver/gpio.h"
#include "../../../../kernel/osal/include/schedule/osal_task.h"

#include "DHT11.h"

//数据
unsigned int rec_data[4] = { 0 };

void DH11_GPIO_Init_OUT(void)
{
uapi_pin_set_mode(ONE_WIRE_DQ, PIN_MODE_0);//STM32代码里设置的是推挽输出,实际上协议要求的是上拉即可,不清楚H3863输出时是不是开漏的,直接驱动不行,接了上拉电阻后还是不行
uapi_gpio_set_dir(ONE_WIRE_DQ, GPIO_DIRECTION_OUTPUT);
}

void DH11_GPIO_Init_IN(void)
{
uapi_pin_set_mode(ONE_WIRE_DQ, PIN_MODE_0);//要设置成浮空输入。不过库函数里就只有输入而已
uapi_gpio_set_dir(ONE_WIRE_DQ, GPIO_DIRECTION_INPUT);
}

//主机发送开始信号
void DHT11_Start(void)
{
DH11_GPIO_Init_OUT(); //输出模式

dht11_high; //先拉高
osal_udelay(30);

dht11_low; //拉低电平至少18us
osal_msleep(20);

dht11_high; //拉高电平20~40us
osal_udelay(30);

DH11_GPIO_Init_IN(); //输入模式

}

//获取一个字节
unsigned char DHT11_Rec_Byte(void)
{
unsigned char i = 0;
unsigned char data = 0;

for(i=0;i<8;i++) //1个数据就是1个字节byte,1个字节byte有8位bit
{
	while(Read_Data == GPIO_LEVEL_LOW); //从1bit开始,低电平变高电平,等待低电平结束
	osal_udelay(30); //延迟30us是为了区别数据0和数据1,0只有26~28us
	
	data <<= 1; //左移
	
	if(Read_Data == GPIO_LEVEL_HIGH) //如果过了30us还是高电平的说就是数据1
	{
		data |= 1; //数据+1
	}
	
	while(Read_Data == GPIO_LEVEL_HIGH); //高电平变低电平,等待高电平结束
}

return data;

}

//获取数据
unsigned char DHT11_REC_Data(void)
{
unsigned int R_H,R_L,T_H,T_L;
unsigned char RH,RL,TH,TL,CHECK;

DHT11_Start(); //主机发送信号
dht11_high; //拉高电平

if(Read_Data == GPIO_LEVEL_LOW) //判断DHT11是否响应
{
	while( Read_Data == GPIO_LEVEL_LOW); //低电平变高电平,等待低电平结束
	while( Read_Data == GPIO_LEVEL_HIGH); //高电平变低电平,等待高电平结束
	
	R_H = DHT11_Rec_Byte();
	R_L = DHT11_Rec_Byte();
	T_H = DHT11_Rec_Byte();
	T_L = DHT11_Rec_Byte();
	CHECK = DHT11_Rec_Byte(); //接收5个数据
	
	dht11_low; //当最后一bit数据传送完毕后,DHT11拉低总线 50us
	osal_udelay(55); //这里延时55us
	dht11_high; //随后总线由上拉电阻拉高进入空闲状态。

    RH = R_H;
    RL = R_L;
    TH = T_H;
    TL = T_L;
	
	if(R_H + R_L + T_H + T_L != CHECK) //和检验位对比,判断校验接收到的数据是否正确
	{
        osal_printk("R_H:%dR_L%dT_H:%dT_L%dCHECK:%d,Error happened!\n", R_H, R_L, T_H, T_L, CHECK);
        return 0;
	}

    rec_data[0] = RH;
    rec_data[1] = RL;
    rec_data[2] = TH;
    rec_data[3] = TL;
    return 1;
}
else
{//此时是没有响应的情况
    osal_printk("Slave no ack\n");
    return 0;
}

}

二、你具体做的所有步骤结果截图


拍摄于正在下雨、穿袄子会略热的重庆,温湿度数值十分合理

三、当前开发板状态全景照片


手头上没有5K的电阻,拿5个1K的串起来,一端接开发板的3.3V,另一端接通信用的GPIO口GPIO_00;模块的GND接在开发板的GND上

四、开发板串口所有日志


在程序里,从机没有应答就会发送一个Slave no ack,发来的数据是错的(和校验位对不上)就会把所有数据都发出来然后说Error happend

1 Answers

这类问题用AI来解决更合适,要学会使用AI

在将STM32的代码移植到H3863平台后,DHT11模块无法正常读取数据,可能的原因有以下几点:

1. GPIO模式设置问题

  • STM32:在STM32中,GPIO模式设置为推挽输出(GPIO_Mode_Out_PP)和浮空输入(GPIO_Mode_IN_FLOATING)。
  • H3863:在H3863中,GPIO模式设置为PIN_MODE_0,并且通过uapi_gpio_set_dir设置方向。PIN_MODE_0可能并不等同于STM32的推挽输出或浮空输入模式。你需要确认PIN_MODE_0的具体含义,并确保它符合DHT11协议的要求。

解决方案:检查H3863的GPIO模式设置,确保在输出模式下是推挽输出,输入模式下是浮空输入。如果PIN_MODE_0不符合要求,尝试使用其他模式或直接配置为上拉/下拉模式。

2. GPIO电平读取问题

  • STM32:使用GPIO_ReadInputDataBit读取GPIO电平。
  • H3863:使用uapi_gpio_get_output_val读取GPIO电平。uapi_gpio_get_output_val可能读取的是输出寄存器的值,而不是实际引脚的电平。

解决方案:确保使用正确的函数读取GPIO引脚的实际电平。通常应该使用uapi_gpio_get_input_val或类似的函数来读取输入引脚的电平。

3. 延时函数精度问题

  • STM32:使用Delay_usDelay_ms进行精确的微秒和毫秒级延时。
  • H3863:使用osal_udelayosal_msleep进行延时。osal_udelayosal_msleep的精度可能不如STM32的延时函数,尤其是在微秒级延时上。

解决方案:检查osal_udelayosal_msleep的实现,确保它们能够提供足够的精度。如果延时不够精确,可能会导致DHT11无法正确响应。

4. 上拉电阻问题

  • DHT11:DHT11的单总线协议要求总线在空闲时保持高电平,通常需要外部上拉电阻(4.7kΩ)。
  • H3863:如果H3863的GPIO输出模式不是开漏输出,可能需要外部上拉电阻来确保总线在空闲时保持高电平。

解决方案:确保在DHT11的数据引脚上连接了合适的上拉电阻(4.7kΩ)。如果H3863的GPIO输出模式不是开漏输出,可能需要手动添加上拉电阻。

5. 电源问题

  • DHT11:DHT11模块需要稳定的电源供应。如果电源不稳定,可能会导致模块无法正常工作。
  • H3863:确保H3863平台为DHT11模块提供了稳定的电源。

解决方案:检查DHT11的电源引脚,确保电压稳定且在3.3V或5V(根据模块规格)。

6. 调试信息

  • 调试信息:在H3863代码中,添加更多的调试信息,例如在每次读取GPIO电平时打印当前电平,或者在每次延时后打印时间信息,以帮助定位问题。

解决方案:在关键位置添加调试信息,例如在DHT11_StartDHT11_Rec_ByteDHT11_REC_Data函数中添加打印语句,观察程序的执行流程和GPIO电平的变化。

7. 硬件连接问题

  • 硬件连接:检查DHT11模块与H3863平台的硬件连接,确保引脚连接正确,没有接触不良或短路。

解决方案:重新检查硬件连接,确保DHT11的数据引脚正确连接到H3863的GPIO引脚,并且电源和地线连接正确。

总结

在移植代码时,需要特别注意GPIO模式、电平读取、延时精度、上拉电阻、电源稳定性等方面的问题。通过逐步排查这些可能的原因,应该能够找到导致DHT11无法正常读取数据的根本原因。