1. 项目背景与应用场景

大家好,今天我想分享一个非常实用的STM32项目——用ADC中断模式采集水位传感器数据,并通过OLED实时显示。这个项目特别适合那些想要做智能花盆、小型水族箱水位监控或者简单液位检测的朋友。我自己在实际项目中用过好几次,效果挺稳的,尤其适合STM32初学者练手。

水位检测在很多小项目中都很常见,比如自动浇水系统、鱼缸水位报警器等等。传统的方式可能用轮询读取ADC,但那样效率低,还耗电。用中断模式的话,STM32可以在数据就绪时才处理,平时处于低功耗状态,非常适合电池供电的场景。我自己之前用轮询方式做过一个智能花盆,后来换到中断模式,电池续航直接翻倍,效果非常明显。

这个项目用的是STM32F103C8T6(也就是常说的BluePill板),成本低、资源够用,而且网上资料多。水位传感器是常见的Water Sensor模块,几块钱一个,简单好用。显示部分用的是0.96寸OLED,I2C接口的,接线简单、显示清晰。整个项目不需要太多外围电路,新手也能快速上手。

2. 硬件准备与连接方式

先说说需要哪些硬件。核心是STM32F103C8T6最小系统板,这款板子性价比高,自带12位ADC,完全够用水位检测。水位传感器我用的是Water Sensor模块,它基于导电性原理,当水位升高时,传感器输出的电压会变化。注意别让水位淹过模块的电子元件部分,一般超过4cm就有短路风险,我自己踩过这个坑,烧过一个传感器。

OLED显示屏选0.96寸I2C接口的,分辨率128x64,显示数字和简单图形都很清晰。接线方面,水位传感器输出接STM32的PA0(ADC1的通道0),OLED的SDA接PB7,SCL接PB6。电源统一用3.3V,避免电压不一致损坏模块。实际接线时,建议用杜邦线先测试,稳定后再焊接到洞洞板上。

这里有个小技巧:Water Sensor的输出电压范围是0-3.3V,但实际水位变化可能只占用其中一部分区间。我实测发现,水位在0-4cm变化时,电压大约在1.0V-2.8V之间波动。所以校准时可以重点关注这个区间,提高检测精度。硬件连接好后,可以用万用表测一下传感器输出电压,确认是否随水位变化,避免接到故障模块。

3. ADC中断模式原理与配置

STM32的ADC支持多种工作模式,中断模式是其中比较高效的一种。它的原理是:当ADC转换完成后,产生一个中断信号,CPU暂停当前任务,读取转换结果,然后继续之前的工作。这样避免了反复查询ADC状态造成的资源浪费。

配置ADC中断的关键步骤有三部分:GPIO初始化、ADC模式设置、中断优先级配置。GPIO设置成模拟输入模式,这点很重要,如果误设成浮空输入,读数会跳变。ADC本身需要设置成独立模式、连续转换、软件触发,这样一次开启后就会持续工作。中断优先级建议设为中等,避免被高优先级任务阻塞。

实际配置时,ADC时钟频率要注意。STM32F103的ADC最大时钟不能超过14MHz,我一般设为9MHz(PCLK2的8分频),这样转换速度够快又稳定。采样时间设为55.5个周期,适合水位这种变化慢的信号。转换结果用右对齐,这样处理起来更直观。中断服务函数里记得清除标志位,否则会连续进入中断。

4. 代码实现:ADC驱动编写

先看ADC的驱动代码,主要分三个函数:GPIO配置、ADC模式配置、中断配置。GPIO配置比较简单,就是使能时钟、设置PA0为模拟输入。ADC模式配置稍复杂些,需要设置连续转换、使能EOC中断、校准ADC等。中断配置主要是设置NVIC,优先级根据系统需求定。

这里贴关键代码:

#include "bsp_adc.h"
__IO uint16_t ADC_ConvertedValue1;

