简介:这份课程设计文档面向电子信息工程、自动化等专业修读单片机课程的学生,用于完成数字温度计课程设计及报告撰写。内容以AT89C51为主控,配合DS18B20单总线温度传感器、4位一体共阴LED数码管显示、上下限温度设置与蜂鸣器红灯报警电路,覆盖设计目的、设计要求、系统框图、时钟与复位电路、传感器与显示电路、报警电路及完整C源程序,可直接作为课程设计模板与调试参考。资源包共1个文件,为doc格式文档,压缩包约1.34MB,含封面、任务书栏目与正文,便于按章节直接改写。已有4053人学习下载,说明该方案在课程设计场景中接受度较高。读者可据此掌握一线总线测温时序、数码管动态显示与报警阈值设定等关键环节,并对照源程序理解按键调节上下限、温度闪烁提示等实现细节。
1. 数字温度计这门课设:51单片机加DS18B20到底能做出什么
答辩前一晚,数码管上的温度还卡在 85℃ 上不去下不来——做基于51单片机数字温度计的课程设计,几乎人人都会撞上这一幕。这套方案的内核其实很朴素:一颗 51 内核单片机当主控,一根 IO 口挂 DS18B20 数字温度传感器,配数码管或 LCD1602 显示,再加蜂鸣器做上下限报警,愿意的话还能把温度从串口发到上位机画曲线。
它解决的是“把温度从模拟量变成一串能直接显示、能直接判断的数字”这件事:量程 -55℃ 到 +125℃,12 位分辨率 0.0625℃,0 到 85℃ 区间误差 ±0.5℃,做室温监测、水温控制、孵化箱、机柜告警都够用。适合两类人:正在交课程设计的同学,以及手上有块旧开发板、想回头把单总线时序补一遍的工程师。原因也简单——单总线时序、定时器扫描、串口通信这些基本功全被压在一张原理图上,绕不过去,也偷不了懒。
2. 选型先立住:51+DS18B20+LCD1602 这条链路为什么值得照抄
动手焊板子之前,先把“为什么是这套组合”讲清楚。选型糊涂,后面调试就会变成玄学:读数不对时你分不清是传感器坏了、时序错了,还是显示译码写反了。这一章把三件事定下来——传感器怎么选、最小系统要哪些外围、显示用哪种。
2.1 三种测温方案摆一起比:热敏电阻、DS18B20、I2C 数字温度芯片
课程设计里最常见的三种测温和方案,差别不在“能不能测”,而在“标定工作量”和“占用资源”。
| 方案 | 接口 | 典型精度 | 外围器件 | 软件工作量 | 适用场景 |
|---|---|---|---|---|---|
| NTC 热敏电阻 + 分压 | 模拟量,需 ADC 或 RC 充放电 | ±0.5℃ 起步,不标定更差 | 分压电阻、基准 | 非线性查表 + 逐点标定 | 成本极敏感、精度要求低 |
| DS18B20 | 单总线,1 根 IO | ±0.5℃(0~85℃) | 一只 4.7k 上拉电阻 | 严格时序 + 补码换算 | 课设、多点测温、长线 |
| I2C 数字温度芯片 | SCL/SDA 两线 | ±0.5℃~±1℃ | 两只上拉、地址引脚 | 标准 I2C 读写 | 板上已有 I2C 总线 |
51 内核单片机内部没有 ADC(少数增强型号才有),走 NTC 路线要么外挂 ADC 芯片,要么用 RC 充放电测时间常数——后者对电源电压和电容温漂极其敏感,标定表得自己做,答辩时被问一句“你这个表怎么来的”就很难接。I2C 方案精度不错,但要占用两根 IO,还要处理总线仲裁和应答。DS18B20 只吃一根 IO,精度出厂已标定,代价是把复杂度从硬件挪到了时序上。
提示:如果题目只要求“测量并显示温度”,DS18B20 是性价比最高的选择;如果题目明确要求“体现模拟电路设计能力”,那才考虑 NTC 加 ADC 的路线。
2.2 单总线省引脚,代价全在时序上
单总线(1-Wire)只有一根数据线,靠“时隙”区分 0 和 1,所有通信由主机发起。理解三个基本时隙,后面写驱动就不会背代码:
- 复位时隙:主机拉低总线至少 480μs,然后释放;从机若在线,会在释放后 15~60μs 内主动拉低 60~240μs,这叫存在脉冲。
- 写 1 时隙:主机拉低 1~15μs 后立刻释放,整个时隙不短于 60μs,从机在 15~60μs 窗口内采样到高电平即为 1。
- 写 0 时隙:主机拉低 60~120μs 再释放,从机采样到低电平即为 0。
- 读时隙:主机先拉低至少 1μs 再释放,从机在总线上输出数据位,主机必须在拉低后的 15μs 内采样,晚了从机就撤了。
数据按低位先出,一个字节 8 个时隙。这套机制的时间容差其实很宽——复位可以 480μs 也可以 900μs,写 0 可以 60μs 也可以 120μs;唯独读时隙的 15μs 采样窗口卡得死。这就决定了延时函数的调法:宁可整体偏快,也别偏慢。
2.3 最小系统接线表:晶振、复位、P0 上拉一个都不能省
硬件上最容易翻车的地方不是传感器,是 P0 口。51 的 P0 口是开漏输出,不接上拉电阻时输出高电平是浮空的,接 LCD1602 数据口会表现为“屏幕亮了但全是黑块”或者“字符乱码”,很多人第一反应是程序写错了,其实是硬件。
| 网络 | 连接 | 参数/说明 |
|---|---|---|
| 晶振 | XTAL1、XTAL2 各接一只电容到地 | 11.0592MHz + 22pF×2,串口要用就选这个频点 |
| 复位 | RST 接 10μF 电容到 VCC,10k 电阻到 GND | 上电复位 + 按键复位并联 |
| EA | 接 VCC | 使用片内程序存储器时必须拉高 |
| P0 口 | 10k 排阻上拉到 VCC | 接 LCD 数据口或数码管段码时必须加 |
| DS18B20 | DQ 接 P3.3,同时经 4.7k 上拉到 VCC | 上拉不能省,寄生供电时更关键 |
| 蜂鸣器 | P2.0 经 NPN 三极管驱动 | 有源蜂鸣器,IO 直接驱动电流不够 |
| 按键 | P3.2、P3.4 接 GND,内部上拉 | 上下限调整、模式切换 |
这里有个血泪经验:DS18B20 有两种供电方式,三线供电(VDD 接 3.3V 或 5V)和寄生供电(VDD 接 GND,靠 DQ 线偷电)。课设建议一律用三线供电,寄生供电在转换期间需要主机强上拉,稍不注意就是“读出来永远是 85℃”。
2.4 显示怎么选:数码管动态扫描与 LCD1602 的引脚账
数码管便宜、亮度高、看着有“仪表感”,但动态扫描要用定时器,还要消隐;LCD1602 能显示字符和符号,接线多 6 根控制线,但刷新简单。
| 对比项 | 4 位共阳数码管 | LCD1602 |
|---|---|---|
| 占用 IO | 段码 8 根 + 位选 4 根(可译码到 2 根) | 数据 8 根(或 4 根)+ 控制 3 根 |
| 显示内容 | 数字、小数点、少量符号 | ASCII 字符,可显示“Temp:”“ALARM” |
| 软件成本 | 定时器中断扫描 + 段码表 + 消隐 | 初始化时序 + 忙标志判断 |
| 常见坑 | 重影、亮度不均、位选串扰 | 上电初始化顺序、对比度电位器 |
我的建议是:答辩要好看用 LCD1602,要体现底层功力用数码管,两个都上也不亏——数码管走 P0+P1,LCD1602 走 P2,互不冲突,正好一页报告能画出两张图。
3. 把温度读出来:复位、写位、读位三个时隙的手写实现
这一章是整篇的核心。DS18B20 的驱动不复杂,但每一行都跟时序绑着,写错一个延时就是“读出来全是 0xFF”。我们按“延时基准 → 复位 → 读写位 → 完整读温度”的顺序推。
3.1 微秒基准怎么定:11.0592MHz 与 12MHz 的延时函数差异
51 的一个机器周期是 12 个时钟周期。11.0592MHz 下约 1.085μs,12MHz 下正好 1μs。这个 8.5% 的差异在串口波特率上是致命的(11.0592 能把 9600 的误差压到 0),但在单总线时序上通常在容差内。
问题在于,C 语言写不出精确的微秒延时——循环体编译成几条指令、编译器优不优化,都会改变实际宽度。所以正确做法不是抄一个函数,而是先校准再使用:
#include <intrins.h> /* 约 n 微秒,nop 数量需按实测校准 */ void delay_us(unsigned int us) { while (us--) { _nop_(); _nop_(); } } /* 校准用:让 P1.0 输出方波,逻辑分析仪量高电平宽度 */ void delay_calib(void) { while (1) { P1_0 = 0; delay_us(1000); P1_0 = 1; delay_us(1000); } }逻辑说明:delay_us里每轮循环执行一次自减、一次比较跳转、两条空指令,编译后大约 6~8 个机器周期。跑一次delay_calib,用逻辑分析仪抓 P1.0,如果高电平是 7000μs 而不是 1000μs,说明单轮循环约 7μs,把两条_nop_()删掉或改成空循环再量。参数上只有一个自由度——循环体里的空指令条数,反复调到delay_us(1000)实测落在 950~1050μs 之间即可。
毫秒级延时不要用delay_us叠加,直接用定时器或者长循环:
void delay_ms(unsigned int ms) /* 11.0592MHz 下约 1ms 每单位 */ { unsigned int i, j; for (i = 0; i < ms; i++) for (j = 0; j < 112; j++); /* 内层系数按实测调 */ }注意:
delay_ms(750)会阻塞 CPU 750ms,期间数码管不刷新、按键不响应。真正的工程写法是把转换启动和读数拆到两个时间点,中间让主循环正常跑。
3.2 复位与存在脉冲:判断器件在不在的第一步
复位是每次通信的起点,也是排查故障的第一现场。返回值为 0 表示器件应答了,返回 1 表示总线上没人——接线断了、上拉没焊、器件烧了,都走这条分支。
sbit DQ = P3^3; /* DS18B20 数据脚 */ /* 返回 0:检测到存在脉冲;返回 1:无器件应答 */ bit ds_reset(void) { bit presence; DQ = 0; delay_us(500); /* 拉低 480~960us,取下限附近即可 */ DQ = 1; /* 释放总线,靠上拉电阻拉高 */ delay_us(70); /* 等 15~60us 后从机拉低,留余量 */ presence = DQ; /* 0 表示从机应答 */ delay_us(430); /* 补足整个复位时隙,给从机恢复时间 */ return presence; }逻辑说明:拉低 500μs 满足“至少 480μs”;释放后等 70μs 再采样,正好落在存在脉冲的 60~240μs 区间内;最后补 430μs,保证整个时隙超过 960μs,避免下一次操作踩到从机的恢复期。三个延时加起来约 1000μs,是单总线里唯一“越长越安全”的地方。
参数上唯一需要动的是第一个 500——如果线缆很长(超过 3 米),可以加到 600~700μs,给分布电容留充放电时间。
3.3 写字节读字节:低位先出与 15 微秒采样窗口
读写都建立在位操作上,先封装位,再拼字节。写的时候注意:写 0 的拉低时间不能超过 120μs,否则从机会误判为复位脉冲。
void ds_write_bit(bit b) { DQ = 0; if (b) { /* 写 1:拉低 10us 后释放 */ delay_us(10); DQ = 1; delay_us(55); /* 补足 60us 以上时隙 */ } else { /* 写 0:拉低 65us 再释放 */ delay_us(65); DQ = 1; delay_us(5); /* 时隙间至少 1us 恢复 */ } } bit ds_read_bit(void) { bit b; DQ = 0; /* 拉低启动读时隙 */ delay_us(2); DQ = 1; /* 释放,从机开始输出 */ delay_us(10); /* 关键:15us 内采样 */ b = DQ; delay_us(50); /* 补足时隙,等从机释放总线 */ return b; } void ds_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { ds_write_bit(dat & 0x01); /* 低位先出 */ dat >>= 1; } } unsigned char ds_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; if (ds_read_bit()) dat |= 0x80; /* 低位先出,故先右移再补高位 */ } return dat; }逻辑说明:ds_read_byte里“先右移再补 0x80”这个顺序是低位先出的直接结果,很多人写成先或再移,读出来刚好是位反转,温度会变成一个离谱的大数。ds_read_bit里的 10μs 是整段代码中最敏感的参数,多 5μs 就可能读到从机已经释放后的高电平,表现为“温度恒为 +125℃ 或全 1”。
参数上:写 1 的 10μs 和写 0 的 65μs 都在容差范围内,可以按实测微调;读时隙的 10μs 不要动,改也只能往小改。
3.4 一次完整的温度读取:跳过ROM、启动转换、读暂存器
单器件时用“跳过 ROM”(0xCC)省掉 64 位地址匹配;总线挂多个器件时才需要“搜索 ROM”(0xF0)或“匹配 ROM”(0x55)。完整流程是:复位 → 跳过 ROM → 启动转换(0x44)→ 等待 → 复位 → 跳过 ROM → 读暂存器(0xBE)→ 连读 9 字节。
/* 返回原始码;0x7FFF 表示无器件应答 */ int ds_read_raw(void) { unsigned char tl, th; int raw; if (ds_reset()) return 0x7FFF; ds_write_byte(0xCC); /* 跳过 ROM */ ds_write_byte(0x44); /* 启动一次温度转换 */ if (ds_reset()) return 0x7FFF; ds_write_byte(0xCC); ds_write_byte(0xBE); /* 读暂存器 */ tl = ds_read_byte(); /* 字节 0:温度低 8 位 */ th = ds_read_byte(); /* 字节 1:温度高 8 位 */ raw = ((int)th << 8) | tl; return raw; }逻辑说明:暂存器一共 9 字节,前两字节是温度,第 3、4 字节是上下限报警值,第 5 字节是配置寄存器,最后一个是 CRC。这里只读两字节就结束,虽然从机还等着被读,但每次通信前都会复位,不影响下一次。
参数说明:上面这段在“启动转换”和“读暂存器”之间没有延时,实际使用时必须在两次调用之间留够转换时间——12 位精度需要 750ms。常见做法是主循环每 1 秒调一次,中间用定时器做显示扫描,而不是在函数里死等。
4. 显示、报警、上传:把原始码变成能看懂的温度值
拿到raw只是拿到了一个 16 位补码,离“人能看懂的温度”还差三步:换算、显示、判断。
4.1 补码与 0.0625℃:定点换算比浮点更省
DS18B20 输出的是 12 位补码,最低位权重 0.0625℃。正温度直接乘,负温度要先取补。51 上跑浮点要拖进几百字节的库,转换一次几毫秒,能不用就不用。
/* 原始码 -> 单位 0.1℃ 的整数,范围 -550 ~ 1250 */ int raw_to_t10(int raw) { return (int)(((long)raw * 5) / 8); /* raw * 0.0625 * 10 = raw * 0.625 */ } /* 拆分显示用:符号、整数部分、小数部分 */ void t10_split(int t10, char *sign, unsigned char *ip, unsigned char *fp) { unsigned int a; *sign = (t10 < 0) ? '-' : ' '; a = (t10 < 0) ? (unsigned int)(-t10) : (unsigned int)t10; *ip = a / 10; *fp = a % 10; }逻辑说明:raw * 5 / 8是 0.625 的整数近似,误差不超过 0.05℃,比 0.0625 的原始分辨率还小一档,完全够显示一位小数。用long是为了防止raw * 5在 -880 这种值上溢出——int装得下,但中间结果类型写明确更安全。
参数说明:想要两位小数就把系数改成raw * 625 / 1000,代价是显示位数变多、数码管得再加一位。课设一般一位小数足够,答辩时也别自己给自己加难度。
负数的处理有个坑:-t10在int上是合法的,但如果t10是-32768就会溢出。温度范围只有 -55℃,不会碰到,但写代码时心里要有这根弦。
4.2 数码管动态扫描:定时器里刷新才不闪
动态扫描的本质是“一次只亮一位,靠人眼视觉暂留合成四位”。放在主循环里扫,一旦遇到 750ms 的转换延时就会集体闪烁;放在定时器中断里扫,主循环再忙也不影响。
/* 共阳数码管段码 0~9,最高位为小数点位 */ code unsigned char SEG[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; unsigned char disp_buf[4]; /* 待显示的 4 位段码 */ unsigned char digit = 0; void timer0_isr(void) interrupt 1 { TH0 = (65536 - 1000) / 256; /* 1ms 一次 */ TL0 = (65536 - 1000) % 256; P1 = 0xFF; /* 先全灭,消隐 */ P0 = disp_buf[digit]; /* 送段码 */ P1 = ~(1 << digit); /* 位选,低电平有效 */ if (++digit >= 4) digit = 0; }逻辑说明:消隐必须在送段码之前,否则上一位的段码会短暂停留在新选通的位上,形成“重影”。1ms 刷新一位,四位一轮 4ms,刷新率 250Hz,肉眼完全看不出闪。
参数说明:定时器初值按晶振算,11.0592MHz 下 1ms 需要 921 个机器周期,所以是65536-921;上面写的 1000 只是示意,实际要按晶振改。位选如果经三极管驱动,极性要跟着翻。
4.3 上下限报警与回差:为什么阈值要留 0.5℃ 余量
最朴素的写法是“超过 30℃ 就响”,结果是温度在 30.0℃ 附近抖动时蜂鸣器哒哒哒响个不停。原因很简单:传感器分辨率 0.0625℃,噪声轻松超过这个数,阈值上必然来回穿越。
解决方法是加回差(滞回):超过上限才置位报警,要跌到上限以下 0.5℃ 才解除。
#define ALARM_HI 300 /* 上限 30.0℃,单位 0.1℃ */ #define ALARM_LO 100 /* 下限 10.0℃,单位 0.1℃ */ #define HYST 5 /* 回差 0.5℃ */ bit alarm_on = 0; void alarm_check(int t10) { if (!alarm_on && (t10 >= ALARM_HI || t10 <= ALARM_LO)) alarm_on = 1; else if (alarm_on && t10 < ALARM_HI - HYST && t10 > ALARM_LO + HYST) alarm_on = 0; BEEP = alarm_on ? 0 : 1; /* 低电平驱动 NPN 三极管 */ }逻辑说明:alarm_on是状态变量,不是即时比较结果。置位条件用“或”,解除条件用“与”,中间夹着 0.5℃ 的死区,这样温度在阈值附近抖动时不会反复触发。
参数说明:HYST取 0.5℃ 对应 8 个最小刻度,对抗噪声足够;如果现场干扰大,可以加到 1.0℃。上下限不要贴得太近,低于 2℃ 的窗口在滞回逻辑下会退化成常报警。
4.4 串口把温度送出去:9600 波特率与帧格式
课设加个串口输出,报告里就能放一张“温度-时间曲线”,比干巴巴的截图有说服力得多。11.0592MHz 晶振配 9600 波特率,误差为 0,这是选这个频点的唯一理由。
void uart_init(void) { SCON = 0x50; /* 方式 1,允许接收 */ TMOD |= 0x20; /* 定时器 1 方式 2,8 位自动重装 */ TH1 = 0xFD; /* 11.0592MHz 下 9600bps */ TL1 = 0xFD; TR1 = 1; ES = 1; EA = 1; } void uart_send_str(unsigned char *s) { while (*s) { SBUF = *s++; while (!TI); TI = 0; } } /* 主循环里每秒调用一次:T25.3\r\n */ void report_temperature(int t10) { char sign; unsigned char ip, fp, buf[8]; t10_split(t10, &sign, &ip, &fp); buf[0] = 'T'; buf[1] = (sign == '-') ? '-' : ' '; buf[2] = '0' + ip / 10; buf[3] = '0' + ip % 10; buf[4] = '.'; buf[5] = '0' + fp; buf[6] = '\r'; buf[7] = '\n'; uart_send_bytes(buf, 8); }逻辑说明:TH1 = 0xFD是 9600 波特率在 11.0592MHz、SMOD=0 时的标准值,换成 12MHz 晶振误差会到 8% 以上,通信会随机丢字节——这是很多人“仿真好好的,实物上位机收乱码”的根因。
参数说明:发送用查询方式(等 TI 标志)最简单,但会阻塞;如果主循环任务多,改成中断里发。数据帧用T开头、\r\n结尾,上位机串口助手按行解析最省事,不需要自己写协议。
5. 避坑与排查:六个让课程设计卡到凌晨的现场问题
下面这六条,每一条都能让人从晚上八点坐到凌晨两点。按“现象 → 原因 → 解决”记,遇到问题先对号入座,比盲目改代码快得多。
5.1 读数类的三个坑:85℃、全 0、上电首读失败
现象一:温度恒定显示 85℃,怎么加热都不变。原因:85℃ 是 DS18B20 上电复位后暂存器的默认值,说明主机根本没读到新数据。常见分支有三个——复位时隙没检测到存在脉冲但代码没判断、启动转换后没等够 750ms 就读、或者器件处于寄生供电但主机没有强上拉。 解决:先让ds_read_raw把“无应答”明确返回出来,用一个 LED 指示复位结果;确认返回值不是 0x7FFF 之后,再检查转换等待时间。如果是寄生供电,要么改成三线,要么在启动转换后把 DQ 强拉高。
现象二:读出来全是 0x00 或全是 0xFF。原因:全 0 通常是读时隙采样太晚,从机已经释放总线,读到的都是低电平;全 0xFF 是总线一直是高电平,说明从机压根没响应,或者上拉电阻没焊、DQ 接错脚。 解决:用万用表量 DQ 静态电压,三线供电时应接近 VCC;如果一直是 0V 或浮空,先查硬件。软件上把ds_read_bit里的 10μs 改小到 8μs 试一次,能读出正常值就说明采样点晚了。
现象三:上电第一次读永远失败,之后正常。原因:上电瞬间电源爬升慢,DS18B20 内部复位电路还没准备好,第一次复位时序被吞掉。 解决:在main开头加delay_ms(100)再初始化;同时把第一次读取的结果丢弃,从第二次开始用。
5.2 时序与硬件类的两个坑:长线和仿真
现象四:在开发板上好好的,焊到自己板子上就不行,或者杜邦线一长就乱码。原因:单总线是准双向口加弱上拉,线长超过 1 米后分布电容把上升沿拖慢,读时隙采样时电平还没爬到阈值。 解决:把 4.7k 上拉改成 2.2k(功耗增加但在 5V 下可接受),缩短走线,必要时用双绞线并把地线跟 DQ 绞在一起。软件上把复位后的等待从 70μs 加到 90μs,给上升沿留时间。
现象五:电路仿真软件里跑得完美,实物就是不出数。原因:仿真软件对单总线时序做了“理想化”处理,延时偏差、上拉不足、电源纹波它都不管。代码在仿真里能过,只说明逻辑对,不说明时序对。 解决:把仿真当“逻辑验证工具”,不当“时序验证工具”。实物调试必须要有一台逻辑分析仪,几十块钱的那种就够,抓一次复位波形,存在脉冲能不能看到一目了然。
注意:逻辑分析仪是这套课设里唯一值得买的调试工具。没有它,你只能靠“改延时碰运气”,那就是玄学了。
5.3 显示与报警类的一个坑:闪烁与误报警
现象六:数码管有重影,或者温度一变化蜂鸣器就哒哒响。原因:重影是动态扫描没消隐,上一位段码残留在新位选通期间;误报警是阈值没有回差,0.0625℃ 的量化噪声在阈值附近来回穿越。 解决:扫描中断里严格执行“先灭位选、再送段码、最后开位选”;报警逻辑改成 4.3 节的滞回写法,并在软件上加一层 4 点滑动平均,把噪声压下去。
/* 4 点滑动平均,抑制 ±0.1℃ 级别的抖动 */ int smooth(int t10) { static int win[4] = {0,0,0,0}; static unsigned char idx = 0; win[idx] = t10; idx = (idx + 1) & 0x03; return (win[0] + win[1] + win[2] + win[3]) / 4; }逻辑说明:idx用位与代替取模,省两个机器周期;窗口长度取 4 是折中——太长反应慢,太短压不住噪声。参数上如果被测对象是水温这种大热惯性系统,窗口可以加到 8。
6. 把课设做成能写进简历的作品:CRC、掉电保存与报告里的三张图
课程设计交完就删代码,是最可惜的一件事。加三个小功能,这套东西就能从“作业”变成“作品”。
6.1 加一条 CRC:让读数不再是黑匣子
DS18B20 暂存器第 9 字节是 CRC,读满 9 字节校验一次,能直接把“线太长导致偶发误码”这类问题挡在数据层。
/* 单总线常用 CRC-8,多项式 x^8+x^5+x^4+1 */ unsigned char crc8(unsigned char *p, unsigned char n) { unsigned char i, c = 0; while (n--) { c ^= *p++; for (i = 0; i < 8; i++) c = (c & 0x01) ? ((c >> 1) ^ 0x8C) : (c >> 1); } return c; }把读取函数改成连读 9 字节,前 8 字节算 CRC 与第 9 字节比对,不等就丢弃本次数据、沿用上次值。代价是 20 来行代码,收益是现场偶发跳变基本消失。
6.2 参数存起来:上下限不该每次上电重设
上下限每次上电都回到默认值,答辩老师一定会问“为什么不能保存”。51 内部有 Flash,可以划一段做参数区(没有的话外挂一只 EEPROM 芯片也行)。注意写 Flash 前要先擦除扇区,且擦写次数有限,别在主循环里写,只在按键确认时写一次。
精度配置也顺便定下来,转换时间直接决定主循环节奏:
| 配置位 | 分辨率 | 最大转换时间 | 适用 |
|---|---|---|---|
| 00 | 9 位 / 0.5℃ | 93.75ms | 只做告警,要快 |
| 01 | 10 位 / 0.25℃ | 187.5ms | 一般显示 |
| 10 | 11 位 / 0.125℃ | 375ms | 需要趋势曲线 |
| 11 | 12 位 / 0.0625℃ | 750ms | 课设默认,也是出厂值 |
改配置用“写暂存器”命令(0x4E),依次写 TH、TL、配置字节,写完记得再读回来确认。
6.3 报告与答辩:三张图一份表
文档部分别写成代码说明书的搬运。真正加分的是一张系统框图(单片机、传感器、显示、报警、串口五个方块加箭头)、一张主程序流程图(初始化、读温、换算、显示、判断、上传,闭环)、一张单总线复位时序图(标出 480μs、70μs、240μs 三个时间点),再加一份实测数据表——用标准温度计做参照,从 10℃ 到 60℃ 每隔 10℃ 记一组,算出误差列。有了这份表,“±0.5℃”就不是抄手册,而是你量出来的。
我自己带新人做这套东西,最常说的一句话是:先把复位波形抓出来,再谈代码。当年第一次做这个课设,我改了三个小时延时函数,最后发现是 P0 口没接上拉电阻——从那以后,我桌上一直放着一台逻辑分析仪和一张最小系统接线表,先量再改,比什么都快。这套方案本身不值钱,值钱的是你从“能跑”走到“知道为什么能跑”的那段路。希望帮到你。
本文还有配套的精品资源,点击获取