从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原始值到实际湿度百分比的转换需要校准。建议采用两点校准法:

  1. 将传感器完全浸入水中,记录ADC值(对应100%湿度)
  2. 将传感器置于完全干燥环境中,记录ADC值(对应0%湿度)
  3. 在实际土壤中测试,建立更精确的转换曲线

校准数据可以存储在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 低功耗优化技巧

对于电池供电的应用,功耗优化至关重要:

  1. ADC采样优化

    • 降低采样频率(如每10分钟采样一次)
    • 使用单次转换模式替代连续转换
    • 采样后立即关闭ADC电源
  2. 系统睡眠模式

void enterSleepMode(uint32_t sleepTime) {
    RTC_SetAlarm(RTC_GetCounter() + sleepTime);
    PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
    SystemInit(); // 唤醒后需要重新初始化系统时钟
}
  1. 外设电源管理
    • 不使用时关闭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;
}

在项目开发过程中,我发现传感器的长期稳定性是关键挑战。最初使用的廉价传感器在两周后读数就开始漂移,后来改用工业级传感器并定期校准才解决问题。另一个实用技巧是在花盆底部加装湿度传感器,因为表层土壤干燥速度通常比深层快得多,底部读数更能反映植物根部的实际水分状况。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