☰
基于STM32F103的智能晾衣架控制系统设计
2026/10/25 1:03:47 网站建设 项目流程

在智能家居的单项设备里,智能晾衣架看起来功能不多,真正动手做控制系统时才会发现,它把传感器采集、电机驱动、限位保护、按键交互、状态显示和低功耗管理全部串在了一条链路上。基于 STM32F103 设计一套智能晾衣架控制系统,是一个适合用来打通 GPIO、ADC、定时器 PWM、DMA、状态机这些基础外设的完整案例。本文从方案选型开始,依次给出硬件连接、引脚分配、CubeMX 工程配置、核心模块代码、运行验证方法和常见问题排查路径,最终目标是让读者从最小系统板出发,把整套控制系统跑起来,并理解每一步为什么这样设计。

1. 系统功能与方案选型

1.1 智能晾衣架要解决哪些问题

传统晾衣架需要人工判断天气,下雨时来不及收,阳光好时又常常忘记晾出去。智能晾衣架控制系统要完成的动作可以归纳为四类:

  • 自动收起:检测到雨滴后,先收回撑杆,再上升归位。
  • 自动晾晒:天气晴朗且无雨时,下降并展开撑杆。
  • 手动控制:用户通过按键随时执行上升、下降、展开、收起。
  • 限位与保护:电机运行到顶部或底部限位开关时立即停止,防止机械损坏。

在此基础上还可以扩展干燥、紫外消毒、远程控制等功能,但核心控制逻辑始终围绕“传感器阈值判断 + 电机动作 + 限位保护”这条主线展开。真正影响可靠性的不是某个外设有多高级,而是传感器误判时能不能兜住,电机堵转时限位有没有生效,按钮按下后状态机是否按预期迁移。

1.2 为什么选择 STM32F103 作为主控

STM32F103 是意法半导体基于 Cortex-M3 内核的 72MHz 主流 MCU,在智能晾衣架这种控制场景里优势非常具体:

  • 定时器资源充足。通用定时器 TIM2、TIM3、TIM4 各带 4 个 PWM 通道,电机调速、LED 呼吸灯、干燥风机调速可以复用。
  • ADC 外设完整。ADC1 支持多通道扫描,雨滴传感器和光敏传感器可以直接接模拟输出脚,不需要外挂专用 ADC 芯片。
  • 低功耗模式可用。待机时进入 STOP 模式,通过按键或雨滴传感器的 EXTI 中断唤醒,适合晾衣架长期待机的使用场景。
  • 生态成熟。HAL 库、标准外设库、寄存器开发三种方式都有大量资料,问题排查成本低。

对比 51 单片机,F103 的定时器、ADC 精度和 DMA 能力明显更强;对比 ESP32,F103 没有无线功能,但控制实时性、功耗表现和成本更适合纯本地控制场景。如果后续想加 App 远程控制,可以保留 F103 做执行控制,再外接 WiFi 模块走串口通信,这是比较稳妥的分层方案。

1.3 系统总体架构

整个系统可以分成三层:

  • 感知层:雨滴传感器、光敏传感器、限位开关、按键。
  • 控制层:STM32F103 负责信号采集、阈值判断、状态机迁移、PWM 输出。
  • 执行层:直流减速电机、电机驱动模块、OLED 显示屏、干燥继电器。

数据流向是:雨滴传感器和光敏传感器输出模拟电压,由 ADC 采集后滤波,再与阈值比较产生“下雨 / 晴天 / 阴天 / 夜晚”等天气结论;状态机根据当前状态和天气结论决定是否启动电机;电机驱动模块接收方向 GPIO 和 PWM 占空比信号,驱动直流电机;限位开关反馈行程是否到顶或到底,状态机收到限位信号后停机关闭 PWM。

这套架构的关键是状态机。晾衣架不是一直转电机,而是“收到触发 -> 执行动作 -> 到达限位 -> 回到待机”的短时工作过程。如果不用状态机而用阻塞延时,雨滴检测和按键扫描都会被电机转动时的大延时卡死,系统看起来就会像“按了没反应”。

2. 硬件设计与引脚分配

2.1 硬件物料清单

下面是一套适合学习验证的物料清单。原始设计没有限定具体型号,落地前要根据自己手上的板子核对引脚和电压。

