别再只读ADC值了!用STM32F103C8T6和土壤湿度传感器做个智能浇花提醒器(附完整代码)
从ADC数值到智能决策:STM32F103C8T6土壤湿度监测系统实战
清晨的阳光透过窗帘洒在窗台的绿植上,你是否曾因为忘记浇水而让心爱的植物枯萎?对于嵌入式开发者和创客来说,将技术转化为解决实际生活问题的工具,正是最有成就感的时刻。本文将带你超越简单的ADC数值读取,用STM32F103C8T6和土壤湿度传感器构建一个真正实用的智能浇花提醒系统。
1. 项目规划与硬件选型
1.1 为什么需要智能浇花提醒
传统浇花方式要么依赖记忆,要么使用固定时间浇水——这两种方法都不够科学。植物实际需要的水分受多种因素影响:
- 环境温度变化导致水分蒸发速度不同
- 不同季节光照强度差异
- 植物种类对水分的需求差异
- 盆土材质和排水性能
基于土壤湿度传感器的实时监测系统可以解决这些问题,让植物获得恰到好处的水分。
1.2 硬件组件清单
| 组件 | 型号 | 备注 |
|---|---|---|
| 主控芯片 | STM32F103C8T6 | 蓝色pill开发板 |
| 土壤湿度传感器 | FC-28 | 带模拟和数字输出 |
| 显示模块 | OLED 0.96寸 | 可选,用于本地状态显示 |
| 报警模块 | 有源蜂鸣器+LED | 声音+视觉提醒 |
| 电源 | 5V USB或3.7V锂电池 | 根据移动性需求选择 |
关键选择考量:FC-28传感器虽然价格低廉,但其电极易氧化。对于长期项目,建议考虑镀金电极的专业型号,或定期更换传感器。
2. 传感器接口与ADC配置
2.1 传感器工作原理深度解析
FC-28土壤湿度传感器通过测量土壤的电导率来间接反映湿度水平。其核心是一个电压分压电路:
Vcc ────[R1]───┬───[Rsoil]─── GND
│
Vout
其中R1是固定电阻,Rsoil随湿度变化的土壤电阻。当土壤湿润时,Rsoil减小,Vout降低;干燥时则相反。
2.2 STM32 ADC配置优化
原始代码中的ADC配置可以进一步优化,以下是改进后的初始化函数:
void ADC_Init_Optimized(void) {
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div8); // 9MHz采样时钟
ADC_DeInit(ADC1);
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);
// 启用DMA功能
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
// 校准过程
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
// 启用温度传感器和Vrefint通道
ADC_TempSensorVrefintCmd(ENABLE);
}
提示:使用DMA可以显著降低CPU负载,特别适合需要连续采样或同时处理多通道的应用场景。
2.3 多采样与滤波算法
原始代码采用了简单的多次采样取平均,我们可以实现更高级的滤波:
#define SAMPLE_COUNT 16
uint16_t filteredADCRead(uint8_t channel) {
uint16_t samples[SAMPLE_COUNT];
uint32_t sum = 0;
// 采集样本
for(int i=0; i<SAMPLE_COUNT; i++) {
samples[i] = Get_ADC2(channel);
delay_ms(1);
}
// 排序用于中值滤波
bubbleSort(samples, SAMPLE_COUNT);
// 去掉最高和最低的25%样本
for(int i=SAMPLE_COUNT/4; i<3*SAMPLE_COUNT/4; i++) {
sum += samples[i];
}
return sum / (SAMPLE_COUNT/2);
}
这种混合了中值滤波和截尾均值的方法能有效抑制突发干扰。
3. 从原始数据到实用指标
3.1 校准与标定方法
ADC原始值到实际湿度百分比的转换需要校准。建议采用两点校准法:
- 将传感器完全浸入水中,记录ADC值(对应100%湿度)
- 将传感器置于完全干燥环境中,记录ADC值(对应0%湿度)
- 在实际土壤中测试,建立更精确的转换曲线
校准数据可以存储在STM32的Flash中:
typedef struct {
uint16_t dryValue;
uint16_t wetValue;
uint8_t plantType;
} CalibrationData;
void saveCalibration(const CalibrationData* data) {
FLASH_Unlock();
FLASH_ErasePage(0x0800FC00);
FLASH_ProgramHalfWord(0x0800FC00, data->dryValue);
FLASH_ProgramHalfWord(0x0800FC02, data->wetValue);
FLASH_ProgramByte(0x0800FC04, data->plantType);
FLASH_Lock();
}
3.2 植物类型与湿度阈值
不同植物对土壤湿度的需求差异很大:
| 植物类型 | 理想湿度范围 | 报警阈值 |
|---|---|---|
| 多肉植物 | 30%-50% | <25%或>55% |
| 观叶植物 | 40%-70% | <30%或>75% |
| 兰花类 | 20%-40% | <15%或>45% |
| 蔬菜类 | 50%-80% | <40%或>85% |
在代码中实现动态阈值调整:
typedef enum {
PLANT_SUCCULENT,
PLANT_FOLIAGE,
PLANT_ORCHID,
PLANT_VEGETABLE
} PlantType;
uint8_t checkHumidityStatus(uint8_t humidity, PlantType type) {
switch(type) {
case PLANT_SUCCULENT:
if(humidity < 25) return TOO_DRY;
if(humidity > 55) return TOO_WET;
break;
case PLANT_FOLIAGE:
if(humidity < 30) return TOO_DRY;
if(humidity > 75) return TOO_WET;
break;
// 其他类型...
}
return NORMAL;
}
4. 系统集成与功能扩展
4.1 多模态提醒系统
除了串口输出,我们可以添加更直观的提醒方式:
void alertSystem(uint8_t status) {
static uint32_t lastAlertTime = 0;
if(HAL_GetTick() - lastAlertTime < 60000) return; // 1分钟内不重复提醒
switch(status) {
case TOO_DRY:
Buzzer_Beep(200, 3); // 短促蜂鸣
LED_Blink(LED_RED, 500, 5);
OLED_ShowString(0, 0, "WATER ME!", 16);
break;
case TOO_WET:
Buzzer_Beep(1000, 1); // 长蜂鸣
LED_Blink(LED_BLUE, 1000, 3);
OLED_ShowString(0, 0, "TOO WET!", 16);
break;
}
lastAlertTime = HAL_GetTick();
}
4.2 数据记录与趋势分析
添加简单的数据记录功能,帮助了解植物的水分变化规律:
#define HISTORY_SIZE 24
typedef struct {
uint8_t humidity[HISTORY_SIZE];
uint8_t index;
} HistoryBuffer;
void updateHistory(HistoryBuffer* buffer, uint8_t newValue) {
buffer->humidity[buffer->index] = newValue;
buffer->index = (buffer->index + 1) % HISTORY_SIZE;
}
void displayTrend(HistoryBuffer* buffer) {
uint8_t min = 100, max = 0, avg = 0;
for(int i=0; i<HISTORY_SIZE; i++) {
if(buffer->humidity[i] < min) min = buffer->humidity[i];
if(buffer->humidity[i] > max) max = buffer->humidity[i];
avg += buffer->humidity[i];
}
avg /= HISTORY_SIZE;
OLED_ShowString(0, 16, "Min:", 12);
OLED_ShowNum(30, 16, min, 2, 12);
// 显示其他统计信息...
}
4.3 低功耗优化技巧
对于电池供电的应用,功耗优化至关重要:
-
ADC采样优化:
- 降低采样频率(如每10分钟采样一次)
- 使用单次转换模式替代连续转换
- 采样后立即关闭ADC电源
-
系统睡眠模式:
void enterSleepMode(uint32_t sleepTime) {
RTC_SetAlarm(RTC_GetCounter() + sleepTime);
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
SystemInit(); // 唤醒后需要重新初始化系统时钟
}
- 外设电源管理:
- 不使用时关闭OLED背光
- 使用MOSFET控制传感器电源
5. 项目进阶方向
5.1 无线连接与云端监控
通过ESP-01模块添加WiFi功能,将数据上传到物联网平台:
void sendToCloud(uint8_t humidity) {
char cmd[128];
sprintf(cmd, "AT+CIPSTART=\"TCP\",\"api.thingspeak.com\",80");
ESP8266_SendCmd(cmd);
sprintf(cmd, "GET /update?api_key=YOUR_KEY&field1=%d", humidity);
sprintf(cmd+strlen(cmd), " HTTP/1.1\r\nHost: api.thingspeak.com\r\n\r\n");
ESP8266_SendData(cmd);
}
5.2 自动灌溉系统集成
在提醒系统基础上,可以扩展为自动灌溉系统:
void controlWaterPump(uint8_t humidity, PlantType type) {
static uint32_t lastWatering = 0;
if(humidity < getDryThreshold(type) &&
HAL_GetTick() - lastWatering > MIN_WATERING_INTERVAL) {
GPIO_SetBits(PUMP_PORT, PUMP_PIN);
delay_ms(WATERING_DURATION);
GPIO_ResetBits(PUMP_PORT, PUMP_PIN);
lastWatering = HAL_GetTick();
}
}
注意:自动灌溉需要谨慎设计,建议保留手动确认环节,避免故障导致过度浇水。
5.3 多传感器数据融合
结合其他传感器提升系统可靠性:
- 温度传感器:修正湿度读数(温度高时蒸发快)
- 光照传感器:预测水分消耗速度
- 重量传感器:检测实际浇水量
typedef struct {
uint8_t soilHumidity;
float temperature;
uint16_t lightIntensity;
uint16_t weight;
} SensorData;
uint8_t calculateRealHumidity(SensorData data) {
// 考虑温度影响的修正
float tempFactor = 1.0 + (data.temperature - 25.0) * 0.02;
uint8_t adjustedHumidity = data.soilHumidity * tempFactor;
// 考虑光照影响的修正
uint16_t lightFactor = 1000 / (1000 + data.lightIntensity/10);
adjustedHumidity = adjustedHumidity * lightFactor / 100;
return adjustedHumidity > 100 ? 100 : adjustedHumidity;
}
在项目开发过程中,我发现传感器的长期稳定性是关键挑战。最初使用的廉价传感器在两周后读数就开始漂移,后来改用工业级传感器并定期校准才解决问题。另一个实用技巧是在花盆底部加装湿度传感器,因为表层土壤干燥速度通常比深层快得多,底部读数更能反映植物根部的实际水分状况。
更多推荐



所有评论(0)