未连接 点击连接 STM32 串口
📟 手动输入: pH EC (μS/cm) 浏览器不支持 Web Serial 时的备用方案

--

电导率 μS/cm

--

土壤 pH 值

--

综合污染指数 CPI

--

系统时间

📸 上传玉米照片

AI 基于植被指数 (ExG/GLI/HSV) 自动识别生长阶段

三叶期 · 抽雄期 · 开花期 · 腊熟期
GROWTH STAGE

玉米生长阶段选择

AI 拍照自动识别,也可手动切换

DASHBOARD

实时监测数据

🔬

电导率 Conductivity

-- μS/cm

下限 --上限 --
⚡

微量元素状态

--

📋

阶段信息

当前阶段--
适宜 EC--
🎯

微量元素需求

下限: -- μS/cm

上限: -- μS/cm

📡

原始传感器

pH ADC: --

EC ADC: --

运行时间: -- s

📈

电导率趋势 (最近120条)

Hardware Gallery

📸 硬件实物照片

系统由 STM32 主控 + 双传感器 + OLED 显示 + USB 串口通信组成

STM32开发板
STM32F103C8T6 开发板
电导率检测模块
EC 电导率检测模块
OLED显示屏
0.96寸 OLED 显示屏
CH340 USB转串口
CH340 USB 转串口模块
ST-LINK下载器
ST-LINK/V2 下载调试器
面包板
MB-102 面包板
杜邦线
杜邦线 (母对母)
面包板跳线
面包板跳线
面包板飞线
面包板飞线
Hardware Modules

📋 硬件模块详解

感知层 → 控制层 → 通信层,三层架构设计

pH/EC传感器
🧪 感知层

1. pH 酸碱度传感器探头

模拟量 pH 复合电极,BNC 接口连接信号调理板,输出 0~5V 模拟电压。
通过 STM32 ADC(PA1)采样,经线性校准公式换算为 pH 值(量程 0~14)。
玉米适宜土壤 pH 范围 6.0~7.5,超出范围会影响微量元素(锌、铁、硼)的有效性。
系统实时监测 pH 并通过串口上传,pH 值参与综合污染指数 CPI 计算。

电导率检测模块
💧 感知层

2. EC 电导率检测模块

模拟量 TDS/EC 传感器探头,交流激励两电极法测量土壤溶液电导率。
通过 STM32 ADC(PA0)采样,12 位分辨率(0~4095),经温度补偿后换算为 μS/cm 值。
电导率反映土壤中可溶性盐和微量元素离子浓度,是玉米营养诊断的核心指标。
不同生长阶段有对应的适宜 EC 范围:三叶期 100~300 μS/cm,开花期 280~550 μS/cm。

STM32开发板
🧠 控制层

3. STM32F103C8T6 主控芯片

ARM Cortex-M3 内核,72MHz 主频,64KB Flash / 20KB SRAM。
负责双通道 ADC 采集(pH + EC)、I2C 驱动 OLED 本地显示、USART1 串口输出数据。
SysTick 毫秒定时器提供 10Hz (100ms) 采集节拍。支持 ST-LINK SWD 一键下载调试。
固件基于 Keil MDK + HAL 库开发,串口输出自定义格式供浏览器 Web Serial API 读取。

OLED显示屏
📺 本地显示

4. 0.96寸 OLED 显示屏

SSD1306 驱动芯片,128×64 分辨率,I2C 接口(地址 0x3C)。
PB0/PB1 软件模拟 I2C 时序驱动。本地实时显示 pH 值 / EC 值 / 运行秒数。
10Hz 刷新频率,方便不连接电脑时也能在田间查看传感器数据。
异常时显示 "ERR" 闪烁告警。

CH340 USB转串口
🔌 通信层

5. CH340 USB 转串口模块

USB 转 TTL 串口芯片,连接 STM32 的 PA9(TX) / PA10(RX),115200 bps 速率。
配合浏览器 Web Serial API,无需安装任何驱动程序即可在 Chrome/Edge 中直接读取串口数据。
实现真正的"零安装"体验——打开网页,点击连接,数据实时呈现。
输出格式: [时间戳] pH=xxx EC=xxx raw0=xxx raw1=xxx

ST-LINK下载器
💻 编程工具

6. ST-LINK/V2 下载调试器

SWD 接口(SWCLK/SWDIO/GND/3.3V)连接 STM32,Keil MDK 一键编译+下载。
支持断点调试、变量查看、寄存器监控。4 根杜邦线连接即可,无需额外配置。

面包板
🔧 结构件

7. 面包板 & 连接线

MB-102 面包板 + 跳线 + 杜邦线 + 飞线,用于搭建传感器与 STM32 的电路连接原型。
无需焊接,方便快速迭代和实验调整。所有传感器模块通过杜邦线连接到 STM32 对应引脚。