模块推荐型号用途备注
主控板STM32F103C8T6 最小系统板控制核心常见 Blue Pill 板载 LED 在 PC13
电机驱动L298N 或 TB6612FNG驱动直流电机TB6612 功耗更低,L298N 更容易买到
直流减速电机6V 或 12V 带减速箱电机升降、展开需要两路,或先验证一路升降
雨滴传感器LM393 雨滴模块检测下雨同时有 DO 数字输出和 AO 模拟输出
光敏传感器光敏电阻模块检测光照强度用 AO 模拟输出
限位开关直动式微动开关顶部 / 底部限位常闭或常开均可,软件按逻辑调整
按键轻触按键 x4手动控制上升、下降、自动、停止
显示屏SSD1306 OLED 0.96 寸 I2C显示状态I2C 地址默认 0x78
继电器5V 单路继电器模块干燥 / 紫外线设备控制交流设备时注意隔离
供电5V 2A 电源 + 12V 电源主控和电机分区供电电机驱动单独供电更稳定

2.2 引脚分配表

这里以 STM32F103C8T6 为例,给出完整引脚分配,实际焊接和接线时对照此表。选择 PA0-PA5 作为 ADC 和 PWM 通道,是因为 ADC1 的输入通道与定时器 PWM 通道天然分布在 PA 口附近,布局方便。

功能引脚说明
升降电机 PWMPA0TIM2_CH1,PWM 输出
展开电机 PWMPA1TIM2_CH2,PWM 输出
升降电机方向 1PB0控制 IN1 电平
升降电机方向 2PB1控制 IN2 电平
展开电机方向 1PB2控制 IN3 电平
展开电机方向 2PB10控制 IN4 电平
雨滴传感器 AOPA4ADC1_IN4
光敏传感器 AOPA5ADC1_IN5
顶部限位开关PB12输入,内部上拉,按下接地
底部限位开关PB13输入,内部上拉,按下接地
上升按键PB14输入,内部上拉
下降按键PB15输入,内部上拉
自动模式按键PA8输入,内部上拉
停止按键PA11输入,内部上拉
OLED SCLPB6I2C1 时钟
OLED SDAPB7I2C1 数据
串口 TXPA9USART1 调试输出
串口 RXPA10USART1 调试输入
干燥继电器PC14输出,高电平闭合
板载 LEDPC13调试指示灯

注意,PC14 和 PC15 在最小系统板上默认连接外部低速晶振引脚,如果时钟配置选择不使用 LSE,这两个引脚可以作为普通 GPIO 使用。CubeMX 里不要把 PC13-PC15 之外的晶振相关配置改乱。

2.3 电机驱动与限位开关电路

直流电机的正反转通过电机驱动模块的 H 桥完成。以 L298N 为例,ENA 接 PWM 控制速度,IN1 和 IN2 组合控制方向:

IN1IN2ENA电机状态
高低PWM正转
低高PWM反转
高高-刹车
低低-停止

STM32 的 GPIO 输出 3.3V 电平,L298N 逻辑输入兼容 5V 和 3.3V 的型号可以直接接,但如果驱动模块要求 5V 逻辑电平,需要加电平转换或选择逻辑供电 3.3V 的模式。TB6612 的 VM 接电机电源,VCC 接 3.3V 或 5V,控制逻辑更友好。

限位开关的接法建议使用“一端接 GND,另一端接 MCU 引脚,内部上拉”的方式。开关未按下时读到高电平,按下时读到低电平。这样即使开关线脱落,也只会读到持续高电平,不会误触发限位动作。

实际接线时最容易踩的坑是驱动模块的 GND 与 MCU 的 GND 没有共地。MCU 的 PWM 信号和驱动模块之间如果没有共同参考地,逻辑电平会悬空,电机要么不转,要么随机抖动。所有模块的 GND 必须接到同一个网络。

2.4 传感器接入与 ADC 设计

雨滴模块和光敏模块都有 AO 模拟输出,输出电压范围约 0 到 3.3V(或 0 到 5V,取决于模块供电)。如果模块供电 5V 输出可能超过 3.3V,需要分压后送入 STM32 ADC 引脚。这里在物料选型时直接选择 3.3V 供电的模块,把输出限制在 MCU ADC 输入范围内。

STM32F103 的 ADC 是 12 位分辨率,参考电压 VREF 通常接 3.3V,因此采样值换算电压的公式是:

float voltage = (float)adc_value * 3.3f / 4095.0f;

