STM32水质检测打捞船嵌软开发:从引脚冲突到系统优化的竞赛实战

1. [项目背景与需求分析]

2. [系统总体设计]

3. [硬件详解](三硬件详解)

4. [软件设计])

5. [调试中的坑与解决方案]

6. [比赛成果与项目展望]

 一、项目背景与需求分析

 1.1 比赛简介

华北五省机器人比赛是面向高校学生的大型机器人竞赛,涵盖多个赛道。我们这个项目参加的是水生态净化工程任务赛赛道

   通过参与水生态净化工程任务赛,学生能够更加深入地了解水生态环境的重 要性,以及人类活动对水环境可能产生的负面影响。这将有助于他们树立环保意 识,形成可持续发展的观念,并可提升学生实践能力,培养创新精神,比赛形式 以团队形式展开,有助于锻炼他们的团队协作能力,培养沟通能力和领导能力, 通过参与比赛,学生可以接触到更多的专业知识和实践经验,了解水生态环境保 护与治理的最新动态和技术进展。这将有助于拓展他们的知识视野,增强综合素质。

主要任务包括:

1.垃圾物回收(机械臂)

2.重灾区资料回传(图传)

3. 水标本采样(水泵)

4. 水质检测(常规3项检测)

1.2 技术指标

参数 水温检测 pH 检测 TDS 检测 舵机响应 续航时间
要求 ±1°C ±0.2 ±5% <100ms >30min
实测 ±0.5°C ±0.15 ±3% <50ms ~45min

 二、系统总体设计

2.1 硬件选型论证

主控为什么选 STM32F103C8T6?

✅ 72MHz Cortex-M3,性能足够处理 3 路 ADC + OLED 刷新

✅ 2 个高级定时器 + 4 个通用定时器,驱动 5 路舵机无压力

✅ 价格低廉(Blue Pill 不到 10 元),竞赛预算友好

✅ 资料丰富,调试问题容易找到参考

备选方案对比:

方案 STM32F103C8T6 Arduino Mega ESP32 STM32F407
优点 便宜, 资料多, PWM 资源丰富 上手简单 WiFi/BT 集成 性能强
缺点 Flash/RAM 偏小, 无 FPU PWM 精度不够, 速度慢 功耗高, 引脚少, ADC 精度差 |价格高, 引脚过多浪费
 结论 ✅选用

2.2 系统框图

 三、硬件详解

3.1 引脚分配表

这是本项目的完整引脚分配,**标红的是我踩过坑的引脚**:

引脚 功能 备注
PA0 pH 传感器 (ADC1_CH0) 模拟输入
PA3 HX1838 红外接收 EXTI3 下降沿中断
PA5 TDS 传感器 (ADC1_CH5) 模拟输入
PA6 DS18B20 温度 单总线,需 4.7kΩ 上拉
PA8 舵机2 (TIM1_CH1) 原与按键3冲突!
PA9 舵机3 (TIM1_CH2) 原与按键4+USART1_TX冲突
PA10 舵机4 (TIM1_CH3) 备用
PA11 舵机5 (TIM1_CH4) 备用
PA15 舵机1 (TIM2_CH1) 需禁用JTAG,保留SWD
PB0 蜂鸣器 推挽输出
PB8/PB9 OLED SCL/SDA 软件I2C,开漏
PB11 水泵 低电平有效
PB12/PB13 按键3/4 (备用) 从 PA8/PA9 迁移至此
PB14/PB15 按键1/2 (备用)
PC13 板载LED 低电平亮

 3.2 传感器接线要点

pH 传感器:

使用模块自带的信号调理电路,输出 0-3.3V 模拟电压

校准公式:`pH = -5.7541 × V + 16.654`(需要根据实际传感器标定)

⚠️ pH 电极需要定期用标准缓冲液校准!

TDS 传感器:

同样输出 0-3.3V 模拟电压

关键:必须做温度补偿!25°C 以上每升高 1°C,电导率增加约 2%

补偿公式:`V_compensated = V_raw / (1 + 0.02 × (T - 25))`

DS18B20 单总线:

数据线 (DQ) 必须接 4.7kΩ 上拉电阻到 3.3V

读取温度约需 750ms(包含转换时间)

使用 `__disable_irq()` 保护关键时序

 四、软件设计

4.1 软件架构(分层设计)

4.2 关键代码讲解

4.2.1 舵机 PWM 驱动(三自由度机械臂)

