利用Proteus仿真STM32实现DHT11温湿度检测

举报
DS小龙哥 发表于 2022/09/17 11:05:14 2022/09/17
【摘要】 先使用keil软件就将程序设计设计好,然后生成HEX文件,等待设计好原理图后进行仿真测试。

1. 前言

Proteus是英国著名的EDA工具(仿真软件),从原理图布图、代码调试到单片机与外围电路协同仿真,一
键切换到PCB设计,真正实现了从概念到产品的完整设计。是世界上唯一将电路仿真软件、PCB设计软
件和虚拟模型仿真软件三合一的设计平台,其处理器模型支持8051、HC11、
PIC10/12/16/18/24/30/DSPIC33、AVR、ARM、8086和MSP430等,2010年又增加了Cortex和DSP系
列处理器,并持续增加其他系列处理器模型。在编译方面,它也支持IAR、Keil和MATLAB等多种编译
器。
前面文章介绍了Proteus的下载,安装,建立工程,完成LED灯仿真运行。这篇文章在这基础上增加串口打印,DHT11温湿度检测。

2. 设计程序

先使用keil软件就将程序设计设计好,然后生成HEX文件,等待设计好原理图后进行仿真测试。

注意: 当前使用的芯片是STM32F103。Proteus的版本是8.9

image-20220524143658130

#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "dht11.h"

/*
(3)温湿度传感器: DHT11
VCC--VCC
GND---GND
DAT---PA5 
*/

#include "stm32f10x.h"
#include <stdio.h>
#include <stdarg.h>
#include "sys.h"
#include <string.h>

#define USART1_RX_LENGTH 1024
extern u8 USART1_RX_BUFFER[USART1_RX_LENGTH]; //保存接收数据的缓冲区
extern u32 USART1_RX_CNT;  //当前接收到的数据长度
extern u8 USART1_RX_FLAG; //1表示数据接收完毕 0表示没有接收完毕

#define USART2_RX_LENGTH 1024
extern u8 USART2_RX_BUFFER[USART2_RX_LENGTH]; //保存接收数据的缓冲区
extern u32 USART2_RX_CNT;  //当前接收到的数据长度
extern u8 USART2_RX_FLAG; //1表示数据接收完毕 0表示没有接收完毕

#define USART3_RX_LENGTH 1024
extern u8 USART3_RX_BUFFER[USART3_RX_LENGTH]; //保存接收数据的缓冲区
extern u32 USART3_RX_CNT;  //当前接收到的数据长度
extern u8 USART3_RX_FLAG; //1表示数据接收完毕 0表示没有接收完毕

void USART1_Init(u32 baud);
void USART2_Init(u32 baud);
void USART3_Init(u32 baud);
void USARTx_StringSend(USART_TypeDef *USARTx,char *str);
void USARTx_DataSend(USART_TypeDef *USARTx,u8 *data,u32 len);

//定义按键IO口
#define KEY_S3 PAin(1) 

//函数声明
void KEY_Init(void);
u8 KEY_Scan(u8 mode);


//LED定义
#define LED1 PBout(6)
#define LED2 PBout(7)
#define LED3 PBout(8)
#define LED4 PBout(9)

//蜂鸣器IO口定义
#define BEEP PAout(6)

//函数声明
void LED_Init(void);
void BEEP_Init(void);



//IO方向设置
#define DHT11_IO_IN()  {GPIOA->CRL&=0XFF0FFFFF;GPIOA->CRL|=0x00800000;}
#define DHT11_IO_OUT() {GPIOA->CRL&=0XFF0FFFFF;GPIOA->CRL|=0x00300000;}
////IO操作函数											   
#define	DHT11_DQ_OUT PAout(5) //数据端口	PA5 
#define	DHT11_DQ_IN  PAin(5)  //数据端口	PA5


u8 DHT11_Init(void);		//初始化DHT11
u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度
u8 DHT11_Read_Byte(void);	//读出一个字节
u8 DHT11_Read_Bit(void);	//读出一个位
u8 DHT11_Check(void);		//检测是否存在DHT11
void DHT11_Rst(void);		//复位DHT11    

