news 2026/4/19 15:40:26

STM32实战:从零构建土壤湿度监测系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32实战:从零构建土壤湿度监测系统

1. 项目背景与硬件选型

第一次接触土壤湿度监测是在去年帮朋友改造智能花盆的时候。当时市面上成品监测模块动辄几百元,而用STM32+传感器方案成本不到50元。这种DIY方案不仅便宜,还能灵活适配各种场景,比如家庭绿植、阳台菜园或是小型农业实验。

核心硬件只需要三样:STM32开发板、土壤湿度传感器和几根杜邦线。我用的是一款常见的STM32F103C8T6最小系统板,价格20元左右,性能足够处理传感器数据。传感器方面推荐YL-69FC-28,这两个都是经典款,带模拟量(AO)和数字量(DO)双输出,防水探头设计,某宝单价不到10块钱。

这里有个选购避坑经验:一定要确认传感器输出类型。早期我买过一款只有DO输出的传感器,结果只能判断"干/湿"二值状态,无法获取具体湿度百分比。后来换的YL-69模块就实用多了——AO输出0-3.3V模拟电压对应湿度变化,DO输出则可通过旋钮调节触发阈值,两种模式配合使用既灵活又可靠。

2. 硬件连接与电路设计

实际接线比想象中简单得多,但新手常犯两个错误:一是电源接反烧毁传感器,二是模拟信号线没接对导致读数异常。正确的连接方式应该是:

  • 传感器VCC接开发板5V引脚(注意不是3.3V!)
  • GND对GND
  • AO接STM32的PA5(或其他ADC通道引脚)
  • DO可接任意GPIO,我习惯用PA0

遇到过最头疼的问题是电源干扰。有次测试时读数总是跳变,后来发现是开发板USB供电不稳。解决方法有两个:要么给传感器单独供电,要么在VCC和GND之间加个100μF的滤波电容。实测下来,第二种方案成本最低效果也好,电容价格不到1毛钱。

3. ADC采集与校准实战

STM32的ADC模块用起来简单,但要获得稳定读数需要点技巧。先看初始化代码关键点:

