前言
本篇基于 STM32F103C8T6 最小系统板,搭配电阻式土壤湿度传感器、I2C‑OLED 屏幕,实现土壤湿度采集、ADC 滤波、两点标定、OLED 屏幕数据显示。可以看到 ADC 原始值、引脚电压、相对湿度百分比、土壤干湿状态,适合单片机入门教学、课程实验。
硬件清单
- STM32F103C8T6 最小系统板
- 电阻式土壤湿度检测模块
- 0.96 寸 I2C OLED 显示屏
- 杜邦线若干
一、整体实现原理
土壤含水量发生改变,会直接改变土壤导电能力;土壤湿度模块把导电能力变化转化为 AO 模拟输出电压。 STM32 芯片通过 ADC 外设读取该模拟电压,经过多次采样平均滤波消除噪声,再通过两点标定算法换算出相对湿度,最终把各项数据输出到 OLED 屏幕上展示。
完整业务流程: 土壤含水情况发生变化 → 探针导电能力改变 → 模块输出 AO 模拟电压 → STM32 PA1 引脚 ADC 采集 → 16 次平均滤波得到稳定 ADC 数值 → 通过干湿标定点计算相对湿度 → OLED 显示 ADC、电压、湿度、干湿状态。

二、硬件接线说明
⚠️重点提醒:所有器件必须共地!传感器 GND、STM32 GND、OLED GND 接在一起。如果不共地,ADC 读数乱跳、数值固定不变,测量完全失效。
| 模块引脚 | STM32F103C8T6 引脚 | 功能说明 |
|---|---|---|
| 土壤湿度模块 VCC | 3.3V | 传感器供电 |
| 土壤湿度模块 AO | PA1 | 模拟信号输出,ADC1 通道 1 采集 |
| 土壤湿度模块 GND | GND | 公共参考地 |
| OLED VCC | 5V | OLED 屏幕供电 |
| OLED SCL | PB8 | I2C 时钟(软件 I2C) |
| OLED SDA | PB9 | I2C 数据(软件 I2C) |
| OLED GND | GND | 公共参考地 |
接线注意点
- STM32F103 ADC 输入最大电压为 3.3V,本项目传感器使用 3.3V 供电,AO 输出不会超过 3.3V,可以直接接 PA1。如果传感器改成 5V 供电,AO 输出有可能超过 3.3V,必须增加分压限压电路,严禁直接接入 STM32 ADC 引脚。
- 本实验没有使用模块 DO 数字输出引脚。DO 引脚是比较器输出,只能输出高低电平,仅判断是否超过电位器阈值,无法获取连续湿度变化;AO 模拟输出可以采集连续变化电压,适合学习 ADC 采集、滤波、标定算法。
三、电阻式土壤湿度模块工作原理
电阻式土壤湿度传感器依靠两根金属探针工作。探针插入土壤之后,土壤充当导电介质。
- 土壤干燥:水分少,导电离子少,探针之间等效电阻大;
- 土壤湿润:水分充足,导电离子变多,探针之间等效电阻变小。
模块内部分压电路,会把电阻变化转换成 AO 引脚输出电压。市面上大部分电阻式土壤湿度模块规律:土壤越湿,AO 输出电压越低,ADC 采样数值越小。