//复位DHT11
void DHT11_Rst(void)	   
{                 
	  DHT11_IO_OUT(); 	//SET OUTPUT
    DHT11_DQ_OUT=0; 	//拉低DQ
    DelayMs(20);    	//拉低至少18ms
    DHT11_DQ_OUT=1; 	//DQ=1 
	  DelayUs(30);     	//主机拉高20~40us
}
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void) 	   
{   
	u8 retry=0;
	DHT11_IO_IN();//SET INPUT	 
  while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
	{
		retry++;
		DelayUs(1);
	};	 
	if(retry>=100)return 1;
	else retry=0;
    while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
	{
		retry++;
		DelayUs(1);
	};
	if(retry>=100)return 1;	    
	return 0;
}
//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void) 			 
{
 	u8 retry=0;
	while(DHT11_DQ_IN&&retry<100)//等待变为低电平
	{
		retry++;
		DelayUs(1);
	}
	retry=0;
	while(!DHT11_DQ_IN&&retry<100)//等待变高电平
	{
		retry++;
		DelayUs(1);
	}
	DelayUs(40);//等待40us
	if(DHT11_DQ_IN)return 1;
	else return 0;		   
}

//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)    
{        
    u8 i,dat;
    dat=0;
	for (i=0;i<8;i++) 
	{
   		dat<<=1; 
	    dat|=DHT11_Read_Bit();
    }						    
    return dat;
}


//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp,u8 *humi)    
{        
 	u8 buf[5];
	u8 i;
	DHT11_Rst();
	//printf("------------------------\r\n");
	if(DHT11_Check()==0)
	{
		for(i=0;i<5;i++)//读取40位数据
		{
			buf[i]=DHT11_Read_Byte();
		}
		if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
		{
			*humi=buf[0];
			*temp=buf[2];
		}
	}else return 1;
	return 0;	    
}


//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在    	 
u8 DHT11_Init(void)
{
	RCC->APB2ENR|=1<<2;    //使能PORTA口时钟 
	GPIOA->CRL&=0XFF0FFFFF;//PORTA.5 推挽输出
	GPIOA->CRL|=0X00300000;
	GPIOA->ODR|=1<<5;      //输出1				    
	DHT11_Rst();
	return DHT11_Check();
}


/*
函数功能:按键初始化
硬件连接:PA1
特性: 按下为低电平---没按下高电平
*/
void KEY_Init(void)
{
    //开时钟
    RCC->APB2ENR|=1<<2;
    //配置模式
    GPIOA->CRL&=0xFFFFFF0F;
    GPIOA->CRL|=0x00000080;
    //上拉
    GPIOA->ODR|=1<<1;
}


/*
函数功能:函数扫描函数
函数参数: mode=1表示使用连续模式  mode=0使用单击模式
返回值:  2 3 4 5 表示具体的按钮   0表示没有按下
*/
u8 KEY_Scan(u8 mode)
{
   static u8 flag=1; //记录上一次按下的状态 
   if(mode)flag=1;
   if(flag&&(KEY_S3==0))
   {
       flag=0;
       delay_ms(20);
       if(KEY_S3==0)return 3;
   }
   else if(KEY_S3)
   {
       flag=1; 
   }
   return 0;
}


/*
函数功能: LED初始化
硬件连接: PB6 PB7 PB8 PB9
特性: 低电平点亮
*/
void LED_Init(void)
{
    //开时钟
    RCC->APB2ENR|=1<<3;
    //配置GPIO口
    GPIOB->CRL&=0x00FFFFFF;
    GPIOB->CRL|=0x22000000;
    GPIOB->CRH&=0xFFFFFF00;
    GPIOB->CRH|=0x00000022;
    //上拉
    GPIOB->ODR|=1<<6;
    GPIOB->ODR|=1<<7;
    GPIOB->ODR|=1<<8;
    GPIOB->ODR|=1<<9;
}

/*
函数功能: 蜂鸣器初始化
硬件连接: PA6
特性: 高电平响
*/
void BEEP_Init(void)
{
   RCC->APB2ENR|=1<<2;
   GPIOA->CRL&=0xF0FFFFFF;
   GPIOA->CRL|=0x02000000;
}


/*
函数功能: 串口1的初始化
硬件连接: PA9(TX)  和 PA10(RX)
*/
void USART1_Init(u32 baud)
{
    /*1. 开时钟*/
    RCC->APB2ENR|=1<<14; //USART1时钟
    RCC->APB2ENR|=1<<2;  //PA
    RCC->APB2RSTR|=1<<14; //开启复位时钟
    RCC->APB2RSTR&=~(1<<14);//停止复位
    /*2. 配置GPIO口的模式*/
    GPIOA->CRH&=0xFFFFF00F;
    GPIOA->CRH|=0x000008B0;
    /*3. 配置波特率*/
    USART1->BRR=72000000/baud;
    /*4. 配置核心寄存器*/
    USART1->CR1|=1<<5; //开启接收中断
    STM32_SetPriority(USART1_IRQn,1,1); //设置中断优先级
    USART1->CR1|=1<<2; //开启接收
    USART1->CR1|=1<<3; //开启发送
    USART1->CR1|=1<<13;//开启串口功能
}

