简介这是一份基于STM32单片机的雨滴监测系统完整源码包适合嵌入式初学者及课程设计开发者参考。系统整合雨滴传感器采集、OLED显示、蜂鸣器报警和串口发送到调试助手等功能覆盖典型外设控制流程。压缩包共280个文件体积8.92MB主要包含C源码与头文件、Keil工程文件、编译产物axf、o及备份中间文件工程结构完整可用Keil直接打开编译。已有70人学习下载。通过该源码可学习STM32标准外设库初始化、ADC读取雨滴传感器、OLED驱动显示、GPIO控制蜂鸣器以及串口打印数据等关键代码同时配套的说明文件也有助于快速理解项目布局适合入门实践与改造扩展。注意资源来自网络分享仅供学习交流使用勿作商业用途。1. 雨滴监测不是把传感器焊上就能用很多第一次做雨滴监测的开发者以为把雨滴传感器的 DO 脚接到 STM32 GPIO 上读到低电平就报警工作就算完成了。实际上 DO 只是模块内部和电位器比较后的数字结果永远无法区分毛毛雨和水膜、无法告诉你应该在哪个雨量点触发蜂鸣器。这套源码的价值在于把 AO 模拟量同时接进 OLED 显示、串口上报和蜂鸣器联动构成一个可标定的闭环。它适合做智能窗、户外机柜防雨、农业大棚这类需要把“是否下雨”升级成“雨量等级”的项目也适合刚接触 STM32 ADC、I2C、串口外设配合的开发者拿来当作学习模板。2. 从雨滴传感器到 STM32 ADC信号链路、分压与量程换算雨滴传感器表面是裸露的梳状铜箔水溅上去后两条电极之间的等效电阻下降。常规模块内部会用 LM393 比较器把变化转成数字量 DO同时把传感器原始电压从比较器前端引出一路模拟量 AO。源码里出现的stm32f10x_adc.c就是让 STM32 通过 ADC 读取 AO 电压从而把“有没有水”扩展成“水量有多少”。2.1 雨滴传感器模块的信号类型与接口定义这类模块通常有四根引脚接线方式有固定套路模块引脚信号方向STM32 端接法作用AO模拟电压输出PA0ADC1_IN0雨量越大电压越低DO数字量输出PB1 或任意输入引脚低于阈值输出低电平仅供简单判断VCC供电3.3V 或 5V供电选择直接决定 AO 是否超量程GND电源地GND共地大多数雨滴模块标注电压范围 3.35V。如果拿 5V 供电AO 在干燥时可能输出接近 5V直接进 STM32 的 ADC 脚会超 3.3V 量程严重时会损坏引脚。我一般习惯直接给模块供 3.3V让 AO 最高点落在 3.03.3V 范围内省掉一颗分压电阻。若工程里已经接了 5V则必须在 AO 与 PA0 之间加两个电阻分压把最大值压到 3.3V 以下。DO 脚在源码里其实不是必须的。ADC 读到的数据已经能反映雨量DO 只是提前帮你判断了一次。真正做雨量分级时用 AO 更合理把 DO 留给后期做“传感器断线检测”或“极端暴雨保护”反而更有价值。2.2 STM32F10x ADC 初始化与连续采样代码源码基于 STM32F10x 标准外设库ADC 部分的核心动作是开时钟、配置引脚为模拟输入、配置 ADC1 独立模式、开启连续转换。下面这段是可在 Keil 里直接编译的模板以 AO 接 PA0 为例void Rain_ADC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); ADC_DeInit(ADC1); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode ENABLE; 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_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); } uint16_t Rain_ADC_Read(void) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); return ADC_GetConversionValue(ADC1); }这段代码里有两个参数最值得注意。第一个是ADC_SampleTime_239Cycles5雨滴传感器的输出阻抗偏高如果采样时间太短采样电容充不满数值会跳得很厉害所以直接用 ADC 最多的 239.5 个周期。第二个是ADC_ExternalTrigConv_None表示不接定时器触发直接在循环里软件启动转换。源码里的stm32f10x_tim.c如果被用到通常是给蜂鸣器做节拍延时不是用来触发 ADC。2.3 采样值与雨量强度的换算ADC 是 12 位的参考电压 3.3V 时读到的数字和电压的关系是电压 adc / 4095 * 3.3。干燥状态电阻非常大AO 接近 VCCADC 值通常高有水后电阻下降AO 被拉低ADC 值变小。以下是一份不需要改电位器时的经验参照表雨滴状态AO 电压参考范围ADC 参考范围完全干燥2.8V 以上35004095零星水珠1.5V2.8V18003500连续水膜0.5V1.5V6001800大面积积水0.5V 以下小于 600注意这个范围不是绝对的。同一块模块滴矿泉水、滴自来水、滴盐水ADC 数值差异很大涂了疏水涂层的传感器响应又会慢一拍。所以源码里阈值不建议写死在注释里应该先跑一遍串口看真实值再往#define里填。3. 串口上报与数据帧设计从原始值到可解析的雨滴状态雨滴监测系统如果只在本地 OLED 显示调参时会非常痛苦你既看不到传感器波形也无法确认蜂鸣器是在哪一档被触发。把雨滴数据通过串口发到调试助手才能把“当时发生了什么”完整还原出来。源码虽然没把 USART 单独立成文件但串口初始化在标准外设库里非常简单自己补一个函数即可。3.1 USART1 初始化与 printf 重定向以 USART1 为例PA9 做 TX、PA10 做 RX波特率 1152008 数据位、1 停止位、无校验。这样配置最通用也方便直接用串口模块接调试助手void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_RX | USART_Mode_TX; USART_Init(USART1, USART_InitStructure); USART_Cmd(USART1, ENABLE); } // Keil 环境下重定向 fputc让 printf 直接走串口 int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }代码里的USART_Mode_RX | USART_Mode_TX同时开了收发虽然本系统只发不收但留着接收方向不会影响功能也方便日后把串口指令“CAL:1200”写成校准命令。重定向fputc后printf的输出会一条条从 TX 引脚发出去在源码的主循环里只需要把上文 ADC 读取函数的结果打印出来即可。3.2 雨滴数据帧格式设计调试助手直接看裸数字很难判断“这个值是哪一档”所以上传的数据最好既有原始 ADC、又有换算后的等级。推荐使用换行分隔的键值对文本格式常见做法如下RAIN:2048,LEVEL:2 RAIN:0512,LEVEL:3 RAIN:3902,LEVEL:0主循环里只需要这样发送printf(RAIN:%04d,LEVEL:%d\r\n, rain_value, rain_level);每帧字段的含义固定RAIN是传感器直接读到的 ADC 原始值LEVEL是雨量等级。等级不建议直接由传感器电压算线性比例最好用分段映射因为雨滴传感器本身不是线性器件。字段前补%04d是为了让数字在调试助手里的宽度一致不会因为位数不同导致对齐乱跳。字段类型范围含义RAIN无符号整数04095雨滴传感器 ADC 原始值LEVEL无符号整数03雨量等级0 干燥、3 大雨帧结束回车换行\r\n方便调试助手按行解析3.3 串口乱码与丢数据排查串口出现乱码时不要先怀疑传感器先检查两处。第一是芯片实际工作频率和SystemCoreClock是否一致如果外置 8M 晶振没起振STM32 内部 HSI 会兜底但波特率就会被算错实际速率不是 115200。第二是串口上位机的“发送新行”设置本系统只上传不下发上位机会不会自动发送无关字节并不重要但如果你准备做双向指令那串口调试助手也要同样配置成 115200、8N1。丢数据通常不是串口本身的问题而是主循环里OLED_Clear()太频繁。OLED 的 I2C 刷新一次可能占用几十毫秒在这段时间里如果串口中断没开printf会一直等TXE标志数据帧排队后表现为“一串一串地到”。解决方法是把 ADC 采样、串口发送和 OLED 刷新放到同一个 100ms 周期节拍里而不是在while(1)里裸奔。4. OLED 显示与蜂鸣器报警I2C 外设与阈值去抖雨滴数据除了串口上报还要在本地 OLED 显示。源码里stm32f10x_i2c.c和OLED.c提供了这套显示链路STM32 通过 I2C 总线把雨滴状态、ADC 原始值写到屏幕。蜂鸣器报警则是另一个独立逻辑但要注意直接拿单阈值做判据会让蜂鸣器在水滴边缘反复跳动。4.1 OLED 模块的 I2C 接线与地址确认市面上多数小尺寸单色 OLED 使用 SSD1306 控制器I2C 接口只用四根线VCC、GND、SCL、SDA。在 STM32F10x 上硬件 I2C1 的 SC L 和 SDA 默认复用为 PB6、PB7接线时不要把 OLED 的 SCL 接到 SDA 上这种低级错误会导致 OLED 白屏时怎么初始化都没反应。I2C 引脚STM32F103 引脚说明SCLPB6I2C1 时钟必须配置为 AF_ODSDAPB7I2C1 数据必须配置为 AF_OD模块地址0x78 或 0x7A7 位地址 0x3C 左移一位OLED.c 里通常已经写好了OLED_Init()它会做 GPIO 与 I2C 时延初始化。如果源码里的 OLED.c 不是用硬件 I2C而是模拟时序那stm32f10x_i2c.c可能只被用来驱动其它外设。判断方法是看OLED_Init()里是否调用了I2C_Cmd()或I2C_GenerateSTART()如果没有说明是 IO 模拟此时 PB6/PB7 要配置成普通推挽输出。4.2 OLED 刷新雨滴状态的核心调用常见的 OLED.c 接口屏蔽了底层 SSD1306 命令主函数只要负责拼内容。下面是基于这类接口的刷新代码extern void OLED_Init(void); extern void OLED_Clear(void); extern void OLED_ShowString(uint8_t x, uint8_t y, char *str); extern void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len); while (1) { uint16_t adc Rain_ADC_Read(); int level Rain_GetLevel(adc); OLED_Clear(); OLED_ShowString(0, 0, Rain ADC:); OLED_ShowNum(56, 0, adc, 4); OLED_ShowString(0, 2, Level:); OLED_ShowNum(56, 2, level, 1); OLED_ShowString(0, 4, adc 1500 ? WARN !!! : SAFE); OLED_Refresh(); }这里的关键是刷新频率。OLED 不必每次都清屏连续清屏会产生明显的闪烁而OLED_ShowString内部往往自带取模延时大约每字符 12ms清一次全屏可能要 30ms。对雨滴监测这种低频应用100ms 刷一次已经足够。如果发现 OLED 上数字重影一般是OLED_Clear()后没有延时再写或者OLED_ShowNum的长度参数比实际数字位数少导致旧字符残留在右侧。4.3 蜂鸣器报警与阈值迟滞蜂鸣器接在普通 GPIO 上有源蜂鸣器直接给电平就能响无源蜂鸣器则需要 PWM 驱动。源码里的stm32f10x_tim.c更有可能服务于无源蜂鸣器的 PWM 输出或者用于驱动系统节拍。无论哪种报警逻辑都不应该只用一个阈值比较否则传感器表面水珠半干不干时ADC 值会在阈值附近来回穿越蜂鸣器就会连续“叭、叭、叭”。正确做法是迟滞比较开启报警用低阈值关闭报警用高阈值中间留一个缓冲区。以下代码假设有源蜂鸣器高电平触发#define RAIN_ON_THRESHOLD 1200 // ADC 低于该值触发报警 #define RAIN_OFF_THRESHOLD 1600 // ADC 高于该值解除报警 void Beep_Update(uint16_t adc, uint8_t *buzzer_state) { if (*buzzer_state 0 adc RAIN_ON_THRESHOLD) { *buzzer_state 1; GPIO_SetBits(GPIOB, GPIO_Pin_0); printf(ALARM:ON\r\n); } else if (*buzzer_state 1 adc RAIN_OFF_THRESHOLD) { *buzzer_state 0; GPIO_ResetBits(GPIOB, GPIO_Pin_0); printf(ALARM:OFF\r\n); } }两个阈值之间那 400 个数是迟滞区。当 ADC 从 3900 开始下降时要跌到 1200 以下才响从积水状态恢复时要升回 1600 以上才停。这样即使传感器表面有一层不均匀的水膜报警状态也能保持稳定。报警触发后再叠一个 500ms 延时翻转的节奏比如响 200ms、停 300ms会比一直长鸣更易察觉。5. 源码工程文件导读与 Keil 环境重建下载包里有不少看起来陌生的文件比如project.uvguix.86187、project.axf、project_sct.Bak。它们不是源码而是 Keil 在编译过程中产生的用户界面记录和链接中间文件。拿到压缩包后先分清哪些该留、哪些该删能省下不少排查时间。5.1 你下载的包里各文件是干什么的文件名类型作用project.uvprojx / project.uvoptxKeil 工程文件真正的工程入口双击.uvprojx打开项目project.uvguix.86187界面布局文件记录窗口位置可删除不影响编译project.axf链接输出镜像编译后生成的调试文件可删除project_sct.Bak分散加载文件备份Keil 自动生成的配置备份可删除keilkill.batWindows 批处理一键清理编译中间文件stm32f10x_adc.cSTM32 标准外设库ADC 外设驱动OLED.c用户驱动OLED 显示初始化与刷屏函数stm32f10x_i2c.cSTM32 标准外设库I2C 外设驱动stm32f10x_tim.cSTM32 标准外设库定时器外设驱动stm32f10x_flash.cSTM32 标准外设库Flash 驱动通常不是雨滴逻辑主文件stm32f10x_rcc.cSTM32 标准外设库时钟配置的关键依赖看到stm32f10x_flash.c不要紧张它通常是标准外设库工程创建时自动加入的 Flash 驱动文件雨滴监测主逻辑用不到 Flash 写入只保留文件但不调用 API 即可。5.2 keilkill.bat 到底删了什么keilkill.bat是一个本地清理脚本作用是把编译产生的*.o、*.axf、*.crf、*.uvguix.*、*.bak这些文件删除让工程回到“刚刚下载”的原始状态。如果直接分享工程先跑一遍这个脚本能大幅缩减压缩包体积。类似脚本的常见写法如下echo off del /s /q *.uvguix.* 2nul del /s /q *.bak 2nul del /s /q *.axf 2nul del /s /q *.o *.__i *.crf *.d 2nul del /s /q BuildingLog.htm 2nul echo clean done pause脚本里的2nul是让删除动作不因文件不存在而中断打印错误。执行后如果 Keil 还开着工程系统会提示某些文件被占用所以务必先关掉 Keil 再双击。这个脚本不会删除.uvprojx和源代码.c所以也经常被放在 git 仓库里作为clean.bat使用。5.3 从标准外设库重建 Keil 工程的步骤如果打开工程后报Cannot open source file stm32f10x.h说明编译器找不到标准外设库头文件路径这时不需要下载新工程重建一次更稳。常见重建步骤如下在 Keil MDK 里新建一个空工程芯片型号选择 STM32F103 系列对应容量例如 STM32F103C8 或 RCT6。在当前工程目录下新建User和Library两个目录把源码里的OLED.c放到User.将标准外设库的src目录下的stm32f10x_adc.c、stm32f10x_i2c.c、stm32f10x_tim.c、stm32f10x_rcc.c、stm32f10x_flash.c添加到工程。添加启动文件startup_stm32f10x_hd.s。点击魔术棒在 C/C 选项卡增加宏定义USE_STDPERIPH_DEVICE, STM32F10X_HD。在 Include Paths 中分别添加标准外设库的inc目录、源码头文件所在目录。编译时如果报SystemInit未定义再向工程添加标准外设库里的system_stm32f10x.c。重建之后再去对照 2.2 节和 3.1 节的代码把 ADC、USART 初始化补进main整个系统就会重新跑起来。Keil 打开工程时报错missing device时再去确认芯片包安装版本这通常和源码本身无关。6. 调试技巧用串口波形校准两个关键阈值雨滴传感器能读回数字但“多大给蜂鸣器报警”必须靠实测校准不能照抄网上的常量。校准的核心思想是先把 ADC 原始值完整打印出来再分步做滴水测试最后根据测试数据反向填阈值。6.1 校准前的串口准备先用第 3 节代码把printf(RAIN:%04d\r\n, adc)跑起来只发原始值不要带任何业务逻辑。把串口调试助手打开选用带 ASCII 曲线绘制功能的工具或者用串口日志把数据存下来。这时不需要 OLED 和蜂鸣器因为它们会干扰你判断传感器本来的表现。校准步骤建议分成五档测试档位操作观察目标干燥基线传感器裸露在空气中记录稳定 ADC 最大值单滴接触滴 1 滴清水到中间记录瞬时最低 ADC分散水珠在 4 个不同位置各滴 1 滴记录 ADC 回落和恢复趋势连续水膜用喷壶均匀喷湿表面记录稳定 ADC 值擦干恢复纸巾吸干表面记录恢复时间和最终值每次测试之间至少间隔 5 秒因为水会在梳状电极间缓慢扩散ADC 会持续下降多拍。等到测试数据稳定后开报警阈值取“干燥基线和单滴接触”的中间偏低值关报警阈值取“连续水膜”和“擦干后”的中间偏高值。6.2 把采样频率放到 50ms 以内校准过程最容易踩的坑是 ADC 采样太慢导致单滴水珠还没被采到就蒸发了。单滴水滴从接触电极到铺展开通常只有几秒钟如果主循环里 OLED 清屏、串口等多任务占用了 200ms采到的只是“水已经干了之后”的值。可以把 ADC 读取放到定时器中断里也可以把主循环简化为只采样、只发送。常见做法是先用一个 10ms 延时的GetADCValue刷串口确认曲线连续后再把 OLED 加回去。一个经验曲线是干燥值 3800单滴瞬间跌到 900稳定在 2200喷湿后稳定在 300擦干后 10 秒回到 3000 以上。这种情况下开报警阈值可以设置在 1200关报警阈值设置在 2600。开阈值必须低于“单滴稳定值”否则偶尔溅上水滴就会触发关阈值必须高于“恢复后的残余水膜值”否则擦干后蜂鸣器还会持续响。校准完成后把数值写回第 4 节的宏里然后做一次不带人的自动测试用滴管在传感器上方 5cm 处连续滴三滴水蜂鸣器应该在第二滴落下后的 1 秒内稳定触发OLED 和串口同一帧里都能看到LEVEL从 0 跳到 2而不是先跳 1 又跳回 0。本文还有配套的精品资源点击获取