☰
温控系统Proteus仿真与编程:从LM35到51单片机完整设计
2026/10/14 16:16:31 网站建设 项目流程

简介:面向嵌入式与物联网初学者的鱼缸温控系统仿真资源,基于Proteus和Keil实现硬件电路验证与C语言编程,覆盖温度采集、LCD显示、PID闭环控制、超限报警等典型环节。资源包共33个文件,压缩后仅208KB,包含DSN仿真工程、C/H源文件、HEX固件、电路备份图纸及取模说明等,可在Proteus中直接打开并运行调试。当前已有491人学习使用。通过该文件包,读者可获取完整的仿真工程和程序源码,参照PID算法C程序、DS18B20与LCD1602/12864驱动代码,理解从传感器读取、界面显示到闭环调节与报警触发的实现思路;同时,工程附带备份与修改版本,便于对比加光耦前后的差异,适合课程设计、毕业设计或嵌入式入门进阶参考。

1. 温控系统先用 Proteus 仿真再编程,能提前暴露多少问题

很多第一次做温控系统的朋友习惯直接焊板子,结果要么加热过头,要么继电器反复吸合几分钟就烧掉,排查起来相当费劲。用 Proteus 做温控系统的仿真和编程,价值在于把“传感器—单片机—执行器”这条闭环链路完整放进原理图里,改参数只是几十秒的事,烧坏继电器也不需要换器件。这篇笔记要讲的是一条能直接上手的路线:LM35 采集温度,运放把信号放大到 ADC 的量程,51 单片机读取、换算、做控制判断,再驱动加热器和风扇。你可能是刚学完单片机课程,也可能是正在做课程设计或小规模产品原型,这条路线能把控制逻辑和硬件接口一次讲透。

2. 仿真电路搭建:从传感器到加热器的信号链路设计

先看清楚温控系统的链路再画图,否则后面接错线都找不到原因。温度被 LM35 转成毫伏级电压,这个电压太弱,直接进 ADC 会浪费量化精度,所以要先用运放放大到满量程附近,再给 ADC0804 转换,单片机拿到数字量之后换算成实际温度,最后通过继电器或 PWM 控制加热负载。整个链路里每一级都有明确职责,下面逐个选型和接线。

2.1 器件选型:为什么选择 LM35 + ADC0804 + 51 这种组合

LM35 是温控仿真里最常见的模拟温度传感器,输出电压与温度呈线性关系,10mV/°C,0°C 时输出 0V,100°C 时输出 1V。它不需要校准和外部补偿,Proteus 库里直接搜索 LM35 就能拖放,仿真模型稳定,不会出现数字传感器那种时序读不到的问题。直接拿 1V 以内的信号去匹配 5V 满量程的 ADC,量化分辨率会很差,所以前级加一个同相放大电路很有必要。

ADC0804 是 8 位并行 ADC,接口简单,CS、RD、WR、INTR 四根控制线就能和单片机对接。它的满量程由 Vref/2 决定,默认取 2.5V 时满量程是 5V,量化精度约 20mV,对应到我们放大后的信号大约 0.4°C,对温控演示完全够用。单片机选择 AT89C51 或者 AT89S52,因为它们和教材、开发板、Keil 工程兼容性最好,引脚定义也直观。如果以后想做更复杂的上位机联动,串口功能在 51 上也能实现,不必换平台。

执行器驱动有两种常见做法。一种是开关量控制,单片机 I/O 口通过三极管或 ULN2003 驱动继电器,继电器控制加热器通断;另一种是 PWM 调速,用 MOS 管或继电器配合可控硅控制加热功率。Proteus 仿真里继电器模型比较直观,适合观察滞后控制效果,而 PWM 更适合后面做 PID 调功。建议两种都在电路里预留位置,先用继电器跑通闭环,再切换到 PWM 看曲线。

2.2 Proteus 原理图接线:最小系统、运放增益和参考电压计算

