Proteus を使用して STM32 をシミュレートし、DHT11 の温度と湿度の検出を実現します

1 はじめに

Proteus は、英国で有名な EDA ツール (シミュレーション ソフトウェア) であり、回路図のレイアウト、コードのデバッグから、MCU と周辺回路の協調シミュレーション、ワンクリックで 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 です。プロテウスのバージョンは 8.9 です

画像-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 コンポーネントを検索します。

画像-20220524143937845

空白の領域をマウスで選択し、マウスの右ボタンをクリックして、電源と GND を配置します。

画像-20220524144345503

設計された効果は次のとおりです。

画像-20220524144418613

3.2 仮想シリアルポート端末の追加

プログラムのシリアル ポート出力を便利に表示するには、シリアル ポート ターミナル表示ボックスを追加します。

仮想計測器モードでvirtual terminalツールを選択し、スケマティック ダイアグラムの空白領域をクリックしてvirtual terminalツールを配置します。

画像-20220524145332372

回路図を描くとき、​​配線が複雑であったり、配線が非常に煩雑であることがよくあります.コンポーネントのピンがMCUマイクロコントローラに直接接続するのが不便な場合は、ラベルまたはバス配線の形式を使用できます. . ここでは、デモ用にシリアル ポート端子を使用し、IO ポートはラベリングによって接続されています。

最初に座標メニューバーでターミナルモードを選択してからマウスをクリックしDEFAULT、スケマティックダイアグラムの空白領域でマウスの左ボタンをクリックすると、中空の接続線が表示されます。この接続線をIOポートに接続するだけですコンポーネントの。

画像-20220524150145275

画像-20220524145810211

配置後、マウスでターミナル (中空の円) をクリックすると、接続された IO ポートを設定するためのダイアログ ボックスがポップアップ表示されます。

画像-20220524150321956

その後、MCU の端子 PA9、PA10 にラベル名を設定します。

画像-20220524151328570

仮想シリアル ディスプレイのボー レートを設定します: 115200

画像-20220524150744558

シミュレーションのデバッグ時に仮想ターミナルがウィンドウを自動的にポップアップできない場合は、メニュー バーの [デバッグ] をクリックして、ポップアップ ウィンドウを復元することを選択できます。

画像-20220524151628962

STM32 チップの水晶発振器を 71MHZ に設定します。

画像-20220524155200053

3.3 シミュレーションを開始する

画像-20220524155229691

画像-20220524155252228

画像-20220524155723214

画像-20230326205221523

この記事は「ゴールデンストーンプロジェクト」に参加しています

おすすめ

転載: juejin.im/post/7214854106179928119