雨滴传感器模块板上有一个蓝色电位器,用来调节比较器跳变阈值。这个电位器直接影响 DO 数字输出,但对 AO 模拟输出没有影响。使用模拟量时,阈值应该在程序里根据实测数据确定,而不是靠模块上的电位器拍脑袋。

3. 开发环境与工程准备

3.1 HAL 库还是标准外设库

STM32F103 的开发方式主要有三种:寄存器、标准外设库、HAL 库。本文代码使用 HAL 库,原因是 CubeMX 配置外设更直观,ADC、PWM、I2C 的初始化代码由工具生成,可以把精力放在业务逻辑上。标准外设库运行效率更高,库更轻量,但遇到外设组合配置时需要手动处理更多细节。

实际项目里更重要的是工程可维护性。HAL 库虽然代码量稍大,但通过 CubeMX 重新生成不会覆盖用户代码段,适合团队协作。如果对 HAL 库的某些实现不满意,可以针对关键路径改写寄存器,这也是常见优化手段。

3.2 CubeMX 工程配置

用 CubeMX 新建工程选择 STM32F103C8Tx,按以下步骤配置外设:

  • RCC:HSE 选择 Crystal/Ceramic Resonator,SYS 选择 Serial Wire(保留 SWD 调试)。
  • Clock Configuration:HSE 8MHz,PLL 倍频到 72MHz,APB1 分频 2 得到 36MHz,APB2 不分频得到 72MHz。
  • ADC1:使能通道 4(PA4)和通道 5(PA5),采样时间选择 239.5 周期,连续转换或扫描模式按需配置。
  • TIM2:选择 Channel1 和 Channel2 为 PWM Generation CH1/CH2,预分频器 71,自动重载值根据 PWM 频率计算。
  • I2C1:使能标准模式或快速模式,速度 400kHz。
  • USART1:异步模式,波特率 115200,8 位数据,无校验。
  • GPIO:PB0、PB1、PB2、PB10 设置为输出推挽,初始电平低;PB12、PB13、PB14、PB15、PA8、PA11 设置为输入上拉。

PWM 频率的计算公式是:

PWM频率 = 定时器时钟 / ((预分频器 + 1) * (自动重载值 + 1))

定时器时钟为 72MHz 时,预分频器设为 71,自动重载值设为 999,PWM 频率约 1kHz。直流电机通常用 1kHz 到 20kHz 之间的 PWM,频率太低电机噪声大,频率太高会加重驱动模块开关损耗。1kHz 用于验证足够。

3.3 最小验证:点亮板载 LED

工程生成后,先不要急着写业务代码,第一步确认编译下载链路正常。在main函数中写一个 LED 翻转逻辑:

while (1) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); HAL_Delay(200); }

编译下载后板载 LED 以 200ms 周期闪烁,说明芯片、下载器、工程配置都正常。如果这一步都没通过,先检查 SWD 连接、复位电路和 BOOT0 跳线,不要继续往下写传感器代码。

3.4 工程目录结构

一个适合长期维护的工程结构建议如下:

project/ ├── Core/ │ ├── Inc/ │ └── Src/ │ ├── main.c │ ├── stm32f1xx_it.c │ └── system_stm32f1xx.c ├── Drivers/ │ ├── CMSIS/ │ └── STM32F1xx_HAL_Driver/ ├── App/ │ ├── app_state_machine.c │ ├── app_state_machine.h │ ├── app_sensor.c │ ├── app_sensor.h │ ├── app_motor.c │ ├── app_motor.h │ ├── app_ui.c │ └── app_ui.h ├── Middlewares/ └── MDK-ARM/

把用户代码放到独立的App目录,而不是全部塞进main.c,是为了避免 CubeMX 重新生成时被覆盖,也方便后续移植到其他型号的 MCU。

4. 核心功能模块实现

4.1 顶层状态机设计

晾衣架的工作状态可以归纳为六种:

状态枚举动作说明
待机STATE_IDLE电机停止,等待触发
上升STATE_LIFTING升降电机正转,晾衣架上升
下降STATE_LOWERING升降电机反转,晾衣架下降
展开STATE_EXTENDING展开电机正转,撑杆伸出
收起STATE_RETRACTING展开电机反转,撑杆收回
手动暂停STATE_STOPPED用户主动停止,解除自动锁定

