尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于STM32F103的智能晾衣架控制系统设计

基于STM32F103的智能晾衣架控制系统设计 在智能家居的单项设备里智能晾衣架看起来功能不多真正动手做控制系统时才会发现它把传感器采集、电机驱动、限位保护、按键交互、状态显示和低功耗管理全部串在了一条链路上。基于 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 能力明显更强对比 ESP32F103 没有无线功能但控制实时性、功耗表现和成本更适合纯本地控制场景。如果后续想加 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_CH1PWM 输出展开电机 PWMPA1TIM2_CH2PWM 输出升降电机方向 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按以下步骤配置外设RCCHSE 选择 Crystal/Ceramic ResonatorSYS 选择 Serial Wire保留 SWD 调试。Clock ConfigurationHSE 8MHzPLL 倍频到 72MHzAPB1 分频 2 得到 36MHzAPB2 不分频得到 72MHz。ADC1使能通道 4PA4和通道 5PA5采样时间选择 239.5 周期连续转换或扫描模式按需配置。TIM2选择 Channel1 和 Channel2 为 PWM Generation CH1/CH2预分频器 71自动重载值根据 PWM 频率计算。I2C1使能标准模式或快速模式速度 400kHz。USART1异步模式波特率 1152008 位数据无校验。GPIOPB0、PB1、PB2、PB10 设置为输出推挽初始电平低PB12、PB13、PB14、PB15、PA8、PA11 设置为输入上拉。PWM 频率的计算公式是PWM频率 定时器时钟 / ((预分频器 1) * (自动重载值 1))定时器时钟为 72MHz 时预分频器设为 71自动重载值设为 999PWM 频率约 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 系统联调步骤分模块通过后按照下列顺序做系统联调先挂空载不要连接实际衣架机械结构防止机械故障损坏设备。串口打印雨滴和光照采样值用手模拟下雨、遮挡光照确认阈值和回差合理。低速测试升降电机逐步提高占空比观察运行电流和机械抖动。手动模式按键测试上升、下降、展开、收起各动作都要独立合格。自动模式验证把雨滴传感器喷水观察系统是否先收起再上升把光敏电阻置于强光下模拟晴天观察是否下降并展开。测试中断保护在电机运行中直接按下停止按键电机应立即停止。5.3 用串口日志定位状态跳变系统联调阶段每个状态迁移都要打印日志。一条理想日志要包含时间、当前状态、触发源。[3000ms] RAIN1250 LIGHT3800 - 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使用 SysTickSTOP 模式下 SysTick 停止唤醒后必须调用HAL_ResumeTick恢复否则后续延时全乱。低功耗模式只建议在系统稳定后再引入。前期调试阶段反复进入 STOP 会让问题定位变难建议功能全部验收后再做功耗优化。6. 常见问题排查实际项目中最常见的几类问题按现象、原因、处理方式整理如下。6.1 电机不转或抖动现象程序正常编译下载手动按下按键后电机没有反应或者听到嗡嗡声但转不动。排查顺序检查电机驱动模块 GND 是否与 MCU GND 共地。没有共地时 PWM 信号没有参考基准这是最容易忽略的问题。检查电机供电。L298N 的供电如果是 5V 或 12V电源功率至少要达到电机启动电流的 2 倍以上电源电压跌落会导致电机转不动。检查 PWM 是否真的输出。用逻辑分析仪或示波器测 PA0、PA1如果占空比一直为 0排查__HAL_TIM_SET_COMPARE是否被正确调用。检查方向引脚。IN1/IN2 如果都是低电平电机不会转如果都是高电平电机处于刹车状态。检查限位开关状态。如果在下降状态但底部限位一直有效状态机会立刻停止电机。用串口打印限位引脚电平确认。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 和无线通信每一步都确保可运行、可验证再进入下一步。
返回列表