当前位置: 首页 > news >正文

基于天空星STM32F407的雨滴传感器模块驱动移植与ADC/GPIO应用实战

基于天空星STM32F407的雨滴传感器模块驱动移植与ADC/GPIO应用实战

最近在做一个智能车窗的小项目,需要检测是否下雨以及雨量大小,于是就用上了雨滴传感器。很多刚开始接触STM32 ADC和GPIO应用的朋友,可能会觉得传感器驱动移植有点无从下手。今天我就以手头的这块立创天空星STM32F407开发板为例,带大家一步步搞定一个基于LM393的雨滴传感器模块,从硬件连接到代码编写,再到功能验证,把整个过程掰开揉碎了讲清楚。

咱们的目标很明确:让开发板能通过传感器判断雨滴板上有没有水,甚至能大致感知雨量大小。这在实际项目中,比如自动雨刷、智能关窗等场景里非常有用。整个过程会涉及到GPIO的输入模式配置、ADC模拟输入采集,以及如何将原始数据转换成我们能理解的百分比。放心,我会把每一步的原理和操作都讲明白,让你不仅能“照做”,更能“看懂”。

1. 认识你的雨滴传感器模块

在动手写代码之前,咱们得先搞清楚要驱动的对象是个啥。我用的这个雨滴传感器模块很常见,网上很容易买到。

1.1 模块是怎么工作的?

这个模块的核心是一块特殊的电路板,上面有像线条一样涂覆的镍层。它的工作原理其实挺巧妙的:利用水的导电性。

你可以把它想象成两个电极中间有个空气间隙。没水的时候,空气是绝缘的,电路是断开的。当雨滴落到这两个电极上时,水就成了一座“导电桥”,把两个电极连接起来,形成一个电流回路。这样一来,两个电极之间的电阻就变小了,它们两端的电压(压降)也就随之改变。

模块上的控制板基于一个叫LM393的运算放大器芯片。它有两个输出口:

  • AO(模拟输出):这个引脚直接输出一个变化的电压值。雨滴板上的水越多,导电性越好,电阻越小,输出的电压就越高。我们需要用单片机的ADC(模数转换器)功能来读取这个变化的电压,从而判断雨量大小。
  • DO(数字输出):这个信号是经过LM393比较器处理过的。模块上有个蓝色的可调电阻,你可以用它设定一个阈值。当AO端的电压超过这个阈值时(意味着雨量达到一定程度),DO引脚就会从高电平变成低电平。这个信号非常干脆,就是简单的0或1,适合直接用单片机的GPIO输入功能来读取,用于做“有雨/无雨”的开关判断。

模块上还有两个LED指示灯:一个电源灯(PWR-LED),通电就常亮;另一个是输出信号灯(DO-LED),当检测到雨滴(DO输出低电平)时才会亮,方便你直观观察。

1.2 模块的关键参数

了解规格参数,才能正确使用它。这个模块的主要参数如下:

参数项规格说明
工作电压3.3V - 5V (和咱们的开发板兼容,可以直接用3.3V供电)
输出方式DO:数字量输出(0或1)
AO:模拟量输出(电压值)
读取方式DO:GPIO数字输入
AO:ADC模拟输入
接口4 Pin 排针(2.54mm间距)

模块的接线很简单,通常四根线分别是:VCC(电源正)、GND(电源地)、AO(模拟输出)、DO(数字输出)。

2. 硬件连接与引脚规划

接下来,要把传感器和天空星开发板连起来,并决定用哪个引脚。

2.1 连接电路

连接非常简单:

  1. VCC-> 连接到开发板的3.3V电源引脚。
  2. GND-> 连接到开发板的GND引脚。
  3. AO-> 连接到我们准备用作ADC输入的引脚(例如PC1)。
  4. DO-> 连接到我们准备用作GPIO输入的引脚(例如PE1)。

注意:供电一定要在3.3V-5V之间,虽然模块支持5V,但为了与开发板电平匹配,建议统一使用3.3V。

2.2 引脚选择与配置思路

选择引脚不是随便选的,需要看芯片数据手册。

  • AO引脚(ADC输入):这是关键。我们需要找一个具有ADC外设功能的引脚。查看STM32F407的数据手册,我发现PC1这个引脚复用了ADC1的第11号输入通道,正好合适。所以,我们决定将传感器的AO线接到PC1。
  • DO引脚(GPIO输入):这个就灵活多了,几乎任何普通的GPIO引脚都可以配置为输入模式。为了布线方便或者个人习惯,我选择了PE1。你完全可以根据自己板子的空闲情况选择其他引脚,记得在代码里改过来就行。