5 路舵机使用 TIM1 (4通道) + TIM2 (1通道) 产生 50Hz PWM:

```c
/**
 * 舵机 PWM 初始化 (关键参数)
 * 频率: 50Hz (20ms 周期)
 * 分辨率: 1us/tick (定时器时钟 1MHz = 72MHz / 72)
 * 脉宽范围: 500-2500 (0.5ms~2.5ms → 0°~180°)
 */
void Servo_InitAll(void)
{
    // TIM1 时基配置: 72MHz / 72 = 1MHz
    TIM_TimeBaseInitStructure.TIM_Period    = 20000 - 1;  // 20ms
    TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;     // 72分频

    // PWM 模式: PWM1, 高电平有效, 初始脉宽 1500 (90°)
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_Pulse  = 1500;  // 中间位置

    // TIM1 是高级定时器,必须使能主输出
    TIM_CtrlPWMOutputs(TIM1, ENABLE);
}

// 角度 → 脉宽转换
void Servo_SetAngle(uint8_t servoNum, float Angle)
{
    uint16_t compare = (uint16_t)(Angle / 180.0f * 2000.0f + 500.0f);
    PWM_SetCompare(servoNum, compare);
}

 4.2.2 TDS 温度补偿

```c
float TDS_Value_Conversion(float temperature)
{
    // 1. 50 次采样取平均
    for (i = 0; i < 50; i++) {
        ad_sum += ADC_GetValue(TDS_ADC_CHANNEL, ADC_SampleTime_55Cycles5);
    }

    // 2. ADC → 电压
    averageVoltage = (float)ad_sum / 4096.0f * 3.3f;

    // 3. 温度补偿 (关键!)
    compensationCoefficient = 1.0f + 0.02f * (temperature - 25.0f);
    compensationVoltage = averageVoltage / compensationCoefficient;

    // 4. 多项式拟合 → TDS ppm
    tds = (133.42 * V³ - 255.86 * V² + 857.39 * V) * 0.5;
    return tds;
}

4.2.3 红外遥控 NEC 解码

// EXTI3 中断服务函数
void EXTI3_IRQHandler(void)
{
    // 1. 检测引导码 (9ms 低 + 4.5ms 高)
    // 2. 测量 32 位数据的高电平脉宽
    // 3. 宽脉冲 (>60×20us) → 逻辑1
    //    窄脉冲 (<50×20us) → 逻辑0
    // 4. 拼装为 32-bit 数据帧
    //    地址码(8bit) + 地址反码(8bit) + 命令码(8bit) + 命令反码(8bit)

    if (Num >= 32) {
        g_ir_data_ready = 1;  // 通知主循环
    }

    EXTI_ClearITPendingBit(EXTI_Line3);
}

4.3 主循环结构

int main(void)
{
    System_HardwareInit();   // 初始化所有硬件
    DS18B20_InitWithRetry(); // 等待 DS18B20 连接
    Task_Init();             // 显示静态标签, 舵机回中

    while (1) {
        Task_SensorRead();      // 传感器采集 + OLED 刷新
        Task_RemoteControl();   // 红外遥控 → 舵机控制
        Task_PumpControl();     // 水泵自动采样 (仅一次)
        Task_LED_Heartbeat();   // LED 心跳
    }
}

 五、调试中的坑与解决方案

这一章是全文最有价值的部分!每一个坑都花了至少 3 小时的排查时间。

坑1:舵机完全不动?引脚冲突!

现象:5 个舵机全部焊好、代码烧录后,舵机纹丝不动。

排查过程:

1. 先用示波器量 PA8/PA9 有没有 PWM 波 → 没有

2. 怀疑定时器配置错误 → 检查 TIM1 寄存器 → 配置正确

3. 用万用表量 PA8 电平 → 固定高电平 → 引脚被拉住了

4. 排查 GPIO 初始化顺序 → 发现 `Key_Init()` 在 `Servo_InitAll()` 之后执行

根因:`Key_Init()` 将 **PA8、PA9** 重新配置为 `GPIO_Mode_IPU`(上拉输入),覆盖了舵机初始化时的 `GPIO_Mode_AF_PP`(复用推挽输出)。PWM 信号直接没了。

解决方案:

将 Key3/Key4 从 PA8/PA9 迁移到 PB12/PB13

用红外遥控替代物理按键控制舵机

在 `app_config.h` 中集中定义引脚映射,避免多人协作时的引脚冲突

