华北五省大学生机器人大赛水中机器人赛(水生态净化工程任务)
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,转载请注明出处。*
更多推荐

所有评论(0)