⚠️注意:不同厂家模块电路设计不一样,也存在越湿 ADC 值越大的模块。本套程序兼容两种情况,只要正确填写干燥、湿润两组标定 ADC 数值即可。
重要知识点:电阻式传感器检测本质是土壤导电能力,不是直接测量土壤含水量。土壤盐分、肥料浓度、土壤紧实度、探针接触松紧,都会干扰读数。必须经过标定,才可以得到可用的相对湿度。
四、STM32 ADC 采集原理
STM32F103C8T6 内置 12 位 ADC,PA1 引脚对应 ADC1 通道 1。 输入电压范围:0 ~ 3.3V;ADC 转换输出数字范围:0 ~ 4095。
电压计算公式: 引脚电压(V) = ADC原始值 × 3.3 ÷ 4095
工程代码内部转换为毫伏计算: voltage_mv = adc_value * 3300 / 4095;
| PA1 电压 | 对应 ADC 数值 |
|---|---|
| 0V | 0 |
| 0.60V | 745 |
| 1.65V | 2048 |
| 2.40V | 2978 |
| 3.30V | 4095 |
ADC 初始化主要完成:开启 GPIOA、ADC1 时钟;PA1 配置模拟输入;ADC 时钟配置 12MHz;配置单通道单次软件触发转换;设置采样时间;执行 ADC 复位与自校准,丢弃第一次无效转换结果。
五、软件滤波处理
ADC 采样容易受到电源噪声、接触抖动干扰,如果单次采样直接显示,OLED 屏幕数字会不停跳动。 本工程采用16 次采样取算术平均值 的平均滤波算法。 平均ADC值 = 16次ADC采样总和 /16
滤波流程:软件触发 ADC 转换,循环读取 16 次 ADC 结果,完成采样之后求平均值,换算电压,再计算湿度,最后刷新 OLED。
平均滤波优点:抑制随机噪声,显示数据稳定;缺点:会轻微降低响应速度,本项目主循环约 200ms 刷新一次屏幕,适合实验观察。
工程关键函数说明
| 源文件 | 函数名 | 功能 |
|---|---|---|
| TR_Detection.c | TR_Detection_Init() | 初始化 PA1、ADC1,执行 ADC 校准 |
| TR_Detection.c | TR_Detection_ReadAdc() | 单次读取 ADC 通道 1 原始数值 |
| TR_Detection.c | TR_Detection_ReadAdcAverage() | 16 次采样平均滤波 |
| TR_Detection.c | TR_Detection_ConvertToVoltageMv() | ADC 值转换成引脚电压(毫伏) |
| TR_Detection.c | TR_Detection_ConvertToMoisturePercent() | 两点标定换算相对湿度百分比 |
| TR_Detection.c | TR_Detection_ReadData() | 一次性读取 ADC、电压、湿度全套数据 |
| main.c | Demo_ShowSoilData() | OLED 屏幕打印全部测量数据和干湿状态 |
六、两点标定算法讲解
❗为什么一定要标定? 不同传感器、不同土质、探针插入深度不一样,同样湿度下 ADC 读数完全不同。不能直接拿 ADC0‑4095 直接当做湿度百分比,必须设置两个参考点。
两个标定参考点:
- dry_raw:干燥土壤对应的 ADC 值,对应湿度 0%
- wet_raw:充分湿润土壤对应的 ADC 值,对应湿度 100%
常用模块(湿度越大 ADC 数值越小)计算公式: 相对湿度 = (干燥ADC值 - 当前ADC值) ×100 ÷ (干燥ADC值 - 湿润ADC值)
同时程序会对输出做限制:结果强制限定在 0~100%。
- 当前 ADC ≥干燥 ADC →湿度强制 0%
- 当前 ADC ≤湿润 ADC →湿度强制 100%
- 中间区间做线性换算
举个例子:干燥 ADC=3000,湿润 ADC=1500,当前 ADC=2250 (3000‑2250)*100/(3000‑1500)=50%
工程头文件默认标定参数:
cpp
#define TR_DETECTION_DRY_ADC 4095u
#define TR_DETECTION_WET_ADC 0u
默认参数只是 ADC 全量程,仅用于程序跑通观察变化趋势,不是真实土壤标定结果。
实操标定步骤
- 探针擦拭干净,插入干燥待测土壤,保持插入深度固定;
- 等待 OLED 上 ADC 读数稳定,记录数值作为 dry_raw;
- 向土壤均匀加水,不要把水淋到传感器电路板;
- 等待水分扩散,读数稳定,记录此时 ADC 值作为 wet_raw;
- 修改头文件标定宏定义,重新编译下载;
- 再次测试,观察湿度百分比随土壤干湿变化。
也可以代码运行时动态设置标定参数:
cpp
TR_Detection_Calibration calibration;
calibration.dry_raw = 3000u;
calibration.wet_raw = 1500u;
TR_Detection_SetCalibration(&calibration);
标定注意:标定必须使用你实际测量的土壤,探针插入深度、压紧程度前后保持一致。
七、OLED 显示逻辑与干湿状态判断
屏幕输出示例:
cpp
SOIL MOISTURE
ADC:2250/4095
PIN:1.813 V
MOIST:050 %
STATE:NORMAL
显示字段含义:
| 显示内容 | 说明 |
|---|---|
| ADC | 滤波之后 ADC 平均值,范围 0‑4095 |
| PIN | PA1 引脚计算得到模拟电压 |
| MOIST | 两点标定计算得到相对湿度百分比 |
| STATE | 土壤干湿状态 |
状态阈值逻辑:
- MOIST<30% → STATE:DRY(干燥)
- 30% ≤ MOIST<70% → STATE:NORMAL(正常)
- MOIST ≥70% → STATE:WET(潮湿)
30%、70% 只是实验演示阈值,不是植物浇水标准,实际项目根据植物需求修改阈值。
八、源代码章节
包含工程关键源码,如 TR_Detection.c、TR_Detection.h、main.c 核心片段。
main.c代码
cpp
/*
* STM32F103C8T6 soil-moisture teaching demonstration
*
* TR_Detection AO -> PA1 (ADC1 channel 1)
* OLED SCL -> PB8
* OLED SDA -> PB9
*/
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "TR_Detection.h"
#define DISPLAY_UPDATE_MS 200u
static void Demo_ShowStartup(void);
static void Demo_ShowSoilData(const TR_Detection_Data *data);
static u8 *Demo_GetMoistureText(u8 moisture_percent);
int main(void)
{
TR_Detection_Data soil_data;
delay_init();
NVIC_Configuration();
OLED_Init();
OLED_ColorTurn(0u);
OLED_DisplayTurn(0u);
Demo_ShowStartup();
TR_Detection_Init();
delay_ms(500u);
while (1)
{
if (TR_Detection_ReadData(&soil_data) == TR_DETECTION_OK)
{
Demo_ShowSoilData(&soil_data);
}
delay_ms(DISPLAY_UPDATE_MS);
}
}
static void Demo_ShowStartup(void)
{
OLED_Clear();
OLED_ShowString(0u, 0u, (u8 *)"SOIL MOISTURE", 16u);
OLED_ShowString(0u, 20u, (u8 *)"AO: PA1 / ADC1", 12u);
OLED_ShowString(0u, 36u, (u8 *)"OLED: PB8 PB9", 12u);
OLED_ShowString(0u, 52u, (u8 *)"INITIALIZING...", 12u);
OLED_Refresh();
}
static void Demo_ShowSoilData(const TR_Detection_Data *data)
{
OLED_Clear();
OLED_ShowString(0u, 0u, (u8 *)"SOIL MOISTURE", 12u);
OLED_ShowString(0u, 13u, (u8 *)"ADC:", 12u);
OLED_ShowNum(30u, 13u, data->raw, 4u, 12u);
OLED_ShowString(60u, 13u, (u8 *)"/4095", 12u);
OLED_ShowString(0u, 26u, (u8 *)"PIN:", 12u);
OLED_ShowNum(30u, 26u, data->voltage_mv / 1000u, 1u, 12u);
OLED_ShowString(36u, 26u, (u8 *)".", 12u);
OLED_ShowNum(42u, 26u, data->voltage_mv % 1000u, 3u, 12u);
OLED_ShowString(60u, 26u, (u8 *)" V", 12u);
OLED_ShowString(0u, 39u, (u8 *)"MOIST:", 12u);
OLED_ShowNum(42u, 39u, data->moisture_percent, 3u, 12u);
OLED_ShowString(60u, 39u, (u8 *)" %", 12u);
OLED_ShowString(0u, 52u, (u8 *)"STATE:", 12u);
OLED_ShowString(42u, 52u,
Demo_GetMoistureText(data->moisture_percent), 12u);
OLED_Refresh();
}
static u8 *Demo_GetMoistureText(u8 moisture_percent)
{
if (moisture_percent < 30u)
{
return (u8 *)"DRY";
}
if (moisture_percent < 70u)
{
return (u8 *)"NORMAL";
}
return (u8 *)"WET";
}
TR_Detection.c代码
cpp
/**
* @file TR_Detection.c
* @brief STM32F103C8T6 土壤湿度检测模块驱动实现。
* @note 文件编码:UTF-8。
*/
#include "TR_Detection.h"
#include "stm32f10x_adc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
/** STM32F103 内部 ADC 为 12 位,最大转换值为 4095。 */
#define TR_DETECTION_ADC_MAX 4095u
/** ADC 参考电压,单位为毫伏;本工程模拟电源为 3.3V。 */
#define TR_DETECTION_REFERENCE_MV 3300u
/** 当前使用的干燥、湿润两点标定参数。 */
static TR_Detection_Calibration g_calibration =
{
TR_DETECTION_DRY_ADC,
TR_DETECTION_WET_ADC
};
/**
* @brief 初始化土壤湿度传感器的模拟量采集接口。
*
* PA1 被配置为模拟输入,ADC1 工作在单次、独立转换模式。初始化末尾执行
* STM32F1 所需的复位校准和校准,并丢弃校准后的第一次转换结果。
*/
void TR_Detection_Init(void)
{
GPIO_InitTypeDef gpio_init;
ADC_InitTypeDef adc_init;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_ADC1, ENABLE);
/* 本工程 PCLK2 为 72MHz,六分频后 ADC 时钟为 12MHz,不超过 14MHz。 */
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
/* PA1 对应 ADC1_IN1,模拟输入模式会关闭数字输入输出电路。 */
gpio_init.GPIO_Pin = GPIO_Pin_1;
gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
gpio_init.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &gpio_init);
/* 使用单通道、单次转换和软件触发,转换结果采用右对齐。 */
ADC_DeInit(ADC1);
adc_init.ADC_Mode = ADC_Mode_Independent;
adc_init.ADC_ScanConvMode = DISABLE;
adc_init.ADC_ContinuousConvMode = DISABLE;
adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
adc_init.ADC_DataAlign = ADC_DataAlign_Right;
adc_init.ADC_NbrOfChannel = 1u;
ADC_Init(ADC1, &adc_init);
/* 较长的采样时间有利于稳定采集土壤湿度模块的模拟输出。 */
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1u,
ADC_SampleTime_239Cycles5);
ADC_Cmd(ADC1, ENABLE);
/* STM32F1 ADC 上电后需要依次完成复位校准和启动校准。 */
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) != RESET)
{
}
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) != RESET)
{
}
/* 丢弃校准后的第一次转换结果,避免其影响后续测量。 */
(void)TR_Detection_ReadAdc();
}
/**
* @brief 读取一次 ADC1 通道 1 的转换结果。
* @return 12 位 ADC 原始值,范围为 0~4095。
*/
u16 TR_Detection_ReadAdc(void)
{
/* 清除上一次完成标志,然后通过软件启动一次规则组转换。 */
ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
/* 等待本次转换完成。 */
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET)
{
}
return ADC_GetConversionValue(ADC1);
}
/**
* @brief 对多次 ADC 采样结果进行算术平均滤波。
* @param times 采样次数。
* @return 四舍五入后的 ADC 平均值;采样次数为 0 时返回 0。
*/
u16 TR_Detection_ReadAdcAverage(u8 times)
{
u8 i;
u32 sum;
if (times == 0u)
{
return 0u;
}
sum = 0u;
for (i = 0u; i < times; i++)
{
sum += TR_Detection_ReadAdc();
}
/* 除法前加上除数的一半,使整数计算结果按四舍五入处理。 */
return (u16)((sum + ((u32)times / 2u)) / (u32)times);
}
/**
* @brief 将 ADC 原始值换算为 PA1 引脚电压。
* @param adc_value ADC 原始值。
* @return 引脚电压,单位为毫伏。
*/
u16 TR_Detection_ConvertToVoltageMv(u16 adc_value)
{
u32 raw;
/* 对输入值限幅,防止错误参数造成换算结果超过参考电压。 */
raw = adc_value;
if (raw > TR_DETECTION_ADC_MAX)
{
raw = TR_DETECTION_ADC_MAX;
}
/* 电压 = ADC值 × 参考电压 ÷ ADC满量程,并进行四舍五入。 */
return (u16)((raw * TR_DETECTION_REFERENCE_MV +
TR_DETECTION_ADC_MAX / 2u) / TR_DETECTION_ADC_MAX);
}
/**
* @brief 使用两点标定法将 ADC 值换算为相对土壤湿度。
* @param adc_value ADC 原始值。
* @return 相对湿度,范围为 0~100%。
*
* 函数同时兼容"湿度越高 ADC 值越小"和"湿度越高 ADC 值越大"的模块。
* 超出干燥、湿润标定区间的输入值会被限制为 0% 或 100%。
*/
u8 TR_Detection_ConvertToMoisturePercent(u16 adc_value)
{
u32 raw;
u32 span;
u32 position;
u32 percent;
u16 dry_raw;
u16 wet_raw;
dry_raw = g_calibration.dry_raw;
wet_raw = g_calibration.wet_raw;
raw = adc_value;
if (raw > TR_DETECTION_ADC_MAX)
{
raw = TR_DETECTION_ADC_MAX;
}
/* 常见电阻式模块在湿度升高时输出电压和 ADC 值降低。 */
if (dry_raw > wet_raw)
{
if (raw >= dry_raw)
{
return 0u;
}
if (raw <= wet_raw)
{
return 100u;
}
span = (u32)dry_raw - wet_raw;
position = (u32)dry_raw - raw;
}
else
{
/* 同时支持湿度升高时 ADC 值增大的反向输出模块。 */
if (raw <= dry_raw)
{
return 0u;
}
if (raw >= wet_raw)
{
return 100u;
}
span = (u32)wet_raw - dry_raw;
position = raw - dry_raw;
}
/* 在两个标定点之间进行线性插值,并对结果进行四舍五入。 */
percent = (position * 100u + span / 2u) / span;
return (u8)percent;
}
/**
* @brief 获取一次完整的土壤湿度测量数据。
* @param data 用于接收 ADC、电压和湿度结果的指针。
* @return 参数有效时返回成功,否则返回参数错误。
*/
u8 TR_Detection_ReadData(TR_Detection_Data *data)
{
if (data == 0)
{
return TR_DETECTION_ERROR_ARGUMENT;
}
data->raw = TR_Detection_ReadAdcAverage(TR_DETECTION_SAMPLE_COUNT);
data->voltage_mv = TR_Detection_ConvertToVoltageMv(data->raw);
data->moisture_percent =
TR_Detection_ConvertToMoisturePercent(data->raw);
return TR_DETECTION_OK;
}
/**
* @brief 设置运行时两点标定参数。
* @param calibration 干燥点和湿润点标定值。
* @return 标定值有效时返回成功,否则返回参数错误。
*/
u8 TR_Detection_SetCalibration(const TR_Detection_Calibration *calibration)
{
/* 两个标定值必须位于 ADC 量程内,并且不能相等。 */
if ((calibration == 0) ||
(calibration->dry_raw > TR_DETECTION_ADC_MAX) ||
(calibration->wet_raw > TR_DETECTION_ADC_MAX) ||
(calibration->dry_raw == calibration->wet_raw))
{
return TR_DETECTION_ERROR_ARGUMENT;
}
g_calibration = *calibration;
return TR_DETECTION_OK;
}
/**
* @brief 读取当前两点标定参数。
* @param calibration 用于接收标定参数的指针。
*/
void TR_Detection_GetCalibration(TR_Detection_Calibration *calibration)
{
if (calibration != 0)
{
*calibration = g_calibration;
}
}
TR_Detection.h代码
cpp
#ifndef __TR_DETECTION_H
#define __TR_DETECTION_H
/**
* @file TR_Detection.h
* @brief 土壤湿度检测模块驱动接口。
* @note 文件编码:UTF-8。
*
* 硬件连接:
* - 土壤湿度模块 AO 接 PA1,对应 ADC1 通道 1;
* - 土壤湿度模块 VCC 接 3.3V,GND 接系统地。
*
* 湿度百分比由干燥、湿润两个参考点线性换算得到。不同传感器及不同
* 土壤环境的输出存在差异,正式使用前应记录实际干燥值和湿润值,修改
* TR_DETECTION_DRY_ADC、TR_DETECTION_WET_ADC,或调用运行时标定接口。
*/
#include "stm32f10x.h"
/** 干燥参考点 ADC 值,对应相对湿度 0%。 */
#define TR_DETECTION_DRY_ADC 4095u
/** 湿润参考点 ADC 值,对应相对湿度 100%。 */
#define TR_DETECTION_WET_ADC 0u
/** 每次读取数据时的 ADC 平均采样次数。 */
#define TR_DETECTION_SAMPLE_COUNT 16u
/** 接口执行成功。 */
#define TR_DETECTION_OK 0u
/** 接口参数无效。 */
#define TR_DETECTION_ERROR_ARGUMENT 1u
/** 土壤湿度两点标定参数。 */
typedef struct
{
u16 dry_raw; /**< 干燥参考点 ADC 值,对应 0%。 */
u16 wet_raw; /**< 湿润参考点 ADC 值,对应 100%。 */
} TR_Detection_Calibration;
/** 一次完整的土壤湿度测量结果。 */
typedef struct
{
u16 raw; /**< 经过平均滤波的 12 位 ADC 原始值。 */
u16 voltage_mv; /**< PA1 引脚电压,单位为毫伏。 */
u8 moisture_percent; /**< 标定后的相对湿度,范围为 0~100%。 */
} TR_Detection_Data;
/** 初始化 PA1、ADC1,并执行 ADC 校准。 */
void TR_Detection_Init(void);
/** 启动一次 ADC 转换并返回 12 位原始值。 */
u16 TR_Detection_ReadAdc(void);
/**
* 连续读取 ADC 并返回平均值。
* @param times 采样次数;传入 0 时直接返回 0。
* @return 四舍五入后的 ADC 平均值。
*/
u16 TR_Detection_ReadAdcAverage(u8 times);
/**
* 将 ADC 原始值换算为 PA1 引脚电压。
* @param adc_value ADC 原始值。
* @return 引脚电压,单位为毫伏。
*/
u16 TR_Detection_ConvertToVoltageMv(u16 adc_value);
/**
* 根据当前两点标定参数换算相对湿度。
* @param adc_value ADC 原始值。
* @return 相对湿度,范围为 0~100%。
*/
u8 TR_Detection_ConvertToMoisturePercent(u16 adc_value);
/**
* 完成平均采样、电压换算和湿度换算。
* @param data 用于接收测量结果的非空指针。
* @return TR_DETECTION_OK 或 TR_DETECTION_ERROR_ARGUMENT。
*/
u8 TR_Detection_ReadData(TR_Detection_Data *data);
/**
* 设置运行时干燥、湿润标定值。
* @param calibration 标定参数;两点必须不同且不能超过 4095。
* @return TR_DETECTION_OK 或 TR_DETECTION_ERROR_ARGUMENT。
*/
u8 TR_Detection_SetCalibration(const TR_Detection_Calibration *calibration);
/**
* 获取当前使用的标定参数。
* @param calibration 用于接收标定参数的指针;空指针不会执行操作。
*/
void TR_Detection_GetCalibration(TR_Detection_Calibration *calibration);
#endif
九、实物演示章节
实验准备实物
STM32 最小系统板、土壤湿度模块、OLED 显示屏、杜邦线、干土、清水、容器。
演示操作步骤
- 按照上面接线表完成接线,再次确认全部 GND 共地,检查传感器供电为 3.3V,确认接线没有短路。
- 将程序编译下载到 STM32 开发板,上电,OLED 屏幕成功点亮,输出土壤湿度相关数据。
- 场景 1:探针放置空气中 探针悬空,相当于干燥环境,可以观察 ADC 数值变大,电压升高,MOIST 湿度百分比变低,STATE 显示 DRY 干燥。
- 场景 2:探针插入干燥土壤 把探针插入干土,保持稳定,等待读数稳定,记录当前 ADC 数值,这就是干燥标定点。
- 场景 3:向土壤加水,模拟浇水 缓慢向土壤加水,不要打湿传感器电路板。等待水分渗透,观察 ADC 数值下降,电压降低,湿度百分比上升,状态从 DRY 切换 NORMAL,继续加水切换 WET。
- 标定效果演示 把上面记录干、湿两组 ADC 数值替换代码中标定点,重新烧录。再次干湿切换,可以看到湿度百分比可以精准落在 0‑100 区间跟随土壤变化。