为什么用状态机而不是直接在主循环里顺序执行?因为晾衣架的动作不是瞬时完成的,电机会运行几百毫秒甚至几秒,期间必须持续检测限位开关、按键和雨滴传感器。状态机把“当前在做什么”和“下一步做什么”分离,每个循环只处理当前状态对应的事件,逻辑清晰且不会阻塞传感器采样。

状态机主循环的核心框架如下:

typedef enum { STATE_IDLE = 0, STATE_LIFTING, STATE_LOWERING, STATE_EXTENDING, STATE_RETRACTING, STATE_STOPPED } RackState; RackState rack_state = STATE_IDLE; void Rack_StateMachine(void) { switch (rack_state) { case STATE_IDLE: Rack_HandleIdle(); break; case STATE_LIFTING: Rack_HandleLifting(); break; case STATE_LOWERING: Rack_HandleLowering(); break; case STATE_EXTENDING: Rack_HandleExtending(); break; case STATE_RETRACTING: Rack_HandleRetracting(); break; case STATE_STOPPED: Rack_HandleStopped(); break; default: rack_state = STATE_IDLE; break; } }

每个状态处理函数只做三件事:检查限位是否到达,检查停止按键是否按下,决定是继续当前动作还是切换状态。例如下降状态下,一旦底部限位开关变成有效电平,立即停止电机,切回待机。

4.2 传感器采集与滤波

ADC 采集不能拿单次采样值直接做判断。传感器受电源纹波、线缆干扰影响,单次值可能有较大波动,需要软件滤波。这里采用“中位值平均滤波法”:连续采集 N 次,去掉最大值和最小值,剩余值求平均。

#define SENSOR_SAMPLE_N 5 uint16_t ADC_ReadFiltered(ADC_HandleTypeDef *hadc, uint32_t channel) { uint16_t samples[SENSOR_SAMPLE_N]; uint16_t max_val = 0, min_val = 4095; uint32_t sum = 0; for (int i = 0; i < SENSOR_SAMPLE_N; i++) { ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = channel; sConfig.Rank = 1; sConfig.SamplingPeriod = ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(hadc, &sConfig); HAL_ADC_Start(hadc); HAL_ADC_PollForConversion(hadc, 10); samples[i] = HAL_ADC_GetValue(hadc); HAL_ADC_Stop(hadc); if (samples[i] > max_val) max_val = samples[i]; if (samples[i] < min_val) min_val = samples[i]; sum += samples[i]; } return (uint16_t)((sum - max_val - min_val) / (SENSOR_SAMPLE_N - 2)); }

这段代码每次循环重新配置通道,适合采样通道少、采样频率要求不高的场景。更高效的做法是开启 ADC 扫描模式加 DMA,一次转换多通道,CPU 只在 DMA 传输完成后取数据。晾衣架的采样频率毫秒级即可满足,轮询方式更直观,也更容易排查问题。

阈值判断必须包含回差(迟滞)。以上为例:如果雨滴采样值从干燥的 3800 降到下雨的 1200,不能只设一个 2500 的阈值,否则雨停时采样值在阈值附近抖动,继电器和电机会频繁启停。正确做法是设置两个阈值:

#define RAIN_WET_THRESHOLD 2000 // 采样值低于该值判定为下雨 #define RAIN_DRY_THRESHOLD 2800 // 采样值高于该值判定为雨停

天气状态从“无雨”变成“有雨”必须低于 2000,从“有雨”变成“无雨”必须高于 2800。中间 2000 到 2800 的区间保持不变,这就是回差。回差不仅能抑制抖动,还能避免传感器表面残留水珠造成的反复触发。实际阈值要按传感器供电和模块型号实测调整,不同批次的传感器输出范围不完全一样。

4.3 电机控制与限位检测

电机的运动封装成一个函数,方向由两个 GPIO 控制,速度由 PWM 占空比控制。占空比范围 0 到 999,对应 0 到 100%。

void Motor1_Set(int8_t direction, uint16_t speed) { if (direction > 0) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); } else if (direction < 0) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0); return; } __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, speed); }

限位开关检测在状态机中每一轮都会执行。因为在上升或下降过程中,限位开关按下可能导致电机堵转,堵转电流会烧毁驱动模块或电机,检测必须在主循环中高频执行。

