Project finished product images:

Bilibili video link:
https://www.bilibili.com/video/BV1CQkTYzEQ9/?share_source=copy_web&vd_source=097fdeaf6b6ecfed8a9ff7119c32faf2
(Material sharing can be found at the end of the article)
01
—
Project Introduction
1. Function Details
STM32 Smart Wardrobe
Functions are as follows:
- 
DHT11 detects temperature and humidity, sets thresholds for heating and ventilation 
- 
RTC obtains real-time time and regularly performs UV disinfection 
- 
Photoelectric sensor detects cabinet door status, disinfects when closed, and determines lighting based on illumination when opened 
- 
Light sensor collects light intensity, displays environmental data, and determines whether to turn on the light based on this 
- 
MQ2 smoke sensor detects smoke concentration; if exceeded, triggers buzzer alarm 
- 
Buttons can adjust time and all threshold information and timing information 
- 
Bluetooth app can remotely control the wardrobe system to execute heating, ventilation, lighting, and disinfection functions 
- 
Bluetooth app can receive real-time environmental information data 
2. Bill of Materials
- 
STM32F103C8T6 microcontroller 
- 
OLED screen 
- 
DHT11 temperature and humidity sensor 
- 
BT04A Bluetooth module 
- 
Light sensor 
- 
Photoelectric infrared sensor 
- 
MQ-2 smoke sensor 
- 
Relay 
- 
Fan module 
- 
High-power LED light module 
- 
Active buzzer 
- 
Stepper motor 
- 
UV disinfection lamp module 
- 
Heating plate module 
02
—
Schematic Design

03
—
PCB Hardware Design
PCB Diagram