画原理图前先算一遍增益。LM35 在 0 到 100°C 输出 0 到 1000mV,想让 100°C 对应接近 5V,放大倍数取 5 比较合适。运放选用 LM358,同相放大倍数等于 1 加反馈电阻比输入端电阻,取 R4 为 20k,R5 为 5k,倍数就是 5。这样 25°C 时 LM35 输出 250mV,运放输出 1.25V,ADC 读到的数字量约 64,分辨率清晰可辨。

接线时注意几点:LM35 供电接 5V,输出接运放同相输入端;运放输出接 ADC0804 的 Vin+,Vin- 接地;ADC0804 的 Vref/2 接 2.5V,可以用两个电阻分压加电容滤波,或者直接用电源经过稳压得到 2.5V;ADC 的数据口 D0-D7 接单片机的 P0 口,并且 P0 口要接上拉电阻,因为 P0 是开漏输出结构。控制线部分,AD C 的 CS 接 P2.0,WR 接 P2.1,RD 接 P2.2,INTR 接 P2.3。

下面给一个完整的引脚连接对照表,照着连不会漏线。

信号方向源器件引脚目的器件引脚说明
温度信号LM35 VoutLM358 IN+10mV/°C 原始信号
放大输出LM358 OUTADC0804 Vin+100°C 对应约 5V
参考电压2.5V 基准ADC0804 Vref/2决定满量程 5V
数据总线ADC0804 D0-D7单片机 P0.0-P0.78 位并行数据
片选控制单片机 P2.0ADC0804 CS低电平有效
写启动单片机 P2.1ADC0804 WR低脉冲启动转换
读使能单片机 P2.2ADC0804 RD低电平读出数据
状态查询ADC0804 INTR单片机 P2.3转换完成拉低
加热驱动单片机 P3.6继电器驱动三极管基极控制加热器通断
状态显示单片机 P1.0-P1.7LCD1602 数据口显示实时温度

ADC0804 的转换时钟不能忽略。Proteus 仿真里如果 CLK 引脚悬空,AD 转换不会启动,INTR 永远保持高电平,程序会卡死在等待状态。常见做法是用 Proteus 的 DCLOCK 信号源,设置频率 500kHz 左右接到 CLK 脚。如果用单片机 ALE 引脚分频,记得 ALE 频率对 ADC0804 可能偏高,稳妥起见接一个 D 触发器分频到 600kHz 以下。

2.3 执行器与降温通道:加热和风扇如何同时存在

纯加热的温控系统只能做升温实验,温度超过设定值之后没有主动降温手段,仿真曲线会长期停留在高位,控制算法看不出好坏。常见做法是加一个风扇继电器,当温度高于上限时开启风扇负载,模拟强制风冷。Proteus 里风扇用一个直流电机模型替代,加热器用电阻或者灯泡模型替代都行,两个负载分别由两个继电器控制。

降温通道的接线和加热通道类似,单片机 P3.7 通过三极管驱动另一个继电器。需要注意的是,Proteus 里的温度传感器并不会被风扇物理降温,它只反映电路电压,所以风扇回路更多是为了验证控制逻辑和展示系统完整性。真实验证降温效果时,需要把温度模型和环境模型配合起来,这个后面在避坑章节单独讲。

3. 编程实现:从 ADC 采集到温度换算到 PWM 输出的完整固件

电路画好后,编程才是温控系统的主战场。程序分为三个层次:底层是 ADC0804 的采集时序,中间是温度换算和显示,上层是控制算法与执行输出。很多工程问题不是算法不行,而是底层采集就有偏差,所以先从最底层的代码说起。

3.1 ADC0804 采集时序:状态位等待比固定延时更可靠

ADC0804 启动一次转换需要给 WR 一个低脉冲,转换完成后 INTR 引脚会由高电平拉低。常见错误是固定延时等待转换完成,但 ADC 的转换时间会随时钟频率变化,延时太短读出来的是旧数据,延时太长又拖慢采样周期。我比较习惯用 INTR 等待的方式,转换完成信号到了才读数据。

下面这段是 Keil C51 工程里的采集函数,引脚定义和时序都做了注释。