int8_t Limit_TopPressed(void) { return (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_RESET) ? 1 : 0; } int8_t Limit_BottomPressed(void) { return (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_13) == GPIO_PIN_RESET) ? 1 : 0; }

限位开关也有抖动问题。机械开关按下和释放的瞬间会产生毛刺,可能导致状态机误判。软件层面可以做一个连续确认机制:连续 5 次读到有效电平才认为限位到达。这样做的另一个好处是避免开关老化后接触不良导致误动作。

4.4 自动控制逻辑

自动模式是系统的核心业务。进入自动模式后,主循环每次运行都调用Rack_AutoControl,输入是滤波后的传感器值和当前状态,输出是状态迁移信号。

void Rack_AutoControl(void) { uint16_t rain_adc = ADC_ReadFiltered(&hadc1, ADC_CHANNEL_4); uint16_t light_adc = ADC_ReadFiltered(&hadc1, ADC_CHANNEL_5); if (rack_state != STATE_IDLE) { return; } if (Light_IsRain(rain_adc)) { if (clothes_extended_flag) { rack_state = STATE_RETRACTING; } else if (clothes_lowered_flag) { rack_state = STATE_LIFTING; } } else if (Light_IsSunny(light_adc) && !clothes_lowered_flag) { rack_state = STATE_LOWERING; } }

这里补充两个标志位clothes_extended_flag和clothes_lowered_flag,它们由状态机在到达限位时更新,用来记录当前衣架在哪个位置,避免每次循环都重新执行已完成的动作。

自动逻辑必须考虑动作完成后的状态持久化。系统断电再上电后,MCU 不知道衣架是在顶部还是底部,如果直接执行自动逻辑可能造成错误动作。生产环境可以在 Flash 或者外挂 EEPROM 中保存状态,也可以要求上电后先回到初始位置,这属于工程化必须处理的边界条件。

4.5 PWM + DMA 实现电机平滑启停

直接给电机 100% 占空比会产生很大的启动电流,同时机械结构会剧烈抖动;直接切断 PWM 会因为惯性造成冲击。用 PWM + DMA 实现占空比斜坡是一个实用的优化方案。

思路是预先准备一段占空比数组,启动时 DMA 按照定时器更新事件的节奏把数组中的值逐个写入比较寄存器,实现占空比从 0 到目标值的平滑递增。以 TIM2_CH1 为例:

#define RAMP_STEPS 200 uint16_t ramp_up_table[RAMP_STEPS]; TIM_HandleTypeDef htim2_dma; DMA_HandleTypeDef hdma_tim2_ch1; void Motor_PwmDmaInit(void) { for (int i = 0; i < RAMP_STEPS; i++) { ramp_up_table[i] = (uint16_t)((uint32_t)i * 999 / RAMP_STEPS); } hdma_tim2_ch1.Instance = DMA1_Channel5; hdma_tim2_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_tim2_ch1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_tim2_ch1.Init.MemInc = DMA_MINC_ENABLE; hdma_tim2_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_tim2_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_tim2_ch1.Init.Mode = DMA_NORMAL; hdma_tim2_ch1.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_tim2_ch1); __HAL_LINKDMA(&htim2, hdma[TIM_DMA_ID_CC1], hdma_tim2_ch1); HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, ramp_up_table, RAMP_STEPS); }

使用 DMA 模式后,CPU 不需要在中断里逐次修改比较寄存器,只需要在发送完成后确认动作是否结束。这个例子对晾衣架来说属于优化项,不是必需项。先跑通普通 PWM 控制,再考虑启动平滑。启动时从 30% 占空比开始、逐步加到目标值,已经能明显减少机械冲击,不一定要引入 DMA。

4.6 OLED 状态显示

0.96 寸 SSD1306 OLED 通过 I2C1 连接,显示内容建议包含:当前模式(自动 / 手动)、雨滴采样值、光照采样值、当前状态(待机 / 上升 / 下降 / 展开 / 收起)。显示刷新不宜太频繁,否则会占用 I2C 总线影响其他逻辑,建议 500ms 刷新一次。

