ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Proteus的51单片机温度电压电流检测系统设计与仿真

基于Proteus的51单片机温度电压电流检测系统设计与仿真 简介基于51单片机与Proteus仿真实现的温度、电压、电流检测系统面向电子、自动化等相关专业的课程设计或入门实践者帮助在没有实体硬件的情况下完成参数采集、处理与显示的全流程验证。资源共有23个文件包括Proteus仿真工程pdsprj/pdsbak、C语言源代码c/uvproj/uvopt、Hex烧录文件、工程列表与说明文档以及两张运行效果截图压缩包整体仅419KB轻量易用。设计涵盖电压信号测量显示、电流信号测量显示、DS18B20温度传感器检测显示并通过液晶屏同时展示三类数据电路和程序结构清晰便于对照学习单片机ADC采样、传感器驱动及显示接口编程。目前已有123人学习下载适合需要快速搭建仿真、参考源码或进行毕业设计/课程设计改写的读者可直接在Proteus中运行调试节省构建电路的时间。1. 为什么用Proteus搭51单片机三参数检测系统用51单片机配合Proteus仿真实现温度、电压、电流同时检测很多人第一反应是搭一个复杂的数采系统但实际上用Proteus仿真可以在不焊接电路板的情况下完整跑通整个流程。这套基于51单片机的温度、电压、电流检测系统就是这样的一个典型项目它把模拟采样、数字控制、单总线传感器和液晶显示都集成在了同一个仿真环境里。对于正在做课程设计或者刚入门单片机的人来说它最大的价值不是那几段看起来简单的代码而是演示了怎么把一个多路采集需求拆成“传感器 → 调理电路 → ADC → 单片机 → 显示”这个标准链路。项目里包含完整的Proteus 8仿真文件和Keil写的C51源代码拿到手可以直接打开跑起来适合对照学习也适合作为毕业设计的基础框架继续扩展。这套系统的仿真文件里有两个关键部分一个是Proteus下的电路图另一个是Keil工程下的“51电流检测程序.uvproj”。后者虽然名字只叫“电流检测”但实际源码里已经把电压、电流、温度三路采集都写了进去。打开工程后你会发现代码注释不算多但结构清晰适合自己动手改一改、加功能。对于想理解“单片机如何读取模拟信号”的人这套资源提供了一个很完整的参考。2. 硬件电路设计电压采样、电流采样与DS18B20接入2.1 系统总体架构与器件选型整个系统以AT89C51单片机为控制核心。AT89C51是51系列中最经典的型号虽然没有内置ADC但配合外部扩展的ADC0808就能同时采集多路模拟量。ADC0808是8位、8通道逐次逼近型ADC最大采样速率约10kSPS对于电压和电流这种变化缓慢的直流信号来说完全够用。温度检测选用DS18B20数字温度传感器它输出的是数字信号不需要ADC直接用普通IO口按照单总线时序读取即可。显示部分用LCD1602两行可以分别显示温度和电气参数。整体数据流是被测电压经过电阻分压后送入ADC0808的IN0通道被测电流通过采样电阻转换成电压再经过运放LM358放大后送入IN1通道DS18B20挂在P3.7口使用单总线协议与单片机通信单片机处理完数据后刷新LCD1602显示。Proteus模型里这些器件都有现成的仿真模型不需要关心封装和上电时序只要逻辑连线正确就能工作。2.2 电压信号采样电路ADC0808的模拟输入范围是0到5V而实际被测电压往往会超过这个范围。最常见的处理方式是先用电阻分压把电压降到ADC可接受的区间。以测量0到25V电压为例选取R120kΩ、R25kΩ分压比就是R2/(R1R2)0.2也就是5:1衰减。25V输入时分压点为5V正好达到ADC满量程。参数名称取值说明分压电阻R120kΩ接被测电压正极分压电阻R25kΩ接地中间抽头接ADC IN0滤波电容C10.1μF并联在R2两端滤除高频干扰分压比5:1对应最大输入25V分压电阻的取值不能太大否则信号源内阻过高会影响ADC采样精度也不能太小否则功耗过大。Proteus仿真时可以用一个电位器来模拟被测电压调整滑动端时观察ADC采到的数值变化。注意分压后的电压绝对不能超过5V否则会损坏ADC0808的输入引脚仿真中则会出现数值饱和或波形削顶。2.3 电流信号采样电路电流检测不能直接进ADC需要先经过采样电阻转化为电压信号。这里选用0.1Ω/2W的精密采样电阻串联在电流回路中当流过2A电流时压降为0.2V。这个电压太小直接用8位ADC分辨率太低所以需要用LM358构成同相放大电路将电压放大到适合ADC的范围。放大倍数计算为 G 1 (Rf/Rg)。取Rf24kΩRg1kΩ则G25倍。0.2V输入放大后得到5V对应满量程2A。下表给出了几个关键器件参数参数名称取值说明采样电阻Rs0.1Ω / 2W电流流过产生压降运放增益G25由Rf/Rg决定Rg1kΩ运放反相端对地电阻Rf24kΩ反馈电阻满量程电流2A对应运放输出5V在Proteus中模拟电流源可以用带内阻的直流电源或者把电压源串联一个大电阻来等效恒流源。如果仿真时运放输出超过5VADC读到的值会一直保持255这时要检查采样电阻是否选得过大或者被测电流是否超过了设计量程。实际做实物时采样电阻的功率选型很关键2A电流下0.1Ω电阻功耗是0.4W留余量选2W比较稳妥。2.4 温度传感器DS18B20接口电路DS18B20只有三根引脚VCC、GND和DQ。DQ接单片机的P3.7口同时需要外接一个4.7kΩ上拉电阻到VCC因为单总线是开漏输出的。它的工作电压范围是3.3V到5VProteus模型里直接用5V供电即可。DS18B20内部有12位ADC温度分辨率为0.0625℃测量范围-55℃到125℃满足环境温度检测需求。这里要注意上拉电阻不是可选的。如果没有上拉复位时序和读写时序都会失败仿真中会看到温度读出固定值-55或者读到0xFF。DS18B20的通信时序是微秒级的后面代码部分会详细解释硬件上只要保证DQ引脚没有过大的负载电容即可。2.5 液晶显示与单片机最小系统LCD1602的8根数据线接P0口但P0口内部没有上拉电阻需要外部加10kΩ上拉排阻。控制线RS、RW、EN分别接P2.0、P2.1、P2.2。LCD的3脚VO用来调节对比度Proteus中通过一个10kΩ电位器接可调电压通常调到1V左右显示效果最佳。VO接地会导致屏幕全部变黑VO接5V则会导致白屏这一项是很多新手在仿真时卡住的主要原因。单片机最小系统包括复位电路和晶振电路。Proteus中晶振设为12MHz对应代码里的延时函数都是按12MHz计算的。注意AT89C51的复位引脚RST高电平有效复位电路用10μF电容和10kΩ电阻组成。在Proteus中这些元件都有默认参数一般不需要修改。3. 51单片机软件实现ADC驱动、DS18B20时序与LCD显示3.1 工程结构与寄存器定义打开Keil工程“51电流检测程序.uvproj”源码中会看到头文件、硬件引脚宏定义和各个功能子函数。标准51的寄存器定义在reg51.h中使用sbit关键字把具体的单片机引脚映射到ADC0808、DS18B20和LCD1602的控制线上。#include reg51.h #include intrins.h #define uchar unsigned char #define uint unsigned int // ADC0808控制引脚 sbit START P1^0; sbit EOC P1^1; sbit OE P1^2; sbit ALE P1^3; // ADC0808通道地址选择 sbit ADDA P1^5; sbit ADDB P1^6; sbit ADDC P1^7; // DS18B20单总线引脚 sbit DQ P3^7; // LCD1602控制引脚 sbit RS P2^0; sbit RW P2^1; sbit EN P2^2;以上代码把ADC0808的数据输出口设定为P0与LCD共用。这里存在总线冲突但通过代码控制使LCD在ADC采样的瞬间处于未使能状态可以规避大部分问题。如果你想在实物上运行建议把ADC0808的数据输出改到P1口P1口同时承担控制引脚这样可以彻底避免冲突。sbit定义的好处是修改硬件连接时只需要改一行代码。3.2 ADC0808驱动与多通道采集ADC0808的转换时序由ALE、START、EOC、OE四个信号控制。启动一次转换时先将通道地址通过ADDA-ADDC传入拉高ALE锁存地址然后给START一个短脉冲转换开始后EOC会变低。转换完成后EOC恢复高电平此时使能OE从数据口读取8位结果。// 从指定通道读取ADC原始值chn取值范围0-7 uchar read_adc(uchar chn) { uchar tmp; // 设置通道地址 ADDA chn 0x01; ADDB chn 0x02; ADDC chn 0x04; // 地址锁存脉宽 ALE 1; START 1; _nop_(); _nop_(); ALE 0; START 0; // 等待转换结束 while (!EOC); // 打开输出使能 OE 1; tmp P0; OE 0; return tmp; }这段代码里_nop_()是空指令在12MHz晶振下一次约1μs让ALE和START保持足够的时间。while(!EOC)是死等如果ADC0808没有接合适的参考时钟转换永远不会完成仿真就会卡死。大部分课程设计会给ADC0808的CLOCK引脚提供500kHz-1MHz的时钟信号可以用AT89C51的ALE引脚直接驱动但ALE频率是晶振的1/6也就是2MHz有些偏快。简单的做法是在Proteus里用信号发生器给CLOCK接一个1MHz方波或者在代码里用P1.4手动翻转时钟但这会占用CPU。我通常习惯在仿真中直接连接时钟源。为了抑制噪声实际采集时会连续读多个值再做滤波。下面的函数实现了一个非常直接的“去极值平均滤波”// 对指定通道采样10次去除最大值和最小值后取平均 uchar get_adc_mid(uchar chn) { uchar i, j, tmp; uchar val[10]; uint sum 0; for (i 0; i 10; i) { val[i] read_adc(chn); } // 冒泡排序交换元素 for (i 0; i 9; i) { for (j 0; j 9 - i; j) { if (val[j] val[j 1]) { tmp val[j]; val[j] val[j 1]; val[j 1] tmp; } } } for (i 1; i 9; i) { sum val[i]; } return (uchar)(sum / 8); }排序10个元素每次耗时很短在仿真中基本无感。但如果在实际产品中冒泡排序在中断里执行会对实时性有影响建议改成正序保存极值的方式时间复杂度O(n)即可。核心思想是丢弃突变值保留中间集中的采样结果。3.3 DS18B20单总线时序与温度读取DS18B20的驱动是所有传感器里最容易出Bug的部分因为时序要求是微秒级的。在12MHz晶振下一条普通NOP约1μs延时函数必须与晶振匹配。复位时序要求主机拉低总线至少480μs释放后等待60-240μs传感器会拉低60-240μs作为存在脉冲。存在脉冲拉低时主机应读取DQ引脚电平。// 延时x微秒12MHz下近似 void delay_us(uint x) { while (x--) _nop_(); } // DS18B20复位返回1表示检测到传感器 bit ds18b20_reset(void) { bit presence; DQ 1; delay_us(2); DQ 0; delay_us(500); DQ 1; delay_us(65); presence DQ; // 读到0表示有存在脉冲 delay_us(400); return (presence 0); } // 写入一个字节低位在前 void ds18b20_write_byte(uchar dat) { uchar i; for (i 0; i 8; i) { DQ 0; DQ dat 0x01; // 在拉低后15us内决定是0还是1 delay_us(60); DQ 1; dat 1; } } // 读取一个字节低位在前 uchar ds18b20_read_byte(void) { uchar i, dat 0; for (i 0; i 8; i) { dat 1; DQ 0; _nop_(); // 拉低一个时间片后释放 DQ 1; _nop_(); if (DQ) dat | 0x80; delay_us(60); } return dat; }写时序和读时序的区别在于采样窗口的位置。写1时在拉低总线后尽快拉高写0时保持低电平。读时序则是主机拉低总线触发然后释放在随后的15μs内读取数据线状态。注意在ds18b20_write_byte里DQ dat 0x01;这行在拉低后立即执行dat的低位如果为1则DQ为1否则为0。由于DQ在DQ0之后马上被赋值人为造成了依赖时序实际芯片上也是用这条语句实现“写1”与“写0”的区别。温度转换完成后从暂存器读取9个字节其中前两个是温度低字节和高字节。温度值以16位有符号补码存储分辨率固定为0.0625℃。下面是将原始值转换为缩小10倍的整型温度值例如25.6℃返回256// 读取环境温度返回温度值*10 int get_temperature(void) { uchar low, high; int temp; if (!ds18b20_reset()) return -400; ds18b20_write_byte(0xCC); // 跳过ROM ds18b20_write_byte(0x44); // 启动温度转换 while (!DQ); // 等待转换完成DS18B20会拉低总线 ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存器 low ds18b20_read_byte(); high ds18b20_read_byte(); temp (high 8) | low; temp temp * 10 / 16; // 补码转换并乘10 return temp; }这里while(!DQ)的等待不太可靠因为如果传感器没接好DQ永远是高程序可以正常跳出但如果DQ被永久拉低程序会卡死。更好的做法是延时750ms后再读取温度。Proteus仿真中DS18B20的转换时间可以根据模型设定通常几百微秒就够了但代码里延时不长会导致读回上一次的值。3.4 数据处理与LCD1602显示ADC原始值需要换算成实际的物理量。电压通道满量程5V对应255所以电压U (adc_val * 5.0) / 255。电流通道经过采样电阻和放大器实际电流I U / (Rs × G)。由于51单片机浮点运算较慢通常采用放大100倍的整数运算。下面代码展示了如何把ADC值转换成带两位小数的字符串并显示// 电压显示adc_val 0-255 - 0.00-5.00V void show_voltage(uchar adc_val) { uint v100; char buf[8]; v100 (uint)(adc_val * 500L / 255); // 放大100倍 buf[0] (v100 / 100) 0; buf[1] .; buf[2] (v100 % 100 / 10) 0; buf[3] (v100 % 10) 0; buf[4] V; buf[5] \0; lcd_show_string(1, 0, Volt: ); lcd_show_string(1, 6, buf); } // 电流显示标度变换系数由采样电阻0.1Ω和放大倍数25决定 void show_current(uchar adc_val) { uint i100; char buf[8]; // I adc_val * 5.0 / 255 / (0.1 * 25) * 100 i100 (uint)(adc_val * 784L / 1000); buf[0] (i100 / 100) 0; buf[1] .; buf[2] (i100 % 100 / 10) 0; buf[3] (i100 % 10) 0; buf[4] A; buf[5] \0; lcd_show_string(1, 8, Curr:); lcd_show_string(1, 13, buf); }代码中电流系数784来源是满量程2A对应2551A对应约127.5放大100倍就是127.5但实际上255/2≈127.5要得到安培数的100倍系数是255/2*10012750需要再除以255? 更直接的方式是提前算好比例2A对应255因此1A对应127.5电流放大100倍的值 adc_val * 100 / 127.5 ≈ adc_val * 0.784。所以上面的784L/1000写法就是 adc_val * 0.784结果放大100倍。这种定点变换避免浮点库在51上能省大量代码空间。3.5 主循环与多任务调度main函数先初始化LCD然后进入死循环。每轮循环读取一次温度、两次ADC再刷新显示。由于DS18B20转换需要时间整个显示周期大约1s满足大多数监控场景。void main(void) { uchar adc_v, adc_i; int temp; lcd_init(); while (1) { temp get_temperature(); adc_v get_adc_mid(0); adc_i get_adc_mid(1); show_temperature(temp); show_voltage(adc_v); show_current(adc_i); delay_ms(50); } }这里的delay_ms是一个软件延时函数在仿真中能让显示稳定一点。如果后续要扩展报警功能可以在循环里判断temp是否越界然后驱动蜂鸣器或继电器。注意不要在延时内做其他事情否则按键响应会变慢。4. Proteus仿真搭建、联调与常见错误4.1 在Proteus中放置元件与连线打开Proteus 8新建工程并选择“Schematics”。从左侧元件库搜索并放置AT89C51、ADC0808、DS18B20、LM358、LCD1602、电阻、电容、电位器。放置顺序建议先放单片机然后按功能模块依次摆放。连线时使用Wire工具注意ADC0808的CLOCK引脚需要接地或接时钟源这里直接接一个1MHz的时钟源。LCD1602的VO脚接一个电位器电位器两端分别接5V和GND中间抽头接VO。连线完成后双击单片机模型在Program File一栏选择Keil生成的.hex文件。如果编译无误点击左下角的运行按钮LCD屏幕应该开始显示数值。如果屏幕没有反应先暂停仿真把鼠标放到LCD上检查引脚颜色高电平为红色低电平为蓝色悬空为灰色这样可以快速找到断路点。4.2 加载HEX文件与运行Keil中需要勾选“Output”选项卡里的“Create HEX File”才能生成hex。生成后文件默认在工程目录下的Objects文件夹内。在Proteus中单击单片机在Program File里选择路径注意文件类型要选.hex。设置晶振频率为12MHz其他保持默认。如果程序正常但LCD显示一串乱码通常是LCD初始化时延时不够。在Proteus中LCD模型响应很快但在实物上要等待几十毫秒。解决方案是在lcd_init()里拉高EN延时一段再清屏。4.3 总线冲突与ADC共P0的坑最容易被忽略的问题是ADC0808和LCD1602同时占用P0口。Proteus仿真中并行总线上的数据会发生“线与”当LCD的EN为高电平时任何写入P0的数据都会被LCD当成显示数据读取。ADC0808读数据时如果LCD使能就会导致LCD显示字符跳变。规避方法有三个第一修改代码在读写ADC之前禁止LCD使能读完立即恢复。这种方法不稳定但在本项目中能跑。第二把ADC0808的数据输出改到P1口完全避开冲突。第三用锁存器74LS373把LCD数据线和P0隔离开来。第三种是实际产品中常用的方案但会增加硬件复杂度。我的建议是第一种最省事但要在代码里严格保证EN引脚在ADC采样期间为低。可以单独写一个函数void adc_enter(void) { EN 0; // 禁止LCD RW 0; RS 0; } void adc_exit(void) { EN 1; }然后在read_adc()开头调用adc_enter结束调用adc_exit。不过这样会改变原有代码结构也增加了出错概率。如果你不想改源码在Proteus里直接把LCD数据线接到P2口代码中把LCD相关的P0改成P2效果一样代价是需要改lcd函数。4.4 排错从现象定位问题现象可能原因排查方法LCD无显示亮度正常对比度不对调整电位器使VO约为1VLCD全黑VO电压过低上拉电位器温度显示-55DS18B20复位失败检查4.7k上拉电阻温度数值不变转换等待时间过短增加750ms延时电压数值满量程分压电路接触不良检查分压电阻连接程序运行但ADC不更新ADC0808 CLOCK未接加1MHz时钟源电压电流显示跳动P0总线冲突改用P1数据口或加锁存器这四个排错项基本覆盖了刚上手时会遇到的大部分问题。确认一个项目是否能真正工作关键看三个信号是否正常ADC0808的EOC是否翻转、DS18B20的DQ引脚是否存在脉冲、LCD的EN引脚是否有周期性下降沿。用Proteus的虚拟示波器挂在这三个引脚上一轮就能看出来。5. 提升采样精度与实时性的几个实用技巧5.1 用移动平均替代去极值平均前面的get_adc_mid函数每次排序10个数据浪费算力。实际采样时如果信号本身是平滑的直流简单算术平均就够了。但如果遇到随机尖峰脉冲去掉极值反而会引入误差。更稳的滤波方式是一次只丢一个最大值和一个最小值然后对剩余数据取平均。改进后的写法只需一次遍历uchar get_adc_filter(uchar chn) { uchar i, tmp; uchar max 0, min 255; uint sum 0; for (i 0; i 10; i) { tmp read_adc(chn); if (tmp max) max tmp; if (tmp min) min tmp; sum tmp; } sum - max min; return (uchar)(sum / 8); }这段代码比排序算法快得多而且在仿真中表现更好。如果你面对的是50Hz工频干扰更好的办法是在ADC采样前加入RC低通滤波硬件上在采样电阻输出端并联一个1μF电容到地截止频率约160Hz。5.2 两点校准法由于分压电阻、采样电阻和运放增益都存在误差直接用理论系数计算出的电压电流值往往有偏差。一个很实用的校准方法是选取两个已知参考点进行线性校正。比如电压测量时输入1.00V记录ADC值adc_low输入5.00V记录adc_high然后在代码中用插值公式计算:// 已知电压点 adc_low 对映1.00Vadc_high 对映5.00V uint get_voltage_100(uchar adc_val) { long slope; slope (5L - 1L) * 100 / (adc_high - adc_low); return slope * (adc_val - adc_low) 100; }校准参数可以预装在单片机EEPROM中没有EEPROM的51单片机可以用#define宏写死在程序开头。温度传感器DS18B20本身出厂校准过不需要额外校准。电流通道如果使用运放同样可以用两点法消除放大器失调电压。5.3 用定时器中断调度采样周期主循环里延时1秒刷新显示拿到实际环境中总觉得“卡顿”。更好的做法是使用定时器0产生10ms时基中断主循环只处理按键和报警。ADC采样和DS18B20转换状态机放在中断外用标志位控制。例如volatile bit flag_10ms; void timer0_isr() interrupt 1 { TH0 0xEE; // 12MHz下重装定时值约10ms TL0 0x00; flag_10ms 1; }在主循环中检查flag_10ms清零后执行采样。这样可以保证按键响应速度在10ms以内而不会因为温度转换的750ms等待而卡死。Proteus仿真中同样可以用这个技巧还能观察虚拟示波器上的采样时钟波形。5.4 把显示刷新与采样解耦温度转换需要等待而ADC采样和LCD刷新都不需要。如果每次都串行完成所有任务整个循环周期会被DS18B20拖到800ms以上。最常见的优化是设置一个温度就绪标志只有在上一次转换完成时才读取温度其他时间只刷新电压和电流。这样电压电流的刷新频率可以提高10倍温度则可以每秒更新一次。这在诸如电机监控这类对电流实时性要求高的场景下很有用。5.5 用Proteus的电表探针做自动验证Proteus中有电压表和电流表探针把它们挂在单片机引脚上可以快速验证传感器是否工作。方法是在仿真运行时从工具栏中拖入电压探针放在ADC0808的IN0引脚仿真后可以看到实时波形。还可以结合图表模式把探针拖入数据图表记录整个仿真过程中电压电流的变化曲线。用这个方式可以直观地验证标度变换是否正确。例如将输入电压从0V调到5V观察LCD数值是否线性变化如果线性度不够优先检查分压电阻是否等比例。以上这几个技巧不需要改动核心硬件却能让整个系统的稳定性和可维护性提升一个台阶。调试仿真和实物时按这个顺序逐步验证至少能减少一半的排查时间。本文还有配套的精品资源点击获取
返回列表