#include <REG51.H> #define ADC_DATA P0 // ADC0804 数据口接 P0 sbit ADC_CS = P2^0; // 片选 sbit ADC_WR = P2^1; // 启动转换 sbit ADC_RD = P2^2; // 读使能 sbit ADC_INTR = P2^3; // 转换完成标志 unsigned char read_adc(void) { unsigned char value; ADC_CS = 0; // 拉低片选,选中 ADC0804 ADC_WR = 0; // 写信号低脉冲 ADC_WR = 1; // 上升沿启动一次转换 while(ADC_INTR == 1); // 等待 INTR 拉低,表示转换完成 ADC_RD = 0; // 读使能有效 value = ADC_DATA; // 从 P0 读入 8 位转换结果 ADC_RD = 1; // 关闭读使能 ADC_CS = 1; // 释放片选 return value; }

代码逻辑很直接:每次调用先选中 ADC,然后发写脉冲启动转换,接着死等 INTR 信号。这里有个容易被忽略的细节,ADC0804 的 INTR 在启动转换后会被拉高,转换完成再拉低,所以代码里等待条件写的是while(ADC_INTR == 1)。如果初始化时 INTR 已经是低电平,这个等待会跳过,直接读到上一次的结果,因此主循环里每次采集之间最好保留几十毫秒间隔。

参数说明:ADC0804 的时钟输入建议 400kHz 到 640kHz,频率越高转换越快,但精度会略降。Proteus 仿真时可以用 DCLOCK 信号源直接给 500kHz,实体板卡上则要注意晶振频率与分频关系。

3.2 温度换算:量程、电压和整数的关系

拿到 8 位采样的数字量之后,要把它换算成真实温度。这里必须先明确硬件的对应关系:LM35 在 100°C 输出 1000mV,经过 5 倍放大后是 5000mV,正好对应 ADC0804 的满量程 5V,所以数字量 255 对应 100°C。反算回去,每一位代表约 19.6mV,也就是约 0.39°C。

换算代码里有三个容易出问题的地方:整数溢出、浮点开销、显示精度。用 51 单片机时我习惯全部用整数运算,先放大再除法,避免浮点库占用程序空间。

unsigned char adc_value; unsigned int voltage_mv; unsigned char temperature; adc_value = read_adc(); // 满量程按 5000mV 计算:数字量 / 255 * 5000 voltage_mv = (unsigned int)((unsigned long)adc_value * 5000 / 255); // 单位电压 50mV 对应 1°C(传感输出 10mV/°C * 放大倍数 5) temperature = (unsigned char)(voltage_mv / 50);

这段代码先算电压再算温度,每一步都有明确物理意义。adc_value * 5000最大是 1275000,16 位整型放不下,所以先强制转成 unsigned long 再做乘法,这是很多人翻车的地方。除以 255 得到毫伏数,再除以 50 得到整数的摄氏度。注意这里没有做四舍五入,如果显示要求高一位小数,可以把 temperature 存成 10 倍温度值,比如 25.3°C 存成 253,显示时再分别取十位和个位。

显示部分我用 LCD1602 来输出实时温度,P1 口接数据线,RS、RW、EN 分别接 P3.0、P3.1、P3.2。显示完整代码其实较长,核心就一句话:把温度的十位、个位、小数位拆出来分别加 0x30 转成 ASCII 码。新手最容易犯的错误是直接用sprintf,51 的存储空间有限,编译出来的库函数体积很大,建议手写拆分。

3.3 开关控制和 PWM 输出的固件骨架

温度换算出来之后,控制策略就要落到输出上。开关控制最简单,设定一个下阈值和一个上阈值,温度低于下阈值就开加热,高于上阈值就关加热,中间的区间保持当前状态不动。这个滞回区间就是防抖区间,下面代码里用两位小数的温度值直接比较。

// 目标温度 30.0°C,防抖区间 1.5°C #define TEMP_SET 300 #define TEMP_HYSTERESIS 15 unsigned int current_temp_x10; // 实际温度,放大 10 倍 if(current_temp_x10 < (TEMP_SET - TEMP_HYSTERESIS)) { HEATER = 1; // 温度偏低,开启加热 } else if(current_temp_x10 > (TEMP_SET + TEMP_HYSTERESIS)) { HEATER = 0; // 温度偏高,关闭加热 } // 中间状态不改变输出,避免继电器频繁动作