Pin Mapping

🔌 引脚接线表

STM32 引脚 连接外设 类型 说明
PA0 EC 探头 AO ADC1_IN0 电导率模拟采样,12位分辨率,10次平均
PA1 pH 传感器 AO ADC1_IN1 酸碱度模拟采样,12位分辨率
PB0 OLED SCL GPIO (软I2C) 软件模拟 I2C 时钟线
PB1 OLED SDA GPIO (软I2C) 软件模拟 I2C 数据线
PA9 CH340 RX USART1_TX 串口发送数据 (115200bps / 8N1)
PA10 CH340 TX USART1_RX 串口接收 (备用,固件升级预留)
PA13 ST-LINK SWDIO SWD 程序下载 / 调试数据线
PA14 ST-LINK SWCLK SWD 程序下载 / 调试时钟线
System Workflow

⚙ 系统工作流程

STM32 上电复位 ↓ 初始化 ADC1 / USART1 / I2C(软) / OLED ↓ 每 100ms 循环 ──────────────────────────────┐ │ │ 读 pH ADC (PA1) → 读 EC ADC (PA0) → OLED 本地显示 → USART1 输出 │ │ └────────────────────────────────────────┘ ↓ CH340 USB-TTL → 电脑 → 浏览器 Web Serial API → 实时仪表盘 零驱动 · 零安装 · 打开网页就能用 ↓ 循环回到 "每 100ms" → 无限采集 📡 串口输出格式 (115200bps / 8N1): [0] pH=700 EC=300 raw0=2048 raw1=1536 [100] pH=702 EC=305 raw0=2050 raw1=1538 [200] pH=698 EC=298 raw0=2045 raw1=1532 ... 解读: pH=700 → 700÷100 = pH 7.00, EC=300 → 300 μS/cm
Software Architecture

🧠 软件系统设计

状态机设计

🟢
初始化 INIT

ADC1 初始化 (12MHz/12bit/双通道)
USART1 初始化 (115200/8N1)
SSD1306 OLED 初始化
SysTick 1ms 定时器启动
传感器上电自检

🔵
采集 ACQUIRING

每 100ms 读 pH ADC (PA1)
每 100ms 读 EC ADC (PA0)
10次采样取平均 + 温度补偿
串口输出格式化数据
OLED 刷新三行显示

🟡
故障 ERROR

pH 超量程(>14 或 <0) → 告警
EC 超量程(>2000μS/cm) → 告警
OLED 显示 "ERR" 闪烁
串口输出 {"error":"sensor"}
3秒间隔自动尝试恢复

⏰ 时序控制

SysTick 毫秒定时器 (72MHz → 1ms 基准) 主循环用 SysTick 中断产生 1ms 基准,HAL_GetTick() 提供毫秒级时间戳。 主循环间隔 100ms = 10Hz 采集频率。 timestamp 为自启动以来的 100ms 周期计数。

📊 ADC 采样策略

PA0 (EC): ADC1_IN0,12位分辨率(0-4095),连续采样 10 次取平均值。 PA1 (pH): ADC1_IN1,12位分辨率(0-4095),连续采样 10 次取平均值。 pH 换算: pH = ADC_avg × 14.0 / 4096.0,钳位到 6.5~9.5。 EC 换算: EC = ADC_avg × (3.3/4096) × 系数 × 温度补偿。

📡 串口通信协议

参数: 115200 bps / 8 数据位 / 无校验 / 1 停止位 (8N1) 格式: [时间戳] pH=整数值 EC=整数值 raw0=原始ADC0 raw1=原始ADC1 示例数据流: [0] pH=700 EC=300 raw0=2048 raw1=1536\r\n [100] pH=702 EC=305 raw0=2050 raw1=1538\r\n [200] pH=698 EC=298 raw0=2045 raw1=1532\r\n pH 值 = 整数值 ÷ 100 (例: 700 → pH 7.00) EC 值 = 整数值 (单位 μS/cm) raw0 = EC 传感器 ADC 原始值 (0-4095) raw1 = pH 传感器 ADC 原始值 (0-4095) timestamp = 自启动以来的 100ms 周期计数 (100 = 10秒)
Firmware Code

💻 核心代码模块

STM32F103C8T6 固件 — Keil MDK + HAL 库开发

pH ADC 采样 + 换算

/**
 * pH 传感器 ADC 采样 — PA1 (ADC1_IN1)
 * 10次采样取平均, 线性换算为 pH 值, 钳位 6.5~9.5
 */