void UI_Update(RackState state, uint8_t mode, uint16_t rain, uint16_t light) { static uint32_t last_refresh = 0; uint32_t now = HAL_GetTick(); if (now - last_refresh < 500) { return; } last_refresh = now; char buf[16]; OLED_Clear(); OLED_ShowString(0, 0, (mode == MODE_AUTO) ? "MODE:AUTO" : "MODE:MANUAL"); snprintf(buf, sizeof(buf), "RAIN:%4d", rain); OLED_ShowString(0, 2, buf); snprintf(buf, sizeof(buf), "LIGHT:%4d", light); OLED_ShowString(0, 4, buf); snprintf(buf, sizeof(buf), "STATE:%d", (int)state); OLED_ShowString(0, 6, buf); }

snprintf 在单片机上会占用较多 Flash 和栈空间,如果芯片容量紧张,可以换成自写的整数转字符串函数。日常调试阶段打印字符串方便移植,生产版本再优化成直接拼接 ASCII 码。

4.7 按键处理与手动优先级

晾衣架必须支持手动模式覆盖自动模式。用户正在手动操作时,如果自动逻辑强行切入,会给用户带来困惑,甚至可能夹到手。因此手动操作的优先级设计成:手动按键按下时,自动逻辑本周期不执行。

按键检测使用轮询加软件消抖。最简单可靠的消抖方法是:检测到电平变化后,延时 20ms 再确认一次。晾衣架的按键操作频率很低,20ms 延时对系统无影响,但如果主循环中还有其他高频任务,建议用定时器中断消抖。

int8_t Key_Scan(uint16_t pin, uint32_t timeout_ms) { if (HAL_GPIO_ReadPin(GPIOB, pin) == GPIO_PIN_RESET) { uint32_t start = HAL_GetTick(); while (HAL_GetTick() - start < timeout_ms) { if (HAL_GPIO_ReadPin(GPIOB, pin) == GPIO_PIN_SET) { return 0; } } return 1; } return 0; }

使用这个函数检测 PB14 上升按键和 PB15 下降按键。停止按键 PA11 在任何状态按下,都直接执行Motor1_Set(0, 0)和Motor2_Set(0, 0),然后回到STATE_STOPPED,直到用户再次按自动或手动方向键。

5. 联调验证与低功耗改进

5.1 分模块验收清单

不要等到全部代码写完再上电,每个模块独立验证,问题范围会小很多。

模块验证方法预期结果
LED下载闪烁程序PC13 闪烁
串口printf 输出测试字符串串口助手收到文本
ADC 雨滴手指沾水触碰传感器采样值明显下降
ADC 光照遮挡光敏电阻采样值明显变化
PWM用逻辑分析仪测 PA0看到 1kHz 方波
电机方向短暂给定方向信号电机以预期方向转动
限位开关按下限位开关观察状态变量状态切换
OLED刷新显示显示内容和变量一致
按键按一次观察打印日志单次触发,无重复

5.2 系统联调步骤

分模块通过后,按照下列顺序做系统联调:

  1. 先挂空载,不要连接实际衣架机械结构,防止机械故障损坏设备。
  2. 串口打印雨滴和光照采样值,用手模拟下雨、遮挡光照,确认阈值和回差合理。
  3. 低速测试升降电机,逐步提高占空比,观察运行电流和机械抖动。
  4. 手动模式按键测试:上升、下降、展开、收起各动作都要独立合格。
  5. 自动模式验证:把雨滴传感器喷水,观察系统是否先收起再上升;把光敏电阻置于强光下模拟晴天,观察是否下降并展开。
  6. 测试中断保护:在电机运行中直接按下停止按键,电机应立即停止。

5.3 用串口日志定位状态跳变

系统联调阶段,每个状态迁移都要打印日志。一条理想日志要包含时间、当前状态、触发源。

[3000ms] RAIN=1250 LIGHT=3800 -> RAIN_EVENT [3000ms] STATE_IDLE -> STATE_RETRACTING [3400ms] LIMIT_TOP OFF -> MOTOR2 STOP [3400ms] STATE_RETRACTING -> STATE_LIFTING [3900ms] LIMIT_TOP ON -> MOTOR1 STOP [3900ms] STATE_LIFTING -> STATE_IDLE

打印位置放在状态迁移函数里,而不是放在每个 case 分支口,避免同一状态被重复打印。日志格式统一用[时间] 事件,后续用脚本就能做自动化分析。

5.4 引入 STOP 低功耗模式

晾衣架大量时间处于待机状态,功耗优化有实际意义。STM32F103 的 STOP 模式可以停止内部时钟,但保留 SRAM 内容,电流可以降到几十微安级别,适合用按键或雨滴传感器边沿唤醒。

