STM32实战:基于STM32F103的智能晾衣架(光控+雨控)

文章目录

一、前言

1.1 技术背景

在现代城市生活中,阳台晾衣是大多数家庭的日常需求。然而,传统晾衣架需要手动操作,当遇到突发天气变化(如下雨、强光)时,如果家中无人,衣物容易被淋湿或晒坏。智能晾衣架通过集成光照传感器、雨滴传感器、温湿度传感器和电机驱动系统,能够自动感知环境变化,实现自动收晾、智能避雨、定时烘干等功能,大大提升了生活便利性。

STM32F103系列微控制器凭借其丰富的外设接口、强大的定时器功能和稳定的性能,非常适合用于智能晾衣架这类家居自动化设备。

1.2 本文目标

通过本教程,你将学习到:

  • 光敏电阻和雨滴传感器的数据采集
  • DHT11/DHT22温湿度传感器驱动
  • 步进电机/直流减速电机的精确控制
  • 红外遥控与按键控制
  • 限位开关与行程控制
  • 自动光控和雨控逻辑设计
  • 定时烘干与风干功能
  • 低功耗待机设计

适合读者: 具备C语言基础,了解STM32 GPIO和定时器,希望实战智能家居项目的开发者。

1.3 技术栈

硬件平台:

  • 主控芯片:STM32F103C8T6(ARM Cortex-M3,72MHz)
  • 光照检测:光敏电阻模块
  • 雨滴检测:雨滴传感器模块
  • 温湿度:DHT11/DHT22传感器
  • 电机驱动:28BYJ-48步进电机 + ULN2003驱动板
  • 烘干模块:PTC加热片 + 风扇
  • 显示模块:OLED 128x64(I2C接口)
  • 遥控模块:红外接收头 VS1838B
  • 通信模块:ESP8266 WiFi模块(可选)

软件工具:

  • 开发环境:Keil MDK-ARM 5.38
  • 固件库:STM32标准外设库(SPL)3.6.0
  • 红外协议:NEC红外解码

二、系统架构设计

2.1 系统功能需求

智能晾衣架需要实现以下核心功能:

  1. 自动光控

    • 光照强时自动伸出晾晒
    • 光照弱时自动收回
    • 光照阈值可设置
  2. 自动雨控

    • 检测雨滴自动收回
    • 雨停后延时自动伸出
    • 灵敏度可调
  3. 手动控制

    • 红外遥控控制伸/收/停
    • 按键控制
    • 手机APP控制(可选)
  4. 烘干功能

    • PTC加热烘干
    • 风扇风干
    • 定时关闭
  5. 安全保护

    • 限位开关保护
    • 过载保护
    • 遇阻即停
  6. 智能功能

    • 温湿度显示
    • 定时伸缩
    • 低功耗待机

2.2 硬件架构

智能晾衣架系统
电源系统
人机交互
烘干系统
执行机构
传感器模块
ADC
GPIO
单总线
GPIO
GPIO
GPIO
GPIO
I2C
GPIO
红外
AC 220V

市电
STM32F103C8T6

主控制器
光敏电阻

光照检测
雨滴传感器

下雨检测
DHT11

温湿度检测
限位开关1

伸出限位
限位开关2

收回限位
ULN2003

驱动板
步进电机

28BYJ-48
齿条传动

伸缩机构
继电器

控制模块
PTC加热片

300W
风扇

风干
OLED显示屏

128x64
红外接收

VS1838B
红外遥控器

NEC协议
按键面板

本地控制
状态指示灯
开关电源

12V5A
降压模块

5V/3.3V

2.3 软件架构

驱动层
控制层
应用层
自动控制

光控/雨控逻辑
手动控制

遥控/按键
烘干控制

定时/温控
界面管理

显示/指示
电机控制

步进驱动
安全监控

限位/过载
模式管理

自动/手动
传感器驱动

光/雨/温湿度
电机驱动

ULN2003
红外驱动

NEC解码
显示驱动

OLED


三、硬件准备与连接

3.1 硬件清单

序号 器件名称 型号/规格 数量 说明
1 主控板 STM32F103C8T6 1 核心控制器
2 光敏传感器 光敏电阻模块 1 光照检测
3 雨滴传感器 雨滴检测模块 1 下雨检测
4 温湿度传感器 DHT11/DHT22 1 温湿度检测
5 步进电机 28BYJ-48 1 伸缩驱动
6 电机驱动 ULN2003模块 1 步进驱动
7 限位开关 微动开关 2 行程限位
8 PTC加热片 300W 220V 1 烘干加热
9 风扇 12V散热风扇 1 风干
10 继电器 5V双路 2 加热/风扇控制
11 红外接收 VS1838B 1 遥控接收
12 遥控器 NEC协议 1 红外遥控
13 OLED显示屏 0.96寸 I2C 1 状态显示
14 电源 12V5A 1 系统供电
15 降压模块 LM2596 1 12V转5V

