文章目录
-
- 一、前言
-
- [1.1 技术背景](#1.1 技术背景)
- [1.2 本文目标](#1.2 本文目标)
- [1.3 技术栈](#1.3 技术栈)
- 二、系统架构设计
-
- [2.1 系统功能需求](#2.1 系统功能需求)
- [2.2 硬件架构](#2.2 硬件架构)
- [2.3 软件架构](#2.3 软件架构)
- 三、硬件准备与连接
-
- [3.1 硬件清单](#3.1 硬件清单)
- [3.2 引脚分配](#3.2 引脚分配)
- [3.3 电路连接图](#3.3 电路连接图)
- 四、核心代码实现
-
- [4.1 步进电机驱动](#4.1 步进电机驱动)
- [4.2 传感器数据采集](#4.2 传感器数据采集)
- [4.3 自动控制逻辑](#4.3 自动控制逻辑)
- 五、测试验证
-
- [5.1 电机测试](#5.1 电机测试)
- [5.2 传感器测试](#5.2 传感器测试)
- [5.3 自动控制测试](#5.3 自动控制测试)
- 六、故障排查与问题解决
-
- [6.1 常见问题](#6.1 常见问题)
- 七、总结
-
- [7.1 核心知识点回顾](#7.1 核心知识点回顾)
- [7.2 扩展方向](#7.2 扩展方向)
一、前言
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 系统功能需求
智能晾衣架需要实现以下核心功能:
-
自动光控:
- 光照强时自动伸出晾晒
- 光照弱时自动收回
- 光照阈值可设置
-
自动雨控:
- 检测雨滴自动收回
- 雨停后延时自动伸出
- 灵敏度可调
-
手动控制:
- 红外遥控控制伸/收/停
- 按键控制
- 手机APP控制(可选)
-
烘干功能:
- PTC加热烘干
- 风扇风干
- 定时关闭
-
安全保护:
- 限位开关保护
- 过载保护
- 遇阻即停
-
智能功能:
- 温湿度显示
- 定时伸缩
- 低功耗待机
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 核心知识点回顾
通过本教程,我们学习了:
- 步进电机控制:四相八拍驱动和速度控制
- 传感器应用:光敏、雨滴、温湿度检测
- 自动控制逻辑:多模式切换和优先级管理
- 安全保护机制:限位保护和超时检测
- 红外遥控:NEC协议解码
7.2 扩展方向
功能扩展:
- 添加称重传感器检测衣物重量
- 实现风速检测和防风收回
- 添加紫外线消毒功能
- 实现手机APP远程控制
性能优化:
- 使用闭环控制(编码器反馈)
- 优化电机加减速曲线
- 添加太阳能供电
💡 提示: 机械结构部分(齿条、滑轨等)需要根据实际安装环境定制,建议使用3D打印或金属加工。