所以,最终的硬件连接和软件配置对应关系是:

  • 传感器AO -> STM32的PC1(ADC1_IN11)
  • 传感器DO -> STM32的PE1(普通GPIO输入)

3. 手把手编写驱动代码(bsp_raindrop)

硬件连好了,现在来写让传感器“活”起来的代码。我们采用模块化的方式,创建bsp_raindrop.cbsp_raindrop.h两个文件。“bsp”是“板级支持包”的意思,这样写代码结构清晰,以后移植到其他项目也方便。

3.1 头文件定义 (bsp_raindrop.h)

头文件的主要作用是进行宏定义和函数声明,把硬件相关的引脚、端口信息固定下来,这样修改硬件连接时只需要改头文件,非常方便。

#ifndef _BSP_RAINDROP_H__ #define _BSP_RAINDROP_H__ #include "stm32f4xx.h" /* 时钟宏定义 */ #define RCC_RAINDROP_GPIO_AO RCC_AHB1Periph_GPIOC // PC1的GPIO时钟 #define RCC_RAINDROP_GPIO_DO RCC_AHB1Periph_GPIOE // PE1的GPIO时钟 #define RCC_RAINDROP_ADC RCC_APB2Periph_ADC1 // ADC1的时钟 /* AO引脚(ADC输入)定义 */ #define BSP_RAINDROP_GPIO_PORT_AO GPIOC #define BSP_RAINDROP_GPIO_PIN_AO GPIO_Pin_1 /* DO引脚(数字输入)定义 */ #define BSP_RAINDROP_GPIO_PORT_DO GPIOE #define BSP_RAINDROP_GPIO_PIN_DO GPIO_Pin_1 /* ADC相关定义 */ #define BSP_ADC ADC1 #define BSP_RAINDROP_ADC_CHANNEL ADC_Channel_11 // PC1对应ADC1的通道11 /* 函数声明 */ void raindrop_gpio_config(void); // 初始化函数 unsigned int get_raindrop_percentage_value(void); // 获取雨量百分比 unsigned char get_raindrop_do_value(void); // 获取数字开关量 #endif

3.2 初始化函数详解 (raindrop_gpio_config)

这是最核心的函数,负责配置GPIO和ADC的工作模式。我把它拆解成几个部分,咱们一步步看。

第一部分:开启硬件时钟STM32的任何外设(GPIO、ADC等)在使用前,都必须先打开对应的时钟,相当于给这个部件通电。

