简介:这份Word文档面向电子信息、自动化及测控专业的学生与工程技术人员,围绕基于80C51单片机的数字压力传感器设计展开,可用于课程设计、毕业设计或工业自控入门参考。内容涵盖电阻应变片与压电传感器两种敏感元件的方案对比、全桥测量电路、三运放大电路、ADC0809模数转换、LED数码显示以及RS-485通信接口与协议,并给出电源电路、数据处理与温度补偿等设计论证。资源包共1个doc文件,约158KB,为完整可编辑的Word版设计文档,结构清晰、图文并茂,便于直接查阅或二次修改。目前已有43人学习下载,适合需要快速获取完整设计方案、理解传感器信号采集与单片机处理流程的读者参考借鉴。
1. 从一颗 80C51 到数字压力表:为什么老架构反而更适合入门
很多人第一次接触压力测量,脑子里冒出来的方案是买一个带 I2C 输出的数字压力传感器,接上开发板读寄存器就完事。但如果你手上只有一颗 80C51,或者你面对的是一个需要长期稳定运行、成本压到极致的工业现场,思路就完全不一样了。基于 80C51 单片机的数字压力传感器设计,核心不是堆算力,而是用最少的资源把模拟信号采准、把数字量算对、把显示和通信做稳。它解决的是“低成本、可标定、能长期跑”的问题,适合刚入门的电子工程师、做课程设计的学生,以及需要给老旧设备加数字显示的现场维护人员。这颗芯片没有硬件乘法器,没有浮点单元,连 ADC 都要外挂,但正因为资源紧,你才会被迫把每一个时钟周期和每一字节内存都算清楚。我见过太多人一上来就选带 24 位 ADC 的 MCU,结果连基准电压的温漂都没搞定,数据跳得比心跳还快。80C51 方案反而逼你先搞明白信号链。
2. 信号链怎么搭:从压阻桥到 80C51 能读的数字
2.1 压力传感器选型与模拟前端
数字压力传感器的“数字”二字,指的是最终输出形式,而不是敏感元件本身。绝大多数低成本方案用的还是压阻式惠斯通电桥,输出差分电压通常在几十毫伏量级。以常见的表压传感器为例,满量程输出可能是 50mV 到 100mV,零点偏置却可能有 ±20mV。这个信号直接进 ADC 会被噪声淹没,所以模拟前端必须做两件事:差分放大和电平搬移。
我一般会选一款仪表放大器,比如 AD620 的国产替代型号,增益电阻根据传感器灵敏度和 ADC 参考电压来算。假设传感器灵敏度是 2mV/V,供电 5V,满量程输出就是 10mV。ADC 参考电压 2.5V,想要满量程对应 2.0V,增益就是 200 倍。增益电阻公式是 Rg = 49.4kΩ / (G-1),算下来约 248Ω。这个电阻要用精度 1% 的金属膜电阻,温漂越小越好。
电平搬移用一只运放搭减法电路,把差分放大后的信号抬到 ADC 量程中点。这里有个血泪经验:不要用单电源运放直接处理接近地的信号,输出会失真。要么用双电源,要么选轨到轨输入输出的型号,并且给负电源端加一个负压产生电路。如果嫌麻烦,可以直接选一款带差分输入和可编程增益的 ADC,比如 HX711 这类专用于桥式传感器的芯片,它内部就把放大和 ADC 做完了,输出直接是数字量,80C51 只需要两个 IO 口模拟时序读取。但 HX711 的更新率低,适合静态压力测量,动态压力就别想了。
2.2 80C51 最小系统与 ADC 接口
80C51 本身没有 SPI 和 I2C 硬件模块,所有时序都要用 IO 口模拟。如果你选的是并行输出的 ADC,比如 ADC0809,那就更方便,直接挂在 P0 口和 P2 口上,用外部中断或者查询方式读。ADC0809 是 8 位、8 通道、逐次逼近型,转换时间约 100μs,对于压力测量绰绰有余。它的启动信号、地址锁存、输出使能都需要用 IO 口控制。
最小系统包括晶振电路、复位电路和电源。晶振选 11.0592MHz 还是 12MHz?如果要做串口通信,11.0592MHz 能产生更准的波特率,误差为 0。如果只做显示和按键,12MHz 计算定时器更方便。我一般用 11.0592MHz,因为后期加串口上传数据时不用改硬件。复位电路用 10μF 电容加 10kΩ 电阻,上电复位时间约 100ms,足够 80C51 初始化。EA 引脚接高电平,使用内部程序存储器。
ADC 的参考电压直接决定测量精度。不要用 7805 的输出当基准,它的温漂和噪声都很大。至少要用 TL431 这类可调基准源,精度 0.5%,温漂 50ppm/℃。如果预算允许,用 REF3025 这类专用基准,初始精度 0.05%,温漂 15ppm/℃。基准电压的稳定性比 ADC 位数更重要,8 位 ADC 配 0.05% 基准,有效精度能到 7 位以上;16 位 ADC 配 5% 基准,实际精度可能只有 8 位。
2.3 采样时序与数字滤波
80C51 读 ADC 的时序必须严格按数据手册来。以 ADC0809 为例,启动转换需要给 START 引脚一个正脉冲,然后等待 EOC 变高,再给 OE 高电平读取数据。用查询方式写起来简单,但占用 CPU 时间。用中断方式,把 EOC 反相后接到 INT0 或 INT1,转换完成触发中断,在中断服务程序里读数据。
采样频率不用太高,压力变化通常很慢。我一般设 10Hz 到 100Hz 的采样率,用定时器 0 产生 10ms 中断,在中断里启动 ADC,下次中断读结果。这样 CPU 占用率很低,还能留出时间做显示和按键扫描。
数字滤波是必须的。8 位 ADC 的原始数据跳动可能有 ±2LSB,直接显示会闪。最简单的办法是滑动平均,开一个 8 个元素的环形缓冲区,每次新数据覆盖最旧数据,然后求平均。求平均时不要用浮点,80C51 做浮点除法很慢。用移位代替除法:8 个数据求和后右移 3 位就是平均值。如果数据跳动还是大,可以加中值滤波,连续采 5 次,去掉最大和最小,剩下 3 个求平均。中值滤波对脉冲干扰特别有效,比如电机启停时产生的尖峰。
// 滑动平均滤波,8 点,用移位代替除法 unsigned char filter_buf[8]; unsigned char filter_idx = 0; unsigned int filter_sum = 0; unsigned char filter_avg(unsigned char new_data) { filter_sum -= filter_buf[filter_idx]; // 减去最旧数据 filter_buf[filter_idx] = new_data; // 存入新数据 filter_sum += new_data; // 累加新数据 filter_idx = (filter_idx + 1) & 0x07; // 环形索引,0-7循环 return (unsigned char)(filter_sum >> 3); // 除以8,右移3位 }这段代码的关键在于 filter_sum 用 unsigned int,因为 8 个 8 位数据求和最大 2040,不会溢出。filter_idx 用按位与代替取模,因为 8 是 2 的幂,这样写编译出来的代码更短。每次调用返回平均值,主循环里直接拿去显示或计算压力值。注意初始化时要把缓冲区清零,否则上电后前 8 次采样会混入随机值。
3. 标定与压力计算:没有浮点单元怎么算得准
3.1 两点标定法的实操步骤
压力传感器的输出和压力值之间理论上是线性的,但实际有零点偏置和灵敏度误差。两点标定是最实用的方法:在零压力和满量程压力下各采一组数据,算出斜率和截距。具体操作是,先不给压力,等读数稳定后记录 ADC 值,记为 adc_zero;然后加满量程标准压力,记录 adc_full。压力计算公式就是:
P = (adc - adc_zero) * P_full / (adc_full - adc_zero)
其中 P_full 是满量程压力值,比如 100kPa。这个公式里有一次除法和一次乘法,80C51 做 16 位除法大概需要几十个机器周期,对于 10Hz 的采样率完全够用。但要注意 adc_full - adc_zero 不能太小,否则除法误差大。如果传感器灵敏度低,放大后的满量程 ADC 变化量至少要有 100 个码值,对应 8 位 ADC 就是 40% 量程。如果只有 20 个码值,标定出来的精度会很差,这时候要么加大增益,要么换 12 位 ADC。
标定时要用标准压力源,不要用打气筒加机械表凑合。机械表的精度通常只有 2.5% 满量程,标出来的数字表不会比它更准。如果实在没有标准源,可以用水柱法:1 米水柱约 9.8kPa,用卷尺量水位差,精度能到 1%。但水柱法只适合低压,高压还是得用活塞式压力计。
3.2 定点数运算与查表法
80C51 没有浮点单元,用 float 类型会调用库函数,代码体积大、执行慢。我一般用定点数:把压力值放大 10 倍或 100 倍,用 unsigned int 或 unsigned long 存储。比如压力范围 0 到 100.0kPa,就用 0 到 1000 表示,单位是 0.1kPa。计算时先乘后除,避免中间结果溢出。
如果压力范围不大,查表法更快。把 ADC 值 0 到 255 对应的压力值预先算好,存到 code 区,运行时直接查表。表的大小是 256 字节,对于 80C51 的 4KB 程序空间来说可以接受。查表法没有计算误差,但标定后要重新生成表。生成表可以用 Excel 或者 Python 脚本,把公式算好导出成 C 数组。
// 定点数压力计算,压力单位 0.1kPa,满量程 1000 unsigned int adc_zero = 20; // 零点 ADC 值 unsigned int adc_full = 230; // 满量程 ADC 值 unsigned int p_full = 1000; // 满量程压力 100.0kPa unsigned int calc_pressure(unsigned char adc) { unsigned long temp; if (adc <= adc_zero) return 0; if (adc >= adc_full) return p_full; temp = (unsigned long)(adc - adc_zero) * p_full; temp = temp / (adc_full - adc_zero); return (unsigned int)temp; }这段代码里 temp 用 unsigned long,因为 (adc - adc_zero) 最大 255,乘以 1000 等于 255000,超过 16 位范围。除法用 32 位除以 16 位,Keil C51 会调用库函数,执行时间约 200 个机器周期,在 11.0592MHz 下约 18μs,对 10Hz 采样率来说微不足道。注意 adc_zero 和 adc_full 要存在非易失存储器里,比如 AT24C02,这样标定一次就不用每次上电重来。
3.3 温度补偿的简化处理
压阻式压力传感器有温漂,零点温漂和灵敏度温漂都有。工业级传感器内部会做补偿,输出已经比较稳定。但如果用的是裸片或者低成本传感器,温漂可能达到 0.5%FS/℃。80C51 方案里加温度补偿,最简单的办法是外挂一颗数字温度传感器,比如 DS18B20,测环境温度,然后在软件里做一阶线性补偿。
补偿公式:P_corrected = P_raw + K_zero * (T - T_ref) + K_span * (T - T_ref) * P_raw
K_zero 和 K_span 需要实验测定。把传感器放在温箱里,分别在 0℃、25℃、50℃ 下测零点和满量程输出,算出系数。没有温箱的话,可以用冰箱和热水浴粗略测,但精度没法保证。如果温度变化不大,比如室内环境 15℃ 到 35℃,可以只做零点补偿,灵敏度补偿忽略。DS18B20 用单总线,80C51 模拟时序要小心,读写时序的延时用 NOP 微调,不同晶振频率下要重新算。
4. 显示与通信:把数字压力值送出去
4.1 数码管驱动与动态扫描
数字压力表最直观的显示是 4 位数码管,显示 0.0 到 100.0。共阳数码管用 P0 口输出段码,P2 口输出位选。动态扫描频率要大于 50Hz,否则会闪。用定时器 1 产生 2ms 中断,每次中断点亮一位,4 位轮流,刷新率 125Hz,肉眼完全看不出闪烁。
段码表用 code 关键字存在程序存储器,共阳和共阴的段码不一样,写代码前先用万用表测一下。限流电阻每段一个,220Ω 到 470Ω,根据数码管亮度调。位选三极管用 S8550 或 S8050,注意共阳数码管的位选是低电平有效,三极管要接成射极跟随器还是集电极开路,取决于数码管共阳端的接法。
// 共阳数码管段码表,0-9,不带小数点 unsigned char code seg_code[] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90 }; // 显示缓冲区,4 位,分别对应千、百、十、个 unsigned char disp_buf[4] = {0, 0, 0, 0}; // 定时器1中断服务程序,2ms 一次 void timer1_isr() interrupt 3 { static unsigned char pos = 0; P2 = 0xFF; // 先消隐,关闭所有位 P0 = seg_code[disp_buf[pos]]; // 输出段码 P2 = ~(1 << pos); // 打开当前位,低电平有效 pos = (pos + 1) & 0x03; // 0-3 循环 }这段代码里 P2 先写 0xFF 消隐,再写段码,最后打开位选,顺序不能反,否则会有鬼影。pos 用按位与循环,和前面滤波代码一个套路。disp_buf 里的数据在主循环里更新,把压力值拆成 4 位数字。拆位用除法和取余,80C51 做 16 位除法稍慢,但 10Hz 更新一次完全没压力。
4.2 串口上传与上位机协议
如果需要把数据传到电脑,80C51 的串口是现成的。用定时器 1 做波特率发生器,11.0592MHz 晶振下,9600bps 的定时器初值是 0xFD。串口工作在模式 1,8 位数据,可变波特率。发送用查询方式就行,压力数据更新慢,不用中断。
协议我一般用简单的 ASCII 帧:帧头 0xAA,然后 4 字节压力值 ASCII 码,最后 0x55 结尾。上位机收到后解析。ASCII 的好处是调试方便,用串口助手直接能看。二进制协议更紧凑,但调试时得对着十六进制看,容易出错。
// 串口发送一个字节,查询方式 void uart_send(unsigned char dat) { SBUF = dat; while (!TI); // 等待发送完成 TI = 0; // 清除发送标志 } // 发送压力值,格式:0xAA + 4字节ASCII + 0x55 void send_pressure(unsigned int p) { uart_send(0xAA); uart_send(p / 1000 + '0'); // 千位 uart_send(p / 100 % 10 + '0'); // 百位 uart_send(p / 10 % 10 + '0'); // 十位 uart_send(p % 10 + '0'); // 个位 uart_send(0x55); }发送前要确保 p 不超过 9999,否则会发乱。如果压力值可能超过,先做限幅。串口发送和数码管扫描都在主循环里调用,注意不要互相阻塞。数码管扫描在定时器中断里,串口发送在主循环,两者不冲突。
5. 避坑与排查:80C51 压力表最容易翻车的 5 个地方
5.1 ADC 读数跳动大,显示最后一位乱闪
现象:压力稳定时,数码管最后一位在 0 和 1 之间跳,或者跳动超过 3 个码值。
原因:基准电压不稳、模拟地和数字地混在一起、滤波不充分。80C51 的 IO 口翻转时会产生电流尖峰,通过地线耦合到模拟部分。
解决:基准源加 10μF 钽电容和 0.1μF 陶瓷电容退耦,位置尽量靠近基准芯片。模拟地和数字地单点连接,在 ADC 下方用 0Ω 电阻或磁珠连接。软件上把滑动平均从 8 点加到 16 点,或者加中值滤波。如果还跳,检查传感器信号线是否太长,用屏蔽线并单端接地。
5.2 标定后线性度差,中间点误差大
现象:零点和满量程都准,但 50% 压力时误差超过 2%。
原因:传感器本身非线性,或者放大器在中间电平处失真。也可能是 ADC 的积分非线性(INL)指标差。
解决:先查传感器数据手册的非线性指标,如果传感器本身非线性就 1%,那系统不可能优于 1%。如果是放大器问题,检查输出是否在轨到轨范围内,中间电平处是否进入非线性区。ADC 的 INL 通常 8 位的是 ±0.5LSB,影响不大。如果要求高,用三点标定,把中间点也纳入,用分段线性插值。
5.3 上电后数码管显示乱码或全亮
现象:一上电所有段都亮,或者显示无意义的字符。
原因:80C51 上电复位时间不够,IO 口状态不确定。或者 disp_buf 未初始化,里面是随机值。
解决:复位电路电容加大到 22μF,确保复位时间大于 100ms。在 main 函数开头先把 disp_buf 清零,P0 和 P2 写 0xFF 消隐。如果还乱,检查 EA 引脚是否接高,程序是否从 0000H 开始执行。有时候是晶振没起振,用示波器看 XTAL2 引脚有没有波形。
5.4 串口通信丢包或乱码
现象:上位机收到的数据偶尔多一个字节或少一个字节,或者全是乱码。
原因:波特率误差大、地线没接、发送时被中断打断。
解决:11.0592MHz 晶振下 9600bps 误差为 0,如果用了 12MHz 晶振,误差约 8.5%,会丢包。检查串口线是否共地,TX 和 RX 不要接反。发送函数里 while(!TI) 是死等,如果发送时来了中断,TI 标志可能被意外清除。解决办法是在发送前关中断,发送完再开中断,或者用中断方式发送。
5.5 长期运行后压力值漂移
现象:跑几个小时或几天后,零压力时显示不为零,慢慢变大或变小。
原因:基准电压温漂、传感器温漂、放大器失调电压漂移。也可能是电源纹波导致 ADC 参考不稳。
解决:基准源换低温漂型号,传感器如果温漂大就加温度补偿。放大器选低失调漂移的,比如 OP07 的失调漂移是 0.5μV/℃,足够用。电源加 LC 滤波,7805 后面接 100μH 电感和 100μF 电容。软件上可以加自动零点跟踪:如果压力值连续 1 分钟小于满量程的 1%,就认为当前是零点,自动更新 adc_zero。但这个方法有风险,如果实际压力就是很小,会被误判为零点。我一般只在开机后前 10 秒做自动零点,之后锁定。
6. 进阶技巧:用 80C51 的剩余资源做自诊断
80C51 的 IO 口和定时器资源有限,但做完压力测量、显示和串口后,通常还能剩几个 IO。我习惯留一个 IO 做传感器断线检测,一个 IO 做超量程报警。断线检测的原理很简单:在传感器桥臂上并一个高阻值电阻,正常时桥臂电阻低,该点电压被拉低;断线时桥臂开路,电压被高阻拉高。用 ADC 读这个电压,超过阈值就报警。
另一个技巧是用定时器 2 做看门狗。80C51 没有内置看门狗,但可以用定时器 2 的溢出中断模拟。在主循环里定时喂狗,如果程序跑飞,定时器 2 溢出后触发复位。具体做法是定时器 2 设成 100ms 溢出,溢出中断里把 RST 引脚拉低 10μs。主循环里每 50ms 清一次定时器 2 的计数器。如果程序卡死,定时器 2 溢出,系统复位。
// 定时器2看门狗初始化,100ms溢出 void wdt_init() { RCAP2H = 0x3C; // 11.0592MHz 下 100ms 初值 RCAP2L = 0xB0; TH2 = 0x3C; TL2 = 0xB0; ET2 = 1; // 使能定时器2中断 EA = 1; TR2 = 1; // 启动定时器2 } // 定时器2中断,溢出说明程序跑飞,复位 void timer2_isr() interrupt 5 { TF2 = 0; // 清除溢出标志 // 拉低复位引脚,需要硬件配合,用一个IO口控制RST RST_PIN = 0; // 等待复位 while(1); } // 主循环里喂狗 void wdt_feed() { TR2 = 0; // 停止定时器 TH2 = 0x3C; // 重装初值 TL2 = 0xB0; TR2 = 1; // 重新启动 }这个看门狗需要硬件上把 RST 引脚通过一个二极管或三极管接到某个 IO 口,不能直接接,因为 80C51 的 RST 是高电平复位,而 IO 口拉低是复位不了的。正确做法是用一个 NPN 三极管,基极接 IO 口,集电极接 RST,发射极接地。IO 口输出高电平时三极管导通,RST 被拉低,但 80C51 的 RST 需要高电平复位,所以这个电路反了。应该用 PNP 三极管,发射极接 VCC,基极接 IO 口,集电极接 RST,IO 口输出低电平时三极管导通,RST 得到高电平。这个细节容易翻车,画板子前先用万用表测一下。
最后说一个我自己的习惯:每次改完代码,先不接传感器,用信号发生器给 ADC 输入一个已知电压,看显示值对不对。确认信号链没问题了,再接传感器标定。这样能把硬件问题和软件问题分开,省得来回猜。希望帮到你。
本文还有配套的精品资源,点击获取