FEATURED · 精选文章

基于51单片机和ADS1110的水位监测报警系统设计

发布时间 / 2026/9/12 15:41:15
来源 / 创域科博编辑部
栏目 / 资讯中心
基于51单片机和ADS1110的水位监测报警系统设计 简介基于51单片机的水位监测报警系统是一套完整可用的课程设计/毕业设计参考方案面向单片机初学者、电子爱好者及嵌入式开发者。系统支持水位上下限设置当水位超过阈值时自动触发声光报警适用于水库、水箱、蓄水池等场景。压缩包共包含44个文件约840KB主要涵盖C语言源代码、Keil工程文件、Proteus仿真工程、原理图、PDF文档、元件清单及流程图等从软件到硬件设计均有覆盖目前已有135人学习下载。代码中用LCD1602进行显示、ADS1110完成数据采集并给出阈值比较、按键设置、报警控制等关键逻辑配合仿真工程可快速复现系统运行效果原理图、流程图与物料清单相互对应方便照图搭建与排查问题文件分类存放适合课设或毕设参考也可在此基础上扩展远程上报、自动排水等功能。1. 水位报警不是拿阈值硬比ADS1110 和 51 的资源分配才是关键水库水位报警这个题目看着只是“超过上限响一声、低于下限响一声”但实际做起来最烦的不是报警本身而是水面波动引起的反复触发。工业里水泵、电磁阀频繁启停多数不是设备坏了而是阈值判断没考虑滞回。这个基于 51 单片机的水位监测报警工程把感知、显示、设置、报警四段链路走完了ADS1110 负责采集液位变送器输出的模拟量LCD1602 显示实时水位按键可现场修改上下限超限时蜂鸣器与 LED 联动。工程设计上最大的亮点是弃用 ADC0809 这类并行 ADC改用 I2C 接口的 ADS1110为后续扩展比如多路采集、串口上传留出了大量 I/O 余量。对正在做 51 单片机课程设计的同学来说这套 Proteus 仿真和源代码能直接复现而且 ADS1110 的使用边界、阈值存储、流程图画法都是可以写进答辩稿的内容。2. 硬件信号链ADS1110 配置、液位变送器和物料清单2.1 ADC 选型为什么是 ADS1110 而不是 ADC0809 或电压比较器早期水位报警方案常用 LM393 电压比较器两个电位器分别设定上下限水位电压落在区间外就翻转输出。这种电路响应快、成本极低但有两个硬伤上限和下限靠电位器旋钮反复校准无法数字化保存水面波动时输出在阈值点附近抖动蜂鸣器会像“喘气”一样断续响必须另加 RC 迟滞电路才能稳住。而用 ADC0809 虽然能读数值但 8 路模拟输入是并行总线DB0-DB7 直接占掉 P0 整个端口还得处理 ALE 锁存时序留给 LCD1602 的引脚就很紧张。这个工程里出现的是 ADS1110一颗 16 位 ΔΣ ADCI2C 接口只占 SCL、SDA 两根线内置 2.048V 基准电压PGA 增益可编程1/2/4/8。对水库水位这种变化缓慢的物理量12.5~15SPS 的采样率绰绰有余。更重要的是 Proteus 的元件库里能直接搜到 ADS1110仿真行为和真片基本一致程序可以原样移植到实物。选型逻辑很清晰慢速物理量 需要数值显示 I/O 资源紧张I2C ADC 就是比并行 ADC 合适。物料清单BOM可以按下面这份准备元件型号和 Proteus 原理图里保持一致序号元件规格/型号数量用途1单片机STC89C52RC1主控与 AT89C52 引脚兼容2ADCADS11101I2C 接口 16 位 ΔΣ ADC3显示屏LCD16021显示水位电压与上下限4晶振11.0592MHz1系统时钟5电容30pF2晶振负载电容6电解电容10uF / 16V1复位电路定时7电阻10kΩ4I2C 上拉、按键下拉8排阻1kΩ×81P0 口上拉9三极管9012 PNP1蜂鸣器驱动10蜂鸣器5V 有源1报警输出11LED红色1报警指示灯12按键轻触 4pin3SET / UP / DOWN13电位器10kΩ1仿真中模拟液位传感器输出14液位变送器4-20mA 两线制1实物采集水位仿真用可省略15采样电阻50Ω 精密14-20mA 转 0.2-1V注意 P0 口在 51 单片机里是开漏输出驱动 LCD1602 数据线时必须要加 1kΩ 排阻上拉否则 LCD 可能只亮不显示。素材里原理图在 PC 端用 Altium Designer 打开 Sheet1.SchDoc 就能看到排阻位置。2.2 ADS1110 配置字与量程计算2.2.1 配置寄存器ADS1110 的配置寄存器是 8 位上电后主机通过 I2C 写一个字节即可完成设置。位定义如下Bit名称说明bit7SC0连续转换1单次转换后掉电bit6-5DR1 DR0数据速率选择00240SPS0160SPS1030SPS1115SPSbit4-3PGA1 PGA0增益001012104118bit2-0保留写入 0针对水位监测这种缓变信号我一般配置连续转换、15SPS、PGA1对应的配置字是 0x1C。15SPS 时片内数字滤波器对工频干扰有较好的抑制效果240SPS 虽然快但噪声明显变大。这个配置字在 ADS1110.C 初始化函数里通过 I2C 写入代码会在第 4 章展开。2.2.2 电压换算ADS1110 输出是 16 位二进制补码。PGA1 时满量程差分输入为 ±2.048VLSB 大小是 2.048V / 32767 ≈ 62.5uV。把 ADC 原始值换算成真实电压的公式为电压(mV) 原始值 * 2.048 * 1000 / 32768代码里一般先取 ADC 原始值乘 1000 得到毫伏再除以 32768如果要显示成“水位百分比”再按满量程做线性映射。这样处理的好处是主程序里不会出现浮点数51 单片机做浮点运算会拖慢节奏整数毫伏运算最适合 Keil C51。2.3 传感器信号链4-20mA 变送器如何接入 ADS1110实物场景里最常用的是两线制 4-20mA 液位变送器。它输出的是电流信号不能直接进 ADC需要在外部并联一个精密采样电阻把电流转成电压。很多人习惯并 250Ω 电阻得到 1-5V但 ADS1110 的量程只有 2.048V1-5V 进去直接削顶。我一般用 50Ω 精密电阻4mA 时产生 0.2V20mA 时产生 1V对应的 ADC 读数范围在满量程的 10%~50% 之间既留足了线性裕量又不会浪费分辨率。仿真工程里不需要纠结变送器模型直接用 Proteus 的电位器中心抽头接 ADS1110 的 AINAIN- 接地电压范围调到 0-2.048V 以内就行。电位器从 0% 旋到 100% 的电压变化模拟的就是水库从低水位涨到高水位的全过程报警阈值也就有了调试空间。3. 上下限设置阈值存放策略、按键状态机与长按连加3.1 阈值存放在哪main.c 全局变量还是外部 EEPROM这套工程的源码目录里只有 main.c、ADS1110.C、lcd1602.c没有单独的 EEPROM 驱动文件说明上下限阈值的默认方式是写在 main.c 里的全局变量中。全局变量方案在断电后会恢复成编译时的默认值好处是逻辑简单适合课程设计答辩时演示坏处是现场改了阈值一断电就丢。如果要做“可掉电保存的上下限”常见做法有两种。第一种是 STC89C52 片内自带 2KB EEPROM通过 ISP/IAP 指令读写不需要额外硬件第二种是外挂 AT24C02用 I2C 访问但这里 I2C 总线已经挂了 ADS1110地址上需要区分ADS1110 的 7 位地址是 0x48AT24C02 的默认地址是 0xA0两者不冲突。我的建议是课程设计用全局变量即可答辩时主动讲一句“如果产品化可以移到 STC 内部 EEPROM”既展示了扩展思路又不用实际增加代码量。阈值初始化时要注意一个边界条件上限必须大于下限否则逻辑会在“同时触发两种报警”的状态里卡死。我在 main.c 里会加一段保护判断#define DEFAULT_HIGH 2400 // 上限 2400mV #define DEFAULT_LOW 800 // 下限 800mV unsigned int high_limit DEFAULT_HIGH; unsigned int low_limit DEFAULT_LOW; void threshold_init(void) { if (high_limit low_limit) { high_limit DEFAULT_HIGH; low_limit DEFAULT_LOW; } }threshold_init() 这段代码的作用是上电时做一次合法性校验只要存储的上下限关系不合理立刻恢复成默认值。参数 DEFAULT_HIGH 和 DEFAULT_LOW 的单位是毫伏2400mV 对应 2.4V 的水位电压800mV 对应 0.8V具体数值要跟传感器的量程标定结果匹配这里只是占位。3.2 三按键状态机实现上下限设置用 3 个按键完成SET 负责在“运行 / 设置上限 / 设置下限 / 保存退出”之间切换UP 和 DOWN 负责加数值、减数值。用状态机来组织这套交互逻辑是最清晰的枚举定义和核心切换代码可以这样写typedef enum { MODE_RUN 0, // 运行态显示实时水位 MODE_SET_H, // 设置上限 MODE_SET_L // 设置下限 } SYS_MODE; SYS_MODE sys_mode MODE_RUN; void key_set_scan(void) { if (KEY_SET 0) { delay_ms(20); // 简单消除机械抖动 if (KEY_SET 0) { if (sys_mode MODE_RUN) { sys_mode MODE_SET_H; // 进入上限设置 } else if (sys_mode MODE_SET_H) { sys_mode MODE_SET_L; // 切换为下限设置 } else { threshold_save(); // 保存并退出 sys_mode MODE_RUN; } while (KEY_SET 0); // 等待按键释放 } } }这段代码里sys_mode 既是状态变量也是 LCD1602 显示内容的控制信号。在 MODE_RUN 状态下按下 SET 会进入上限设置再按一次切到下限设置第三次按下就调用 threshold_save() 保存当前修改并回到运行态。while (KEY_SET 0) 的作用是防止一次按下被重复触发多次配合 delay_ms(20) 完成消抖。注意这里 delay_ms(20) 只用在按键处理上因为按键是低频事件阻塞 20ms 不会影响 ADC 采集。3.3 按键去抖与长按连加设置上下限时要调到 2400mV 这种精确值靠一次一次按不太现实必须支持长按连加。长按的通用实现方案是用定时器产生 10ms 中断在中断里扫描按键并计数而不是把扫描放在主循环里用 delay 拖时间。unsigned int key_hold_cnt 0; unsigned int *p_set_value NULL; // 指向当前要修改的阈值 void timer0_isr(void) interrupt 1 { TH0 0xDC; // 重装初值10ms 中断一次 TL0 0x00; if (KEY_UP 0) { key_hold_cnt; if (key_hold_cnt 10) // 按住 100ms 触发第一次加 { (*p_set_value) 10; } else if (key_hold_cnt 50) // 按住超过 500ms 后连续加 { if (key_hold_cnt % 5 0) // 每 50ms 加一次避免跳太快 { (*p_set_value) 10; } } } else { key_hold_cnt 0; // 按键释放计数清零 } if (*p_set_value 2000) *p_set_value 2000; // 上限钳位 if (*p_set_value 100) *p_set_value 100; // 下限钳位 }key_hold_cnt 变量是核心它记录按键持续按下的时间片数量。计时到 10 次100ms时触发第一次数值增加超过 50 次500ms后进入连加模式并且用取模运算控制每隔 5 个中断50ms才加一次这样连加时数值不会瞬间冲到边界。钳位判断保证设置值永远落在 100mV 到 2000mV 的合理区间内防止用户把上限设成远超量程的数值。p_set_value 是全局指针进入 MODE_SET_H 时让它指向 high_limit进入 MODE_SET_L 时让它指向 low_limit这样 UP/DOWN 按键逻辑只需要写一份。矩阵键盘在这个场景里不是必须的因为 3 个按键足够完成所有交互。但如果你的设计里增加了模式切换、手动/自动切换功能按键超过 4 个就值得用 4×4 矩阵键盘加状态扫描来扩展核心判断逻辑与上面这套状态机完全一致只是按键扫描换成行列反转法。4. 主程序这样写I2C 采集、滞回比较和 LCD1602 刷新4.1 I2C 读写子程序与 ADS1110 读取时序ADS1110 的 I2C 时序看起来简单但有两个细节容易栽跟头地址的高低位方向和 ACK 应答位。先从底层函数开始梳理。sbit SCL P3^5; sbit SDA P3^4; void i2c_start(void) { SDA 1; SCL 1; delay_5us(); SDA 0; delay_5us(); SCL 0; } void i2c_stop(void) { SDA 0; SCL 1; delay_5us(); SDA 1; } bit i2c_write_byte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { SDA (dat 0x80) ? 1 : 0; // 高位先出 dat 1; SCL 1; delay_5us(); SCL 0; } SDA 1; // 释放 SDA准备接收 ACK SCL 1; delay_5us(); return SDA; // 返回从机应答位 } unsigned char i2c_read_byte(bit ack) { unsigned char i, dat 0; for (i 0; i 8; i) { SCL 1; dat 1; if (SDA) dat | 0x01; SCL 0; } SDA ack; // 主机发送 ACK(0) 或 NACK(1) SCL 1; delay_5us(); SCL 0; SDA 1; return dat; }i2c_start() 和 i2c_stop() 是 I2C 总线最基本的时序SCL 高电平期间 SDA 产生下降沿是起始条件上升沿是停止条件。i2c_write_byte() 每次发送 8 位数据最高位在前发送完第 8 位后释放 SDA 线并拉高 SCL读取从机的 ACK 应答位。i2c_read_byte(bit ack) 接收数据时在时钟高电平期间读取 SDA 电平拼装成字节读完一个字节后通过参数 ack 控制主机发送应答还是非应答。这两层函数是所有 I2C 器件驱动的地基ADS1110 和 AT24C02 都能复用。读取 ADS1110 的完整初始化与采样函数如下#define ADS_WR_ADDR 0x90 // 0x48 左移 1 位 写位 0 #define ADS_RD_ADDR 0x91 // 0x48 左移 1 位 读位 1 void ads_init(void) { i2c_start(); i2c_write_byte(ADS_WR_ADDR); // 发送器件写地址 i2c_write_byte(0x1C); // 连续转换、15SPS、PGA1 i2c_stop(); } unsigned int ads_read(void) { unsigned char i, high, low; unsigned int value; i2c_start(); i2c_write_byte(ADS_RD_ADDR); // 发送器件读地址 high i2c_read_byte(0); // 读高字节主机回 ACK low i2c_read_byte(1); // 读低字节主机回 NACK i2c_stop(); value ((unsigned int)high 8) | low; return value; }ads_init() 里写入的 0x1C 是和 2.2 节配置字对应的bit7 为 0 表示连续转换模式bit6-5 为 11 表示 15SPSbit4-3 为 00 表示 PGA1。bit2-0 保留位写 0。ads_read() 中读地址是 0x91注意不是 0x900x90 是写配置时用的地址。如果地址搞反I2C 总线会一直收不到 ACKads_read 返回 0xFFFFLCD 上显示的就是满量程。读回 16 位原始值后主函数里换算成毫伏unsigned int adc_value_mv; adc_value_mv (unsigned int)(((unsigned long)ads_read() * 2048) / 32768);这一步用 32 位中间变量做了乘除法避免 16 位乘法溢出。原始值 32768 对应 2048mV换算结果就是当前水位的电压值单位毫伏。水位呈现非线性时可以在这一行之后加查表或分段线性修正不过对课程设计场景线性映射已经足够。4.2 带滞回的双阈值判断逻辑这是整个工程最值得展开的部分。如果不做滞回只写“电压大于上限就报警”那么当水位恰好在上限附近波动时蜂鸣器会反复响-停-响-停严重影响使用体验。滞回判断的要点是进入报警和退出报警使用不同的阈值点。当前状态进入报警条件退出报警条件正常运行adc_mv ≥ 上限阈值-高水位报警-adc_mv ≤ 上限阈值 - 滞回值低水位报警adc_mv ≤ 下限阈值adc_mv ≥ 下限阈值 滞回值我在 main.c 中的实现是这样的#define HYSTERESIS 50 // 滞回窗口单位 mV unsigned char flag_high 0; unsigned char flag_low 0; void alarm_check(unsigned int adc_mv) { // 高水位判断进入用高阈值退出用高阈值减滞回 if (!flag_high) { if (adc_mv high_limit) { flag_high 1; } } else { if (adc_mv (high_limit - HYSTERESIS)) { flag_high 0; } } // 低水位判断进入用低阈值退出用低阈值加滞回 if (!flag_low) { if (adc_mv low_limit) { flag_low 1; } } else { if (adc_mv (low_limit HYSTERESIS)) { flag_low 0; } } if (flag_high || flag_low) { BUZZER 0; // 蜂鸣器低电平导通 LED_RED 0; // LED 点亮 } else { BUZZER 1; LED_RED 1; } }用 flag_high 和 flag_low 两个标志位记录当前是否处于报警状态而不是直接用电压比较结果去驱动蜂鸣器这是整个逻辑的关键。HYSTERESIS 取 50mV当水位超过上限时报警必须回落到上限以下 50mV 才解除低水位报警同理必须回升到下限以上 50mV 才解除。50mV 这个值来自水面波动幅度和 ADC 噪声的折中波动大时可以调到 100纯实验室环境也可以减到 20。定时器中断每隔 200ms 调用一次 alarm_check()这个周期对水位变化足够快又不会让蜂鸣器因为高频刷新而出现杂音。蜂鸣器驱动在实物里接 PNP 三极管I/O 口输出低电平导通输出高电平关闭仿真里 Proteus 的 BUZZER 模型直接接 P1.0 也能工作但实物必须加三极管51 单片机 I/O 口直接驱动有源蜂鸣器电流不够。4.3 LCD1602 的显示刷新策略lcd1602.c 里的底层函数只做四件事写命令、写数据、清屏、光标定位。显示内容控制在两行以内第一行显示实时水位电压第二行显示当前上下限值。void disp_refresh(unsigned int adc_mv) { unsigned char buf[17]; lcd_write_cmd(0x80); // 第一行起始地址 sprintf(buf, Level:%4d.%d V , adc_mv / 1000, (adc_mv % 1000) / 100); lcd_write_str(buf); lcd_write_cmd(0xC0); // 第二行起始地址 sprintf(buf, H:%d.%02d L:%d.%02d, high_limit / 1000, (high_limit % 1000) / 10, low_limit / 1000, (low_limit % 1000) / 10); lcd_write_str(buf); }disp_refresh() 每 500ms 调用一次。刷新频率太快LCD 会看到明显的闪烁太慢水位变化看起来卡顿。500ms 对 1602 这种静态显示是合适的。代码里 sprintf 把数值拆成整数位和小数位显示LCD1602 是字符型屏显示“Level:2.4 V”和“H:2.40 L:0.80”这种格式比显示纯数字直观得多。注意 lcd_write_cmd(0x80) 是准备第一行缓冲区0xC0 是第二行缓冲区这两条指令是 1602 显示控制的惯例写法。显示出现乱码先看是否每次刷新前都 lcd_write_cmd(0x01) 清屏了——清屏指令会全屏刷新调用太频繁就是闪烁根源。我的做法是只覆盖当前行不清屏用 0x80/0xC0 直接定位光标然后整行覆盖写入末尾的 0x20 空格负责擦除上次残留的字符。5. Proteus 8.7 仿真搭建与实物联调验证5.1 从 pdsprj 打开工程到跑通仿真工程压缩包里的 New Project.pdsprj 是 Proteus 8.7 工程文件直接用 Proteus 8 Professional 打开即可。双击原理图中的 AT89C52 芯片把 Program File 指向主目录下编译生成的 main.hex再把 Crystal Frequency 改成 11.0592MHz点运行就能看到 LCD1602 亮起来显示当前模拟水位电压。仿真中 ADS1110 的接线需要注意三个点AIN 接电位器抽头AIN- 直接接地SCL/SDA 分别接 P3.5/P3.4且两个引脚各接一个 4.7kΩ 上拉电阻到 VCC。在 Proteus 里不加上拉电阻I2C 总线也能跑通因为 ADS1110 仿真模型内部做了弱上拉处理但实物板子上 ADS1110 属于开漏输出不加上拉电阻SCL/SDA 信号根本抬不到高电平通信必然失败。仿真通过不代表实物没问题这一点要在接线时区分清楚。5.2 仿真和实物的三个典型差异第一个差异是电源噪声。Proteus 里的电位器输出是理想的直流电压ADC 读到的数值纹丝不动实物中 4-20mA 变送器是电流环供电电源纹波和变送器本身噪声都会叠加到采样电阻上。我给实物的 ADS1110 电源引脚并联了 100nF 陶瓷电容和 10uF 电解电容AIN 输入端再串一个 1kΩ 电阻并联 100nF 电容做低通滤波截止频率大约在 1.6kHz对缓慢变化的水位信号毫无影响但对 50Hz 以上噪声有明显的衰减作用。第二个差异是报警器件驱动。Proteus 里 BUZZER 模型两端加 5V 就能响但真实的有源蜂鸣器工作电流通常在 20-30mA51 单片机 I/O 口最大灌电流只有 20mA 左右推不动。用 9012 三极管放大基极串 1kΩ 电阻接 P1.0发射极接 VCC集电极接蜂鸣器负极蜂鸣器正极接 VCC。这样 P1.0 输出低电平时三极管导通蜂鸣器得电发声。仿真里没有三极管照样响容易让人忽略这一步实物焊接时漏掉三极管报警功能大概率起不来。第三个差异是阈值电压的标定。仿真里可以随便把上限设成 1.5V 来演示实物里液位变送器在 0% 水位输出 4mA对应 0.2V100% 水位输出 20mA对应 1V。所以实物调试时应该在水位最低点时读一下 ADC 毫伏值记作 zero_scale在水位最高点读最大值记作 full_scale再把 high_limit 和 low_limit 设为两个刻度之间的百分比位置而不是直接沿用仿真的电压值。5.3 功能验证清单跑完仿真和实物后按这张表逐项确认能覆盖这个工程的绝大部分功能点检查项操作预期结果默认阈值显示上电后观察 LCD第一行显示当前水位电压第二行显示默认上下限上限报警电位器电压调到上限之上蜂鸣器响、LED 亮、LCD 数值随电压变化滞回恢复电压缓慢降到上限-50mV 以下报警解除下限报警电压调到下限之下报警器触发阈值设置按 SET 进入设置UP/DOWN 修改修改后回到运行态新阈值立即生效阈值边界尝试把下限设到上限之上数值被钳位或恢复默认系统不死机阈值边界这一项在仿真里容易被忽略但答辩时评委最喜欢问“如果我把下限设得比上限还高会怎样”因为初始化做了 clamp 校验加上 alarm_check 里的滞回判断即使出现这种误操作也只是同时触发高水位和低水位标志系统不会死循环。这个细节值得在 turn-in 报告里单独写一段比贴整页代码更能体现工程意识。仿真通过后把 ADS1110 换成实物把电位器换成液位变送器和采样电阻原理图和源代码不需要任何改动这就是这套工程在硬件上留出的余量——ADS1110 的 I2C 地址固定为 0x48不占用额外 I/O51 单片机的 P1/P2 口还能继续挂矩阵键盘和继电器模块。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