教训:初始化顺序决定一切!建议画一张"引脚使用矩阵图",在项目开始时就标注每个引脚的原始功能和最终用途。

坑2:DS18B20 读数偶尔跳变到 85°C 或 -55°C

现象:温度读数大部分时间正常 (~25°C),但偶尔出现 85.0°C 或 -55°C 的异常值。

排查过程:

1. 85°C 是 DS18B20 的上电默认值 → 说明读数没有更新

2. -55°C 是负温度最大值 → 说明读到了无效数据

3. 检查单总线波形 → 发现不定期的时序扰乱

根因:SysTick 中断或其他 IRQ 打断了单总线的微秒级时序。DS18B20 的读写时序要求精确到 1-15us,中断延迟 5-10us 就足以破坏数据。

解决方案:

u8 DS18B20_Read_Bit(void)

{

    __disable_irq();  // 关闭全局中断!保护 15us 的时序窗口



    DS18B20_LOW();    // 拉低启动读时序

    delay_us(2);

    DS18B20_HIGH();   // 释放总线

    delay_us(12);

    data = DS18B20_READ();



    __enable_irq();   // 恢复中断

    return data;

}

教训:裸机开发的"临界区"思维— 任何需要精确时序的通信协议(单总线、WS2812 等),都要考虑中断干扰。

 坑3:TDS 值漂移严重,同一杯水上午和下午差 200ppm

现象:TDS 读数随环境温度明显变化。

排查过程:

1. 怀疑传感器本身温漂 → 查阅数据手册,确认模块已做硬件补偿

2. 怀疑 ADC 基准电压漂移 → 量 3.3V 稳定

3. 检查代码 → 发现 `TDS_Value_Conversion(22)` 传的是固定值 22!而实际水温可能是 10°C 或 35°C

根因:水的电导率随温度显著变化(~2%/°C)。22°C 时的 500ppm 溶液,在 12°C 时读数只有 ~400ppm,误差高达 20%。

解决方案:

// 修复前: 硬编码 22°C

TDS_value = TDS_Value_Conversion(22);



// 修复后: 传入 DS18B20 实测温度

float temp = (float)DS18B20_Get_Temp() / 10.0f;

TDS_value = TDS_Value_Conversion(temp);

教训:传感器数据不要硬编码!温度是水质检测中影响最广的参数 —— pH、TDS、溶解氧全部受温度影响。

六、比赛成果与项目展望

 6.1 最终效果

去比赛啦

这次的比赛的时间很紧张好多视频照片都没来得及拍,等学弟的第二代船做出来再给大家上演示视频。

6.2 源码开源

本项目已开源在 GitHub:

- 源码地址:Mzh327/-: 核心实现了水泵、红外遥控,机械臂、水质常规三项检测(PH TDS Temperture)

- 包含完整的 Keil 工程文件和硬件驱动代码

这艘船能顺利从图纸变成实物,大半功劳要归我的硬件搭档。从 PCB 设计、焊接调测、可靠性极限验证,到一轮轮改板迭代最终定型,还有 3D 打印结构建模、器件选型和光电检测模块的全流程落地,全是他一手扛下来的。 必须专门鸣谢我的神仙队友,顺便在这里挖个坑:后续的硬件相关的设计文件都会陆续开源,感兴趣的可以蹲一波。

 6.3 未来改进方向

方向 具体方案 优先级
RTOS 移植 FreeRTOS,提升多任务实时性 ⭐⭐⭐
无线传输 ESP8266 WiFi → 手机 APP 远程监控 ⭐⭐⭐
GPS 导航 自动巡航 + 路径规划 ⭐⭐
SD 卡记录 长时间水质数据离线存储 ⭐⭐
上位机 Python/PyQt 实时曲线显示 ⭐⭐
电池管理 电量检测 + 低功耗优化

 写在最后

这个项目从面包板上的第一颗 LED 闪烁,到最终在比赛现场稳定运行,中间踩过的坑远超本文列出的 4 个。但正是这些坑,让我真正理解了:

1. 硬件调试思维 —— 从现象到根因的逻辑链

2. 嵌入式开发的"临界区"意识" —— 时序 vs 中断的权衡

3. 写代码前先规划引脚 —— 一张引脚矩阵图省去 3 天调试

这里也给的队友挖个坑,pcd设计和

如果你也在做类似的竞赛项目,欢迎在评论区交流

*文章首发于 CSDN,转载请注明出处。*

Logo

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

更多推荐