现象异常排查
- OLED 不亮:检查 5V 供电、SCL PB8、SDA PB9 接线,确认 I2C 设备地址是否匹配。
- ADC 数值固定不变:检查 AO 引脚是否真正接到 PA1,确认共地,确认传感器 3.3V 供电正常。
- 数值不停乱跳:检查地线是否接牢,增加滤波电容,确认 16 次平均滤波代码正常运行。
十、传感器使用注意事项
- 探针腐蚀问题:电阻式探针长期通电会发生电解腐蚀。课堂短时实验可以持续上电;长期野外采集项目,建议测量瞬间才给传感器供电,采集完成立刻断电,延长探针寿命。
- 盐分肥料干扰:传感器测导电率,土壤施肥、含盐量高,相同含水量 ADC 读数会改变。更换土壤必须重新标定,不能直接复用旧标定参数。
- 探针插入影响:插入深度、土壤空隙、压紧程度都会改变测量结果,做对比实验,探针插入条件必须统一。
- 水分扩散需要时间:浇水之后不要立刻读数,等待水分在土壤扩散均匀。严禁水直接淋到传感器电路板。
- 百分比只是相对值:OLED 上面湿度是两点标定得到的相对百分比,不是专业设备测量的体积含水率。如果需要高精度土壤含水率,需要称重法或者专业土壤传感器。
十一、总结
电阻式土壤湿度传感器把土壤导电能力转换为模拟电压输出;STM32 通过 ADC 外设读取模拟信号,经过多次采样平均滤波抑制噪声,使用两点标定算法,把 ADC 原始值转换成相对湿度百分比,最终在 OLED 屏幕展示 ADC、电压、湿度和干湿状态。