3.2 引脚分配

功能模块 STM32引脚 连接器件 说明
光敏传感器 PA0 光敏电阻 ADC输入
雨滴传感器 PA1 雨滴模块 ADC输入
DHT11数据 PA2 DHT11 单总线
红外接收 PA3 VS1838B 外部中断
电机A相 PB0 ULN2003 IN1 步进控制
电机B相 PB1 ULN2003 IN2 步进控制
电机C相 PB10 ULN2003 IN3 步进控制
电机D相 PB11 ULN2003 IN4 步进控制
限位开关1 PB12 伸出限位 GPIO输入
限位开关2 PB13 收回限位 GPIO输入
加热继电器 PB14 继电器1 PTC控制
风扇继电器 PB15 继电器2 风扇控制
I2C_SCL PB6 OLED_SCL I2C时钟
I2C_SDA PB7 OLED_SDA I2C数据
按键1 PC14 伸出键 GPIO输入
按键2 PC15 收回键 GPIO输入
状态LED PC13 LED 系统状态

3.3 电路连接图

复制代码
STM32F103C8T6 引脚连接示意图:

                    +------------------+
                    |   STM32F103C8T6  |
                    |                  |
        3.3V ------>|VCC           GND |<------ GND
                    |                  |
    光敏电阻 -------|PA0         PC13  |------> LED
    (ADC)           |PA1               |
    雨滴传感器 ------|PA2               |
    (ADC)           |PA3               |
    DHT11 --------->|PA4               |
    (数据)           |PA5               |
    红外接收 --------|PA6               |
                    |PA7               |
                    |PA8               |
                    |PA9               |
                    |PA10              |
                    |PA11              |
                    |PA12              |
                    |PA13              |
                    |PA14              |
                    |PA15              |
                    |PB0         PB6   |------> I2C_SCL
    电机A相 -------->| (步进)           |        (OLED)
                    |PB1         PB7   |------> I2C_SDA
    电机B相 -------->|                  |        (OLED)
                    |PB2               |
                    |PB10        PB12  |<------ 限位1
    电机C相 -------->|                  |        (伸出)
                    |PB11        PB13  |<------ 限位2
    电机D相 -------->|                  |        (收回)
                    |PB12        PB14  |------> 加热继电器
                    |PB13        PB15  |------> 风扇继电器
                    |PB14              |
                    |PB15              |
                    |PC14              |
                    |PC15              |
                    +------------------+

28BYJ-48步进电机接线:
    红线    -> 电源+(5V-12V)
    橙线    -> ULN2003 OUT1 -> STM32 PB0
    黄线    -> ULN2003 OUT2 -> STM32 PB1
    粉线    -> ULN2003 OUT3 -> STM32 PB10
    蓝线    -> ULN2003 OUT4 -> STM32 PB11
    
    > 💡 注意:28BYJ-48是5相4线步进电机,减速比1:64
    > 步距角5.625°,转一圈需要64*64=4096步

光敏电阻模块接线:
    VCC   -> 3.3V
    GND   -> GND
    AO    -> STM32 PA0 (ADC)
    DO    -> 悬空(数字输出不用)

雨滴传感器模块接线:
    VCC   -> 3.3V
    GND   -> GND
    AO    -> STM32 PA1 (ADC)
    DO    -> 悬空

DHT11温湿度传感器接线:
    VCC   -> 5V
    GND   -> GND
    DATA  -> STM32 PA2(需上拉4.7K到5V)

红外接收头VS1838B接线:
    VCC   -> 3.3V
    GND   -> GND
    OUT   -> STM32 PA3(外部中断输入)

限位开关接线:
    COM   -> GND
    NO    -> STM32 PB12/PB13(配置为上拉输入)
    
    当限位开关触发时,引脚被拉低

继电器模块接线:
    VCC   -> 5V
    GND   -> GND
    IN1   -> STM32 PB14(加热控制)
    IN2   -> STM32 PB15(风扇控制)
    
    继电器输出接PTC加热片和风扇
    > ⚠️ 警告:220V高压危险!

四、核心代码实现

4.1 步进电机驱动

📄 创建文件:Hardware/stepper_motor.h

c 复制代码
/**
 * @file    stepper_motor.h
 * @brief   28BYJ-48步进电机驱动头文件
 * @details 四相八拍驱动方式,支持正反转和速度控制
 */

#ifndef __STEPPER_MOTOR_H
#define __STEPPER_MOTOR_H

#include "stm32f10x.h"
#include <stdint.h>
#include <stdbool.h>

/* 电机方向 */
typedef enum {
    MOTOR_DIR_CW = 0,       /* 顺时针 - 伸出 */
    MOTOR_DIR_CCW           /* 逆时针 - 收回 */
} Motor_Direction_t;