/*
函数功能: 串口2的初始化
硬件连接: PA2(TX)  和 PA3(RX)
*/
void USART2_Init(u32 baud)
{
    /*1. 开时钟*/
    RCC->APB1ENR|=1<<17; //USART2时钟
    RCC->APB2ENR|=1<<2;  //PA
    RCC->APB1RSTR|=1<<17; //开启复位时钟
    RCC->APB1RSTR&=~(1<<17);//停止复位
    
    /*2. 配置GPIO口的模式*/
    GPIOA->CRL&=0xFFFF00FF;
    GPIOA->CRL|=0x00008B00;
    /*3. 配置波特率*/
    USART2->BRR=36000000/baud;
    /*4. 配置核心寄存器*/
    USART2->CR1|=1<<5; //开启接收中断
    STM32_SetPriority(USART2_IRQn,1,1); //设置中断优先级
    USART2->CR1|=1<<2; //开启接收
    USART2->CR1|=1<<3; //开启发送
    USART2->CR1|=1<<13;//开启串口功能
}

/*
函数功能: 串口3的初始化
硬件连接: PB10(TX)  和 PB11(RX)
*/
void USART3_Init(u32 baud)
{
    /*1. 开时钟*/
    RCC->APB1ENR|=1<<18; //USART3时钟
    RCC->APB2ENR|=1<<3;  //PB
    RCC->APB1RSTR|=1<<18; //开启复位时钟
    RCC->APB1RSTR&=~(1<<18);//停止复位
    
    /*2. 配置GPIO口的模式*/
    GPIOB->CRH&=0xFFFF00FF;
    GPIOB->CRH|=0x00008B00;
    /*3. 配置波特率*/
    USART3->BRR=36000000/baud;
    /*4. 配置核心寄存器*/
    USART3->CR1|=1<<5; //开启接收中断
    STM32_SetPriority(USART3_IRQn,1,1); //设置中断优先级
    USART3->CR1|=1<<2; //开启接收
    USART3->CR1|=1<<3; //开启发送
    USART3->CR1|=1<<13;//开启串口功能
}

u8 USART1_RX_BUFFER[USART1_RX_LENGTH]; //保存接收数据的缓冲区
u32 USART1_RX_CNT=0;  //当前接收到的数据长度
u8 USART1_RX_FLAG=0; //1表示数据接收完毕 0表示没有接收完毕

//串口1的中断服务函数
void USART1_IRQHandler(void)
{
    u8 data;
    //接收中断
    if(USART1->SR&1<<5)
    {
        TIM1->CNT=0; //清除计数器
        TIM1->CR1|=1<<0; //开启定时器1
        data=USART1->DR; //读取串口数据
      //  if(USART1_RX_FLAG==0) //判断上一次的数据是否已经处理完毕
        {
            //判断是否可以继续接收
            if(USART1_RX_CNT<USART1_RX_LENGTH)
            {
               USART1_RX_BUFFER[USART1_RX_CNT++]=data;
            }
            else  //不能接收,超出存储范围,强制表示接收完毕
            {
                USART1_RX_FLAG=1;
            }
        } 
    }
}


u8 USART2_RX_BUFFER[USART2_RX_LENGTH]; //保存接收数据的缓冲区
u32 USART2_RX_CNT=0;  //当前接收到的数据长度
u8 USART2_RX_FLAG=0; //1表示数据接收完毕 0表示没有接收完毕

//串口2的中断服务函数
void USART2_IRQHandler(void)
{
    u8 data;
    //接收中断
    if(USART2->SR&1<<5)
    {
        TIM2->CNT=0; //清除计数器
        TIM2->CR1|=1<<0; //开启定时器2
        data=USART2->DR; //读取串口数据
      //  if(USART2_RX_FLAG==0) //判断上一次的数据是否已经处理完毕
        {
            //判断是否可以继续接收
            if(USART2_RX_CNT<USART2_RX_LENGTH)
            {
               USART2_RX_BUFFER[USART2_RX_CNT++]=data;
            }
            else  //不能接收,超出存储范围,强制表示接收完毕
            {
                USART2_RX_FLAG=1;
            }
        } 
    }
}

