简介:一份基于51单片机的热敏电阻数字温度计毕业设计资料包,面向电子类专业学生、单片机初学者及需要完成课程设计或毕业设计的读者。内容围绕PT100热敏电阻测温系统展开,完整覆盖测温、信号放大、A/D转换、控制显示四大模块,并包含AT89C51单片机选型、ADC0804转换电路、Keil µVision 4软件编程及Proteus仿真调试等关键环节。压缩包内仅有1个doc文档,大小543KB,文档为完整毕业设计说明书,包含摘要、目录、硬件电路原理、软件流程与测试分析,还提供误差分析和系统测试结论,便于直接参考或二次开发。目前已有424人学习/下载,尤其适合课程设计、毕业设计或工程入门阶段作为温度测量类项目从方案设计到仿真实现的全流程参考。
1. 别再纠结DS18B20,AT89C51+PT100这套模拟链路其实更值得复现
很多人一提到51单片机做温度计,第一反应就是DS18B20单总线数字传感器,接线简单、代码少,好像理所当然。但真正在工业现场或者毕业设计里被反复验证的方案,反而是“PT100铂热电阻 + LM324运放 + ADC0804并行AD”这条全模拟链路。原因很现实:PT100在-200℃到650℃范围内线性度好、互换性强,而且通过恒流源激励配合差分放大,能明显削弱导线电阻和电源纹波的影响——这是DS18B20这类PN结传感器做不到的。这套设计用AT89C51做处理核心,把PT100随温度变化的电阻值转换成电压,再经ADC0804量化后查表显示到数码管。适合想搞懂“传感器信号调理到单片机接口”完整链条的嵌入式初学者,也适合要写课程设计说明书的人。本文按硬件链路、仿真、C代码到标定坑的顺序把整个工程撕开讲。
2. 信号链全局设计:从PT100到数码管的每一级都在干什么
2.1 为什么选PT100恒流源测温而非桥式电路
PT100的测温本质是把电阻变化变成电信号。最常见的两种采样电路是电桥和恒流源。原设计用的是恒流源式,即向PT100灌入固定电流I,然后测量其两端电压U=I×R。这样做的好处是输出电压与电阻值成正比,后续计算不用处理电桥的非线性衰减。若取I=2.55mA,0℃时PT100阻值100Ω,输出电压就是0.255V;100℃时阻值138.5Ω,输出电压约0.353V。这个电压范围对ADC0804来说偏小,所以必须加放大级。
这里有个容易忽略的点:恒流源电流不是随便选的。电流太大,PT100自身发热会引入测温误差;电流太小,输出信号幅度不足,放大级噪声占比高。2.55mA是一个折中值,PT100自热功率约0.65mW(按100Ω计算),对工业级测温可以接受。
2.2 LM324同相放大电路参数计算
LM324是四运放,这里只用其中一路,接成同相放大。设PT100输出电压为Ui,放大倍数为A,则ADC0804输入端电压Uo = A × Ui。原设计取放大倍数25倍,这样0℃时Uo≈6.375V,超过ADC0804的满量程5V,所以实际电路常把电流降到2.5mA或调整放大倍数到20倍左右。我在Proteus里仿真时,习惯把恒流源调成2mA,放大倍数设为20倍,这样0℃对应4V,100℃对应5.54V,又超了。正确做法是先标定ADC0804参考电压,再反推放大倍数。
如果参考电压Vref=5V,想覆盖0~100℃,需要0℃到100℃的电压差正好落在0~5V范围内。PT100在0~100℃电阻变化38.5Ω,恒流源2mA时电压变化77mV,放大倍数A = 5V / 0.077V ≈ 64.9。所以原设计里“2.55mA + 25倍”组合对应的电压变化是0.158V×25=3.95V,接近满量程,但0℃起点电压偏高。实际工程中要么加入偏移电路,要么只用差分测量。这里先按原设计的参数继续讲,后面第5章再讲怎么调。
2.3 ADC0804的并行接口与工作时序
ADC0804是8位逐次逼近型AD转换器,转换时间约100μs,对于温度采样绰绰有余。它和AT89C51的接口很简单:数据线D0~D7并行接P1口或P0口(需上拉),控制端有CS(片选)、RD(读)、WR(写)、INTR(转换结束)。典型启动转换时序是先拉低CS,再拉低WR,然后拉高WR,等待INTR变低表示转换完成,之后再拉低CS和RD读取数据。
下面是一段直接用P2口控制控制端的示例逻辑:
sbit CS = P2^0; sbit RD = P2^1; sbit WR = P2^2; sbit INTR = P2^3; #define ADC_DATA P1 // ADC0804数据口接P1 unsigned char read_adc(void) { unsigned char val; CS = 0; WR = 0; WR = 1; // 启动转换 while(INTR); // 等待INTR变低,表示转换完成 RD = 0; val = ADC_DATA; // 读取8位结果 RD = 1; CS = 1; return val; }这段代码里CS = 0是选中芯片,WR脉冲触发一次转换。while(INTR)是阻塞等待,若INTR一直为高则死等。实际项目中建议加超时计数,防止ADC0804损坏导致程序卡死。读取到的val范围是0~255,对应参考电压0~Vref。注意ADC0804支持差动输入,VIN+和VIN-之间才是有效模拟输入,单端接地时VIN-接GND。
2.4 显示模块:LED数码管的静态与动态切换
显示部分采用共阴或共阳数码管。多数毕业设计用两个或四个数码管显示温度值。这里要区分静态显示和动态扫描:静态显示占用I/O口多,但程序简单;动态扫描用段选和位选分时点亮,实际中常用74HC573或三极管驱动位选。原设计用了P0口输出段码,P2口的部分引脚做位选。动态扫描必须利用人眼视觉暂留,刷新频率不能低于50Hz。
3. 硬件电路搭建和Proteus仿真复现要点
3.1 AT89C51最小系统连接
在Proteus里搭建这个系统,核心器件是AT89C51、PT100(Proteus里用电阻代替赋值)、LM324、ADC0804、数码管。先列一下引脚分配表,方便对照连线。
| 模块 | 接口 | 单片机引脚 | 说明 |
|---|---|---|---|
| ADC0804数据 | D0~D7 | P1.0~P1.7 | 并行数据输入 |
| ADC0804控制 | CS | P2.0 | 片选,低有效 |
| ADC0804控制 | RD | P2.1 | 读信号,低有效 |
| ADC0804控制 | WR | P2.2 | 写信号,低有效 |
| ADC0804控制 | INTR | P2.3 | 转换结束输出 |
| 数码管段码 | a~dp | P0.0~P0.7 | 经限流电阻接数码管 |
| 数码管位选 | 位1/位2 | P2.4/P2.5 | 选择第1位或第2位 |
连接时ADC0804的VIN+接LM324输出,VIN-接GND,VREF/2引脚通过一个电位器分压,用于调整满量程。时钟输入CLK接一个RC振荡电路(典型值10kΩ电阻和150pF电容),也可以从AT89C51的ALE引脚分频获得。PT100的恒流源可以用一个TL431做基准再加三极管扩流,但仿真阶段直接用电流源代替。
3.2 Proteus仿真电路的操作步骤
在Proteus中按下面步骤操作:
- 新建工程,从元件库中选AT89C51、LM324、ADC0804、7SEG-MPX2-CA(双位共阳数码管)、电阻、电容、电源。
- 按上面的引脚表连线。注意P0口作为I/O使用时必须接上拉电阻组,阻值选10kΩ。
- PT100在仿真里可以用一个可变电阻,阻值范围设在80~140Ω,模拟-50℃到100℃区间。也可以用“电阻+电位器”串联。
- 设置LM324反馈电阻,使放大倍数符合计算值。若反馈电阻Rf=240kΩ,反向端接地电阻Rg=10kΩ,则放大倍数A=1+Rf/Rg=25倍。
- 双击AT89C51,载入编译好的HEX文件,点击运行,然后调节可变电阻阻值,观察数码管显示是否随之变化。
3.3 仿真中常见的信号失真问题
仿真时最容易遇到的问题是LM324输出饱和。因为LM324在单电源供电下,输出最高只能到VCC减1.5V左右,如果VCC=5V,输出上限约3.5V。前面算过0℃输出可能到6V,显然会削顶。解决办法是降低恒流源电流到1mA,放大倍数取20倍,这样0℃时输出2V,100℃时输出2.77V,都在线性范围内。另一个办法是给LM324提供双电源(±5V),但51单片机系统里通常没有负电源。
| 现象 | 原因 | 解决方式 |
|---|---|---|
| 数码管显示一直为0xFF | ADC0804数据口未接上拉,或RD时序不对 | 检查P1口是否接上拉,或在程序中延长RD低电平时间 |
| 温度数值跳变剧烈 | 电源纹波大,或LM324放大倍数过高 | 在PT100输出电压端并一个0.1μF电容,降低放大倍数 |
| 温度显示非线性严重 | 恒流源电流不稳定 | 改用TL431基准源,而非简单电阻恒流 |
| 数码管亮度不均匀 | 动态扫描频率太低 | 将定时器中断间隔设为2ms,刷新周期小于10ms |
4. 软件代码拆分:从启动AD转换到显示温度的完整流程
4.1 函数声明与管脚定义
用Keil C51编写程序。先把头文件、引脚定义和全局变量写清楚。注意ADC0804的引脚定义要和硬件连线一致。
#include <reg51.h> // ADC0804控制引脚 sbit CS = P2^0; sbit RD = P2^1; sbit WR = P2^2; sbit INTR = P2^3; // 数码管位选 sbit DIG1 = P2^4; sbit DIG2 = P2^5; #define ADC_DATA P1 // P1口读取ADC0804数据 unsigned char code table[] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, // 0~4 0x92, 0x82, 0xF8, 0x80, 0x90 // 5~9 }; // 共阳数码管段码表这里table[]是共阳数码管的段码:0xC0表示0,0xF9表示1,依此类推。如果用的是共阴数码管,段码要取反。P0口外接上拉电阻后,输出低电平点亮段码。
4.2 启动AD转换与读取数据子程序
unsigned char read_adc(void) { unsigned char value; CS = 0; WR = 0; WR = 1; // 产生一个上升沿启动转换 while(INTR == 1); // 等待转换完成,INTR变低 RD = 0; value = ADC_DATA; // 读取转换结果 RD = 1; CS = 1; return value; }这个函数先拉低CS选中芯片,再给WR一个低脉冲启动转换。while(INTR == 1)循环等待转换结束——ADC0804转换期间INTR为高,完成后变低。读数据时拉低RD,数据线上的8位结果是有效的。这里没有做超时保护,实际项目里建议加一个计数器,超过1000次循环就跳出,避免芯片异常时死循环。
4.3 温度数据换算与线性化处理
拿到ADC数值后,第一步要换算出PT100的电压。设ADC实际输出为8位值adc_val,参考电压为5V,则输入电压v_in = (adc_val / 255.0) * 5.0。再除以放大倍数得到PT100上的压降,最后除以恒流源电流得到电阻值。温度则用简化线性公式T = (R - 100) / 0.385计算。这里的0.385是PT100在0~100℃的平均电阻温度系数。
float calc_temperature(unsigned char adc_val) { float v_in, r_pt100, temp; v_in = ((float)adc_val * 5.0) / 255.0; // 反推ADC输入电压 r_pt100 = v_in / 25.0 / 0.00255; // 除以放大倍数和恒流源电流 temp = (r_pt100 - 100.0) / 0.385; // 线性化公式 return temp; }这段代码里25.0是LM324的放大倍数,0.00255是恒流源电流2.55mA。参数需要和硬件实际焊接的电阻值严格对应。注意这个公式只适用于0~100℃范围,超过后误差会增大。如果你在仿真里把放大倍数改了,这里也要同步改。
4.4 数码管动态显示函数
显示部分采用两个数码管显示温度整数部分,如果需要小数点,可以加一位。典型动态扫描函数如下:
void display(unsigned int temp) { unsigned char ge, shi; shi = temp / 10; ge = temp % 10; P0 = table[shi]; // 输出十位的段码 DIG1 = 0; // 打开位选1 DIG2 = 1; delay(2); // 延时2ms P0 = table[ge]; // 输出个位的段码 DIG1 = 1; DIG2 = 0; // 打开位选2 delay(2); }这里的temp是整数温度值。位选低电平有效是因为共阳数码管公共端接高电平,三极管反相驱动。delay(2)实现2ms延时,保证亮度。两个位选轮流打开,循环频率约250Hz,肉眼看不到闪烁。
4.5 定时器初始化与主函数
主循环里不断读AD、换算、显示即可。但为了保持显示稳定,建议用定时器中断做时基。
void timer0_init(void) { TMOD = 0x01; // 定时器0,模式1(16位) TH0 = 0xFC; // 定时1ms初值,12MHz晶振 TL0 = 0x18; ET0 = 1; TR0 = 1; EA = 1; } void main(void) { unsigned char adc_val; unsigned int temp; timer0_init(); while(1) { adc_val = read_adc(); temp = (unsigned int)calc_temperature(adc_val); display(temp); } }主程序就是“采集-转换-显示”三件事。定时器0的中断服务程序里可以放一个计数器,用于动态扫描的延时控制,避免在display函数里用软件延时阻塞CPU。这里为了简化,直接用了软件延时,实际工程中建议把位选切换放在中断里。
5. 参数标定和复现过程中的常见坑
5.1 恒流源电流精度对温度计算的影响
原设计用2.55mA恒流源,这个值的来源是把0℃和100℃的输出电压差恰好映射到ADC0804的满量程附近。但实际电路里恒流源由三极管和稳压管构建,温度漂移会让电流发生微小变化。比如电流从2.55mA漂到2.58mA,同样电阻下电压会增大1.2%,对应温度读数偏高约3℃。解决办法是用精密基准源LM334或REF200,或者每次上电时用标准电阻校准电流。
// 校准后的电流值,实际测量后写入 #define I_REF 0.00255 #define AMP_GAIN 25.0参数定义成宏的好处是调试时只改一处。但注意宏定义是编译期常量,如果要用电位器在线校准,就得做按键输入或串口标定。毕业设计一般做到这里就够。
5.2 LM324放大倍数的温度漂移
LM324的输入失调电压典型值2mV,最大7mV。放大25倍后,输出端会有50~175mV的直流偏置,这个偏置会被当成温度信号。所以实际电路里常在放大器输入端加一个调零电位器,或者在软件里做“零点校准”:把PT100放入冰水混合物(0℃)时记下ADC值,之后每次读数减去这个偏移量。
| 校准步骤 | 操作 |
|---|---|
| 1 | 将PT100置于0℃冰水混合物中 |
| 2 | 读取ADC值并记录为adc_offset |
| 3 | 正常工作时,实际adc值减去adc_offset |
这种软件校零比硬件调零更快,适合批量生产。
5.3 ADC0804参考电压的设定陷阱
ADC0804的参考电压由VREF/2引脚决定,当该引脚悬空时,内部默认Vref=5V。如果VREF/2引脚接2.5V,则满量程变成5.0V,正好适合0~100℃的电压范围。但很多人会直接用5V电源作为满量程,结果发现到100℃时ADC值才180左右,分辨率不足。正确做法是用电位器把VREF/2调到1.5V,这样满量程就是3.0V——但要注意输入电压不能超过3V,否则会溢出。
我测试时把恒流源设为1.5mA,放大倍数20倍,0℃时电压1.5V,100℃时电压1.77V,对应ADC值约77和90,分辨率只有0.02V/bit,相当于1.3℃/bit。这个精度偏低,所以更好的办法是把恒流源提高到2mA,放大倍数提高到40倍,同时调节VREF/2让满量程匹配。总之,ADC0804只有8位分辨率,任何设计都要把被测电压范围尽量铺满整个量程。
5.4 Proteus仿真和实际硬件的差异
Proteus里的PT100是理想电阻,不会自发热,也没有导线电阻。但实际接线中,PT100引线可能长达几米,两根导线电阻各0.5Ω就相当于1℃误差。解决方法是使用三线制接法:一根线接恒流源正极,一根线接放大器的正输入端,第三根线接恒流源回路。这样导线电阻被分到恒流源回路上,不会影响电压测量。原设计里提到了三线制,但电路图显示的是两线制。如果你要实际做板子,建议改成三线制。
另外,Proteus里的LM324是理想模型,输出可以接近电源轨,但实际只能到3.5V(5V供电)。所以仿真时波形正常,焊板后可能发现高温段读数封顶。可以改用轨到轨运放如LMV324,信号范围会更宽。
6. 进阶技巧:用查表法替代线性公式做全温段修正
前面用的T = (R - 100) / 0.385在0~100℃内误差约±1℃,到150℃时误差会到±3℃。这是因为PT100的电阻-温度曲线并非严格直线,尤其是0℃以下,非线性比较明显。一种工程上常用的做法是把PT100分度表存入单片机,用二分查找定位阻值区间,再线性插值得到精确温度。
先建立一组标准分度表,只取关键点:
code float pt100_table[] = { -50, 80.31, -30, 88.22, 0, 100.00, 20, 107.79, 50, 119.40, 70, 127.08, 100, 138.51, 150, 157.33 }; // 每两个元素一对:温度,电阻查表函数传入计算出的电阻值,返回对应的温度。核心逻辑是遍历数组找到电阻所在的区间,然后做线性插值。
float lookup_temperature(float r) { char i; float t0, r0, t1, r1; for(i = 0; i < 14; i += 2) { // 每个条目占2个float t0 = pt100_table[i]; r0 = pt100_table[i+1]; t1 = pt100_table[i+2]; r1 = pt100_table[i+3]; if(r >= r0 && r <= r1) { return t0 + (t1 - t0) * (r - r0) / (r1 - r0); } } return 0xFF; // 越界返回错误 }查表法比公式法多消耗几百字节ROM(79C51有4KB,完全放得下),但能获得全量程接近0.1℃的精度。表越密,精度越高。如果你不想手动维护分度表,可以用PT100的分度多项式公式在PC端生成数组,再粘贴到代码里。
实际操作中要注意两点:一是表必须按电阻升序排列,否则二分查找失效;二是表所在内存要使用code关键字放到ROM里,而不是占用宝贵的RAM。查表函数的返回值是浮点数,如果想节省资源,可以用整数温度扩大10倍存储,比如显示“23.5℃”就存235。这样数码管显示时只需要拆BCD码,不需要调用浮点库。
做完这一步,整个数字温度计就从“能亮”进步到“能测准”了。后面如果再上RS485或者LCD1602,其实只是在这个信号链的尾部加接口,前端PT100的调理才是这套设计的核心门道。
本文还有配套的精品资源,点击获取