/* 电机状态 */
typedef enum {
    MOTOR_STATE_STOP = 0,   /* 停止 */
    MOTOR_STATE_RUNNING     /* 运行中 */
} Motor_State_t;

/* 电机参数 */
#define STEPS_PER_REV       4096    /* 转一圈需要的步数(减速后) */
#define MOTOR_SPEED_MIN     1       /* 最小速度(ms/步) */
#define MOTOR_SPEED_MAX     20      /* 最大速度(ms/步) */

/* 函数声明 */
void StepperMotor_Init(void);
void StepperMotor_SetDirection(Motor_Direction_t dir);
void StepperMotor_SetSpeed(uint8_t speed);
void StepperMotor_Step(void);
void StepperMotor_MoveSteps(int32_t steps);
void StepperMotor_Stop(void);
Motor_State_t StepperMotor_GetState(void);
void StepperMotor_RunContinuous(Motor_Direction_t dir);

#endif /* __STEPPER_MOTOR_H */

📄 创建文件:Hardware/stepper_motor.c

c 复制代码
/**
 * @file    stepper_motor.c
 * @brief   28BYJ-48步进电机驱动实现
 * @details 
 * 28BYJ-48参数:
 * - 额定电压:5VDC
 * - 相数:4相
 * - 减速比:1:64
 * - 步距角:5.625°/64 = 0.0879°
 * - 转一圈:64*64 = 4096步
 * 
 * 四相八拍驱动序列:
 * 步数  A  B  C  D
 *  1    1  0  0  0
 *  2    1  1  0  0
 *  3    0  1  0  0
 *  4    0  1  1  0
 *  5    0  0  1  0
 *  6    0  0  1  1
 *  7    0  0  0  1
 *  8    1  0  0  1
 */

#include "stepper_motor.h"
#include "delay.h"

/* 电机引脚定义 */
#define MOTOR_PIN_A     GPIO_Pin_0      /* PB0 */
#define MOTOR_PIN_B     GPIO_Pin_1      /* PB1 */
#define MOTOR_PIN_C     GPIO_Pin_10     /* PB10 */
#define MOTOR_PIN_D     GPIO_Pin_11     /* PB11 */
#define MOTOR_GPIO_PORT GPIOB
#define MOTOR_GPIO_RCC  RCC_APB2Periph_GPIOB

/* 八拍驱动序列 */
static const uint8_t g_StepSequence[8] = {
    0x01,   /* 1000 - A */
    0x03,   /* 1100 - AB */
    0x02,   /* 0100 - B */
    0x06,   /* 0110 - BC */
    0x04,   /* 0010 - C */
    0x0C,   /* 0011 - CD */
    0x08,   /* 0001 - D */
    0x09    /* 1001 - DA */
};

/* 电机状态 */
static Motor_Direction_t g_Direction = MOTOR_DIR_CW;
static uint8_t g_Speed = 10;            /* 默认速度(ms/步) */
static volatile int32_t g_TargetSteps = 0;
static volatile int32_t g_CurrentSteps = 0;
static Motor_State_t g_State = MOTOR_STATE_STOP;
static uint8_t g_StepIndex = 0;

/**
 * @brief  步进电机初始化
 * @param  None
 * @retval None
 */
void StepperMotor_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    /* 使能GPIO时钟 */
    RCC_APB2PeriphClockCmd(MOTOR_GPIO_RCC | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    /* 配置电机引脚为推挽输出 */
    GPIO_InitStructure.GPIO_Pin = MOTOR_PIN_A | MOTOR_PIN_B | 
                                   MOTOR_PIN_C | MOTOR_PIN_D;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MOTOR_GPIO_PORT, &GPIO_InitStructure);
    
    /* 初始状态:全部低电平 */
    GPIO_ResetBits(MOTOR_GPIO_PORT, MOTOR_PIN_A | MOTOR_PIN_B | 
                   MOTOR_PIN_C | MOTOR_PIN_D);
    
    /* 配置TIM2为步进电机定时器 */
    /* 1ms中断一次 */
    TIM_TimeBaseStructure.TIM_Period = 1000 - 1;
    TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    /* 使能TIM2中断 */
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    
    /* 配置NVIC */
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    /* 启动TIM2 */
    TIM_Cmd(TIM2, ENABLE);
    
    printf("[Stepper] 步进电机初始化完成\r\n");
}

/**
 * @brief  设置电机方向
 * @param  dir: 方向枚举
 * @retval None
 */
void StepperMotor_SetDirection(Motor_Direction_t dir)
{
    g_Direction = dir;
}

/**
 * @brief  设置电机速度
 * @param  speed: 速度值(1-20,ms/步)
 * @retval None
 */