u8 USART3_RX_BUFFER[USART3_RX_LENGTH]; //保存接收数据的缓冲区
u32 USART3_RX_CNT=0;  //当前接收到的数据长度
u8 USART3_RX_FLAG=0; //1表示数据接收完毕 0表示没有接收完毕

//串口3的中断服务函数
void USART3_IRQHandler(void)
{
    u8 data;
    //接收中断
    if(USART3->SR&1<<5)
    {
        TIM3->CNT=0; //清除计数器
        TIM3->CR1|=1<<0; //开启定时器3
        data=USART3->DR; //读取串口数据
      //  if(USART3_RX_FLAG==0) //判断上一次的数据是否已经处理完毕
        {
            //判断是否可以继续接收
            if(USART3_RX_CNT<USART3_RX_LENGTH)
            {
               USART3_RX_BUFFER[USART3_RX_CNT++]=data;
            }
            else  //不能接收,超出存储范围,强制表示接收完毕
            {
                USART3_RX_FLAG=1;
            }
        } 
    }
}


/*
函数功能: 字符串发送
*/
void USARTx_StringSend(USART_TypeDef *USARTx,char *str)
{
   while(*str!='\0')
   {
       USARTx->DR=*str++;
       while(!(USARTx->SR&1<<7)){}
   }
}

/*
函数功能: 数据发送
*/
void USARTx_DataSend(USART_TypeDef *USARTx,u8 *data,u32 len)
{
   u32 i;
   for(i=0;i<len;i++)
   {
       USARTx->DR=*data++;
       while(!(USARTx->SR&1<<7)){}
   }
}

//printf函数底层函数接口
int fputc(int c, FILE* stream)
{
    USART1->DR=c;
    while(!(USART1->SR&1<<7)){}
    return c;
}


u8 dht11_temp;
u8 dht11_humidity;

int main()
{
   u8 key_val;
   u32 time=0;
   LED_Init();
   BEEP_Init();
   KEY_Init();
   USART1_Init(115200);    //串口1初始化-打印调试信息
   //初始化DHT11
   DHT11_Init();
    
   while(1)
   {
      key_val=KEY_Scan(0); //PA1
      if(key_val)
      {
         BEEP=!BEEP;
         LED1=!LED1;   //PB6
      }
      delay_ms(5);
      
      time++;
      if(time>=10)
      {
        time=0;
        LED2=!LED2; //PB7
          
        //读取温湿度
        if(DHT11_Read_Data(&dht11_temp,&dht11_humidity))
        {
            printf("温度读取失败.\r\n");
        }        
        printf("T:%d,H:%d\r\n",dht11_temp,dht11_humidity);
        
        //湿度大于80以上就关闭插座
        if(dht11_humidity>80)
        {
            LED1=1;
        }
      }
   }
}

3. 设计电路图

3.1 添加DHT11器件

打开Proteus,搜索DHT11元器件。

image-20220524143937845

鼠标选择空白区域,点击鼠标右键,放置电源和GND。

image-20220524144345503

设计好的效果如下:

image-20220524144418613

3.2 添加虚拟串口终端

为了方便查看程序的串口输出,添加一个串口终端显示框。

在虚拟仪表模式下,选择virtual terminal工具,然后在原理图空白区域点击一下就可以放virtual terminal工具。

image-20220524145332372

在绘制原理图的经常遇到连线复杂,或者布线很乱,如果元器件的引脚不方便直接与MCU单片机连接,可以采用标签的形式或者总线方式布线。这里以串口终端演示,采用标签方式连接IO口。

首先在坐标的菜单栏里选择终端模式,然后鼠标点击DEFAULT,然后在原理图的空白区域,点击一下鼠标左键,会出现一个空心的连接线条,将这个连接线条连接到元器件的IO口上就行。

image-20220524150145275

image-20220524145810211

放置好之后,鼠标点击这个接线端子–空心圆圈,弹出对话框,设置连接的IO口。

image-20220524150321956

然后MCU的PA9和PA10的端子上也设置好标签名称。

image-20220524151328570

设置虚拟串口显示器的波特率为:115200

image-20220524150744558

如果在调试仿真时, Virtual Terminal无法自动弹出窗口,可以点击菜单栏的调试,选择恢复弹出窗口。

image-20220524151628962

设置STM32芯片的晶振为:71MHZ

image-20220524155200053

3.3 开始仿真

image-20220524155229691

image-20220524155252228

image-20220524155723214

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。