简介:这是一份面向电子信息、计算机及自动化专业本科毕业设计的开题报告文档,选题为基于单片机的智能鱼缸设计与实现,适合正在准备嵌入式方向毕设、需要撰写开题材料或搭建智能家居小型控制系统的学生与开发者参考。文档完整呈现了研究目的与意义、任务要求与可行性分析、关键问题及解决思路、所需工作条件、工作方案与进度计划等开题报告必备板块,并围绕系统集成思路展开论述,涉及自动充氧、自动换水、灯光变换、自动喂食等功能模块,以及传感器电路设计、滤波算法改进、蓝牙通信、Android Studio 配合 JAVA 开发手机端 APP 等具体技术要点,还给出 STC12C5A60S2 单片机、外围电路与安卓手机等硬件条件说明和分阶段时间安排。资源包内仅 1 个 pdf 文件,约 38KB,体量轻便、便于查阅与打印。目前已有 315 人浏览学习,可作为选题论证、框架参照与写作模板使用。
1. 从单件养鱼设备到 STC12C5A60S2 集成控制:这份开题报告要解决什么
鱼缸旁边摆着加氧泵、喂食器、灯,每个都插在独立定时插座上,出差三天回来水已经发浑——这是很多人第一次想做智能鱼缸的真实触发点。这份开题报告选择 STC12C5A60S2 单片机作为主控,把自动充氧、自动换水、灯光变换和自动喂食收进一套系统,再用蓝牙把参数设置和状态显示挪到 Android 手机端。它不是现成源码包,而是一份技术路线清晰的单片机毕业设计开题材料,适合做单片机课程设计的人拿来拆模块,也适合已经会点 C51、想补齐传感器采样和蓝牙通信链路的开发者。报告里明确了传感器电路、滤波算法、蓝牙通信和 Android Studio 手机 APP 四个关键问题,后面的硬件、代码和排错基本都围绕这四件事展开。
2. 硬件选型与传感器采样链路:STC12C5A60S2、ADC 和驱动电路怎么落地
2.1 为什么是 STC12C5A60S2 而不是普通 51
普通 89C52 做鱼缸控制不是不行,但要多挂 ADC0832 之类的外部芯片,再占用 IO 口,板子面积和排错成本都上去了。STC12C5A60S2 属于 1T 内核的 stc单片机,片内常见资源包括 60KB Flash、1280B RAM、8 通道 10 位 ADC、PWM 和双串口,在 5V 系统里直接接继电器、光敏电阻、浊度模块都比较省事。对鱼缸这种慢变量系统来说,10 位 ADC 已经够用:温度变化几分钟才动一次,浊度换水判断也不需要 16 位精度。真正的坑在模拟部分,不是单片机本身。
选型时要盯住三个点:ADC 参考电压是否直接用 VCC、PWM 是否够驱动舵机、串口是否留一路给蓝牙。STC12C5A60S2 的 ADC 以 VCC 为参考时,5V 供电下理论分辨率约 4.88mV/LSB,但继电器一吸合,VCC 抖几十毫伏,采样值就会跳十几个字。常见做法是模拟部分加 LC 或 RC 去耦,ADC 采样前先采一次丢弃,再进入滤波。
2.2 传感器与执行器接口映射
鱼缸端不需要很贵的传感器,关键是接口方式和失效保护。下面这张表是开题报告里几个功能模块最常见的落地组合,实际打板时按自己的传感器输出类型改。
| 类型 | 常见器件 | 接口 | 采样/驱动方式 | 注意事项 |
|---|---|---|---|---|
| 温度 | DS18B20 | 单总线 | 定时读取 | 数据脚加 4.7k 上拉 |
| 水位 | 电极或浮球 | GPIO | 高低电平 | 电极长期通直流会电解,建议间歇供电 |
| 浊度 | 光电对管/浊度模块 | ADC | 10 位采样 | 避光安装,远离气泡 |
| 光照 | 光敏电阻 | ADC | 分压后采样 | 注意灯光 PWM 串扰 |
| 充氧 | 气泵+继电器 | GPIO | 高低电平 | 继电器线圈并续流二极管 |
| 换水 | 电磁阀+水泵 | GPIO/继电器 | 互锁控制 | 加防干烧判断 |
| 喂食 | 舵机 | PWM | 脉宽 0.5ms~2.5ms | 别堵转,喂食量用转动时间控制 |
| 灯光 | LED 灯带 | PWM/继电器 | 占空比调节 | 低频 PWM 可能可见频闪 |
接线时把继电器、水泵、气泵的电源地和单片机地单点共地,不要沿着信号线串过去。蓝牙模块如果是 HC-05,供电电流峰值能到几十毫安,别直接从单片机 IO 取电。
2.3 ADC 采样代码与参数
下面是 STC12C5A60S2 的 ADC 轮询采样写法,寄存器名按常见头文件风格给出,实际工程里以自己用的头文件为准。
#include <reg51.h> #include <intrins.h> sfr ADC_CONTR = 0xBC; sfr ADC_RES = 0xBD; sfr ADC_RESL = 0xBE; sfr P1ASF = 0x9D; #define ADC_POWER 0x80 #define ADC_FLAG 0x10 #define ADC_START 0x08 #define ADC_SPEED 0x60 // 转换速度档位,数值越大转换越慢、越稳 unsigned int adc_read(unsigned char ch) { unsigned int val; P1ASF |= (1 << ch); // 把 P1.x 设为模拟输入 ADC_CONTR = ADC_POWER | ADC_SPEED | ch; _nop_(); _nop_(); _nop_(); _nop_(); ADC_CONTR |= ADC_START; // 启动转换 while (!(ADC_CONTR & ADC_FLAG)); // 等待完成 ADC_CONTR &= ~ADC_FLAG; // 清标志 val = (ADC_RES << 2) | (ADC_RESL & 0x03); return val; // 10 位结果,范围 0~1023 } void main() { unsigned int turbidity; adc_read(0); // 第一次丢弃,减少通道切换残留 while (1) { turbidity = adc_read(0); // P1.0 接浊度传感器 // 后续滤波与阈值判断 } }这段代码的逻辑很直白:P1ASF 选择模拟通道,ADC_CONTR 设置电源、速度和通道号,启动后查询 ADC_FLAG,最后把 ADC_RES 和 ADC_RESL 拼成 10 位结果。参数上,ADC_SPEED 不要盲抄,主频高时用较慢档能换稳定性;通道号 0~7 对应 P1.0~P1.7,但已被数字功能占用的脚要避开。如果 VCC 是 5V,返回 512 大约对应 2.5V,判断浊度阈值时先把清水和浑水两个状态的值记下来,再取中间偏浑水一侧。
提示:ADC 跳得厉害时,先查地线和继电器电源,再改代码滤波。硬件噪声用软件只能压,不能消。
3. 滤波算法与阈值决策:从高通/低通/带通到可落地的滑动均值
3.1 鱼缸里 ADC 为什么会跳
开题报告提到高通、低通和带通滤波,这是信号处理课上的通用分类,但鱼缸里真正造成干扰的来源很具体:水泵启停、继电器吸合、气泵振动、LED PWM 耦合、传感器供电纹波。温度是慢变量,适合低通;浊度在换水瞬间会突变,更适合中值加低通;带通滤波想滤掉某个频段的振动噪声,实际效果往往不如把传感器线远离电机线。对 51 单片机来说,浮点运算代价高,优先用整数滑动平均和一阶低通。
| 滤波方法 | 计算量 | 适用信号 | 关键参数 | 缺点 |
|---|---|---|---|---|
| 滑动平均 | 中 | 温度、光照、浊度 | 窗口 N | N 越大越滞后 |
| 一阶低通 | 低 | 温度、水位 | 系数 a | a 选不好会拖尾 |
| 中值滤波 | 低 | 剔除尖峰 | 窗口 3 或 5 | 对连续噪声一般 |
| 限幅滤波 | 极低 | 防止野值 | 最大变化量 | 需要合理阈值 |
3.2 一阶低通与滑动平均代码
下面三个函数可以放在同一个 C 文件里,按传感器类型调用。整数运算避免浮点,适合 51 单片机。
#define FILTER_N 8 unsigned int moving_average(unsigned int new_val) { static unsigned int buf[FILTER_N] = {0}; static unsigned char idx = 0; static unsigned long sum = 0; sum -= buf[idx]; // 减去最旧值 buf[idx] = new_val; // 存入新值 sum += new_val; idx = (idx + 1) % FILTER_N; return sum / FILTER_N; // 返回平均值 } unsigned int low_pass(unsigned int new_val) { static unsigned int last = 0; // a 约等于 0.2,越大越跟手,越小越稳 last = last + ((int)(new_val - last) * 2) / 10; return last; } unsigned int median3(unsigned int a, unsigned int b, unsigned int c) { if (a > b) { unsigned int t = a; a = b; b = t; } if (b > c) { unsigned int t = b; b = c; c = t; } if (a > b) { unsigned int t = a; a = b; b = t; } return b; }滑动平均用循环缓冲保存最近 8 次采样,sum 用 unsigned long 防止累加溢出;FILTER_N 改成 16 会更平滑,但换水判断会慢半拍。一阶低通里乘 2 除以 10 是整数近似,相当于系数 0.2,温度这类慢变量可以再用小一点;如果发现温度显示总是追不上实际变化,把 2 改成 3 或 4。中值滤波适合取三次采样后排序取中间值,对气泡、触点抖动造成的尖峰很有效。
3.3 阈值决策与状态机
滤波后的值要进入阈值判断,但阈值不能只设一个点。比如浊度超过 800 启动换水,如果回到 799 就停,执行器会在临界点反复启停。加回差之后,超过 800 启动,低于 700 才允许停止,状态就稳定了。下面这张表是常见参数起点,实际要按鱼缸大小和传感器特性标定。
| 参数 | 典型起点 | 动作 | 回差 |
|---|---|---|---|
| 温度 | 30 度 | 开充氧或报警 | 1 度 |
| 浊度 | ADC 值 800 | 启动换水 | 100 |
| 水位 | 低电平有效 | 立即停水泵 | 无 |
| 喂食 | 8:00、18:00 | 舵机转动 1 秒 | 无 |
| 灯光 | 18:00~23:00 | PWM 60% | 无 |
状态机建议写成 switch-case,每个执行器一个状态变量,不要把所有 if 叠在一起。换水时先排水、再进水,中间留 2 秒死区,避免两个继电器同时吸合拉低电源。
注意:阈值和回差都要存在单片机内部,掉电后重新上电不能丢。常见做法是写内部 EEPROM,或者手机 APP 连接后重新下发一次。
4. 蓝牙串口通信协议与上下位机数据帧设计
4.1 HC-05/HC-06 透传模式与 STC 双串口
蓝牙模块常用 HC-05 或 HC-06,配对后就是串口透传。接线时模块 TXD 接单片机 RXD,模块 RXD 接单片机 TXD,供电按模块要求来,HC-05 一般 3.3V 逻辑,底板带稳压的可以接 5V。STC12C5A60S2 有双串口,常见做法是 UART1 接蓝牙,UART2 留给串口助手看调试信息,这样手机连上以后,电脑这边还能观察 ADC 和状态机。
波特率建议两边一致,初次调试用 9600,稳定后再上 115200。蓝牙模块刚上电时可能还在 AT 模式,必须先发 AT 确认,再退出到透传。手机端搜索设备前要确认模块已经配对过,Android 4.0 以上对经典蓝牙 SPP 的支持比较稳。
4.2 自定义数据帧格式
串口透传只保证字节流,不保证一包一收,所以必须自己定帧。下面是一种够用且好查的格式:帧头、命令、长度、数据、异或校验、帧尾。
| 字段 | 字节 | 说明 |
|---|---|---|
| 帧头 | 1 | 固定 0xAA |
| 命令 | 1 | 0x01 查询状态,0x02 设置温度上限,0x03 手动喂食,0x04 手动换水,0x05 灯光开关 |
| 长度 | 1 | 数据区字节数 |
| 数据 | N | 参数或状态值 |
| 校验 | 1 | 从命令到数据末尾逐字节异或 |
| 帧尾 | 1 | 固定 0x55 |
命令码不要用 0x00 和 0xFF,这两个值在调试时容易和空闲电平混淆。状态上报可以用 0x81,设置确认用 0x82,出错用 0xE0。
4.3 下位机接收与打包代码
串口中断里只做收字节和简单校验,复杂处理放到主循环,避免中断执行时间过长。
#define FRAME_HEAD 0xAA #define FRAME_TAIL 0x55 unsigned char rx_buf[32]; unsigned char rx_len = 0; void uart_isr() interrupt 4 { if (RI) { unsigned char ch = SBUF; RI = 0; if (rx_len == 0 && ch != FRAME_HEAD) return; // 等帧头 rx_buf[rx_len++] = ch; if (rx_len >= 4 && rx_buf[rx_len - 1] == FRAME_TAIL) { unsigned char x = 0, i; for (i = 1; i < rx_len - 2; i++) x ^= rx_buf[i]; if (x == rx_buf[rx_len - 2]) { // 处理命令 rx_buf[1],数据从 rx_buf[3] 开始 } rx_len = 0; } if (rx_len >= sizeof(rx_buf)) rx_len = 0; // 防溢出 } } void send_status(unsigned char temp, unsigned char turb) { unsigned char frame[7], i, x; frame[0] = FRAME_HEAD; frame[1] = 0x81; // 状态上报 frame[2] = 2; // 数据长度 frame[3] = temp; frame[4] = turb; x = 0; for (i = 1; i <= 4; i++) x ^= frame[i]; frame[5] = x; frame[6] = FRAME_TAIL; for (i = 0; i < 7; i++) { SBUF = frame[i]; while (!TI); TI = 0; } }接收逻辑先等 0xAA,再收满一帧后看 0x55,最后异或校验。rx_buf 给 32 字节,正常命令够用;如果数据长度可能超过,长度字段要做上限判断。发送函数里每一字节都等 TI,9600 波特率下 7 字节大约几毫秒,放在主循环里不会影响控制。参数上,温度可以乘 2 再传,手机端除 2,这样能保留 0.5 度分辨率。
4.4 Android 端 Java 解析
Android Studio 里用经典蓝牙 SPP,UUID 固定为 00001101-0000-1000-8000-00805F9B34FB。发送命令时按同样帧格式打包。
private void sendCommand(byte cmd, byte[] data) throws IOException { byte[] frame = new byte[5 + data.length]; frame[0] = (byte) 0xAA; frame[1] = cmd; frame[2] = (byte) data.length; System.arraycopy(data, 0, frame, 3, data.length); byte xor = 0; for (int i = 1; i < 3 + data.length; i++) xor ^= frame[i]; frame[3 + data.length] = xor; frame[4 + data.length] = (byte) 0x55; outputStream.write(frame); }这段 Java 代码把帧头、命令、长度、数据、校验和帧尾拼成一个 byte 数组,再通过 BluetoothSocket 的 OutputStream 发出。参数上,data 长度别超过单片机 rx_buf 能承受的范围,手动喂食可以发空数据,长度写 0。Android 6.0 以后扫描蓝牙需要定位权限,连接已配对设备一般不需要动态申请定位,但不同版本行为有差异,调试时先看 Logcat 有没有 SecurityException。
5. Android Studio 手机端 APP 与自动喂食/换水流程
5.1 蓝牙连接与界面参数下发
手机端不需要很复杂的界面,一个连接按钮、温度上限输入框、浊度阈值输入框、手动喂食和手动换水按钮就够。连接必须放在工作线程,主线程直接 connect 会抛 NetworkOnMainThreadException。
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); BluetoothDevice device = adapter.getRemoteDevice("00:11:22:33:44:55"); BluetoothSocket socket = device.createRfcommSocketToServiceRecord( UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")); adapter.cancelDiscovery(); socket.connect(); OutputStream out = socket.getOutputStream(); out.write(buildFrame((byte)0x02, new byte[]{30})); // 设置温度上限 30MAC 地址从已配对设备列表里取,不要硬编码。buildFrame 就是上一节的打包函数。参数下发后,单片机最好回一帧 0x82 确认,APP 收到再更新界面,否则用户不知道设置有没有生效。Android 4.0 以上系统对经典蓝牙的 API 基本一致,但 12 以后需要 BLUETOOTH_CONNECT 权限,编译时按目标 SDK 处理。
5.2 自动喂食舵机控制
喂食机构常见做法是舵机转一个角度,把饲料仓的挡板打开,延时后再转回。舵机控制需要 50Hz PWM,也就是 20ms 周期,脉宽 0.5ms 到 2.5ms 对应角度。
// 定时器0 产生约 50Hz PWM,舵机脉宽按主频校准 void timer0_isr() interrupt 1 { static unsigned char cnt = 0; TH0 = (65536 - 20000) / 256; TL0 = (65536 - 20000) % 256; cnt++; if (cnt < 2) P1_0 = 1; // 约 1.0ms,具体角度实测 else P1_0 = 0; if (cnt >= 20) cnt = 0; }这段代码用定时器中断数 20ms 周期,cnt 小于 2 时输出高电平,改变比较值就能改脉宽。参数上,20000 是按 1T 或 12T 主频折算的计数初值,换晶振必须重新算;喂食量不要靠角度硬调,用“转到开、延时 800ms、转回关”更容易复现。舵机堵转电流大,电源要和单片机分开,否则喂食瞬间可能复位。
5.3 自动换水互锁流程
换水是整套系统里最容易出问题的动作,必须做互锁和超时保护。常见步骤可以写成下面这样:
- 读水位,如果低电平有效且持续 2 秒,立即停水泵并报警。
- 打开排水阀,启动排水泵,延时 T1,T1 按鱼缸水量标定。
- 关闭排水阀和排水泵,等待 2 秒死区。
- 打开进水阀,启动进水泵,延时 T2。
- 关闭进水阀和进水泵,恢复充氧和过滤。
| 步骤 | 动作 | 条件 | 超时保护 |
|---|---|---|---|
| 1 | 水位检测 | 低电平 | 2 秒消抖 |
| 2 | 排水 | 浊度超阈值 | 最长 60 秒 |
| 3 | 死区 | 排水结束 | 固定 2 秒 |
| 4 | 进水 | 水位未到高限 | 最长 90 秒 |
| 5 | 恢复 | 进水结束 | 开充氧 |
超时保护一定要有,否则水位传感器卡住时水泵会一直转。换水期间手机 APP 应该显示“正在换水”,手动喂食按钮置灰,避免误操作。
注意:排水和进水继电器不要用同一个电源同时吸合,线圈反电动势容易让单片机复位。加续流二极管,电源入口并 1000uF 电解和 0.1uF 瓷片。
6. 联调排错与验收技巧:从 STC-ISP 烧录到长时间运行
6.1 烧录与串口版本确认
STC12C5A60S2 用 STC-ISP 烧录,选对型号和串口,冷启动上电。烧录失败常见原因就三个:串口被占用、波特率上限选太高、板子没完全断电。烧录成功后,先别急着连蓝牙,用 UART2 接串口助手看启动打印。
一个很实用的技巧是在固件启动时把编译时间打出来:
#include <stdio.h> void print_version() { printf("FW %s %s\r\n", __DATE__, __TIME__); }__DATE__和__TIME__是编译器内置宏,每次编译都会变。烧录后串口第一行时间戳对不上,就说明烧的还是旧程序。这个习惯在单片机毕业设计联调阶段特别省时间,尤其是同时改了硬件和软件的时候。
6.2 常见联调问题速查
| 现象 | 可能原因 | 处理 |
|---|---|---|
| ADC 值乱跳 | 地线环路、继电器干扰 | 单点共地,加 RC,软件滤波 |
| 蓝牙连不上 | 模块供电不足、波特率不对 | 独立 3.3V,先 AT 再透传 |
| 继电器吸合后复位 | 电源浪涌 | 续流二极管,单片机与负载分开供电 |
| 喂食量不准 | 舵机堵转、延时波动 | 限流,固定转动时间,实测标定 |
| 换水不停止 | 水位传感器失效 | 超时保护,回差判断 |
长时间运行测试至少跑 24 小时,每隔一小时记录一次温度和浊度,看有没有缓慢漂移。把__DATE__和__TIME__打出来后,再看串口第一行时间戳,能省掉很多“我明明烧了新程序”的扯皮。
本文还有配套的精品资源,点击获取