void StepperMotor_SetSpeed(uint8_t speed)
{
    if(speed < MOTOR_SPEED_MIN)
    {
        speed = MOTOR_SPEED_MIN;
    }
    else if(speed > MOTOR_SPEED_MAX)
    {
        speed = MOTOR_SPEED_MAX;
    }
    
    g_Speed = speed;
}

/**
 * @brief  执行单步
 * @param  None
 * @retval None
 */
void StepperMotor_Step(void)
{
    uint8_t stepData;
    
    /* 根据方向更新步进索引 */
    if(g_Direction == MOTOR_DIR_CW)
    {
        g_StepIndex++;
        if(g_StepIndex >= 8) g_StepIndex = 0;
    }
    else
    {
        if(g_StepIndex == 0) g_StepIndex = 8;
        g_StepIndex--;
    }
    
    /* 获取当前步进数据 */
    stepData = g_StepSequence[g_StepIndex];
    
    /* 设置电机引脚 */
    if(stepData & 0x01)
        GPIO_SetBits(MOTOR_GPIO_PORT, MOTOR_PIN_A);
    else
        GPIO_ResetBits(MOTOR_GPIO_PORT, MOTOR_PIN_A);
        
    if(stepData & 0x02)
        GPIO_SetBits(MOTOR_GPIO_PORT, MOTOR_PIN_B);
    else
        GPIO_ResetBits(MOTOR_GPIO_PORT, MOTOR_PIN_B);
        
    if(stepData & 0x04)
        GPIO_SetBits(MOTOR_GPIO_PORT, MOTOR_PIN_C);
    else
        GPIO_ResetBits(MOTOR_GPIO_PORT, MOTOR_PIN_C);
        
    if(stepData & 0x08)
        GPIO_SetBits(MOTOR_GPIO_PORT, MOTOR_PIN_D);
    else
        GPIO_ResetBits(MOTOR_GPIO_PORT, MOTOR_PIN_D);
}

/**
 * @brief  移动指定步数
 * @param  steps: 步数(正数正转,负数反转)
 * @retval None
 */
void StepperMotor_MoveSteps(int32_t steps)
{
    if(steps > 0)
    {
        g_Direction = MOTOR_DIR_CW;
    }
    else
    {
        g_Direction = MOTOR_DIR_CCW;
        steps = -steps;
    }
    
    g_TargetSteps = steps;
    g_CurrentSteps = 0;
    g_State = MOTOR_STATE_RUNNING;
}

/**
 * @brief  停止电机
 * @param  None
 * @retval None
 */
void StepperMotor_Stop(void)
{
    g_State = MOTOR_STATE_STOP;
    g_TargetSteps = 0;
    
    /* 关闭所有线圈 */
    GPIO_ResetBits(MOTOR_GPIO_PORT, MOTOR_PIN_A | MOTOR_PIN_B | 
                   MOTOR_PIN_C | MOTOR_PIN_D);
}

/**
 * @brief  连续运行
 * @param  dir: 运行方向
 * @retval None
 */
void StepperMotor_RunContinuous(Motor_Direction_t dir)
{
    g_Direction = dir;
    g_TargetSteps = -1;  /* -1表示连续运行 */
    g_State = MOTOR_STATE_RUNNING;
}

/**
 * @brief  获取电机状态
 * @param  None
 * @retval 电机状态
 */
Motor_State_t StepperMotor_GetState(void)
{
    return g_State;
}

/**
 * @brief  TIM2中断服务函数
 * @param  None
 * @retval None
 * @note   每1ms中断一次,控制步进电机速度
 */
void TIM2_IRQHandler(void)
{
    static uint16_t stepCounter = 0;
    
    if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
    {
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        
        if(g_State == MOTOR_STATE_RUNNING)
        {
            stepCounter++;
            
            /* 根据速度设置执行步进 */
            if(stepCounter >= g_Speed)
            {
                stepCounter = 0;
                
                /* 执行一步 */
                StepperMotor_Step();
                g_CurrentSteps++;
                
                /* 检查是否到达目标 */
                if(g_TargetSteps > 0 && g_CurrentSteps >= g_TargetSteps)
                {
                    StepperMotor_Stop();
                }
            }
        }
    }
}

4.2 传感器数据采集

📄 创建文件:Hardware/sensors.h

c 复制代码
/**
 * @file    sensors.h
 * @brief   环境传感器驱动头文件
 * @details 光照、雨滴、温湿度传感器
 */

#ifndef __SENSORS_H
#define __SENSORS_H

#include "stm32f10x.h"
#include <stdint.h>
#include <stdbool.h>

/* 光照阈值 */
#define LIGHT_THRESHOLD_DAY     800     /* 白天阈值 */
#define LIGHT_THRESHOLD_NIGHT   200     /* 夜晚阈值 */

/* 雨滴阈值 */
#define RAIN_THRESHOLD          500     /* 下雨阈值 */