PWM 输出则需要一个定时器周期触发。用 Timer0 工作在模式 2,即 8 位自动重装模式,晶振 11.0592MHz 时每个计数周期约 1.085 微秒,设置初值 0xA4 可以得到约 0.1ms 的中断周期。累计 100 次中断就是 10ms,即 100Hz 的 PWM 频率,对人眼和继电器都足够平滑。

sbit HEATER = P3^6; // 加热器控制引脚 #define PWM_PERIOD 100 // 10ms 周期对应 100 个 0.1ms 计数 static unsigned char pwm_cnt = 0; static unsigned char pwm_duty = 0; void Timer0_ISR(void) interrupt 1 { TL0 = 0xA4; // 重装载初值,保证 0.1ms 周期 TH0 = 0xA4; if(++pwm_cnt >= PWM_PERIOD) { pwm_cnt = 0; } // 占空比比较:计数小于目标占空比时输出有效 HEATER = (pwm_cnt < pwm_duty) ? 1 : 0; }

pwm_duty的范围是 0 到 100,它不是固定常量,而是由控制算法计算后不断更新。比如 PID 输出的 0 到 255 可以直接缩放到 0 到 100,也可以直接让周期变成 255 个计数点,这样 PID 输出和 PWM 占空比一一对应,省一次除法。PWM 频率的选择有讲究,控制加热器时太高会增加开关损耗,太低会让温度波动明显,10ms 周期在绝大多数教学仿真里都比较适中。

4. 控制策略与参数整定:从通断控制到 PID 的递进思路

很多教程一上来就讲 PID,但实际工程里通断控制已经能解决 80% 的温控问题。单片机课程的温控仿真,先把通断控制调稳,再考虑 PID,这样理解更深,调试也有对照。

4.1 通断控制的滞回区间怎么设才合理

通断控制的本质是让系统在目标温度附近振荡。如果没有滞回区间,温度刚到设定值就关加热,结果热量还在继续传导,温度过冲;等温度降到设定值以下又开加热,又会反复波动。继电器在这种振荡下频繁吸合,寿命急剧下降。滞回区间的设置直接影响温控精度和继电器寿命。

以目标 30°C 为例,滞回区间设 1°C,即 29.5°C 开启加热、30.5°C 关闭加热,温度波动范围约 1°C。这个区间越窄,温控精度越高,但继电器动作越频繁,Proteus 仿真里因为响应快,很容易看到继电器不断切换。我一般把仿真里的滞回区间放到 1.5°C 左右,既能看出明显的趋势,又不会把继电器模型折腾出噪声。实体系统里还要结合加热功率和传感器响应时间调整,总原则是保证两次加热间隔足够长,一般不小于继电器动作周期的 5 到 10 倍。

通风断控制的参数调整也有规律。加热功率不变的情况下,采样周期越短,越容易在临界点反复触发;采样周期如果太长,温度可能已经过冲之后才被采集到。建议采样周期 500ms 到 1s 之间,每次采样后更新一次继电器状态,不需要在中断里做控制判断。

4.2 增量式 PID 在 51 上的实现和三个整定参数

通断控制只能让温度在目标附近波动,做不到平稳。想看到一条平滑逼近设定值的曲线,就需要 PID 的输出连续可调,用 PWM 控制加热功率。PID 在单片机上有位置式和增量式两种写法,我推荐增量式,原因有三个:它输出的是控制增量,误动作影响小;计算基于最近几个误差值,不需要累加历史误差,不易积分饱和;在 51 这种低算力平台上浮点运算也不吃力。

增量式 PID 的核心公式是输出增量等于三项误差变化的加权和。积分项直接选当前误差作为增量的一部分,微分项用误差的一阶差分和二阶差分表达,最终输出是上一次输出加上这个增量。