进入 STOP 模式前必须满足前提条件:

  • 电机已停止,PWM 已关闭。
  • 所有 I2C、ADC 外设已退出工作状态。
  • 唤醒源已配置为 EXTI 中断。

按键和雨滴传感器的 DO 信号都可以作为 EXTI 唤醒源。雨滴模块的 DO 输出在有雨时跳变,可以作为唤醒信号;按键使用上升沿或下降沿唤醒,取决于按键接法。

void System_EnterStop(void) { HAL_UART_DeInit(&huart1); HAL_ADC_Stop(&hadc1); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9 | GPIO_PIN_10); HAL_GPIO_DeInit(GPIOB, GPIO_PIN_6 | GPIO_PIN_7); HAL_SuspendTick(); HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); HAL_ResumeTick(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_ADC1_Init(); }

STOP 模式唤醒后,时钟系统需要重新初始化,串口和 ADC 也要重新初始化,否则打印和采集都会异常。注意HAL_Delay使用 SysTick,STOP 模式下 SysTick 停止,唤醒后必须调用HAL_ResumeTick恢复,否则后续延时全乱。

低功耗模式只建议在系统稳定后再引入。前期调试阶段反复进入 STOP 会让问题定位变难,建议功能全部验收后再做功耗优化。

6. 常见问题排查

实际项目中最常见的几类问题,按现象、原因、处理方式整理如下。

6.1 电机不转或抖动

现象:程序正常编译下载,手动按下按键后电机没有反应,或者听到嗡嗡声但转不动。

排查顺序:

  1. 检查电机驱动模块 GND 是否与 MCU GND 共地。没有共地时 PWM 信号没有参考基准,这是最容易忽略的问题。
  2. 检查电机供电。L298N 的供电如果是 5V 或 12V,电源功率至少要达到电机启动电流的 2 倍以上,电源电压跌落会导致电机转不动。
  3. 检查 PWM 是否真的输出。用逻辑分析仪或示波器测 PA0、PA1,如果占空比一直为 0,排查__HAL_TIM_SET_COMPARE是否被正确调用。
  4. 检查方向引脚。IN1/IN2 如果都是低电平,电机不会转;如果都是高电平,电机处于刹车状态。
  5. 检查限位开关状态。如果在下降状态但底部限位一直有效,状态机会立刻停止电机。用串口打印限位引脚电平确认。

6.2 雨滴传感器误触发

现象:没有下雨,但系统频繁执行收起动作。

原因有两类。一类是雨滴传感器安装位置容易积水或者被风吹动的水滴溅到;另一类是模块的 AO 输出本身随湿度缓慢变化,阈值设置太靠近边界。

处理方式:

  • 将雨滴传感器安装在晾衣架顶部靠内侧,避免被风吹到;
  • 软件中增加连续触发确认,连续多次采样值低于阈值才判断为下雨;
  • 重新标定阈值,将“干燥”、“微湿”、“下雨”三档采样值分别记录下来,阈值取中间值并保留回差。

6.3 ADC 读数不稳定

现象:OLED 上显示的同一传感器数值在几百甚至上千的范围内跳动。

原因通常是引脚悬空、采样时间过短、或者传感器的 GND 与 MCU 之间有电位差。

处理方式:

  • PA4、PA5 在 PCB 上尽量走短线,远离电机线缆;
  • ADC 采样时间改为 239.5 周期,给内部采样电容充分充电时间;
  • 软件做中位值平均滤波;
  • 电机运行时 ADC 跳动严重,可以考虑在电机运行时暂停 ADC 采样,电机停止后再恢复。

6.4 按键和限位开关抖动

现象:按一次按键,日志却打印两次;或者限位开关按下后状态反复切换。

原因:机械触点通断瞬间产生毛刺,MCU 在几十微秒内反复读到高低电平。

处理方式:软件消抖至少 20ms,限位开关建议 50ms 连续确认,必要时在按键两端并联 0.1uF 电容做硬件消抖。

6.5 PWM + DMA 无输出

现象:启用HAL_TIM_PWM_Start_DMA后 PA0 没有波形,或者只输出一次。

