💻 核心代码模块
STM32 固件 — ADC 轮询读取
/**
* ADC1 四通道轮询读取 (每通道 4次取平均)
* PA0=pH PA1=EC PA2=温度 PA3=光敏
*/
void adc_read(void) {
for (int ch = 0; ch < 4; ch++) {
u32 sum = 0;
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_55Cycles5);
for (int i = 0; i < 4; i++) {
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 软件触发转换
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待完成
sum += ADC_GetConversionValue(ADC1); // 读 12bit 值
}
g_adc[ch] = sum / 4; // 均值滤波
}
}
STM32 固件 — CPI 综合污染指数
float calc_cpi(float pH, u16 ec) {
float pH_part = fabs(pH - 6.8) / 6.8;
float ec_part = ec / 150.0; // EC_clean = 150 μS/cm
return 0.5 * pH_part + 0.5 * ec_part;
}
// 四级污染判定
if (cpi < 0.3) { led_green(); buzzer_off(); } // 正常
else if (cpi < 0.6) { led_yellow(); buzzer_slow(); } // 轻度
else if (cpi < 1.0) { led_red(); buzzer_fast(); } // 中度
else { led_flash(); buzzer_cont(); } // 重度
STM32 固件 — 主循环 + 串口输出
int main(void) {
cfg_clock(); // HSE 8MHz → PLL ×9 → 72MHz
cfg_gpio(); // PA0/1 AIN, PA9/10 AF_PP, PB6/7 AF_OD, etc.
cfg_adc(); // ADC1 独立模式, ExternalTrigConv=None (关键!)
cfg_usart(); // USART1 115200-8N1
cfg_i2c(); // I2C1 100KHz, SSD1306 OLED
cfg_tim2(); // TIM2 100Hz (7200-1 / 100-1)
cfg_nvic(); // DMA + TIM2 中断优先级
while (1) {
if (g_tick % 10 == 0) { // 100Hz: ADC 采集
adc_read();
}
if (g_tick % 100 == 0) { // 10Hz: 算法计算
float ph = g_adc[0] * 14.0 / 4096;
u16 ec = g_adc[1] * 1000 / 4096;
float cpi = calc_cpi(ph, ec);
}
if (g_tick % 1000 == 0) { // 1Hz: OLED + 串口输出
oled_show(ph, ec, cpi);
uart_send("["); uart_num(g_tick/1000);
uart_send("] pH="); uart_num((int)(ph*100));
uart_send(" EC="); uart_num(ec);
uart_send("\r\n");
// 输出示例: [60] pH=720 EC=315
}
}
}
Python Flask — AI 图像识别后端
def analyze_corn_stage(image_bytes):
"""PIL + numpy 植被指数分析, 无外部模型依赖"""
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 色彩分割: 绿色 H[40,150] / 黄色 H[10,50]
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, hsv[:,:,2]/255
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
# 纹理分析: 20px patch 局部标准差 (Meyer & Neto 2008)
tex_map = np.zeros_like(gli)
for y in range(0, 400, 20):
for x in range(0, 400, 20):
yy, xx = slice(y,min(y+20,400)), slice(x,min(x+20,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 = 2, 50
return {"stage": stage, "name": STAGES[stage]["name"],
"confidence": min(95, max(50, conf)),
"veg_cover": round(veg_cover,1), "method": "ExG+GLI+HSV"}
JavaScript — Web Serial API 串口读取
async function connectSerial() {
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 115200 });
const reader = port.readable.getReader();
let buffer = '';
while (true) {
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 m = line.match(/\[(\d+)\]\s+pH=(\d+)\s+EC=(\d+)/);
if (m) {
const data = {
ts: parseInt(m[1]),
ph: parseInt(m[2]) / 100, // 还原为 7.20
ec: parseInt(m[3]) // 电导率 μS/cm
};
renderData(data); // 更新仪表盘
}
}
}
}