void ADC1_Init(void) { // 时钟使能部分不能少! RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 必须设为模拟输入 GPIO_Init(GPIOA, &GPIO_InitStructure); ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换模式 ADC_InitStructure.ADC_NbrOfChannel = 1; // 单通道 ADC_Init(ADC1, &ADC_InitStructure); // 校准步骤千万不能省! ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); }

实际采集时建议用多次采样取平均的方法。我封装了个实用函数:

float Get_SoilHumidity(uint8_t times) { uint32_t sum = 0; for(uint8_t i=0; i<times; i++) { sum += Get_Adc(ADC_Channel_5); // PA5对应Channel5 Delay_ms(5); // 适当延时 } float voltage = (sum/times) * (3.3f/4096); // 转电压值 return (100 - (voltage/3.3)*100); // 转百分比 }

注意两个细节:一是STM32F103的ADC是12位精度(0-4095),二是土壤越湿输出电压越低,所以要做个100减的操作。

4. 数字信号处理技巧

DO口的处理看似简单,但直接读取会有抖动问题。我的解决方案是加状态检测和软件防抖:

#define DRY_THRESHOLD 800 // 自定义干燥阈值 uint8_t Check_SoilStatus(void) { static uint32_t last_change = 0; static uint8_t last_state = 0; // 硬件防抖:连续5次检测相同才认为有效 uint8_t stable_cnt = 0; for(uint8_t i=0; i<5; i++) { if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)) stable_cnt++; Delay_ms(10); } uint8_t current = (stable_cnt >= 3) ? 1 : 0; if(current != last_state) { if(HAL_GetTick() - last_change > 200) { // 200ms内状态不变才更新 last_state = current; last_change = HAL_GetTick(); } } return last_state; }

这个方案比简单延时防抖更可靠,我在多个项目中验证过稳定性。阈值DRY_THRESHOLD需要根据实际土壤类型调整,建议先用ADC采集不同状态下的数值,记录几个关键点:

  • 完全干燥时的读数(比如我的盆栽土干燥时ADC值约950)
  • 浇水后的读数(同一盆土浇水后约200)
  • 理想湿度时的读数(多数植物适宜在400-600之间)

5. 数据优化与滤波算法

原始ADC数据会有波动,分享几种实测有效的滤波方法:

移动平均法最简单实用:

#define FILTER_LEN 10 float filter_buf[FILTER_LEN]; float Moving_Average(float new_val) { static uint8_t index = 0; filter_buf[index++] = new_val; if(index >= FILTER_LEN) index = 0; float sum = 0; for(uint8_t i=0; i<FILTER_LEN; i++) { sum += filter_buf[i]; } return sum/FILTER_LEN; }

中值滤波抗干扰更强:

float Median_Filter(float new_val) { static float buffer[5] = {0}; static uint8_t count = 0; buffer[count++] = new_val; if(count >= 5) count = 0; float temp[5]; memcpy(temp, buffer, sizeof(temp)); // 冒泡排序 for(uint8_t i=0; i<4; i++) { for(uint8_t j=i+1; j<5; j++) { if(temp[i] > temp[j]) { float swap = temp[i]; temp[i] = temp[j]; temp[j] = swap; } } } return temp[2]; // 取中值 }

对于需要快速响应的场景,推荐一阶滞后滤波

float FirstOrder_Filter(float new_val) { static float last = 0; last = 0.2*new_val + 0.8*last; // 系数可调 return last; }

6. 实用功能扩展

基础功能实现后,可以增加这些实用特性:

阈值报警功能

void Check_Humidity_Alert(float humidity) { if(humidity < 30.0f) { Buzzer_On(); // 触发蜂鸣器 LED_Blink(200); // LED快闪 } else if(humidity > 80.0f) { LED_Blink(1000); // LED慢闪 } else { Buzzer_Off(); LED_On(); // 正常状态常亮 } }

自动浇水控制(需接继电器):

void Auto_Watering(float humidity) { static uint32_t last_water = 0; if(humidity < 40.0f && (HAL_GetTick()-last_water)>3600000) { Relay_On(); // 开启水泵 Delay_ms(5000); // 浇水5秒 Relay_Off(); last_water = HAL_GetTick(); } }

数据记录功能(配合EEPROM):

typedef struct { float humidity[24]; // 24小时数据 uint8_t index; } Log_TypeDef; void Save_Humidity_Log(float val) { Log_TypeDef log; EE_ReadBytes(0, (uint8_t*)&log, sizeof(log)); log.humidity[log.index++] = val; if(log.index >= 24) log.index = 0; EE_WriteBytes(0, (uint8_t*)&log, sizeof(log)); }

7. 常见问题排查

遇到过最诡异的问题是传感器读数始终为0,排查过程很有代表性:

  1. 首先检查硬件连接,发现VCC和GND接反了(教训:彩色杜邦线不一定可靠)
  2. 修正后读数固定在1023,测量AO引脚发现电压始终3.3V
  3. 更换传感器后正常,确认是传感器内部电路损坏
  4. 后来发现是焊接时没断电,静电击穿了传感器

其他典型问题及解决方案:

  • 读数跳动大:尝试加大滤波系数,或在传感器电源端并联0.1μF电容
  • 响应延迟:检查是否在循环中加了不必要延时,建议用定时器中断采样
  • 数值不准:用万用表测量AO输出电压,对比ADC读数,校准分压电阻
  • DO不变化:调节传感器上的蓝色电位器,用螺丝刀旋转直到指示灯状态变化

8. 低功耗优化方案

对于电池供电的场景,这几个技巧能大幅延长续航:

  1. 将STM32设为睡眠模式,用定时器唤醒(配置RTC唤醒间隔)
void Enter_StopMode(uint32_t sec) { RTC_SetAlarm(sec); // 设置唤醒时间 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); SystemInit(); // 唤醒后需重新初始化时钟 }
  1. 传感器供电改用GPIO控制,采样时才上电
#define SENSOR_PWR_PIN GPIO_Pin_1 void Sensor_Power(uint8_t state) { GPIO_WriteBit(GPIOA, SENSOR_PWR_PIN, (BitAction)state); if(state) Delay_ms(100); // 等待电源稳定 }
  1. 降低ADC采样频率(调整ADC_SampleTime参数)
  2. 关闭调试接口和不用的外设时钟

实测下来,1分钟采集1次的情况下,800mAh的锂电池可以连续工作3个月以上。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/19 15:35:28

保姆级教程:用Python的Scipy库搞定基因表达数据的层次聚类与热图绘制

基因表达数据分析实战&#xff1a;从矩阵到热图的层次聚类全流程解析 在生物信息学研究中&#xff0c;基因表达数据的聚类分析是揭示基因功能关系和样本分类模式的基础工具。想象一下&#xff0c;当你面对数千个基因在数十个样本中的表达量矩阵时&#xff0c;如何快速识别出具有…

作者头像 李华
网站建设 2026/4/19 15:34:55

《最强大脑》项目全解析:这些烧脑游戏背后的数学原理与开源实现(附资源链接)

《最强大脑》项目全解析&#xff1a;烧脑游戏背后的数学原理与开源实现 当电视荧幕上的选手在《最强大脑》节目中完成一个个看似不可能完成的挑战时&#xff0c;屏幕前的观众往往既惊叹又困惑。这些令人眼花缭乱的游戏背后&#xff0c;其实隐藏着深厚的数学原理和计算机科学基础…

作者头像 李华
网站建设 2026/4/19 15:27:40

AGI物流决策引擎实测对比:传统TMS vs. 类脑调度系统,响应延迟下降83%,成本优化率达19.4%——数据来自顺丰、菜鸟闭门测试

第一章&#xff1a;2026奇点智能技术大会&#xff1a;AGI与物流管理 2026奇点智能技术大会(https://ml-summit.org) AGI驱动的物流决策中枢 在2026奇点智能技术大会上&#xff0c;多家头部物流企业联合发布了基于通用人工智能&#xff08;AGI&#xff09;架构的物流决策中枢v…

作者头像 李华
网站建设 2026/4/19 15:27:32

PC电源EMI滤波电路:从元件构成到高效设计实战解析

1. 为什么你的PC电源需要EMI滤波电路&#xff1f; 每次按下电脑开机键的瞬间&#xff0c;你有没有想过220V的交流电是如何变成主板需要的12V/5V直流电的&#xff1f;这个过程中最容易被忽视却至关重要的环节&#xff0c;就是EMI滤波电路。我拆解过上百款电源&#xff0c;发现很…

作者头像 李华