基于CW32F030C8T6的雨滴传感器模块驱动移植与ADC/GPIO双模式数据采集实战
基于CW32F030C8T6的雨滴传感器模块驱动移植与ADC/GPIO双模式数据采集实战最近在做一个智能车窗的小项目需要检测是否下雨以及雨量大小就用到了常见的雨滴传感器模块。这类模块很便宜网上十几块钱就能买到但很多资料都是基于Arduino或者STM32的。我手头正好有立创的CW32F030C8T6开发板就想把驱动移植过来。整个过程踩了一些坑也总结了一些经验今天就跟大家详细分享一下如何从零开始把一个通用的雨滴传感器模块成功驱动在国产的CW32F030C8T6单片机上并且实现模拟量ADC和数字量GPIO两种数据采集方式。咱们这个教程会手把手带你走一遍完整的流程从理解传感器原理、查看硬件资料、编写驱动代码到最后在开发板上验证功能。即使你是嵌入式新手跟着做一遍也能完全掌握。1. 认识你的雨滴传感器模块在开始写代码之前咱们得先搞清楚手头的“兵器”到底是个啥。我用的这个模块是市面上非常通用的一款核心是LM393电压比较器芯片。1.1 模块是怎么工作的你可以把雨滴传感器想象成一块“智能毛巾”。它的核心是一块特殊的电路板上面用导电材料比如镍画了很多平行的线条。平时这些线条之间是空气电阻非常大相当于“断路”。当雨滴落到板子上水是导电的它就像一座桥把两条原本分开的线条连接起来了。这会导致两个电极之间的电阻变小。模块内部的电路主要是LM393会检测这个电阻的变化。模块提供了两种方式告诉我们检测结果模拟输出AO引脚直接输出一个电压值。雨滴越多、越大板子上的水膜越连续电阻就越小输出的电压就越高。这个电压是连续变化的能反映雨量的“大小”。数字输出DO引脚输出一个简单的高低电平。模块上有个蓝色的可调电阻你可以设定一个“阈值”。当雨量达到这个阈值即AO电压超过某个值DO引脚就从高电平变成低电平告诉你“喂下雨了” 板子上有个DO-LED灯亮起就代表检测到雨滴。1.2 模块的关键参数与接线拿到模块先看看它的基本规格这对我们供电和编程很重要参数说明工作电压3.3V - 5V。我们的CW32开发板通常提供3.3V直接接上就行。输出信号AO模拟电压输出 (0-VCC)。DO数字电平输出 (0或VCC)。引脚定义一共4个引脚顺序可能因模块而异以实物标注为准VCC电源正极 (3.3V/5V)GND电源地DO数字量输出引脚AO模拟量输出引脚注意模块上通常有标注。接线前一定要确认清楚VCC和GND接反了可能会烧坏模块2. 硬件连接与MCU引脚规划接下来我们要把传感器和CW32F030C8T6开发板连起来并决定用哪个引脚来读取信号。2.1 电路连接连接非常简单传感器VCC-开发板3.3V传感器GND-开发板GND传感器DO-开发板任意GPIO引脚我选了PA6传感器AO-开发板具有ADC功能的引脚我选了PA52.2 为什么选PA5和PA6选择引脚不是随意的尤其是AO引脚它必须连接到单片机支持ADC模数转换功能的引脚上。查看数据手册我翻阅了CW32F030的用户手册在ADC章节的引脚复用功能里找到PA5引脚支持ADC输入通道5AIN5。这就是我选择PA5的原因。DO引脚选择DO输出的是数字高低电平所以几乎任何GPIO引脚都可以只要配置为输入模式就行。我选择了PA6纯粹是因为它就在PA5旁边布线方便。提示CW32F030的ADC功能引脚是固定的比如PA0对应AIN0PA1对应AIN1……编程时必须匹配。如果你要用其他引脚一定要去手册里确认它是否支持ADC。3. 驱动代码移植与编写这是最核心的部分。我们需要创建两个文件bsp_raindrop.c源文件和bsp_raindrop.h头文件。bsp是“板级支持包”的缩写意思是把和这个具体硬件模块相关的代码都放在一起方便管理和移植。3.1 头文件 (bsp_raindrop.h) 的配置头文件主要用来做宏定义和函数声明把硬件相关的引脚、端口信息固定下来以后要换引脚只改这里就行非常方便。#ifndef _BSP_RAINDROP_H__ #define _BSP_RAINDROP_H__ #include board.h // 包含CW32的板级基础头文件 /* 时钟使能宏定义 */ #define RCC_RAINDROP_GPIO_ENABLE() __RCC_GPIOA_CLK_ENABLE() // 使能GPIOA的时钟 #define RCC_RAINDROP_ADC_ENABLE() __RCC_ADC_CLK_ENABLE() // 使能ADC的时钟 /* 引脚与端口定义 */ #define BSP_RAINDROP_GPIO_PORT CW_GPIOA // 使用的GPIO端口是A #define BSP_RAINDROP_GPIO_PIN_AO GPIO_PIN_5 // AO引脚接在PA5 #define BSP_RAINDROP_GPIO_PIN_DO GPIO_PIN_6 // DO引脚接在PA6 /* 将PA5设置为模拟输入模式的宏 */ #define ANALOG_GPIO_ENABLE() PA05_ANALOG_ENABLE() /* ADC通道定义PA5对应通道5 */ #define BSP_RAINDROP_ADC_CHANNEL ADC_ExInputCH5 /* 函数声明 */ void raindrop_gpio_config(void); // 初始化函数 unsigned int get_raindrop_percentage_value(void); // 获取雨量百分比 unsigned char get_raindrop_do_value(void); // 获取数字开关状态 #endif3.2 源文件 (bsp_raindrop.c) 的完整实现源文件里包含了所有具体的函数实现。咱们一个一个函数来看。3.2.1 初始化函数raindrop_gpio_config()这个函数负责初始化用到的GPIO和ADC外设。这是最复杂的一步但别怕我把它拆解开讲。void raindrop_gpio_config(void) { GPIO_InitTypeDef GPIO_InitStruct; // 定义一个GPIO初始化结构体 /* 第一步打开时钟开关 */ RCC_RAINDROP_GPIO_ENABLE(); // 打开GPIOA的时钟 RCC_RAINDROP_ADC_ENABLE(); // 打开ADC的时钟 /* 第二步配置DO引脚为输入模式 */ GPIO_InitStruct.Pins BSP_RAINDROP_GPIO_PIN_DO; // 指定要初始化的引脚是PA6 GPIO_InitStruct.Mode GPIO_MODE_INPUT; // 模式设置为输入因为我们要读取DO引脚的电平 GPIO_Init(BSP_RAINDROP_GPIO_PORT, GPIO_InitStruct); // 调用初始化函数 /* 第三步配置AO引脚为模拟输入模式 */ ANALOG_GPIO_ENABLE(); // 这个宏专门用于将PA5设置为ADC模拟输入模式内部禁用了数字功能 /* 第四步配置ADC外设 */ ADC_InitTypeDef ADC_InitStructure; // ADC主初始化结构体 ADC_WdtTypeDef ADC_WdtStructure; // ADC看门狗结构体本例未使用但需配置 ADC_SingleChTypeDef ADC_SingleChStructure; // ADC单通道转换结构体 // 填充ADC主初始化参数 ADC_InitStructure.ADC_OpMode ADC_SingleChOneMode; // 单通道单次转换模式转换一次就停 ADC_InitStructure.ADC_ClkDiv ADC_Clk_Div4; // ADC时钟分频。系统时钟64MHz4分频后为16MHz需保证ADC时钟16MHz ADC_InitStructure.ADC_SampleTime ADC_SampTime5Clk; // 采样时间5个ADC时钟周期时间越长精度越高但速度越慢 ADC_InitStructure.ADC_VrefSel ADC_Vref_VDDA; // 参考电压选择VDDA即3.3V电源 ADC_InitStructure.ADC_InBufEn ADC_BufDisable; // 关闭输入缓冲器一般保持关闭 ADC_InitStructure.ADC_TsEn ADC_TsDisable; // 关闭内部温度传感器通道 ADC_InitStructure.ADC_DMAEn ADC_DmaDisable; // 不使用DMA ADC_InitStructure.ADC_Align ADC_AlignRight; // 转换结果右对齐方便阅读 ADC_InitStructure.ADC_AccEn ADC_AccDisable; // 不使能累加功能 // 初始化ADC看门狗本例未使用其功能但需进行初始化操作 ADC_WdtInit(ADC_WdtStructure); // 配置单通道转换的具体通道 ADC_SingleChStructure.ADC_DiscardEn ADC_DiscardNull; // 溢出数据保存方式 ADC_SingleChStructure.ADC_Chmux BSP_RAINDROP_ADC_CHANNEL; // 选择通道5PA5 ADC_SingleChStructure.ADC_InitStruct ADC_InitStructure; // 关联主配置 ADC_SingleChStructure.ADC_WdtStruct ADC_WdtStructure; // 关联看门狗配置 // 将以上配置写入ADC寄存器 ADC_SingleChOneModeCfg(ADC_SingleChStructure); // 最后使能ADC模块 ADC_Enable(); }关键点解释时钟使能单片机的外设GPIO、ADC等都需要独立的时钟驱动就像给设备通电。使用前必须先打开。GPIO模式DO是数字信号所以PA6配置为GPIO_MODE_INPUT输入。AO是模拟信号PA5必须配置为模拟输入模式使用PA05_ANALOG_ENABLE()这个专用宏。ADC配置CW32的ADC配置稍微繁琐需要配置三个结构体。核心是ADC_SingleChOneMode单次转换和ADC_Chmux选择通道5。ADC_Clk_Div4确保ADC时钟在安全范围内。3.2.2 ADC数值读取函数ADC_GET()与get_adc_value()初始化完成后我们需要一个函数来启动一次ADC转换并读取结果。uint32_t ADC_GET(void) { ADC_SoftwareStartConvCmd(ENABLE); // 软件触发启动一次ADC转换 uint32_t adcValue ADC_GetConversionValue(); // 读取ADC数据寄存器的值 return adcValue; } unsigned int get_adc_value(void) { return ADC_GET(); }这里封装了两层ADC_GET()是实际操作的函数get_adc_value()只是它的一个别名为了保持和其他传感器代码的命名风格一致。每次调用这个函数MCU就会对PA5的电压进行一次采样并返回一个0-4095的数字因为CW32F030的ADC是12位的2^124096。3.2.3 获取雨量百分比函数get_raindrop_percentage_value()直接读到的ADC值比如2156不直观我们把它转换成“湿度”或“雨量”百分比。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); // 每次采样间隔100ms } adc_new adc_new / count; // 计算平均值 delay_1ms(100); // 核心转换公式百分比 (1 - ADC当前值 / ADC最大值) * 100 Percentage_value ( 1.0f - ( (float)adc_new / (float)adc_max ) ) * 100; return Percentage_value; }为什么这样换算传感器板干燥时电阻极大AO引脚电压接近0VADC值接近0。代入公式(1 - 0/4095)*100 ≈ 100%。可以理解为“干燥度100%”。传感器板完全浸湿时电阻极小AO引脚电压接近VCC(3.3V)ADC值接近4095。代入公式(1 - 4095/4095)*100 0%。可以理解为“干燥度0%”即“湿度100%”。所以这个百分比值越大表示越干燥越小表示雨水越多。你可以根据习惯在公式前加个负号让它正向表示湿度。3.2.4 获取数字开关状态函数get_raindrop_do_value()这个函数就简单多了直接读取DO引脚的电平状态。unsigned char get_raindrop_do_value(void) { return GPIO_ReadPin(BSP_RAINDROP_GPIO_PORT, BSP_RAINDROP_GPIO_PIN_DO); }它返回0低电平表示检测到雨或1高电平表示无雨。这个阈值可以通过模块上的蓝色电位器调节。4. 在主程序中测试验证驱动写好了最后就是在主函数里调用它们看看效果。#include board.h #include stdio.h #include bsp_uart.h // 用于串口打印 #include bsp_raindrop.h int32_t main(void) { board_init(); // 开发板基础初始化系统时钟等 uart1_init(115200); // 初始化串口1波特率115200用来打印数据到电脑 raindrop_gpio_config(); // 初始化雨滴传感器 while(1) { // 读取并打印雨量百分比 printf(Raindrop Percentage %d%%\r\n, get_raindrop_percentage_value()); // 读取并打印数字状态 unsigned char do_state get_raindrop_do_value(); if(do_state 0) { printf(DO Status: RAIN DETECTED!\r\n); } else { printf(DO Status: No rain.\r\n); } printf(-------------------\r\n); delay_ms(1000); // 每隔1秒打印一次 } }将代码编译下载到CW32F030C8T6开发板打开串口助手如SecureCRT、Putty设置好波特率115200。你就能看到实时数据了干燥时百分比接近100%DO状态为“No rain”。用手指沾水涂抹传感器板百分比会下降当低于某个阈值时DO状态会变成“RAIN DETECTED!”。调节模块上的蓝色电位器可以改变DO状态翻转的灵敏度。实际使用中的小贴士传感器板放置检测面要朝上并且有一定倾斜角度比如30度这样雨水能流走不会积水导致一直报“有雨”。数据滤波实际环境中ADC读数可能会有跳动。除了代码里简单的多次平均你还可以尝试“中值滤波”或“一阶滞后滤波”算法让数据更平滑。阈值设定get_raindrop_percentage_value()返回的百分比结合你的安装环境可以设定一个阈值比如30%。当百分比低于这个阈值就启动雨刷或关窗。这个阈值需要在实际场景中调试确定。好了整个驱动移植和双模式采集的过程就是这样。代码我已经在实际项目里跑过了稳定可靠。如果你在移植过程中遇到问题比如ADC读数不对首先检查接线然后用万用表量一下AO引脚的电压变化是否正常一步步排查肯定能搞定。