// 增量式 PID 参数 float Kp = 8.0f; // 比例增益 float Ki = 0.5f; // 积分增益 float Kd = 2.0f; // 微分增益 float err_now = 0.0f; // 当前误差 float err_prev = 0.0f; // 上一拍误差 float err_prev2 = 0.0f; // 上上拍误差 float out = 0.0f; // 当前输出 unsigned char pwm_target = 0; // 每个控制周期调用一次,current 为当前温度 void pid_update(unsigned int current, unsigned int setpoint) { float delta = 0.0f; err_now = (float)(setpoint - current) / 10.0f; // P 项:当前误差与上拍误差之差 // I 项:当前误差本身 // D 项:误差二阶差分,用于抑制过度趋势 delta = Kp * (err_now - err_prev) + Ki * err_now + Kd * (err_now - 2.0f * err_prev + err_prev2); out += delta; if(out > 100.0f) out = 100.0f; // 占空比上限钳位 if(out < 0.0f) out = 0.0f; // 下限钳位 pwm_target = (unsigned char)out; err_prev2 = err_prev; err_prev = err_now; }

增量式公式有个优势:如果系统已经稳定,误差不变,那么err_now - err_prev为零,二阶差分也是零,delta 只剩 Ki 乘以当前误差,但当前误差为零时输出就不再变化,不会出现位置式 PID 积分项不断累积导致超调的问题。输出钳位也相当于抗积分饱和,如果 PID 算出来超过 100%,直接截断,不把多余的部分带入下一次计算。

整定参数时不要直接三个一起调。常规顺序是先让 Ki 和 Kd 为零,只调 Kp。在目标温度 40°C 下,从 Kp=2 开始加大,观察温度曲线是否在目标附近等幅振荡,记下临界振荡的 Kp 值和振荡周期。然后按经验比例缩小 Kp,加入 Ki 消除稳态误差,最后加 Kd 减小超调。Proteus 的优势是修改参数后立即重新仿真,几分钟就能看到一组完整的整定曲线。

值得说清楚的是,Proteus 里的温度模型比较理想,传热时间常数比实体设备快很多,所以在仿真里整定好的 PID 参数直接搬到板子上往往偏激进。实体系统热惯性大,同样的 Kp 可能不够快,需要重新观察曲线微调。这一点很多初学者容易误解,以为仿真参数可以直接移植。

5. 避坑:Proteus 温控仿真里最常遇到的 5 个问题

这一章是我自己做温控仿真时反复踩过的坑,每条都按现象、原因、解决方式写清楚,希望能帮你节省排查时间。

5.1 温度显示始终不变,或者数值为 0

现象:程序跑起来 LCD 一直显示 0,加热器开开关关,但温度读数纹丝不动。

原因:LM35 的输出没有正确接到运放输入端,或者 ADC0804 的转换时钟没有接。我遇到最多的情况是 Proteus 里元件引脚悬空,看起来连了线,实际上网络标号没对齐,导致信号根本没传过去。

解决:先在仿真里放两个电压表,一个测 LM35 输出,一个测运放输出。用鼠标拖动 LM35 旁边的温度调节箭头,改变仿真温度,看电压表是否随之线性变化。如果 LM35 输出正常但运放输出不变,查运放供电和反馈电阻网络;如果运放输出正常但 ADC 读数为零,查 CLK 时钟源和 CS、RD、WR 控制线是否真的连到了对应 I/O 口。

5.2 INTR 一直为高,程序卡死

现象:程序停留在 read_adc 函数的 while 循环里,单步调试发现ADC_INTR永远读回 1。

原因:ADC0804 必须有外部时钟才能完成转换。Proteus 中直接拖出来的 ADC0804 默认没有 CLK 信号,如果忘了接 DCLOCK 或者接了但频率设置不对,转换永远不会完成。

解决:从 Proteus 左侧器件库搜 DCLOCK,放到 ADC0804 的 CLK 引脚旁边,属性里把频率设为 500kHz。如果用的是单片机 ALE 输出,要通过分频器降到 640kHz 以下再接 CLK。仿真通过后画实体板时,CLK 引脚通常会用一个 555 多谐振荡器产生,或者直接用单片机的定时器翻转输出。

5.3 继电器疯狂通断,仿真噪音很大

现象:温度到达设定值后,继电器不是稳定保持,而是快速反复吸合释放,Proteus 仿真波形看上去密密麻麻。

