简介面向STM32初学者的雨滴监测实战源码围绕雨水检测与报警防护场景覆盖传感器采集、显示反馈与串口通信等关键环节适用于智能家居、户外设备监测、嵌入式课程设计与毕业设计参考。代码基于STM32F10x系列包含雨滴传感器状态读取的底层驱动逻辑、基于I2C控制OLED屏幕实时刷新、触雨时蜂鸣器报警以及将雨滴数据发送至串口调试助手的完整处理流程。资源包共280个文件以C源程序、头文件、Keil工程配置为主另有编译生成的中间文件和调试信息文件压缩包整体约8.92MB目前已有70人学习下载。压缩包提供可直接打开的Keil工程结构源码模块划分较清晰便于二次移植与功能扩展适合作为学习外设驱动和状态机设计的参考。资料为网络分享仅供学习交流请勿用于商业用途。1. 把“STM32单片机雨滴监测系统源代码”当成搜索词时你要找的其实是稳定的状态判断把“STM32单片机雨滴监测系统源代码”当成一个搜索词来理解大多数人要的不是能把每一滴水都数出来的计数器而是一套从采集到执行的闭环F103 在跑、传感器在读数、继电器在按预期动作、串口在上报结果。雨滴监测和温湿度监测在工程上完全不同水珠砸在 PCB 交叉走线上的瞬间电阻变化迅速但场景决策是慢的你不需要每毫秒都看一眼传感器真正要回答的是“现在有没有雨、雨大致有多强、要不要关窗/排水/报警”。代码结构因此应该围绕 ADC 采裸值、滑动滤波、阈值判断三段展开而不是在网上找一份语焉不详的库然后反复猜接线。做“基于stm32的毕业设计”“stm32鱼缸”这类项目最容易卡住人的不是硬件而是采样节奏怎么定、阈值怎么不抖。本文把这套判断逻辑和对应源码拆开讲读完你可以直接整理出一个能丢进 Keil 编译的最小工程。2. 雨滴模块的 AO/DO 引脚与 STM32 外设选型读模拟量还是读电平状态2.1 常见雨滴模块内部电路和引脚定义市面上的雨滴传感器模块常见丝印对应 FC-37、FR04 等基本是同一种结构一块梳齿状的 PCB 做水敏感面板上带一片 LM393 比较器外加一个电位器用来整定阈值。模块引出的四根线一般固定为 VCC、GND、DO数字输出、AO模拟输出。你拿到模块的第一件事不是写代码而是先测一下上电后 DO 和 AO 的空载电压因为不同批次模块在干态下的输出电压能差出三四百毫伏直接决定阈值要不要按你的板子单独校准。引脚电平性质干燥环境有水膜/雨滴推荐接 STM32VCC电源3.3V 或 5V3.3V 或 5V接 3.3V与 MCU 同源GND地0V0VGNDDO数字高电平低电平多数模块任意 GPIO 输入可接 EXTIAO模拟接近 VCC随水膜增厚而下降任意 ADC 通道把 VCC 接成 3.3V 是很多人会忽略的一个细节。雨滴传感器在 5V 供电下 AO 输出范围更大但要记得 STM32 的 ADC 参考电压就是 3.3V5V 供电会导致引脚电压超过 VDDA轻则读数满量程重则损伤 ADC。我一般直接把模块 VCC 接到开发板的 3V3MCU 和传感器共地出来的 AO 值就在 03.3V 之间进 ADC 也不需要分压。DO 的电平极性最好在写入代码前确认一遍。多数模块的默认状态是上拉高电平水膜足够厚时传感器面到地电阻变小比较器翻转输出低电平。这个极性对程序逻辑本身没有影响你只要在初始化时记录一次“干态电平”作为参考。但如果你把 DO 接到 EXTI 外部中断上建议用下降沿而不是上升沿触发因为从“干到湿”的变化才是系统需要立刻响应的事件。2.2 为什么雨滴监测优先读 AO而不是只依赖 DO 数字量DO 是电位器分压和传感器动态电阻相互作用的结果你拧电位器让阈值变敏感同时也会丢失对降雨强度的感知。雨滴监测里最常见的误用是只接一根 DO 线就把系统当成完成了结果一团薄雾压过阈值系统在晴天误关了一次窗户。读 AO 的真正好处是拿到一条连续的判断带而不是一个孤立的 0/1。按 F103 的 12 位 ADC 来划分一套常见参考区间是ADC 值 3800 以上完全干燥26003800空气潮湿或轻微水雾18002600小雨传感器面可见离散水珠1800 以下水膜连续中到大雨程度。这套分级在代码里实现就是多阈值区间写法和单阈值差不多但后续做雨量趋势、联动策略都会方便很多。比如电动雨棚的逻辑可以设计成“低于 1800 才关窗”而鱼缸补水的防溢逻辑则可以用“低于 2600 就停止补水”都是同一份原始数据的不同切片。普通 STC 单片机或 51 单片机也能做这个采集但 F103 的优势在于 12 位 ADC、多通道以及 DMA 配合你可以让同一个 ADC 顺带读光敏电阻或土壤湿度51 常规型号在这些流程上要写大量软件模拟。毕业设计或竞赛选型时蓝桥杯这类平台上 F103 也是出现率最高的芯片生态资料比 STC 完整换芯片的成本反而更低。2.3 外设分配和 CubeMX 初始化思路一套最小可用的雨滴监测系统四组外设就够了一路 ADC 读 AO一路 GPIO 读 DO 做二次确认一路 GPIO 控制 LED 或继电器一路串口做调试输出。推荐引脚分配如下STM32 引脚功能说明PA1ADC1_IN1雨滴模块 AO模拟输入不加上下拉PC14雨滴模块 DO数字输入内部上拉PC13板载 LED / 继电器输出指示或开关执行PA9/PA10USART1上位机或 ESP8266TTL 电平注意共地PA1 和 PC14 分别属于 GPIOA 和 GPIOC初始化时分开配置并不麻烦。CubeMX 里 ADC1 选择通道 1采样时间选最长一档239.5 周期即可明显降低高阻抗输出带来的读数抖动PC13 在大多数 F103 最小系统板上默认接了 LED可以先拿它当执行机构避免一开始就去外接继电器。这个阶段不要上 DMA雨滴系统的采样频率很低每 100ms 手动触发一次 ADC 足够代码直白也方便后续加滤波逻辑。3. STM32 雨滴监测源代码的核心三段式初始化、滤波、迟滞判断3.1 初始化代码GPIO 和 ADC1 的最小工程下面这段代码基于 STM32CubeMX 生成工程改造而来省略了系统时钟和 SystemInit 部分保留外设初始化的核心。编译环境是 Keil5芯片选 STM32F103C8T6标准 HAL 库。#include main.h ADC_HandleTypeDef hadc1; UART_HandleTypeDef huart1; static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /* PC13 作为执行输出初始为高电平表示“无雨” */ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); GPIO_InitStruct.Pin GPIO_PIN_13; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOC, GPIO_InitStruct); /* PC14 读 DO 信号内部上拉 */ GPIO_InitStruct.Pin GPIO_PIN_14; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOC, GPIO_InitStruct); /* PA1 作为模拟输入引脚不进数字输入缓冲 */ GPIO_InitStruct.Pin GPIO_PIN_1; GPIO_InitStruct.Mode GPIO_MODE_ANALOG; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); } static void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig {0}; hadc1.Instance ADC1; hadc1.Init.ScanConvMode ADC_SCAN_DISABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 1; HAL_ADC_Init(hadc1); sConfig.Channel ADC_CHANNEL_1; sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); }这段初始化逻辑分三层理解先使能 GPIO 时钟再配置数字输出和模拟输入最后配置 ADC1 的通道和采样时间。PA1 设为GPIO_MODE_ANALOG之后数字输入缓冲被关闭ADC 直接采样引脚电压ScanConvMode关闭是因为只转换一个通道ContinuousConvMode关闭代表每次转换由软件触发正适合低速轮询。NbrOfConversion设置为 1意味着每次注入或规则转换只扫一组通道。SamplingTime选239CYCLES_5在 F103 上这是可选的最长采样时间对雨滴模块这种内阻偏大的源效果最稳。如果去掉这一长采样时间你经常能看到同一个传感器面在读数上跳 200 个 LSB那不是 MCU 坏了是采样电容还没充到引脚实际电压。读取函数单独封装一层后面接滤波或者换传感器都不需要反复动初始化代码uint16_t read_rain_adc(void) { HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); uint16_t val HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return val; }HAL_ADC_PollForConversion的第二个参数是等待超时单位毫秒F103 单次 ADC 转换只要十几微秒基本不会走到超时分支。返回的val是 12 位右对齐范围 04095。3.2 滑动滤波为什么雨滴数据用窗口均值而不是中值雨滴模块在干燥状态下读数通常在 3800 附近稳定但一旦表面有半干水珠读数会在几百个 LSB 的范围内跳动。原因是水珠在梳齿间滚动接触电阻本身就一直在变这个阶段做单点判断就是在找罪受。常见做法是维护一个长度为 16 的循环队列每次采样进去一个数、出来一个数只做一次加法和一次减法#define FILTER_LEN 16 static uint16_t filter_buf[FILTER_LEN]; static uint8_t filter_idx; uint16_t rain_filtered(uint16_t raw) { static uint32_t sum 0; sum - filter_buf[filter_idx]; /* 去掉最旧的一个采样 */ sum raw; filter_buf[filter_idx] raw; /* 存入当前采样 */ filter_idx (filter_idx 1) % FILTER_LEN; return (uint16_t)(sum / FILTER_LEN); }这个实现叫滑动平均它的特点是永远保留最近 16 次采样的平均值一个异常尖峰最多贡献 1/16 的权重不会让系统产生一次“假下雨”的跳变。为什么不用中值滤波中值滤波对脉冲噪声的抑制更强但它每收到一个新点都要做排序16 个点的插入排序放在 Cortex-M3 上不划算而且雨滴信号里的“连续偏低”才是雨膜存在均值更贴合这个物理过程。窗口长度和采样周期需要一起调。以 100ms 采样一次、窗口 16 来算等效时间常数是 1.6 秒这个值能滤掉单滴水珠的抖动也不会把阵雨的开头抹没了。想更灵敏就改FILTER_LEN为 8想更平滑就改 32不需要动其他代码。但窗口改到 32 之后从第一滴雨到状态翻转的响应会明显变慢雨稍小时可能刚翻到“有雨”雨已经停了用于关窗可以用于报警会偏迟钝。前文提过 F103 也可以配合 DMA 和定时器做自动连续采样工业上那种需要精确到毫秒的雨强模拟才会用。这里的低速系统完全没必要手动HAL_ADC_Start加HAL_Delay的方式可读性更好。3.3 迟滞判断防止干湿临界点的状态反复滤波后的值如果只给一个固定阈值比如“小于 2500 就算下雨”逻辑看起来没问题现场却会出状况傍晚湿度上升时读数可能在 2490 和 2510 之间来回摆动每次越过 2500 就触发一次继电器动作蜂鸣器复读机一样响。迟滞hysteresis是这类系统必须的结构不是优化项。它的核心是设置两个阈值进入有雨状态的阈值和退出有雨状态的阈值。两段之间留一个滞回带状态一旦进入潮湿侧就要越过更干的一个阈值才会回来。#define RAIN_DRY_TH 2800 /* 从“有雨”回到“无雨”的读数上限 */ #define RAIN_WET_TH 2200 /* 从“无雨”进入“有雨”的读数下限 */ static uint8_t rain_state; uint8_t rain_judge(uint16_t adc_val) { if (rain_state 0 adc_val RAIN_WET_TH) { rain_state 1; /* 无雨 - 有雨 */ } else if (rain_state 1 adc_val RAIN_DRY_TH) { rain_state 0; /* 有雨 - 无雨 */ } return rain_state; }两个阈值之间的 22002800 区间就是滞回带。状态转移表如下当前状态本次滤波值下一状态无雨大于 2800无雨无雨22002800保持无雨无雨小于 2200有雨有雨小于 2200保持有雨有雨22002800保持有雨有雨大于 2800无雨这段代码的关键在于“只有一边越过阈值时状态才变化”和普通的比较器不同。滞回带宽的设置直接决定系统灵敏度带宽太窄比如只留 100 个 LSB仍然有概率在雷阵雨和阴天过渡时抖一次带宽太宽雨停十分钟系统还认为在下雨。我的经验是把宽度控制在满量程的 10%15%即两个阈值的差在 400600 LSB 之间比较合适。4. 从雨滴状态到闭环执行继电器、串口输出和与 ESP8266 的对接4.1 根据 rain_state 驱动继电器和蜂鸣器状态机输出的是一个干净的数字量 0/1执行层就变得很直接。F103 的 GPIO 驱动能力有限不能直接接继电器线圈需要经过三极管或光耦驱动先把 PC13 上默认的板载 LED 作为执行机构调通逻辑再接真实继电器更稳妥。void rain_actuate(uint8_t state) { /* 有雨时 PC13 输出低电平LED 点亮 */ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, state ? GPIO_PIN_RESET : GPIO_PIN_SET); }这段代码把“判断”和“执行”分开以后再换成 MOS 管驱动的雨棚电机只改这个函数内部的引脚号即可。大多数继电器模块是低电平触发和这里GPIO_PIN_RESET的写法正好对应高电平触发的模块要把逻辑反过来别直接照抄。主循环里串起来的完整流程while (1) { uint16_t raw read_rain_adc(); /* 裸采样 */ uint16_t avg rain_filtered(raw); /* 滑动平均 */ uint8_t st rain_judge(avg); /* 迟滞判断 */ rain_actuate(st); /* 执行动作 */ HAL_Delay(100); /* 100ms 采样节奏 */ }HAL_Delay(100)定了整个系统的采样节奏。做“stm32鱼缸”这种需要长期运行的场景可以把延时放宽到 500ms再配合 MCU 进入低功耗模式平均电流能明显下降。判断放在主循环里是教科书式的写法适合入门真正小体积应用会把读取和判断挪进定时器中断避免掉进延时阻塞 UART 的坑但工程结构是同一套。注意PC13 在多数 F103 最小系统板上直接连着 LED主循环工作正常时它每秒闪 10 次其实看不出问题只有逻辑反转时才会暴露。接入真实继电器之前先用万用表量一次继电器输入端的电平变化确认无误再上强电。4.2 用 printf 重定向串口调试输出只看一个 LED 永远不够你还要知道滤波前后的数据长什么样。给 Keil 工程的fputc做重定向是最省事的串口观察方案方法是在任意 C 文件里重写标准库的字符输出函数int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 100); return ch; }注意 Keil 工程需要勾选 MicroLIB否则printf打出来的是半主机模式的输出串口上什么都看不到。函数签名里FILE *f在这个场景下不会被用到保留即可。主循环里加一行整条调试链就通了printf(RAW%u AVG%u STATE%u\r\n, raw, avg, st);串口助手按 115200/8N1 打开能看到类似RAW3820 AVG3812 STATE0的输出。看这三个值就是一套判断系统是否正常的方法RAW 跳动大说明传感器面有动态变化AVG 平滑说明滤波窗口生效STATE 稳定说明迟滞带在干活。如果 RAW 稳定但 AVG 一直偏高多半是传感器表面已经干了或者模块供电掉了这类问题不用示波器就能定位。4.3 与 ESP8266、k210 或 Modbus 设备通信的帧设计雨滴监测系统往往要把状态交出去最常见的接收方是 ESP8266 做 WiFi 网关。ESP8266 AT 固件模式下STM32 直接通过 UART 发送数据即可发送内容用一段 JSON 最省事char buf[64]; snprintf(buf, sizeof(buf), {\rain\:%u,\level\:%u}\r\n, st, avg); HAL_UART_Transmit(huart1, (uint8_t *)buf, strlen(buf), 200);如果 ESP8266 已经工作在透传模式这段 JSON 可以直达服务器或上位机。要注意 STM32 和 ESP8266 之间必须共地否则 UART 会在湿度高的环境里出现乱码雨滴监测放在户外尤其容易踩这个。如果对接的是 k210 这类 AI 协处理器或者走 Modbus RTU 与变频器通信JSON 这种变长格式在裸机接收端解析成本偏高。我一般固定成“帧头 长度 数据 校验”的二进制结构比如0xAA 0x55 len data checksum。接收方拿到一个字节就进中断缓存判一帧结束则用“空闲间隔”方法开一个定时器在接收中断里清零超过 3.5 个字符时间没有新字节就认为一帧结束。这个规则也是“modbus单片机帧接收数据程序”这类实践中反复被问到的核心点——帧结束判定不是靠回车换行而是靠超时窗口。5. 雨滴监测系统的校准、调试和移植关键点位5.1 Keil5 调试雨滴监测工程时“当前不会命中断点”的常见原因带 ST-Link 烧录成功却在 Keil5 下断点时提示“当前不会命中断点”优先看三处设置。第一是优化等级Options → C/C → Optimization 若被改成 -O1 以上局部变量和判断语句会被合并或删除断点自然失效改回 -O0 重新编译。第二是调试器配置Debug 里选择 ST-Link Debugger勾选 Flash Download 中的 “Reset and Run”重新上电再进入调试断点一般能命中。第三是驱动使用 USB-TTL 走系统 BootLoader 烧录的场合先确认 USB 识别正常异常时换数据线再换驱动别反复去怀疑程序。工具链上装上 KEIL 后再用 Pack Installer 安装 STM32F1 系列 Device Pack和 C51 的 Pack 共存于同一工程环境互不覆盖。5.2 干湿两点校准把 ADC 读数映射成百分比固定阈值不是银弹换一批传感器板基线差异可能有三四百 LSB。常见做法是上电后进入 5 秒校准流程先擦干传感器面采样记作dry_val再滴一点水采样记作wet_val两个变量存入全局uint8_t rain_percent(uint16_t avg) { if (avg dry_val) return 0; if (avg wet_val) return 100; return (uint8_t)((dry_val - avg) * 100 / (dry_val - wet_val)); }上位机只显示 0100 的百分比不需要理解 ADC 原始值。校准结果通过串口打印出来如果发现dry_val与wet_val相差不到 200 个 LSB主要怀疑传感器表面老化或污损不是代码问题。5.3 从 F103 换型时 CubeMX 配置和晶振匹配许多项目做汇编转产时直接替代料如 APM32F103 与 STM32F103 管脚兼容程序大体可以平移但外设库要用厂商各自的版本。真正该注意的是在“stm32 cube 程序更改单片机型号”时重新选择器件型号并生成代码随后确认 HAL 库版本差异。换开发板时还要核对外部晶振负载电容匹配经验公式是CL ≈ (C1 × C2) / (C1 C2) Cstray8MHz 晶振通常配 12pF22pF 的 C1/C2计算出的 CL 要贴近晶振手册标注的负载电容偏差过大会导致起振慢甚至失败。雨滴监测系统对时钟精度要求不高但 HSE 起振失败会让程序卡死在时钟初始化循环里此时串口一片静默最容易被人误判成逻辑或接线问题。本文还有配套的精品资源点击获取 SEO 优化官网定制响应式建站教育培训建站