float pH_Read(void) {
    u32 adc_sum = 0;

    for (int i = 0; i < 10; i++) {
        ADC_SoftwareStartConvCmd(ADC1, ENABLE);
        while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
        adc_sum += ADC_GetConversionValue(ADC1);
    }
    float adc_avg = adc_sum / 10.0;

    // 线性换算: pH = ADC值 × 14 / 4096
    float ph = adc_avg * 14.0 / 4096.0;

    // 钳位到土壤合理范围
    if (ph < 6.5) ph = 6.5;
    if (ph > 9.5) ph = 9.5;

    return ph;
}

EC 电导率 ADC 采样 + 温度补偿

/**
 * EC 电导率 ADC 采样 — PA0 (ADC1_IN0)
 * 10次采样取平均, 电压换算 + 温度补偿 → μS/cm
 */
u16 EC_Read(float temp) {
    u32 adc_sum = 0;

    for (int i = 0; i < 10; i++) {
        ADC_SoftwareStartConvCmd(ADC1, ENABLE);
        while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
        adc_sum += ADC_GetConversionValue(ADC1);
    }
    float adc_avg = adc_sum / 10.0;

    // 电压换算: V = ADC × 3.3V / 4096
    float voltage = adc_avg * 3.3 / 4096.0;

    // EC 计算 (含温度补偿): EC = V × coeff / (1 + 0.02×(temp-25))
    float compensation = 1.0 + 0.02 * (temp - 25.0);
    u16 ec = (u16)(voltage * 600.0 / compensation);

    return ec;
}

主循环 — 串口数据输出 + OLED 显示

/**
 * 主循环: 100ms 周期采集 → OLED 显示 → 串口输出
 * 串口格式: [timestamp] pH=xxx EC=xxx raw0=xxx raw1=xxx\r\n
 */
int main(void) {
    System_Init();                              // 时钟 72MHz
    ADC1_Init();                                // PA0+PA1 双通道, 12bit
    UART1_Init(115200);                      // PA9 TX, 8N1
    OLED_Init();                                // SSD1306, I2C 软模拟

    float ph;
    u16 ec, raw_ec, raw_ph;
    u32 tick = 0;
    char buf[64];

    while (1) {
        ph = pH_Read();                         // ADC1_IN1 → pH 值
        ec = EC_Read(20.0);                    // ADC1_IN0 → EC 值 (默认25°C补偿)

        // OLED 本地显示 (三行)
        OLED_Clear();
        OLED_Printf(0, 0, "pH : %.1f", ph);
        OLED_Printf(0, 2, "EC : %d uS", ec);
        OLED_Printf(0, 5, "Time: %ds", tick / 10);

        // 串口输出 → 浏览器 Web Serial API 读取
        // 格式: [时间戳] pH=整数值 EC=整数值 raw0=ADC raw1=ADC
        u16 ph_int = (u16)(ph * 100);    // pH 7.0 → 700
        sprintf(buf, "[%lu] pH=%d EC=%d\r\n",
                tick, ph_int, ec);
        UART1_SendString(buf);

        delay_ms(100);                      // 10Hz 采集频率
        tick++;
    }
}
Web Serial API

🌐 浏览器端 — Web Serial API 数据接收

零驱动 · 零安装 · Chrome/Edge 原生支持

串口连接 & 数据解析

// ===== Web Serial API — 连接 STM32 串口 =====
async function connectSerial() {
  if (!('serial' in navigator)) {
    // 浏览器不支持 → 启用手动输入降级方案
    document.getElementById('manual-bar').classList.add('show');
    return;
  }
  port = await navigator.serial.requestPort();
  await port.open({ baudRate: 115200 });
  readSerialLoop();
}

// ===== 数据读取循环 =====
async function readSerialLoop() {
  reader = port.readable.getReader();
  let buffer = '';
  while (serialKeepAlive) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += new TextDecoder().decode(value);
    const lines = buffer.split('\n');
    buffer = lines.pop();  // 保留不完整行
    for (const line of lines) {
      const data = parseLine(line.trim());
      if (data) renderData(data);
    }
  }
}

// ===== 数据解析: [时间戳] pH=xxx EC=xxx =====
function parseLine(line) {
  const m = line.match(/\[(\d+)\]\s+pH=(\d+)\s+EC=(\d+)/);
  if (!m) return null;
  let ph = parseInt(m[2]) / 100.0;  // 700 → 7.00
  if (ph < 6.5) ph = 6.5;
  if (ph > 9.5) ph = 9.5;
  return {
    ts: parseInt(m[1]),
    ph: ph,
    ec: parseInt(m[3])
  };
}

// ===== 综合污染指数 CPI =====
function calcCPI(ph, ec) {
  return 0.5 * Math.abs(ph - 6.8) / 6.8
       + 0.5 * ec / 150.0;
}
AI Image Analysis

🧠 AI 图像分析 — 玉米生长阶段识别

纯图像处理 · 无外部 AI 依赖 · PIL + NumPy 本地计算

植被指数算法 (ExG + GLI + HSV 纹理)

