
简介本资源是一套面向单片机初学者与毕业设计学生的温度电压双参数检测系统完整开发包解决低温环境低至-50℃下高精度数据采集、AD转换与可视化显示的实际工程问题。资源共49个文件涵盖Keil工程源码.c/.a51/.uvproj等、Proteus 7.5/8.10仿真文件.dsn/.pdsprj、LCD12864与传感器驱动说明PDF/DOC、硬件选型手册ZIP器件库、操作视频MP4及答辩指导文档含常见问题与PPT模板压缩包大小为7.47MB。已有46人学习下载内容组织清晰按“设计说明→源程序→仿真→技术手册”四级目录展开配套视频教程与必读使用指南显著降低上手门槛。读者可直接导入Keil与Proteus运行调试复现DS1621测温I²C通信、MAX1241电压采样12位精度及12864中文界面动态刷新全过程是单片机课程设计、电子类毕设及嵌入式入门实践的高复用性参考方案。1. 这个项目到底解决什么问题先想清楚再动手前段时间接了个小需求要用51单片机做一个温度电压检测装置显示用12864最关键的一条是温度要能测零下。一开始我的第一反应和大多数人一样——温度传感器用DS18B20ADC用PCF8591这两颗芯片便宜、教程多、网上代码一抓一大把。但真正梳理需求后我发现这个方案对可零下这个要求其实存在隐患。先说DS18B20。它当然支持负温度测量测温范围-55℃到125℃12位分辨率下精度能到0.0625℃看起来完全够用。但它是单总线协议时序要求很高对延时精度特别敏感尤其当系统里还有其他外设要跑的时候单总线的时序很容易被中断干扰。更麻烦的是DS18B20在Proteus里仿真时偶尔会出现ROM搜索不稳定、读出来全是0xFFFFFFFF之类的问题排查起来很费劲。所以在这类检测显示仿真的项目里我反而更倾向于用I2C接口的DS1621。I2C有标准时序不依赖精确延时和51单片机的GPIO配合很宽松Proteus仿真兼容性也更好。再说电压采集。PCF8591是8位ADC分辨率只有256级如果测0-5V电压每一步代表约19.5mV显示出来会有明显跳变。MAX1241是12位ADC分辨率4096级0-5V下每步约1.22mV精度差了16倍。MAX1241走的是SPI接口恰好和DS1621的I2C分开两条总线互不干扰从软件架构上说也更清晰。所以最终方案定为AT89C51或AT89C52作为主控DS1621负责温度采集MAX1241负责电压采集12864液晶ST7920控制器带中文字库做显示。这套组合的核心思路是温度用I2C总线电压用SPI总线显示用并行总线三条通道完全独立程序里无论是初始化还是数据刷新都互不牵连调试起来非常直接。这篇内容适合正在做单片机课程设计、毕业设计或者想快速搭一套多通道检测系统的朋友参考。会把这套方案的硬件连接、驱动代码、Proteus仿真步骤一次说透也会把我在实际调试中踩过的坑一并列出来。2. 硬件电路设计三条总线的分工与接口细节2.1 51最小系统与资源规划主控我选了AT89C5112MHz晶振。RST引脚接经典的10uF电容加10k电阻复位电路P0口必须接上拉电阻10k排阻这个不用多说。关键是把IO口规划清楚P3.6 / P3.7 接DS1621的SCL / SDA这个是硬件I2C的常规位置方便以后用寄存器模拟或者直接升级成带硬件I2C的STC单片机。P1.0 - P1.3 接MAX1241的CS / SCLK / DIN / DOUT纯GPIO模拟SPI。P2.0 - P2.7 接12864的DB0-DB7数据口P3.4 / P3.5 接RS和ERW直接接地少一根控制线。这套分配有个好处P1口完整留给SPIP2口完整留给LCD数据P3口的上下半区分别给I2C和LCD控制没有任何引脚冲突写代码的时候也容易记忆。2.2 DS1621的I2C接口与地址设置DS1621是Maxim现在的ADI出的数字温度传感器I2C接口测量范围-55℃到125℃分辨率和DS18B20一样可以配置成9到12位12位模式下温度分辨率0.0625℃。和DS18B20最核心的区别是DS1621不需要精确的单总线时序I2C的SCL时钟只要在100kHz-400kHz以内都能正常工作对GPIO模拟的要求低很多这在Proteus仿真里优势特别明显。接线很简单VCC、GND、SCL、SDA另外有A0、A1、A2三个地址引脚。这三个引脚接GND时I2C从机地址是0x487位地址写地址是0x90读地址是0x91。如果总线上还要挂别的I2C设备可以改这三个引脚的接法来避开地址冲突。SDA和SCL必须各接一个4.7k上拉电阻到VCC这个电阻不接的话I2C通信完全跑不起来Proteus仿真也一样。DS1621的地址判断在程序里就是这一行#define DS1621_WRITE 0x90 // 0x48 1 #define DS1621_READ 0x91 // 0x48 1 | 1DS1621上电默认工作在连续转换模式也就是说上电后它会自动周期性地进行温度转换不需要额外写启动命令。但为了程序逻辑明确我在初始化时还是写了一次配置寄存器把工作模式设为连续转换、输出极性为高、12位分辨率然后再发启动转换命令。2.3 MAX1241的SPI接口与电压输入范围MAX1241是12位单通道ADCSPI接口最大采样率约73kSPS供电范围2.7V-5.25V直接用5V供电没问题。它内部有一个2.5V基准电压所以VREF引脚不需要接外部参考只要在VREF引脚对地接一个0.1uF去耦电容它自己就能稳定输出基准。模拟输入电压范围是0-2.5V。这里有个特别需要注意的点如果被测电压范围是0-5V不能直接把5V接到MAX1241的AIN引脚否则超量程会损坏芯片。需要在输入前加分压电阻把0-5V映射到0-2.5V。比如用两个10k电阻分压实际电压就是ADC读数乘以2。10k这个阻值的选择逻辑是不能太大否则信号源内阻会影响采样精度也不能太小否则分压电阻本身会消耗被测电路的电流。10k对绝大多数信号源来说都是安全值。MAX1241的引脚分配SCLK接P1.1DIN接P1.2DOUT接P1.3CS接P1.0。VDD和VREF各接一个0.1uF电容到地电容要尽量靠近芯片引脚这个在实物布局上很关键在Proteus仿真中影响不大但还是要养成规范习惯。2.4 12864液晶的ST7920接法12864液晶有很多种控制器最常见的是ST7920带中文字库和KS0108不带字库。建议用ST7920版本好处是内置中文字库显示温度电压这些汉字时直接写入GB2312内码就能显示完全不用自己做字模。而KS0108需要自己提取和存储字模程序体积大一大截对51的片内Flash是个考验。ST7920支持并行和串行两种模式由PSB引脚决定。把PSB接VCC选并行模式这样数据线用8根DB0-DB7控制线只要RS和E两根R/W接地固定为写模式。CPU读液晶状态的操作就不做了每次操作前用延时代替。接线表整理如下12864引脚51引脚说明DB0-DB7P2.0-P2.78位数据总线RSP3.40指令 1数据RWGND固定写模式EP3.5使能信号下降沿锁存PSBVCC并行模式V0接电位器调节对比度仿真中可接地12864的供电是5V背光A脚经电阻接5VK脚接地。V0是液晶对比度调节脚实物中接一个10k电位器调节范围大概在0-1.5V之间Proteus仿真里直接接地就能显示清楚。3. 软件程序设计三路驱动完整代码思路3.1 主程序流程初始化、轮询刷新、数据换算整个软件结构非常清晰主循环就是一个读一遍、换算一遍、显示一遍的循环用定时器0做1秒定时每1秒刷新一次显示。刷新太快反而不好12864的清屏和写数据操作本身耗时刷新过于频繁还会造成视觉闪烁而且MAX1241和DS1621的读数也不会有秒级以下的有效变化。主程序的逻辑大致是这样void main(void) { clk_init(); // 定时器初始化1秒中断 lcd_init(); // 12864液晶初始化 ds1621_init(); // DS1621配置启动连续转换 adc_init(); // MAX1241初始化这里主要是GPIO配置 while(1) { if(flag_1s) // 1秒定时标志 { flag_1s 0; // 读取DS1621温度 temp_raw ds1621_read_temp(); // 补码转实际温度12位分辨率下乘0.0625 temp_value temp_raw * 0.0625; // 读取MAX1241电压 adc_raw max1241_read(); // 12位ADC参考电压2.5V乘2补偿分压 volt_value (adc_raw * 2.5 * 2) / 4096.0; // 刷新显示 lcd_display_temp(temp_value); lcd_display_volt(volt_value); } } }注意temp_raw是int类型的16位有符号数据DS1621读出来是两个字节高字节在前。如果温度是负的高字节最高位为1直接把它当成有符号int来乘0.0625C语言的整数提升会自动把负数转换成正确的浮点负数。但是有坑如果把两个字节硬拼成unsigned int再转float负温度就会变成很大的正值显示出的温度会极其离谱。3.2 DS1621驱动I2C起始、读写与温度换算51没有硬件I2C全靠GPIO模拟。DS1621的I2C时序是标准的起始条件、从机地址、命令字节、重复起始、读取数据、停止条件。下面是核心驱动函数。先看I2C的底层操作void i2c_start(void) { SDA 1; SCL 1; _nop_(); SDA 0; _nop_(); SCL 0; } void i2c_stop(void) { SCL 0; SDA 0; _nop_(); SCL 1; _nop_(); SDA 1; } void i2c_write_byte(unsigned char dat) { unsigned char i; for(i 0; i 8; i) { if(dat 0x80) SDA 1; else SDA 0; dat 1; SCL 1; _nop_(); SCL 0; _nop_(); } // 释放SDA总线等待从机应答 SDA 1; SCL 1; _nop_(); SCL 0; _nop_(); } unsigned char i2c_read_byte(void) { unsigned char i, dat 0; SDA 1; // 释放总线让从机驱动SDA for(i 0; i 8; i) { SCL 1; _nop_(); dat 1; if(SDA) dat | 0x01; SCL 0; _nop_(); } return dat; }读取DS1621温度寄存器的完整流程int ds1621_read_temp(void) { unsigned char hi, lo; int raw; i2c_start(); i2c_write_byte(DS1621_WRITE); // 写地址 i2c_write_byte(0xAA); // 读温度寄存器命令 i2c_start(); // 重复起始 i2c_write_byte(DS1621_READ); // 读地址 hi i2c_read_byte(); lo i2c_read_byte(); // 最后一个字节后不发送应答 i2c_stop(); raw (int)(hi 8 | lo); return raw; }这里有个细节必须要解释读最后一个字节之后要回非应答NAK表示主机不再需要更多数据然后发停止条件。有些代码在读完最后一个字节后还发ACK从机就会继续发下一字节导致总线状态混乱。我在第一次调试时就犯了这个错数据读出来是乱的排查了半天才发现是多发了一个ACK。温度换算逻辑是DS1621在12位分辨率下低字节只有高4位有效bit3-bit0恒为0。原本的16位温度寄存器值乘以0.0625就是实际温度。负温度时是补码表示比如-20℃寄存器里存的是0xFF60即-160-160 * 0.0625 -10.0换算正确。这里顺便验证一下0xFF60换算成十进制最高位为1说明是负数先取反加一得到0x00A0即160所以原值是-160乘以0.0625等于-10℃没毛病。3.3 MAX1241驱动模拟SPI时最容易出错的三个细节MAX1241虽然是一个SPI器件但它的时序和标准SPI略有不同需要分两个阶段第一阶段主机通过DIN发送8位配置字bit71启动转换bit60选择单极性输入第二阶段主机在SCLK作用下从DOUT读取12位转换结果MSB在前。unsigned int max1241_read(void) { unsigned char i; unsigned char control 0x80; // b71启动转换, b60单极性 unsigned int val 0; ADC_CS 0; _nop_(); // 发送8位配置字 for(i 0; i 8; i) { if(control 0x80) ADC_DIN 1; else ADC_DIN 0; control 1; ADC_SCLK 1; _nop_(); ADC_SCLK 0; _nop_(); } // 读取12位转换结果 for(i 0; i 12; i) { ADC_SCLK 1; _nop_(); val 1; if(ADC_DOUT) val | 0x01; ADC_SCLK 0; _nop_(); } ADC_CS 1; return val; }这里要看懂一个时序问题发送配置字时DIN在SCLK上升沿被锁存所以要先设置DIN数据位再拉高SCLK。读取数据时MAX1241是在SCLK的下降沿更新DOUT数据那么主机可以在SCLK为高电平期间直接读DOUT也就是上面代码里先置SCLK1再读DOUT然后再拉低。这两个方向搞反了就会得到全0或者错位的数据。另外两个容易踩的坑第一CS拉低后至少要有几微秒的延时等待转换启动虽然MAX1241的tCSL时序很紧凑但在51这种慢速单片机上加_nop_()更稳妥第二控制字必须是0x80不要写成0xC0或别的值。0xC0里bit6为1选择的是双极性差分输入模式对于单端电压检测来说会得到完全错误的读数。电压值换算公式// VREF内部2.5V分压2倍所以满量程对应5.0V float volt (adc_raw * 5.0) / 4096.0;很多教程写成adc_raw * 2.5 / 4096.0这是没考虑分压电阻的。我没有用分压电阻的时候MAX1241的输入范围是0-2.5V显示出来最大值2.5V这倒没错但我测的是0-5V信号就要在外部加两个10k电阻分压所以最终满量程对应5V计算时乘5.0。3.4 12864驱动ST7920初始化序列与中文显示ST7920的初始化顺序是一个固定套路网上好多版本细节不一致我实测下来最稳妥的是这个序列void lcd_init(void) { delay_ms(50); // 上电等待ST7920内部复位 lcd_write_cmd(0x30); // 基本指令集8位模式 delay_ms(5); lcd_write_cmd(0x30); delay_ms(5); lcd_write_cmd(0x0C); // 显示开光标关 delay_ms(2); lcd_write_cmd(0x01); // 清屏 delay_ms(10); lcd_write_cmd(0x06); // 地址自动加1 delay_ms(2); }注意0x30要连续写两次第一次是唤醒ST7920第二次才是真正的功能设置。有人偷懒只写一次实测在某些屏上会偶发初始化不成功显示全黑或者全白。这里不省那5ms的延时。写命令和写数据的底层函数void lcd_write_cmd(unsigned char cmd) { RS 0; LCD_DATA cmd; E 1; _nop_(); E 0; delay_us(50); } void lcd_write_data(unsigned char dat) { RS 1; LCD_DATA dat; E 1; _nop_(); E 0; delay_us(50); }ST7920的中文显示区域和英文显示区域地址不同。在中文模式下每行最多8个汉字四行的起始地址分别是0x80、0x90、0x88、0x98。要注意第二行是0x90而不是0x88这个地址表很多初学者会搞错。显示温度: xx.x C的完整函数可以这样写void lcd_display_temp(float temp) { unsigned char buf[8]; lcd_write_cmd(0x80); // 第一行起始地址 lcd_display_str(温 度); // 显示中文 lcd_write_cmd(0x87); // 第一行第7个字符位置 // 把float转成字符串 sprintf(buf, %5.1f, temp); lcd_display_str(buf); }sprintf在51上使用需要包含stdio.h而且会比较大但12MHz的51跑这个还是可以的只是要注意Keil里必须勾选Use MicroLIB才能正常使用浮点格式化功能否则链接时会报错或者程序体积爆炸。如果不想用sprintf也可以用整数拆分的方式手动转换后面避坑部分单独说。4. Proteus仿真调试从搭电路到跑通全流程4.1 仿真环境搭建与元件清单Proteus的仿真步骤分为新建工程、放置元件、连线、加载HEX文件、运行。元件清单如下直接搜索这些名字就能找到元件搜索关键字备注主控AT89C51也可用AT89C52温度传感器DS1621I2C接口ADCMAX1241SPI接口液晶LGM12641BS1RST7920控制器12864带中文字库分压电阻RES两个10k上拉电阻RESPACK-8P0口排阻4.7k或10kProteus里的12864模型选LGM12641BS1R这个型号是ST7920控制器支持中文显示。在摆放元件时注意DS1621的SDA和SCL要分别接上拉电阻到VCCMAX1241的VREF引脚接0.1uF电容到地AIN引脚接两个10k电阻的分压点。加载程序在AT89C51的属性里双击在Program File一栏选择Keil编译生成的HEX文件然后点运行。如果看不到效果先检查有没有加载HEX文件——这是Proteus仿真里出现概率最高的问题没有之一。4.2 负温度与电压信号的模拟验证方法温度验证Proteus里的DS1621可以直接在元件属性里修改温度值。双击DS1621在属性对话框里有一个Temperature范围-55到125选项把它改成-20运行后12864上应该显示-20.0℃。修改后不必重启仿真继续运行几秒就会更新。电压验证在MAX1241的AIN输入引脚接一个电位器POT-HG中间抽头接AIN电位器两端分别接5V和GND。仿真运行时拖动电位器改变输出电压LCD上的电压读数应该跟着变化。最大到5.0V最小到0.0V。这里要注意PROTEUS里模拟电压源如果直接接到MAX1241的AIN而MAX1241的VREF是内部2.5V输入超过2.5V时仿真可能不会报错但数据会饱和在4095。所以用5V供电时一定要加分压电阻。我见过很多人在仿真图里直接把5V电源接AIN读数永远是4095还以为是程序问题其实是输入超量程了。4.3 仿真中常见的排查思路如果运行后12864完全不显示优先查以下四项第一LGM12641BS1R的PSB接VCC没有很多Proteus例程默认PSB接地串行模式但程序是按并行模式写的自然没反应第二对比度调节脚V0是否接地不接地可能全黑第三E引脚和RS引脚有没有接反第四AT89C51的RST和晶振是否接好。如果DS1621温度读出来一直是FF或者0重点抓I2C地址。检查A0-A2是不是全接地了代码里0x90/0x91的地址就要对应。如果A2接了VCC地址就变成0x92/0x93代码不改就不通。如果MAX1241读出来一直等于4095除了超量程外还要检查芯片上有没有其他功率源。有些Proteus版本里MAX1241的SHDN引脚如果悬空ADC会一直处于关断状态。把SHDN引脚直接接VCC就能排除这个干扰。5. 实测中遇到的坑与处理经验5.1 DS1621的负温度显示显示乱码问题我刚写完温度显示功能时把int类型直接无符号处理正温度显示完全正常但把DS1621设成负温度后LCD上显示了一个巨大的正数比如-10℃变成了65526℃一眼就知道是符号位被当成了数据位。解决办法有两种第一种直接把读回来的(hi8|lo)强转成intC语言会自动识别最高位符号这个方法最简单第二种手动判断最高位如果是1就先取反加一再加负号if(raw 0x8000) { raw ~raw 1; temp -(float)raw * 0.0625; } else { temp (float)raw * 0.0625; }两种方法结果一致但第一种对初学者来说容易在后续格式化输出时犯错。建议采用第二种逻辑清晰也好在LCD上拼字符串。5.2 MAX1241的读数波动与去耦技巧MAX1241在仿真中读数非常稳定但在实物中会出现末位跳动的现象。我在调试中发现跳动的主要原因是VREF引脚的去耦电容离芯片太远。MAX1241内部2.5V基准是通过一个开关电容网络产生的每一次采样都会从VREF引脚抽取微小电荷如果去耦电容不足或者离引脚太远基准电压就会有纹波反映到ADC结果上就是低位跳动。解决方法是VREF引脚对地接一个1uF的钽电容或陶瓷电容放在尽可能靠近芯片的位置同时VDD对地再接一个0.1uF电容。实测下来末位跳动从±3个字降到±1个字效果立竿见影。另外MAX1241的AGND和DGND在芯片内部是分开的Proteus仿真中不需要区分但实物PCB上如果条件允许模拟地和数字地要单点连接。5.3 12864使用sprintf的坑与手动拼串方案Keil C51里用sprintf(buf, %5.1f, temp)会把程序体积撑大好几K而且如果不勾选MicroLIB链接时会有不可预料的错误。更稳妥的做法是手动拆数拼字符串void lcd_display_temp(float temp) { char buf[6]; int t; if(temp 0) { temp -temp; buf[0] -; } else { buf[0] ; } t (int)(temp * 10); // 乘以10得到一位小数 buf[1] t / 100 0; buf[2] (t / 10) % 10 0; buf[3] .; buf[4] t % 10 0; buf[5] \0; lcd_display_str(buf); }这段代码要理解两点第一(int)(temp * 10)强制转int是四舍五入效果还是截断效果取决于编译器Keil里默认是截断如果你需要四舍五入就加0.5(int)(temp * 10 0.5)第二温度范围如果是-55到125℃整数部分最多三位所以buf[1]存的是百位buf[2]是十位如果温度只有几十度百位会被字符0占位显示成025.3这样的格式可以把buf[1]判断一下如果等于0就替换成空格。5.4 定时器1秒刷新与按键扩展思路在实际项目中检测装置往往不只显示温度电压还要加报警、按键切换显示。这套程序的主循环已经预留了定时器中断的框架扩展起来很方便。在定时器0的中断服务函数里累加计数到达20次每次50ms就置flag_1s主循环检测到这个标志再刷新数据。这样做的好处是DS1621的温度转换需要时间MAX1241采样也有建立时间用固定1秒节奏去读数据每次读到的都是转换完成后的稳定值规避了上一秒还没转换完就读的时序问题。如果后续要加报警功能可以在温度超过阈值时让蜂鸣器响接一个NPN三极管驱动就行。按键切屏的话用P3.2和P3.3接两个独立按键在主循环里扫描切换只显温度只显电压双显三种模式。整体框架不用动只是在显示部分加判断。6. 最后再分享两个小技巧第一个是关于Proteus仿真速度。如果12864刷新太频繁仿真会变得非常慢因为Proteus对液晶的字符扫描做了逐点模拟每次写显示数据都会消耗大量计算资源。把刷新率从1秒改成3秒甚至5秒仿真流畅度会明显提升。对验证功能来说5秒刷新完全够用反正数据是实时更新的。第二个是实际焊接时的排错顺序。如果实物上电后什么都没显示不要急着怀疑程序先用万用表量一下晶振两脚电压正常应该在1.5V-2.5V之间再量RST引脚电平正常应该是低电平最后确认P0口上拉电阻是否焊好。硬件顺序确认无误后再烧程序这样可以少走很多弯路。这个项目我用这套顺序排查过三次每次都能快速定位问题是在硬件还是软件。对了还有一点忘说了MAX1241的SHDN引脚如果悬空芯片默认是不工作的实物和仿真都要把它接VCC才能正常转换。这个我差点忘记写在上面算是悄悄藏了一个最容易被人忽略的细节。整套项目从设计到仿真跑通大约需要两到三个晚上大部分时间都花在I2C和SPI时序调试上但只要把上面这几个关键点记牢整个过程会很顺利。本文还有配套的精品资源点击获取