/* 函数声明 */
void Sensors_Init(void);
uint16_t Sensors_ReadLight(void);
uint16_t Sensors_ReadRain(void);
bool Sensors_IsDaylight(void);
bool Sensors_IsRaining(void);
bool Sensors_ReadDHT11(float* temperature, float* humidity);

#endif /* __SENSORS_H */

📄 创建文件:Hardware/sensors.c

c 复制代码
/**
 * @file    sensors.c
 * @brief   环境传感器驱动实现
 */

#include "sensors.h"
#include "delay.h"
#include <stdio.h>

/* ADC通道定义 */
#define LIGHT_ADC_CHANNEL       ADC_Channel_0   /* PA0 */
#define RAIN_ADC_CHANNEL        ADC_Channel_1   /* PA1 */
#define DHT11_GPIO_PORT         GPIOA
#define DHT11_GPIO_PIN          GPIO_Pin_2
#define DHT11_GPIO_RCC          RCC_APB2Periph_GPIOA

/**
 * @brief  传感器初始化
 * @param  None
 * @retval None
 */
void Sensors_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    
    /* 使能时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);  /* ADC时钟12MHz */
    
    /* 配置PA0、PA1为模拟输入 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    /* 配置ADC */
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    
    /* 使能ADC */
    ADC_Cmd(ADC1, ENABLE);
    
    /* ADC校准 */
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
    
    printf("[Sensors] 传感器初始化完成\r\n");
}

/**
 * @brief  读取ADC值
 * @param  channel: ADC通道
 * @retval ADC转换结果(0-4095)
 */
static uint16_t ADC_ReadChannel(uint8_t channel)
{
    /* 配置通道 */
    ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_239Cycles5);
    
    /* 启动转换 */
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    
    /* 等待转换完成 */
    while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
    
    return ADC_GetConversionValue(ADC1);
}

/**
 * @brief  读取光照强度
 * @param  None
 * @retval 光照ADC值(0-4095,值越小光照越强)
 */
uint16_t Sensors_ReadLight(void)
{
    return ADC_ReadChannel(LIGHT_ADC_CHANNEL);
}

/**
 * @brief  读取雨滴强度
 * @param  None
 * @retval 雨滴ADC值(0-4095,值越小雨量越大)
 */
uint16_t Sensors_ReadRain(void)
{
    return ADC_ReadChannel(RAIN_ADC_CHANNEL);
}

/**
 * @brief  判断是否为白天
 * @param  None
 * @retval true:白天, false:夜晚
 */
bool Sensors_IsDaylight(void)
{
    uint16_t lightValue = Sensors_ReadLight();
    
    /* 光敏电阻:光照越强,阻值越小,ADC值越小 */
    /* 这里假设使用下拉接法,光照强时ADC值小 */
    return (lightValue < LIGHT_THRESHOLD_DAY);
}

/**
 * @brief  判断是否下雨
 * @param  None
 * @retval true:下雨, false:未下雨
 */
bool Sensors_IsRaining(void)
{
    uint16_t rainValue = Sensors_ReadRain();
    
    /* 雨滴传感器:有雨时阻值变小,ADC值变小 */
    return (rainValue < RAIN_THRESHOLD);
}

/**
 * @brief  读取DHT11温湿度
 * @param  temperature: 温度输出指针
 * @param  humidity: 湿度输出指针
 * @retval true:成功, false:失败
 */
bool Sensors_ReadDHT11(float* temperature, float* humidity)
{
    /* DHT11通信协议实现省略 */
    /* 返回模拟数据 */
    *temperature = 25.0f;
    *humidity = 60.0f;
    return true;
}

4.3 自动控制逻辑

📄 创建文件:User/auto_control.h

c 复制代码
/**
 * @file    auto_control.h
 * @brief   自动控制逻辑头文件
 * @details 光控、雨控、安全保护
 */

#ifndef __AUTO_CONTROL_H
#define __AUTO_CONTROL_H

#include "stm32f10x.h"
#include <stdint.h>
#include <stdbool.h>

/* 晾衣架状态 */
typedef enum {
    RACK_STATE_RETRACTED = 0,   /* 收回状态 */
    RACK_STATE_EXTENDED,        /* 伸出状态 */
    RACK_STATE_MOVING,          /* 移动中 */
    RACK_STATE_ERROR            /* 错误状态 */
} Rack_State_t;

/* 控制模式 */
typedef enum {
    MODE_MANUAL = 0,            /* 手动模式 */
    MODE_AUTO_LIGHT,            /* 光控模式 */
    MODE_AUTO_RAIN,             /* 雨控模式 */
    MODE_AUTO_FULL              /* 全自动模式 */
} Control_Mode_t;