原因:通断控制的滞回区间设得太小,加上仿真系统热响应极快,温度在设定值附近来回穿越,继电器每次穿越都切换一次。还有一个因素是采样周期太短,每次采集到新的温度值就立刻改变输出,没有给系统自然冷却留时间。

解决:把滞回区间从 0.5°C 增大到 1.5°C 左右,同时把控制周期从几十毫秒拉长到至少 500ms。如果还是频繁通断,检查温度换算是不是有抖动,适当对采样值做中值滤波,取连续 3 到 5 次采样的中间值再参与控制。

5.4 温度只升不降,降温通道不起作用

现象:温度超过设定值很多,风扇继电器也动作了,但温度曲线没有任何下降趋势。

原因:Proteus 里的 LM35 模型本质是一个电压源,它不感知环境热交换,风扇对它没有物理降温效果。很多新手以为仿真里能直接看到温度下降,实际不是这样,必须通过外部方式模拟降温过程。

解决:有两个常见做法。一是把温度模型外部化,用一个可控电压源给运放输入一个随时间下降的电压,模拟降温阶段;二是直接用信号源替换 LM35,在仿真中预设温度曲线,让控制逻辑去响应。如果只是为了验证风扇控制逻辑,也可以给风扇回路加一个独立的反馈源,让风扇开启时拉低模拟温度电压。

5.5 Keil 代码编译正常,Proteus 却运行异常

现象:程序编译零错误,也生成了 hex 文件,但 Proteus 里单片机完全没有反应,LED 不亮,LCD 无显示。

原因:最常见的是没有把 hex 文件加载进单片机模型,或者加载了但晶振频率设置和程序里的参数不一致。另一种是 Keil 生成的 hex 文件格式不兼容,选择了错误的输出格式。

解决:双击 Proteus 里的单片机模型,在程序文件路径选择 Keil 生成的 hex 文件。晶振频率要与实际仿真电路一致,如果代码按 11.0592MHz 计算定时器初值,而模型里设置的晶振是 12MHz,定时就全偏了。Keil 输出设置里记得勾选 Create HEX File,并在 Output 选项卡里确认格式是 Intel HEX。

6. 仿真转实物前的最后一道验证:边界条件测试与信号注入

把仿真代码放到实体板之前,我习惯做一组边界条件测试,这一步能发现大量只在特定输入下才暴露的问题。方法很简单:用 Proteus 的信号发生器或电压源直接替代 LM35,在运放输入端注入固定的直流电压,分别对应 25°C、50°C、75°C 和 100°C,观察 LCD 显示值和控制输出。这样绕过了传感器误差,把重点放在单片机程序和 ADC 采样链路上。

注入电压的计算是:25°C 对应 LM35 输出 250mV,放大后 1.25V;50°C 放大后 2.5V;75°C 放大后 3.75V;100°C 放大后 5V。用电压源在这四个点分别仿真,如果显示出来的温度误差超过 1°C,问题基本出在 ADC0804 的参考电压精度或运算放大器的失调上。这个测试做好之后,再把 LM35 接回电路,仿真通过就意味着核心逻辑没大问题。

另一个值得做的验证是看 PWM 占空比输出。把控制目标设为 50°C,初始温度设为 20°C,用 Proteus 的数字示波器观察 P3.6 引脚的波形,确认占空比随温度逼近目标而逐渐减小。如果占空比始终满值直到超调,说明 PID 积分项过强;如果稳定后还有高频抖动,说明微分增益偏大。波形比只看数码管数字直观得多,强烈建议每一个做温控仿真的朋友都打开示波器观察一次。

我自己的习惯是:每改一次参数,先记下温度曲线的过冲量和稳定时间,做成一张表格,再调整下一个参数。仿真不是调完就结束,它真正的价值是在投板前把控制逻辑和异常路径全部跑一遍。当年做某课程设计时,我就是在仿真里发现继电器驱动引脚初始化默认是高电平,上电瞬间加热器会误动作,于是加了一行上电强制关闭的初始化代码,才避免实机一上电就全功率加热的问题。这种细节只有认真做边界测试才能发现。希望这篇笔记能让你在温控仿真与编程这条路上少走几个弯,每一步都有可验证的依据。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询