ExG (Excess Green) — Woebbecke 1995: ExG = 2G − R − B,区分植被与土壤背景。
GLI (Green Leaf Index) — Gitelson 2002: GLI = (2G−R−B) / (2G+R+B),归一化绿叶覆盖度。
HSV 色彩分割 — 绿色调 (H:40°~150°) vs 黄色调 (H:10°~50°) 比例判断成熟度。
纹理分析 — 局部标准差 (Meyer & Neto 2008),20×20 滑动窗口,反映叶片纹理复杂度。

四条件级联判定: 黄色比例>10% → 腊熟期 | 覆盖度>40%+黄色>8% → 开花期 | 覆盖度<28%+土壤>22% → 三叶期 | 覆盖度>35%+ExG>15 → 抽雄期

Flask 后端 — AI 分析核心代码

# ===== 玉米生长阶段 AI 分析 (PIL + NumPy) =====
def analyze_corn_stage(image_bytes):
    from PIL import Image
    import numpy as np

    img = Image.open(io.BytesIO(image_bytes))\
              .convert('RGB').resize((400, 400))
    arr = np.array(img, dtype=np.float32)
    r, g, b = arr[:,:,0], arr[:,:,1], arr[:,:,2]

    # ExG 植被指数 (Woebbecke 1995)
    exg = np.clip(2.0*g - r - b, -255, 255)
    exg_mean = float(np.mean(exg))

    # GLI 绿叶指数 (Gitelson 2002)
    denominator = 2.0*g + r + b + 1e-6
    gli = (2.0*g - r - b) / denominator
    gli_mean = float(np.mean(gli))

    # 植被覆盖度
    veg_cover = float(np.mean(exg > 20)) * 100

    # HSV 色彩分割 — 绿色/黄色比例
    hsv = np.array(Image.fromarray(
        np.clip(arr, 0, 255).astype(np.uint8)
    ).convert('HSV'), dtype=np.float32)
    h, s, v = hsv[:,:,0], hsv[:,:,1]/255.0, hsv[:,:,2]/255.0

    green_h_pct  = float(np.mean((h>40)&(h<150)&(s>0.15))) * 100
    yellow_h_pct = float(np.mean((h>10)&(h<50)&(s>0.08)&(s<0.6))) * 100
    soil_pct     = float(np.mean((s<0.2)&(v<0.5))) * 100

    # 纹理分析 (20×20 局部标准差)
    patch = 20
    tex_map = np.zeros_like(gli)
    for y in range(0, 400, patch):
        for x in range(0, 400, patch):
            yy = slice(y, min(y+patch, 400))
            xx = slice(x, min(x+patch, 400))
            tex_map[yy, xx] = np.std(exg[yy, xx])
    tex_mean = float(np.mean(tex_map))

    # 四条件级联阶段判定
    yg_ratio = yellow_h_pct / (green_h_pct + 1e-6)

    if yellow_h_pct > 10 and yg_ratio > 0.28 and green_h_pct < 55:
        stage, conf = 4, 70 + min(20, int(yg_ratio * 50))  # 腊熟期
    elif veg_cover > 40 and yellow_h_pct > 8:
        stage, conf = 3, 60 + min(30, int(yellow_h_pct * 3))  # 开花期
    elif veg_cover < 28 and soil_pct > 22:
        stage, conf = 1, 60 + min(25, int(soil_pct / 2))  # 三叶期
    elif veg_cover > 35 and exg_mean > 15:
        stage, conf = 2, 60 + min(30, int((veg_cover-35)/2))  # 抽雄期
    else:
        stage, conf = 1, 50

    return {
        "stage": stage,
        "name": STAGES[stage]["name"],    # 三叶期/抽雄期/开花期/腊熟期
        "confidence": min(95, conf),
        "veg_cover": round(veg_cover, 1),
        "exg_mean": round(exg_mean, 1),
        "gli_mean": round(gli_mean, 4),
        "green_h_pct": round(green_h_pct, 1),
        "yellow_h_pct": round(yellow_h_pct, 1),
        "soil_pct": round(soil_pct, 1),
        "texture": round(tex_mean, 1),
        "method": "ExG+GLI+HSV"
    }
Growth Stages

🌱 玉米生长阶段 — EC 阈值参考

阶段 名称 EC 下限 (μS/cm) EC 上限 (μS/cm) 关键微量元素需求
1 🌱 三叶期 100 300 锌 (Zn)、铁 (Fe) — 促进根系发育
2 🌿 抽雄期 250 500 氮 (N)、钾 (K) — 促进茎叶生长
3 🌼 开花期 280 550 硼 (B)、锌 (Zn) — 促进花粉活力
4 🌽 腊熟期 250 500 钾 (K)、磷 (P) — 促进籽粒饱满