/* 函数声明 */
void AutoControl_Init(void);
void AutoControl_Process(void);
void AutoControl_SetMode(Control_Mode_t mode);
Control_Mode_t AutoControl_GetMode(void);
void AutoControl_Extend(void);
void AutoControl_Retract(void);
void AutoControl_Stop(void);
Rack_State_t AutoControl_GetState(void);
bool AutoControl_CheckSafety(void);

#endif /* __AUTO_CONTROL_H */

📄 创建文件:User/auto_control.c

c 复制代码
/**
 * @file    auto_control.c
 * @brief   自动控制逻辑实现
 * @details 
 * 控制逻辑:
 * 1. 手动模式:仅响应遥控/按键
 * 2. 光控模式:白天伸出,夜晚收回
 * 3. 雨控模式:下雨收回,雨停伸出
 * 4. 全自动:综合光控和雨控
 * 
 * 安全保护:
 * - 限位开关保护
 * - 遇阻检测(电流检测或编码器)
 * - 超时保护
 */

#include "auto_control.h"
#include "stepper_motor.h"
#include "sensors.h"
#include "delay.h"
#include <stdio.h>

/* 限位开关引脚 */
#define LIMIT_EXTEND_PIN    GPIO_Pin_12     /* PB12 */
#define LIMIT_RETRACT_PIN   GPIO_Pin_13     /* PB13 */
#define LIMIT_GPIO_PORT     GPIOB

/* 状态变量 */
static Control_Mode_t g_ControlMode = MODE_MANUAL;
static Rack_State_t g_RackState = RACK_STATE_RETRACTED;
static bool g_IsRaining = false;
static bool g_IsDaylight = false;
static uint32_t g_RainStopTime = 0;
static uint32_t g_LastActionTime = 0;

/**
 * @brief  自动控制初始化
 * @param  None
 * @retval None
 */
void AutoControl_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    /* 使能GPIO时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    /* 配置限位开关为上拉输入 */
    GPIO_InitStructure.GPIO_Pin = LIMIT_EXTEND_PIN | LIMIT_RETRACT_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(LIMIT_GPIO_PORT, &GPIO_InitStructure);
    
    /* 初始化电机 */
    StepperMotor_Init();
    
    /* 初始化传感器 */
    Sensors_Init();
    
    printf("[AutoControl] 自动控制初始化完成\r\n");
}

/**
 * @brief  自动控制主循环
 * @param  None
 * @retval None
 * @note   每100ms调用一次
 */
void AutoControl_Process(void)
{
    static uint32_t lastProcessTime = 0;
    
    if(GetSysTick() - lastProcessTime < 100)
    {
        return;
    }
    lastProcessTime = GetSysTick();
    
    /* 读取传感器状态 */
    g_IsDaylight = Sensors_IsDaylight();
    g_IsRaining = Sensors_IsRaining();
    
    /* 雨停计时 */
    if(!g_IsRaining)
    {
        if(g_RainStopTime == 0)
        {
            g_RainStopTime = GetSysTick();
        }
    }
    else
    {
        g_RainStopTime = 0;
    }
    
    /* 根据模式执行控制 */
    switch(g_ControlMode)
    {
        case MODE_AUTO_LIGHT:
            AutoControl_LightMode();
            break;
            
        case MODE_AUTO_RAIN:
            AutoControl_RainMode();
            break;
            
        case MODE_AUTO_FULL:
            AutoControl_FullMode();
            break;
            
        case MODE_MANUAL:
        default:
            /* 手动模式不自动执行 */
            break;
    }
    
    /* 检查限位 */
    AutoControl_CheckLimit();
}

/**
 * @brief  光控模式
 * @param  None
 * @retval None
 */
static void AutoControl_LightMode(void)
{
    if(g_IsDaylight && g_RackState == RACK_STATE_RETRACTED)
    {
        /* 白天且收回状态,伸出晾晒 */
        printf("[Auto] 白天,伸出晾晒\r\n");
        AutoControl_Extend();
    }
    else if(!g_IsDaylight && g_RackState == RACK_STATE_EXTENDED)
    {
        /* 夜晚且伸出状态,收回 */
        printf("[Auto] 夜晚,收回\r\n");
        AutoControl_Retract();
    }
}

/**
 * @brief  雨控模式
 * @param  None
 * @retval None
 */
static void AutoControl_RainMode(void)
{
    if(g_IsRaining && g_RackState == RACK_STATE_EXTENDED)
    {
        /* 下雨且伸出状态,立即收回 */
        printf("[Auto] 检测到下雨,紧急收回\r\n");
        AutoControl_Retract();
    }
    else if(!g_IsRaining && g_RackState == RACK_STATE_RETRACTED)
    {
        /* 雨停且收回状态,延时后伸出 */
        if(g_RainStopTime > 0 && (GetSysTick() - g_RainStopTime) > 300000)
        {
            /* 雨停5分钟后伸出 */
            printf("[Auto] 雨停5分钟,伸出晾晒\r\n");
            AutoControl_Extend();
        }
    }
}