static void ADCx_GPIO_Config(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

static void ADCx_Mode_Config(void) {
    ADC_InitTypeDef ADC_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    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);
    RCC_ADCCLKConfig(RCC_PCLK2_Div8);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
    ADC_Cmd(ADC1, ENABLE);
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

void ADCx_Init(void) {
    ADCx_GPIO_Config();
    ADCx_Mode_Config();
    ADC_NVIC_Config();
}

中断服务函数写在stm32f10x_it.c里:

#include "bsp_adc.h"
extern __IO uint16_t ADC_ConvertedValue1;

void ADC1_2_IRQHandler(void) {
    if (ADC_GetITStatus(ADC1, ADC_IT_EOC) == SET) {
        ADC_ConvertedValue1 = ADC_GetConversionValue(ADC1);
        ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
    }
}

这段代码实现了ADC的基本功能。注意ADC校准不能省略,否则读数可能有偏差。我最初没做校准,结果电压值总是差0.1V左右,后来加上校准就准确了。

5. 水位数据转换与校准

ADC读取的是原始数字值,需要转换成实际电压和水位深度。STM32F103的ADC是12位的,所以原始值范围是0-4095,对应0-3.3V电压。转换公式很简单:电压值 = (原始值 / 4096) * 3.3。但实际水位深度和电压关系不是线性的,需要根据实验数据校准。

我的做法是:先用尺子测量水位高度,记录对应的ADC原始值,然后做曲线拟合。简单点可以用分段线性校准。比如我发现水位0-2cm时,每升高0.5cm电压增加约0.4V;2-4cm时,每升高0.5cm电压增加约0.3V。根据这个关系,可以写出校准代码:

ADC_ConvertedValueLocal = (float)ADC_ConvertedValue1 / 4096 * 3.3;
if (ADC_ConvertedValue1 > 1550) {
    Depth = (ADC_ConvertedValue1 - 1550) / 100;
} else {
    Depth = 0;
}

这里的1550和100是校准参数,需要根据实际传感器调整。建议每换个传感器就重新校准一次,因为不同模块可能有差异。我最初没注意这点,结果两个看似相同的模块,读数差10%左右。

6. OLED显示设计与实现

OLED显示部分我用的是SSD1306驱动,通过I2C接口通信。显示内容主要包括三行:原始ADC值、电压值、水位深度。为了美观,可以加个单位(比如V、cm),还可以用进度条形式显示水位高低。

初始化OLED后,主要用两个函数:OLED_ShowString显示文字,OLED_ShowNum显示数字。注意刷新频率不要太高,否则屏幕会闪烁。我一般100ms刷新一次,这样既流畅又不会占用太多资源。显示数字时,建议保留一位小数,精度足够又不会显得乱。

实际显示时,遇到个问题:当水位快速变化时,数字跳动很厉害。后来我加了软件滤波,取10次测量值平均,显示就稳定多了。这也是中断模式的一个优势——可以轻松实现多次采样取平均,而不影响主程序运行。

7. 主程序逻辑与调试技巧

主程序逻辑很简单:初始化ADC和OLED后,进入循环,不断更新显示。注意中断模式不需要在主循环中读取ADC,只需要使用中断服务函数更新全局变量即可。这样主循环可以处理其他任务,比如按键检测、通信等。

调试时,建议先用串口打印ADC原始值,确认传感器工作正常再接OLED。遇到读数不准时,检查电源是否稳定、接地是否良好。我遇到过因为接地不良导致读数漂移的情况,后来加了滤波电容就好了。

如果OLED不显示,检查I2C地址是否正确(通常是0x78或0x7A),还有上拉电阻是否接好。STM32的I2C引脚需要外部上拉电阻,一般4.7kΩ就行。软件上,确保初始化顺序正确:先GPIO、再I2C、最后OLED。

8. 常见问题与解决方案

做这个项目可能会遇到几个典型问题。一是ADC读数跳动大,可能是电源噪声或传感器接触不良。解决方案是加滤波电容(100nF并联10μF),并在软件中做移动平均滤波。我通常取8-16次平均,效果不错。

二是水位深度计算不准,除了校准问题,还可能是传感器放置位置不当。传感器应该垂直安装,避免气泡影响读数。如果是透明容器,注意避开强光直射,以免光敏效应干扰。

三是中断频繁触发导致系统卡顿。ADC转换速度不要设得太快,一般1kHz采样率就够用水位检测了。如果还需要处理其他中断,合理设置优先级,避免冲突。

最后提醒一下,Water Sensor模块长期浸水可能氧化,影响寿命。如果是长期项目,可以考虑不锈钢电极的传感器,或者定期更换模块。我的智能花盆项目运行半年后读数开始漂移,换新传感器后就恢复正常了。

更多推荐