void raindrop_gpio_config(void) { RCC_AHB1PeriphClockCmd (RCC_RAINDROP_GPIO_AO, ENABLE); // 使能GPIOC时钟 RCC_AHB1PeriphClockCmd (RCC_RAINDROP_GPIO_DO, ENABLE); // 使能GPIOE时钟 RCC_APB2PeriphClockCmd (RCC_RAINDROP_ADC, ENABLE); // 使能ADC1时钟

第二部分:配置AO引脚为模拟输入模式AO引脚要测量变化的电压,必须设置为模拟输入模式。这个模式下,引脚内部的所有数字电路(上拉、下拉电阻等)都被断开,直接连接到ADC。

GPIO_InitTypeDef GPIO_InitStructure; /* 配置PC1 (AO) 为模拟输入 */ GPIO_InitStructure.GPIO_Pin = BSP_RAINDROP_GPIO_PIN_AO; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; // 关键!模拟输入模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; // 速度模式对输入影响不大,可保持默认 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 模拟输入模式下,禁止上拉下拉 GPIO_Init(BSP_RAINDROP_GPIO_PORT_AO, &GPIO_InitStructure);

第三部分:配置DO引脚为数字输入模式DO引脚输出的是干净的高/低电平,我们只需要读取它,所以配置为浮空输入模式即可。

/* 配置PE1 (DO) 为浮空输入 */ GPIO_InitStructure.GPIO_Pin = BSP_RAINDROP_GPIO_PIN_DO; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; // 输入模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 浮空输入 GPIO_Init(BSP_RAINDROP_GPIO_PORT_DO, &GPIO_InitStructure);

第四部分:配置ADC外设这部分稍微复杂点,但按照固定流程走就没问题。STM32的ADC有很多高级功能,我们这里用最基本、最常用的独立单次转换模式。

/* ADC公共配置(影响ADC1/2/3的共用设置)*/ ADC_CommonInitTypeDef ADC_CommonInitStruct; ADC_DeInit(); // 先复位ADC到默认状态,这是个好习惯 ADC_CommonInitStruct.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; // 不用DMA ADC_CommonInitStruct.ADC_Mode = ADC_Mode_Independent; // 独立模式,ADC1单独工作 ADC_CommonInitStruct.ADC_Prescaler = ADC_Prescaler_Div4; // ADC时钟分频,决定转换速度 ADC_CommonInitStruct.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; // 采样间隔 ADC_CommonInit(&ADC_CommonInitStruct); /* ADC1自身配置 */ ADC_InitTypeDef ADC_InitStruct; ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // 单次转换模式,转换一次就停止 ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐,方便读取 ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; // 不使用外部触发,用软件触发 ADC_InitStruct.ADC_NbrOfConversion = 1; // 只进行1个通道的转换 ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b; // 12位分辨率,结果范围0-4095 ADC_InitStruct.ADC_ScanConvMode = DISABLE; // 非扫描模式(单通道) ADC_Init(BSP_ADC, &ADC_InitStruct); // 指定ADC用哪个通道(第11通道)进行转换,并设置采样时间 ADC_RegularChannelConfig(BSP_ADC, BSP_RAINDROP_ADC_CHANNEL, 1, ADC_SampleTime_480Cycles); // 使能ADC ADC_Cmd(BSP_ADC, ENABLE); }

提示:ADC_SampleTime_480Cycles表示采样时间为480个ADC时钟周期,时间较长,可以提高精度,适合测量像传感器电压这样变化不快的信号。

3.3 数据读取函数

初始化完成后,我们就能读取数据了。需要两个函数,分别读取AO和DO。

读取ADC原始值函数这个函数负责启动一次ADC转换,并等待转换完成,最后返回12位的数字值(0-4095)。

unsigned int get_adc_value(void) { ADC_SoftwareStartConv(BSP_ADC); // 软件触发,开始一次转换 while(!ADC_GetFlagStatus(BSP_ADC, ADC_FLAG_EOC )); // 等待转换结束标志位 uint32_t data = ADC_GetConversionValue(BSP_ADC); // 读取转换结果 delay_ms(100); // 简单延时,避免读取过于频繁 return data; }

将ADC值转换为雨量百分比直接看0-4095的数字不直观,我们把它转换成“湿度”或“雨量”百分比。这里有个逻辑:传感器板越干燥,电阻越大,AO电压越低,ADC值越小。所以,ADC值越大表示水越多。

unsigned int get_raindrop_percentage_value(void) { int adc_max = 4095; // 12位ADC最大值 uint32_t adc_new = 0; int Percentage_value = 0; int i = 0; int count = 3; // 采样3次取平均,让数值更稳定 for(i = 0; i < count; i++) { adc_new += get_adc_value(); // 累加ADC值 delay_1ms(100); } adc_new = adc_new / count; // 计算平均值 // 核心转换公式:百分比 = (1 - (ADC值 / 4095)) * 100 // 当ADC值=0(最干),百分比=100%;当ADC值=4095(最湿),百分比=0% Percentage_value = ( 1.0f - ( (float)adc_new / (float)adc_max ) ) * 100; return Percentage_value; }

注意:这个百分比是一个相对值,表示“干燥程度”。100%表示完全干燥,0%表示传感器板被水完全覆盖(或导电性极好)。你可以根据这个百分比来划分雨量等级(如:>80%无雨,30%-80%小雨,<30%大雨)。

读取数字开关量函数这个函数就简单多了,直接读取DO引脚的电平状态。

unsigned char get_raindrop_do_value(void) { // 读取PE1引脚的电平,返回0(低电平,有雨)或1(高电平,无雨) return GPIO_ReadInputDataBit(BSP_RAINDROP_GPIO_PORT_DO, BSP_RAINDROP_GPIO_PIN_DO); }

模块上的蓝色电位器就是用来调节这个电平翻转的阈值的。顺时针拧,灵敏度提高(一点水就触发);逆时针拧,灵敏度降低(需要更多水才触发)。

4. 在主函数中调用与验证

驱动写好了,最后一步就是在主函数里把它们用起来,并且通过串口打印出来看看效果。

#include "board.h" #include "bsp_uart.h" #include <stdio.h> #include "bsp_raindrop.h" int main(void) { // 开发板基础初始化(系统时钟、滴答定时器等) board_init(); // 初始化串口1,用于打印信息到电脑,波特率115200 uart1_init(115200U); // 初始化雨滴传感器(配置GPIO和ADC) raindrop_gpio_config(); printf("雨滴传感器测试程序启动...\r\n"); while(1) { // 读取并打印雨量百分比 printf("雨水百分比 = %d%%\r\n", get_raindrop_percentage_value()); // 读取并打印数字开关状态 unsigned char do_state = get_raindrop_do_value(); if(do_state == 0) { printf("DO状态:检测到雨滴(低电平)\r\n"); } else { printf("DO状态:无雨(高电平)\r\n"); } printf("-----------------\r\n"); delay_ms(1000); // 每秒读取一次 } }

上电验证:

  1. 将代码编译下载到天空星开发板。
  2. 用USB转串口模块连接开发板的串口1到电脑,打开串口助手(如Xshell、Putty),设置波特率115200。
  3. 给传感器模块的雨滴板上滴几滴水。
  4. 观察串口助手打印的信息。你会看到“雨水百分比”数值下降,同时DO状态很可能变为“检测到雨滴”。用手或纸巾擦干水渍,数值和状态又会恢复。
  5. 尝试调节模块上的蓝色电位器,观察DO状态变化的灵敏度。

通过这个实战,你不仅学会了如何驱动一个具体的传感器,更重要的是掌握了STM32处理模拟量(ADC)和数字量(GPIO输入)的通用方法。下次遇到其他类似的传感器,比如光照传感器、土壤湿度传感器(模拟输出型),你完全可以照猫画虎,快速上手。

http://www.jsqmd.com/news/486901/

相关文章:

  • 老旧Mac设备的系统升级:使用开源工具突破硬件限制的技术指南
  • Wireshark网卡列表消失?5分钟搞定NPCAP驱动加载问题(附详细步骤)
  • 迎战2026新托福!多次元托福APP,你的AI智能备考核心引擎 - 速递信息
  • Phi-3-Mini-128K代码实例:扩展支持Markdown渲染与代码块高亮显示
  • 2026年工业塑料管帽优质供应商深度评测与推荐 - 深度智识库
  • REFramework全栈开发指南:从入门到架构师的思维跃迁
  • AI智能文档扫描仪 vs 全能扫描王:性能对比实战评测
  • Repo Wiki实战:5分钟搞定代码仓库自动文档化(附避坑指南)
  • Windows 10下PyTorch3D安装避坑指南:从CUDA版本匹配到环境变量配置
  • Linux V4L2 摄像头采集与 YUYV 到 RGB 转换的嵌入式实现
  • Qwen3-TTS-12Hz-1.7B语音合成在智能家居中的应用
  • YUV420SP_NV12格式详解:安卓相机和视频编码背后的秘密(含海思ISP实战配置)
  • 别再手搓集群了:用 Terraform + Helm 把数据平台“养成宠物”变“放养牛群”
  • 实战指南:如何用Python实现图像去模糊(附逆滤波与维纳滤波对比)
  • 3D点云标注实战:用Xtreme1开源框架搭建标注平台(附避坑指南)
  • Allegro Aurora(五)-Return Path Workflow实战解析
  • 宇树G1机器人SSH连接实战:MobaXterm配置与网络调试指南
  • 避开SAP时间戳那些坑:convert_abap_timestamp_to_java函数深度使用指南
  • 基于立创EDA与AIR001的智能手持风扇DIY:PWM调速、快充与充电宝功能全解析
  • RobotStudio新手必看:5步搞定夹取工件程序(附常见错误排查)
  • GLM-OCR赋能内容创作:快速提取图片素材文字用于AIGC生成
  • FireRedASR Pro语音识别Java开发实战:SpringBoot集成与API服务构建
  • 从收音机到智能手机:多级放大电路耦合方式演变史(含现代IC设计对比)
  • 手把手教你搭建Xilinx PCIe XVC调试环境:从扩展卡选购到FPGA引脚配置
  • 冯诺依曼与哈佛架构对比解析
  • Cosmos-Reason1-7B低代码/无代码平台后端逻辑生成:以简化业务流程为例
  • 一张显卡跑通Qwen3-14B:消费级GPU部署方案与实测效果分享
  • 深圳坪山青少年篮球培训机构口碑测评:哪家最值得报名? - 前沿公社
  • Qwen3-0.6B-FP8 Java开发实战:SpringBoot微服务集成与部署指南
  • Swin2SR效果集锦:多张模糊图高清重构成果展示