04
—
Program Design
#include "sys.h"
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "math.h"
#include "delay.h"
#include "gpio.h"
#include "key.h"
#include "oled.h"
#include "usart.h"
#include "rtc.h"
#include "dht11.h"
#include "adc.h"
#include "motor_bujin.h"
int main(void){
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // Configure interrupt priority group
  Delay_Init(); // Delay initialization
  Gpio_Init(); // IO initialization
  Key_Init(); // Key initialization
  Oled_Init(); // OLED initialization
  Oled_Clear_All(); // Clear screen
  Usart1_Init(9600); // Serial port 1 initialization
  Adc_Init(); // ADC initialization
  Step_Motor_Init(); // Stepper motor initialization
  while(RTC_Init()); // RTC initialization
  while(DHT11_Init()); // DHT11 initialization
  Delay_ms(1000);
  Delay_ms(1000);
  // DHT11_Init();
  while(1) {
    key_num = Chiclet_Keyboard_Scan(0); // Key scan
    if(key_num != 0) { // If a key is pressed
      switch(key_num) {
        case 1: // Key 1, switch to settings interface
          flag_display++;
          if(flag_display >= 15)
            flag_display = 0;
          if(flag_display >= 1 && flag_display <= 6) {
            RTC_ITConfig(RTC_IT_SEC, DISABLE);
          } else {
            RTC_Set(calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);
            RTC_ITConfig(RTC_IT_SEC, ENABLE);
          }
          Oled_Clear_All(); // Clear screen
          break;
        case 2: // Key 2
          switch(flag_display) {
            case 0:
              if(flag_bujin_state == 0)
                flag_bujin_foreward = 1;
              break;
            case 1: // Interface 1: Modify year +1
              calendar.w_year++;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 2: // Interface 2: Modify month +1
              calendar.w_month++;
              if(calendar.w_month > 12)
                calendar.w_month = 1;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 3: // Interface 3: Modify day +1
              calendar.w_date++;
              if(calendar.w_date > 31)
                calendar.w_date = 1;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 4: // Interface 4: Modify hour +1
              calendar.hour++;
              if(calendar.hour > 23)
                calendar.hour = 0;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 5: // Interface 5: Modify minute +1
              calendar.min++;
              if(calendar.min > 59)
                calendar.min = 0;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 6: // Interface 6: Modify second +1
              calendar.sec++;
              if(calendar.sec > 59)
                calendar.sec = 0;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 7: // Interface 7: Interval timing start hour +1
              time_shi_begin++;
              if(time_shi_begin >= 24)
                time_shi_begin = 0;
              break;
            case 8: // Interface 8: Interval timing start minute +1
              time_fen_begin++;
              if(time_fen_begin >= 60)
                time_fen_begin = 0;
              break;
            case 9: // Interface 9: Interval timing end hour +1
              time_shi_end++;
              if(time_shi_end >= 24)
                time_shi_end = 0;
              break;
            case 10: // Interface 10: Interval timing end minute +1
              time_fen_end++;
              if(time_fen_end >= 60)
                time_fen_end = 0;
              break;
            case 11: // Interface 11: Minimum temperature +1
              if(temp_min < 99)
                temp_min++;
              break;
            case 12: // Interface 12: Maximum humidity +1
              if(humi_max < 99)
                humi_max++;
              break;
            case 13: // Interface 13: Minimum light +1
              if(light_min < 99)
                light_min++;
              break;
            case 14: // Interface 14: Maximum smoke +1
              if(smog_max < 200)
                smog_max++;
              break;
            default:
              break;
          }
          break;
        case 3: // Key 3
          switch(flag_display) {
            case 0:
              if(flag_bujin_state == 1)
                flag_bujin_reversal = 1;
              break;
            case 1: // Interface 1: Modify year -1
              calendar.w_year--;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 2: // Interface 2: Modify month -1
              calendar.w_month--;
              if(calendar.w_month < 1)
                calendar.w_month = 12;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 3: // Interface 3: Modify day -1
              calendar.w_date--;
              if(calendar.w_date < 1)
                calendar.w_date = 31;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 4: // Interface 4: Modify hour -1
              if(calendar.hour == 0)
                calendar.hour = 23;
              else 
                calendar.hour--;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 5: // Interface 5: Modify minute -1
              if(calendar.min == 0)
                calendar.min = 59;
              else 
                calendar.min--;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 6: // Interface 6: Modify second -1
              if(calendar.sec == 0)
                calendar.sec = 59;
              else
                calendar.sec--;
              RTC_Set(calendar.w_year,calendar.w_month ,calendar.w_date ,calendar.hour ,calendar.min ,calendar.sec);
              break;
            case 7: // Interface 7: Interval timing start hour -1
              time_shi_begin--;
              if(time_shi_begin < 0)
                time_shi_begin = 23;
              break;
            case 8: // Interface 8: Interval timing start minute -1
              time_fen_begin--;
              if(time_fen_begin < 0)
                time_fen_begin = 59;
              break;
            case 9: // Interface 9: Interval timing end hour -1
              time_shi_end--;
              if(time_shi_end < 0)
                time_shi_end = 23;
              break;
            case 10: // Interface 10: Interval timing end minute -1
              time_fen_end--;
              if(time_fen_end < 0)
                time_fen_end = 59;
              break;
            case 11: // Interface 11: Minimum temperature -1
              if(temp_min > 0)
                temp_min--;
              break;
            case 12: // Interface 12: Maximum humidity -1
              if(humi_max > 0)
                humi_max--;
              break;
            case 13: // Interface 13: Minimum light -1
              if(light_min > 0)
                light_min--;
              break;
            case 14: // Interface 14: Maximum smoke -1
              if(smog_max > 0)
                smog_max--;
              break;
            default:
              break;
          }
          break;
        default:
          break;
      }
    }
    if(flag_display == 0) // Measurement interface
    {
      if(time_num % 10 == 0) // Get data
      {
        DHT11_Read_Data(&temp_value,&humi_value); // Get temperature and humidity values
        light_value = 99-30*(Get_Adc_Average(1,3)*3.3/4096.0); // Get light intensity
        if(SMOG == 0)
          smog_value = 60*(Get_Adc_Average(0,3)*3.3/4096.0); // Get smoke value
        else
          smog_value = 0;
      }
      if(time_num % 30 == 0) // Send data
      {
        UsartPrintf(USART1,"Temperature: %d.%dC\r\n",temp_value/10,temp_value%10);
        UsartPrintf(USART1,"Humidity: %d.%dC\r\n",humi_value/10,humi_value%10);
        UsartPrintf(USART1,"Light: %dlux\r\n",light_value);
        UsartPrintf(USART1,"Smoke: %dPPM\r\n",smog_value);
      }
      if(USART1_WaitRecive() == 0) // If Bluetooth data is received
      {
        switch(usart1_buf[0])
        {
          case('A'): // A: Switch to automatic mode
            flag_mode = 0;
            break;
          case('B'): // B: PA7
            flag_mode = 1;
            RELAY_TF = !RELAY_TF;
            break;
          case('C'): // C: PA6
            flag_mode = 1;
            RELAY_JR =!RELAY_JR;
            break;
          case('D'): // D: PA5
            flag_mode = 1;
            RELAY_XD =!RELAY_XD;
            break;
          case('E'): // E: PA4
            flag_mode = 1;
            RELAY_ZM =!RELAY_ZM;
            break;
          default:
            break;
        }
        USART1_Clear();
      }
    }
    switch(flag_display) // Display different interfaces based on different display mode flags
    {
      case 0: // Interface 0:
        sprintf(display_buf,"%d-%d%d-%d%d",calendar.w_year,calendar.w_month/10,calendar.w_month%10,calendar.w_date/10,calendar.w_date%10);
        Oled_ShowString(1, 2, display_buf);
        sprintf(display_buf,"%d%d:%d%d:%d%d",calendar.hour/10,calendar.hour%10,calendar.min/10,calendar.min%10,calendar.sec/10,calendar.sec%10);
        Oled_ShowString(2, 3, display_buf);
        Oled_ShowString(3,0,"T:");
        sprintf(display_buf,"%d.%dC ",temp_value/10,temp_value%10);
        Oled_ShowString(3,2,display_buf);
        Oled_ShowString(3,8,"H:");
        sprintf(display_buf,"%d.%dC ",humi_value/10,humi_value%10);
        Oled_ShowString(3,10,display_buf);
        Oled_ShowString(4,0,"L:");
        sprintf(display_buf,"%dlux ",light_value);
        Oled_ShowString(4,2,display_buf);
        Oled_ShowString(4,8,"S:");
        sprintf(display_buf,"%dppm  ",smog_value);
        Oled_ShowString(4,10,display_buf);
        break;
      case 1: // Interface 1: Display set time year
        Oled_ShowCHinese(1,2,"Set Time");
        if(time_num % 5 == 0)
        {
          sprintf(display_buf,"%d-%d%d-%d%d",calendar.w_year,calendar.w_month/10,calendar.w_month%10,calendar.w_date/10,calendar.w_date%10);
          Oled_ShowString(2, 2, display_buf);
          sprintf(display_buf,"%d%d:%d%d:%d%d",calendar.hour/10,calendar.hour%10,calendar.min/10,calendar.min%10,calendar.sec/10,calendar.sec%10);
          Oled_ShowString(3, 3, display_buf);
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(2, 2, "    ");
        }
        break;
      case 2: // Interface 2: Display set time month
        Oled_ShowCHinese(1,2,"Set Time");
        if(time_num % 5 == 0)
        {
          sprintf(display_buf,"%d-%d%d-%d%d",calendar.w_year,calendar.w_month/10,calendar.w_month%10,calendar.w_date/10,calendar.w_date%10);
          Oled_ShowString(2, 2, display_buf);
          sprintf(display_buf,"%d%d:%d%d:%d%d",calendar.hour/10,calendar.hour%10,calendar.min/10,calendar.min%10,calendar.sec/10,calendar.sec%10);
          Oled_ShowString(3, 3, display_buf);
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(2, 7, "  ");
        }
        break;
      case 3: // Interface 3: Display set time day
        Oled_ShowCHinese(1,2,"Set Time");
        if(time_num % 5 == 0)
        {
          sprintf(display_buf,"%d-%d%d-%d%d",calendar.w_year,calendar.w_month/10,calendar.w_month%10,calendar.w_date/10,calendar.w_date%10);
          Oled_ShowString(2, 2, display_buf);
          sprintf(display_buf,"%d%d:%d%d:%d%d",calendar.hour/10,calendar.hour%10,calendar.min/10,calendar.min%10,calendar.sec/10,calendar.sec%10);
          Oled_ShowString(3, 3, display_buf);
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(2, 10, "  ");
        }
        break;
      case 4: // Interface 4: Display set time hour
        Oled_ShowCHinese(1,2,"Set Time");
        if(time_num % 5 == 0)
        {
          sprintf(display_buf,"%d-%d%d-%d%d",calendar.w_year,calendar.w_month/10,calendar.w_month%10,calendar.w_date/10,calendar.w_date%10);
          Oled_ShowString(2, 2, display_buf);
          sprintf(display_buf,"%d%d:%d%d:%d%d",calendar.hour/10,calendar.hour%10,calendar.min/10,calendar.min%10,calendar.sec/10,calendar.sec%10);
          Oled_ShowString(3, 3, display_buf);
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(3, 3, "  ");
        }
        break;
      case 5: // Interface 5: Display set time minute
        Oled_ShowCHinese(1,2,"Set Time");
        if(time_num % 5 == 0)
        {
          sprintf(display_buf,"%d-%d%d-%d%d",calendar.w_year,calendar.w_month/10,calendar.w_month%10,calendar.w_date/10,calendar.w_date%10);
          Oled_ShowString(2, 2, display_buf);
          sprintf(display_buf,"%d%d:%d%d:%d%d",calendar.hour/10,calendar.hour%10,calendar.min/10,calendar.min%10,calendar.sec/10,calendar.sec%10);
          Oled_ShowString(3, 3, display_buf);
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(3, 6, "  ");
        }
        break;
      case 6: // Interface 6: Display set time second
        Oled_ShowCHinese(1,2,"Set Time");
        if(time_num % 5 == 0)
        {
          sprintf(display_buf,"%d-%d%d-%d%d",calendar.w_year,calendar.w_month/10,calendar.w_month%10,calendar.w_date/10,calendar.w_date%10);
          Oled_ShowString(2, 2, display_buf);
          sprintf(display_buf,"%d%d:%d%d:%d%d",calendar.hour/10,calendar.hour%10,calendar.min/10,calendar.min%10,calendar.sec/10,calendar.sec%10);
          Oled_ShowString(3, 3, display_buf);
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(3, 9, "  ");
        }
        break;
      case 7: // Interface 7: Display set interval timing start hour
        Oled_ShowCHinese(1,0,"Set Timing Start Hour");
        if(time_num % 5 == 0)
        {
          Oled_ShowNum_2(2,5,time_shi_begin);
          Oled_ShowNum_2(2,8,time_fen_begin);
          Oled_ShowString(2,7,":");
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(2,5,"  :");
        }
        break;
      case 8: // Interface 8: Display set interval timing start minute
        Oled_ShowCHinese(1,0,"Set Timing Start Minute");
        if(time_num % 5 == 0)
        {
          Oled_ShowNum_2(2,5,time_shi_begin);
          Oled_ShowNum_2(2,8,time_fen_begin);
          Oled_ShowString(2,7,":");
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(2,7,":  ");
        }
        break;
      case 9: // Interface 9: Display set interval timing end hour
        Oled_ShowCHinese(1,0,"Set Timing End Hour");
        if(time_num % 5 == 0)
        {
          Oled_ShowNum_2(2,5,time_shi_end);
          Oled_ShowNum_2(2,8,time_fen_end);
          Oled_ShowString(2,7,":");
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(2,5,"  :");
        }
        break;
      case 10: // Interface 10: Display set interval timing end minute
        Oled_ShowCHinese(1,0,"Set Timing End Minute");
        if(time_num % 5 == 0)
        {
          Oled_ShowNum_2(2,5,time_shi_end);
          Oled_ShowNum_2(2,8,time_fen_end);
          Oled_ShowString(2,7,":");
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(2,7,":  ");
        }
        break;
      case 11: // Interface 11: Display set minimum temperature
        Oled_ShowCHinese(1,0,"Set Minimum Temperature");
        if(time_num % 5 == 0)
        {
          sprintf(display_buf,"%d  ",temp_min);
          Oled_ShowString(2, 7, display_buf);
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(2, 7, "    ");
        }
        break;
      case 12: // Interface 12: Display set maximum humidity
        Oled_ShowCHinese(1,0,"Set Maximum Humidity");
        if(time_num % 5 == 0)
        {
          sprintf(display_buf,"%d  ",humi_max);
          Oled_ShowString(2, 7, display_buf);
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(2, 7, "    ");
        }
        break;
      case 13: // Interface 13: Display set minimum light
        Oled_ShowCHinese(1,0,"Set Minimum Light");
        if(time_num % 5 == 0)
        {
          sprintf(display_buf,"%d  ",light_min);
          Oled_ShowString(2, 7, display_buf);
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(2, 7, "    ");
        }
        break;
      case 14: // Interface 14: Display set maximum smoke
        Oled_ShowCHinese(1,0,"Set Maximum Smoke");
        if(time_num % 5 == 0)
        {
          sprintf(display_buf,"%d  ",smog_max);
          Oled_ShowString(2, 7, display_buf);
        }
        if(time_num % 10 == 0)
        {
          Oled_ShowString(2, 7, "    ");
        }
        break;
      default:
        break;
    }
    u8 time_shi,time_fen; // Current time
    if(flag_display == 0) // Measurement interface
    {
      if(flag_mode == 0) {
        time_shi = calendar.hour; // Get current time
        time_fen = calendar.min;
        if(time_shi_begin*60+time_fen_begin > time_shi_end*60+time_fen_end) // Start time > End time
        {
          if((time_shi_end*60+time_fen_end <= time_shi*60+time_fen) && (time_shi*60+time_fen < time_shi_begin*60+time_fen_begin)) // Current time not in set time
            flag_time_on = 0;
          else // Otherwise
            flag_time_on = 1;
        }
        else if(time_shi_begin*60+time_fen_begin < time_shi_end*60+time_fen_end) // Start time < End time
        {
          if((time_shi_begin*60+time_fen_begin <= time_shi*60+time_fen) && (time_shi*60+time_fen < time_shi_end*60+time_fen_end)) // Current time in set time
            flag_time_on = 1;
          else // Otherwise
            flag_time_on = 0;
        }
        if(DOOR == 1) // If the wardrobe door is closed, turn off the light
        {
          RELAY_ZM = 0;
          if(flag_time_on == 1) // If within timing time, turn on disinfection
          {
            RELAY_XD = 1;
          }
          else // If outside timing time, turn off disinfection
          {
            RELAY_XD = 0;
          }
        }
        else // If the wardrobe door is open, turn off disinfection, if light is below minimum value, turn on light
        {
          RELAY_XD = 0;
          if(light_value < light_min)
          {
            RELAY_ZM = 1;
          }
          else
          {
            RELAY_ZM = 0;
          }
        }
        if(temp_value < temp_min*10 || humi_value > humi_max*10) // If temperature is below minimum value or humidity is above maximum value, then turn on heating, if humidity is above maximum value also need to ventilate
        {
          RELAY_JR = 1;
          if(humi_value > humi_max*10)
            RELAY_TF = 1;
          else
            RELAY_TF = 0;
        }
        else
        {
          RELAY_JR = 0;
          RELAY_TF = 0;
        }
        if(smog_value > smog_max) // Smoke value exceeds set maximum value, sound and light alarm
        {
          if(time_num % 3 == 0)
          {
            LED=~LED;
            BEEP=~BEEP;
          }
        }
        else
        {
          LED = 1;
          BEEP = 0;
        }
      }
    }
    else // Settings interface, turn off all relays
    {
      LED = 1;
      BEEP = 0;
      RELAY_ZM = 0;
      RELAY_TF = 0;
      RELAY_JR = 0;
      RELAY_XD = 0;
    }
    time_num++; // Timing variable +1
    Delay_ms(10);
    if(time_num %10 == 0)
      LED_SYS = ~LED_SYS;
    if(time_num >= 5000)
    {
      time_num = 0;
    }
} 05
—
Experimental Results


Material Sharing (Baidu Cloud)
https://pan.baidu.com/s/1aADIDQRNWVJwFHP4u-GZIg?pwd=9ekh Extraction code: 9ekh
(Or scan the QR code below to obtain) Purchase physical items by scanning the QR code below
Purchase physical items by scanning the QR code below