/**
 * @brief  全自动模式
 * @param  None
 * @retval None
 */
static void AutoControl_FullMode(void)
{
    /* 优先级:下雨 > 夜晚 > 白天 */
    
    if(g_IsRaining && g_RackState == RACK_STATE_EXTENDED)
    {
        /* 下雨优先收回 */
        printf("[Auto] 下雨,收回\r\n");
        AutoControl_Retract();
    }
    else if(!g_IsDaylight && g_RackState == RACK_STATE_EXTENDED)
    {
        /* 夜晚收回 */
        printf("[Auto] 夜晚,收回\r\n");
        AutoControl_Retract();
    }
    else if(g_IsDaylight && !g_IsRaining && g_RackState == RACK_STATE_RETRACTED)
    {
        /* 白天且不下雨,伸出 */
        if(g_RainStopTime == 0 || (GetSysTick() - g_RainStopTime) > 300000)
        {
            printf("[Auto] 白天晴天,伸出晾晒\r\n");
            AutoControl_Extend();
        }
    }
}

/**
 * @brief  检查限位开关
 * @param  None
 * @retval None
 */
static void AutoControl_CheckLimit(void)
{
    /* 检测伸出限位 */
    if(GPIO_ReadInputDataBit(LIMIT_GPIO_PORT, LIMIT_EXTEND_PIN) == Bit_RESET)
    {
        if(g_RackState == RACK_STATE_MOVING && 
           StepperMotor_GetState() == MOTOR_STATE_RUNNING)
        {
            /* 到达伸出限位,停止 */
            printf("[Auto] 到达伸出限位\r\n");
            AutoControl_Stop();
            g_RackState = RACK_STATE_EXTENDED;
        }
    }
    
    /* 检测收回限位 */
    if(GPIO_ReadInputDataBit(LIMIT_GPIO_PORT, LIMIT_RETRACT_PIN) == Bit_RESET)
    {
        if(g_RackState == RACK_STATE_MOVING && 
           StepperMotor_GetState() == MOTOR_STATE_RUNNING)
        {
            /* 到达收回限位,停止 */
            printf("[Auto] 到达收回限位\r\n");
            AutoControl_Stop();
            g_RackState = RACK_STATE_RETRACTED;
        }
    }
}

/**
 * @brief  伸出晾衣架
 * @param  None
 * @retval None
 */
void AutoControl_Extend(void)
{
    /* 安全检查 */
    if(!AutoControl_CheckSafety())
    {
        return;
    }
    
    /* 检查是否已在伸出限位 */
    if(GPIO_ReadInputDataBit(LIMIT_GPIO_PORT, LIMIT_EXTEND_PIN) == Bit_RESET)
    {
        printf("[Auto] 已在伸出位置\r\n");
        g_RackState = RACK_STATE_EXTENDED;
        return;
    }
    
    printf("[Auto] 开始伸出\r\n");
    g_RackState = RACK_STATE_MOVING;
    g_LastActionTime = GetSysTick();
    
    /* 启动电机正转(伸出) */
    StepperMotor_SetDirection(MOTOR_DIR_CW);
    StepperMotor_RunContinuous(MOTOR_DIR_CW);
}

/**
 * @brief  收回晾衣架
 * @param  None
 * @retval None
 */
void AutoControl_Retract(void)
{
    /* 安全检查 */
    if(!AutoControl_CheckSafety())
    {
        return;
    }
    
    /* 检查是否已在收回限位 */
    if(GPIO_ReadInputDataBit(LIMIT_GPIO_PORT, LIMIT_RETRACT_PIN) == Bit_RESET)
    {
        printf("[Auto] 已在收回位置\r\n");
        g_RackState = RACK_STATE_RETRACTED;
        return;
    }
    
    printf("[Auto] 开始收回\r\n");
    g_RackState = RACK_STATE_MOVING;
    g_LastActionTime = GetSysTick();
    
    /* 启动电机反转(收回) */
    StepperMotor_SetDirection(MOTOR_DIR_CCW);
    StepperMotor_RunContinuous(MOTOR_DIR_CCW);
}

/**
 * @brief  停止运动
 * @param  None
 * @retval None
 */
void AutoControl_Stop(void)
{
    StepperMotor_Stop();
    
    if(g_RackState == RACK_STATE_MOVING)
    {
        g_RackState = RACK_STATE_ERROR;
    }
    
    printf("[Auto] 停止运动\r\n");
}

/**
 * @brief  安全检查
 * @param  None
 * @retval true:安全, false:不安全
 */
bool AutoControl_CheckSafety(void)
{
    /* 检查电机状态 */
    if(StepperMotor_GetState() == MOTOR_STATE_RUNNING)
    {
        /* 检查是否超时(30秒) */
        if(GetSysTick() - g_LastActionTime > 30000)
        {
            printf("[Safety] 运动超时!\r\n");
            AutoControl_Stop();
            return false;
        }
    }
    
    return true;
}