排查方式:

  • 确认 DMA 请求映射。TIM2_CH1 对应 DMA1 的 Channel5,映射错误时事件不会触发传输;
  • NORMAL 模式下 DMA 只能传输一次,需要循环启动或使用 CIRCULAR 模式;
  • 检查数组是否定义为全局变量并保持生命周期,局部数组在函数返回后内容不可用;
  • 使用 HAL_DMA_Start_IT 等接口时确认中断回调函数已实现。

7. 工程化改造与最佳实践

7.1 从实验板到量产板的差异

实验板用杜邦线连接,验证逻辑没问题,但距离可靠产品还有明显差距。量产方向需要关注以下改造点:

  • 电机驱动从 L298N 换成专用电机驱动芯片,体积更小,保护更完善;
  • 电源增加防反接、过流保护、TVS 和电解电容,电机启停会产生浪涌;
  • PCB 布局上把功率地和信号地分开,单点接地,避免电机回路干扰传感器采样;
  • 增加硬件看门狗,防止程序跑飞后电机无法停止;
  • 限位开关建议使用常闭型,断线时状态更安全;
  • 传感器阈值不要写死在代码里,建议存储在 Flash 模拟 EEPROM 中,产品出厂时通过串口指令标定。

开源发布时,仓库里至少要有原理图、PCB 文件、固件源码、README 使用说明和物料清单。许可证建议在 MIT、Apache-2.0、GPL-3.0 之间按意图选择:希望别人随意使用和闭源,选 MIT 或 Apache-2.0;希望衍生代码也必须开源,选 GPL-3.0。README 中要写明引脚定义、烧录方法和机械结构照片,否则别人很难复现。

7.2 可复用代码检查清单

以下清单适用于 STM32 类小型控制项目,发布前逐项检查:

  • 状态枚举没有魔法数字,状态迁移统一走函数;
  • 所有按键、限位开关都做了软件消抖;
  • 传感器判断都有回差,且阈值可配置;
  • 电机运行前检查限位状态,方向改变前先停止;
  • 串口日志统一格式,状态迁移只打印一次;
  • 所有阻塞延时都来自 HAL_Delay 或自定义非阻塞延时,没有裸空循环;
  • 全局变量用volatile修饰,中断和主循环共享的数据有保护;
  • 进入 STOP 前确认外设已关闭,唤醒后重新初始化时钟和串口;
  • 看门狗在电机运行时不会被错误喂狗导致保护失效。

7.3 引入 FreeRTOS 的任务划分

如果系统要扩展网络通信、传感器采样、UI 刷新等多个并发任务,可以引入 FreeRTOS。任务划分建议:

任务周期 / 触发优先级说明
传感器任务50ms中ADC 采样、滤波、天气判断
状态机任务20ms高状态迁移、电机控制、限位检测
UI 任务500ms低OLED 刷新
通信任务事件触发中串口指令解析、WiFi 数据上报

引入 RTOS 后,原先主循环里的非阻塞逻辑可以拆成多个任务,但共享变量必须用互斥量或消息队列保护,尤其是状态枚举和传感器阈值。FreeRTOS 不是银弹,任务多了以后,优先级反转、栈溢出和延时抖动会变成新的排查难点。对于核心功能只有晾衣架的场景,裸机状态机足够,RTOS 属于锦上添花。

7.4 扩展方向

当前系统完成本地控制后,可以沿几个方向扩展:

  • 无线通信:外接 WiFi 模块或蓝牙模块,通过 AT 指令或 MQTT 协议接入智能家居平台,实现手机远程控制。
  • 多传感器:加入风速传感器、温湿度传感器,在强风天气自动收起,湿度高时自动展开晾晒。
  • 衣物状态识别:加入称重或热电偶检测,判断衣物是否晾干,达到干燥目标后自动收起。
  • 安全防护:在下降路径上加红外避障或防夹检测,遇到障碍物立即停止并回退。

扩展时同步评估 MCU 资源。STM32F103C8T6 的 64KB Flash 和 20KB SRAM 在加入网络协议栈后可能紧张,可以换用更高容量型号或把通信任务放在 WiFi 模块内部处理,F103 只负责执行控制。

智能晾衣架这套系统的价值在于,它把一个看似简单的家电设备拆成了完整的嵌入式控制链路。从传感器标定到状态机设计,从限位保护到低功耗处理,每个环节都是一类通用工程问题。建议先按本文的模块顺序完成一次裸机版本,再逐步加入 DMA、低功耗、RTOS 和无线通信,每一步都确保可运行、可验证,再进入下一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询