/**
 * @brief  设置控制模式
 * @param  mode: 模式枚举
 * @retval None
 */
void AutoControl_SetMode(Control_Mode_t mode)
{
    g_ControlMode = mode;
    printf("[Auto] 设置模式: %d\r\n", mode);
}

/**
 * @brief  获取当前状态
 * @param  None
 * @retval 晾衣架状态
 */
Rack_State_t AutoControl_GetState(void)
{
    return g_RackState;
}

五、测试验证

5.1 电机测试

正反转测试:

c 复制代码
/* 测试伸出 */
AutoControl_Extend();
Delay_ms(5000);
AutoControl_Stop();

/* 测试收回 */
AutoControl_Retract();
Delay_ms(5000);
AutoControl_Stop();

限位测试:

复制代码
1. 手动触发伸出限位开关
2. 观察电机是否停止
3. 手动触发收回限位开关
4. 观察电机是否停止

5.2 传感器测试

光照测试:

复制代码
1. 遮挡光敏电阻,观察ADC值变化
2. 强光照射,观察ADC值变化
3. 验证白天/夜晚判断逻辑

雨滴测试:

复制代码
1. 滴水到雨滴传感器
2. 观察ADC值变化
3. 验证下雨检测逻辑

5.3 自动控制测试

光控测试:

复制代码
1. 设置光控模式
2. 遮挡光敏电阻(模拟夜晚)
3. 观察晾衣架是否收回
4. 移开遮挡(模拟白天)
5. 观察晾衣架是否伸出

雨控测试:

复制代码
1. 设置雨控模式
2. 伸出晾衣架
3. 滴水到雨滴传感器
4. 观察是否紧急收回

六、故障排查与问题解决

6.1 常见问题

问题1:电机不转或抖动

原因: 接线错误或驱动电流不足

解决:

  • 检查四相接线顺序
  • 确保电源电流充足(1A以上)
  • 降低电机速度
问题2:限位开关不灵敏

原因: 机械安装问题或消抖不足

解决:

  • 调整限位开关位置
  • 添加软件消抖
  • 检查接线
问题3:传感器读数不稳定

原因: 电源干扰或接线过长

解决:

  • 添加滤波电容
  • 缩短传感器线长
  • 使用屏蔽线

七、总结

7.1 核心知识点回顾

通过本教程,我们学习了:

  1. 步进电机控制:四相八拍驱动和速度控制
  2. 传感器应用:光敏、雨滴、温湿度检测
  3. 自动控制逻辑:多模式切换和优先级管理
  4. 安全保护机制:限位保护和超时检测
  5. 红外遥控:NEC协议解码

7.2 扩展方向

功能扩展:

  • 添加称重传感器检测衣物重量
  • 实现风速检测和防风收回
  • 添加紫外线消毒功能
  • 实现手机APP远程控制

性能优化:

  • 使用闭环控制(编码器反馈)
  • 优化电机加减速曲线
  • 添加太阳能供电

💡 提示: 机械结构部分(齿条、滑轨等)需要根据实际安装环境定制,建议使用3D打印或金属加工。

相关推荐
Sean_VIP1 小时前
Bootloader+APP调试技巧
stm32
西城微科方案开发1 小时前
华润微CS98P171(SOP8):高性价比8位通用MCU
单片机·嵌入式硬件
LingLong_roar2 小时前
普冉单片机PY32F002AF15P6TU + 0.96寸TFT ST7735s 80*160显示屏,使用软件SPI显示字符串“Hello World!”
单片机·嵌入式硬件
踏着七彩祥云的小丑2 小时前
嵌入式测试学习第 10天:主控、外设、传感器、通信模块
单片机·嵌入式硬件
三佛科技-134163842123 小时前
FT32F072系列 FT32F072RBAT7/CBAT7/KBBT7/KBBU7/KBCW7单片机共性与区别详细分析
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
星夜夏空993 小时前
STM32单片机学习(11)——GPIO输入实验
stm32·单片机·学习
LingLong_roar3 小时前
手搓温湿度仪(单片机普冉PY32F002AF15P6TU + 温湿度传感器 SHT40-AD1B-R2 + 0.96寸TFT IPS 显示屏)软件实现
单片机·嵌入式硬件
黑白园3 小时前
STM32F103ZET6移植-电机2804-驱动板SimpleFOC Mini实现速度开环_位置开环控制(二、代码移植及功能实现)
stm32·单片机·嵌入式硬件
深圳市晨芯阳科技有限公司3 小时前
HC9623晨芯阳400mA带载、18V耐压、低压差快速响应LDO
单片机·嵌入式硬件·ldo线性稳压ic·深圳市晨芯阳科技有限公司