|
一、设计简介 1.1 项目背景与目标在电子实验、设备维修及教学实践中,电阻测量是一项基础且频繁的操作。传统万用表在超高阻值测量时精度下降,且无法实现自动化数据记录。本项目旨在开发一款基于嵌入式系统的电阻测量仪,具备宽量程、自动切换、直观显示和低成本的特点,作为传统仪表的补充或嵌入式测控模块。本项目设计并实现了一种高性价比、宽量程的智能电阻测量系统。系统以STC89C52RC单片机为核心,复用XPT2046ADC芯片实现模数转换,采用CD4051BE模拟开关切换参考电阻网络,从而将测量量程扩展至10kΩ至20MΩ。测量结果通过迪文串口屏实时显示,系统具备自动量程切换功能。设计报告详细阐述了方案选择、硬件电路设计、软件算法开发、系统调试与校准的全过程。最终测试表明,系统在各量程内测量误差可控制在±5%以内,运行稳定,达到了设计要求。 1.2 系统功能概述第一,宽量程测量:覆盖1kΩ至20MΩ范围,分为三个子量程。
第二,自动量程切换:根据被测电阻大小,自动选择最佳测量档位。
第三,实时显示:通过彩色串口屏直观显示电阻值(两位整数、两位小数)及当前量程。
第四,基础通信:预留串口调试接口,可输出原始ADC数据,便于校准与诊断。 1.3 设计指标
量程范围:1kΩ ~ 20MΩ (10kΩ, 100kΩ, 1MΩ三档)。
显示分辨率:0.01kΩ (即10Ω)。
测量精度:≤ ±5% (在标准电源与校准后)。
显示界面:迪文屏,显示电阻值与量程状态。
供电电压:DC 5V (±5%)。
核心控制器:STC89C52RC。
二、方案选择2.1 核心控制器选择
选用STC89C52RC单片机。理由:其资源丰富(8KB Flash, 512B RAM, 32个I/O口),完全满足本项目对SPI模拟、UART通信和逻辑控制的需求;且价格低廉,开发资料齐全,易于快速实现。 2.2 ADC转换方案选择
选用XPT2046芯片。理由:作为四线电阻触摸屏控制器,其内部集成了一个12位、125kHz采样率的SAR ADC。通过将其模拟输入通道复用为电阻分压测量点,可以极低成本获得精密ADC功能,无需额外采购专用ADC芯片,大大简化了硬件设计。 2.3 量程扩展方案选择
选用CD4051BE 8通道模拟开关。理由:与被考虑的继电器方案相比,模拟开关具有无机械触点、寿命长、切换速度快、体积小、驱动简单的优点。CD4051BE的导通电阻(约100Ω)在测量较大电阻时引入的误差百分比很小,在可接受范围内。通过其切换不同的参考电阻(1MΩ, 100kΩ, 10kΩ),即可实现量程扩展。 2.4 人机交互方案选择
选用迪文DGUS串口屏。理由:与传统LCD屏+按键方案相比,串口屏将显示驱动和图形处理封装在屏内,单片机只需通过串口发送简单的指令和数据即可控制显示,极大地减轻了单片机的负担,简化了软件设计,并能实现更美观的界面。 2.5 最终系统流程
系统工作流程如下:待测电阻接入由CD4051公共输出端和参考电阻构成的分压电路。单片机通过P2.0-P2.2控制CD4051,选择接入1MΩ、100kΩ或10kΩ参考电阻。分压点电压送入XPT2046的IN3通道。单片机通过P3.4-P3.7模拟SPI协议读取XPT2046转换的ADC值。单片机内部程序根据ADC值计算电阻,并通过P3.1(TXD)以115200波特率将结果和量程信息发送给迪文屏显示。
三.硬件部分3.1 核心控制电路
采用典型的STC89C52最小系统:11.0592MHz晶振(为产生精确串口波特率)、上电复位电路、EA引脚接VCC。电源入口处并联100μF电解电容和0.1μF陶瓷电容进行退耦。 3.2 电阻测量与ADC电路
这是系统的核心模拟前端。
分压原理:基本测量原理为电阻分压法。待测电阻(Rx)与已知参考电阻(Rref)串联于VCC与GND之间,测量两者的连接点电压(Vmeasure)。公式为:V_measure = (Rx / (Rref + Rx)) × VCC。由此可反推:Rx = (V_measure × Rref) / (VCC - V_measure)。
XPT2046连接:其IN3通道(Pin 7)连接至分压测量点。VREF引脚(Pin 12)连接独立的5V参考源,决定ADC量程。单片机通过P3.4-P3.7引脚模拟SPI协议与其通信。 3.3 量程切换电路
这是实现多量程的关键。
CD4051BE配置:工作在单电源模式。VDD接+5V,VSS和VEE接GND,INH使能端接地。地址线A、B、C(Pins 11,10,9)由单片机P2.0、P2.1、P2.2控制。
参考电阻网络:三个精密金属膜电阻(1MΩ, 100kΩ, 10kΩ, 精度1%)一端共同接VCC,另一端分别接CD4051的通道输入端Ch2(Pin13)、Ch1(Pin14)、Ch0(Pin15)。
输出连接:CD4051的公共输出端COM(Pin 3)连接至测量点(即XPT2046的IN3和待测电阻Rx的一端)。
切换逻辑:目标量程1(10kΩ档)对应参考电阻10kΩ,CD4051控制CBA=000,单片机P2.2,P2.1,P2.0输出(0,0,0)。目标量程2(100kΩ档)对应参考电阻100kΩ,控制CBA=001,输出(0,0,1)。目标量程3(1MΩ档)对应参考电阻1MΩ,控制CBA=010,输出(0,1,0)。 3.4 人机交互电路
迪文屏的TX、RX分别接单片机的P3.0 (RXD)、P3.1 (TXD)。屏与单片机共地。单片机串口通过定时器2产生115200波特率与迪文屏通信。
四.软件设计4.1 软件开发环境与整体流程
环境:Keil μVision5 C51编译器。
整体流程:系统上电初始化后,进入主循环。循环内依次执行:设置量程通道->读取ADC->判断并执行量程切换->计算电阻->发送数据至迪文屏。 4.2 底层驱动模块
第一,XPT2046_ReadADC():模拟SPI时序,向XPT2046发送控制字0xE4(选择IN3通道,单端输入,12位模式),并读取12位转换结果。关键修正:最初误用0x93(差分模式),导致通道选择错误。 第二,SetCD4051Channel():根据传入的通道号(0,1,2),设置P2.0-P2.2的电平,并延时约10ms等待模拟开关稳定 // 设置CD4051通道 (0, 1, 2 对应三个参考电阻)void SetCD4051Channel(BYTE channel) { CD4051_A = channel & 0x01; CD4051_B = (channel >> 1) & 0x01; CD4051_C = (channel >> 2) & 0x01; DelayMs(10); // 等待模拟开关稳定} // ==================== XPT2046 读取函数 ==================== DWORD XPT2046_ReadADC(void) { BYTE i; DWORD adc_value = 0; BYTE command = 0xE4; // IN3通道,单端模式
XPT2046_CS = 0; for(i=0; i<8; i++) { XPT2046_DIN = (command >> (7-i)) & 0x01; XPT2046_DCLK = 0; _nop_(); _nop_(); XPT2046_DCLK = 1; _nop_(); _nop_(); } XPT2046_DCLK = 0; _nop_(); _nop_(); XPT2046_DCLK = 1; _nop_(); _nop_(); XPT2046_DCLK = 0; _nop_(); _nop_(); for(i=0; i<12; i++) { XPT2046_DCLK = 1; _nop_(); _nop_(); XPT2046_DCLK = 0; _nop_(); _nop_(); adc_value <<= 1; if(XPT2046_DOUT) adc_value |= 0x01; } for(i=0; i<4; i++) { XPT2046_DCLK = 1; _nop_(); _nop_(); XPT2046_DCLK = 0; _nop_(); _nop_(); } XPT2046_CS = 1; return adc_value;} 。
4.3 核心算法模块
第一,电阻计算函数 CalculateResistance():
voltage_mv = (adc_value * VREF_CAL) / ADC_MAX; // 计算测量点电压
if(voltage_mv >= VCC) return OVER_RANGE;
resistance_ohm = (ref_resistor * voltage_mv) / (VCC - voltage_mv);
return resistance_ohm / 1000.0; float CalculateResistance(DWORD adc_value, DWORD ref_resistor) { float voltage_mv, resistance_ohm;
if(adc_value == 0 || adc_value >= ADC_MAX) return 0.0;
voltage_mv = (adc_value * VREF_CAL) / (float)ADC_MAX; if(voltage_mv >= VCC) return 99999.0; // 超量程
resistance_ohm = (ref_resistor * voltage_mv) / (VCC - voltage_mv); return resistance_ohm / 1000.0; // 转换为kΩ }
第二,自动量程切换逻辑 AutoRange():
升量程:若当前ADC值小于阈值ADC_TOO_SMALL(如100),说明分压过小,待测电阻过大,应切换至更大的参考电阻(即升量程)。
降量程:若ADC值大于阈值ADC_TOO_LARGE(如4000),说明分压过大,待测电阻过小,应切换至更小的参考电阻(即降量程)。
BYTE AutoRange(DWORD adc_value, BYTE cur_range) { if(adc_value < ADC_TOO_SMALL) { // ADC值太小,说明待测电阻太大,应切换到更大的参考电阻(升量程) if(cur_range < 2) return cur_range + 1; // 10k->100k, 100k->1M } else if(adc_value > ADC_TOO_LARGE) { // ADC值太大,说明待测电阻太小,应切换到更小的参考电阻(降量程) if(cur_range > 0) return cur_range - 1; // 1M->100k, 100k->10k } return cur_range; // 保持不变 }
防振荡:在主程序中,当量程切换后,立即continue跳回循环开始,重新采样,避免在临界点附近反复跳档。 4.4 系统校准模块
校准是保证精度的关键。系统采用两点校准法,核心是确定VREF_CAL(ADC实际参考电压)的值。
原理:在公式 Rx = (V_measure × Rref) / (VCC - V_measure) 中,V_measure = (ADC / 4095) × VREF_CAL。因此,VREF_CAL的准确性直接决定测量结果。
步骤:
a. 在10kΩ量程下,连接一个已知精密的电阻(如R_known = 5.1kΩ)。
b. 读取稳定的ADC值(如ADC_read = 2730)。
c. 根据公式反推或采用迭代逼近法计算VREF_CAL。
d. 将计算出的VREF_CAL(本项目最终值为2833)代入代码常数中。
校准常数:
#define VCC 5000 // 系统电源电压,单位mV
#define VREF_CAL 2833 // XPT2046的实际参考电压,通过校准得出,单位mV 4.5与迪文屏通信首先设置迪文屏,在显示阻值的页面增添一个文本变量,变量地址设置为1000,使用4位长整数,2位整数,2位小数用于显示电阻数值,再添加一个图标变量,设置地址为1100用于显示电阻的单位,还需额外准备三张分别显示【1k】,【100k】,【1M】的图标并且设置为对应数据0,1,2。下面时对应的单片机代码: void UART_SendByte_DWIN(BYTE dat) { SBUF = dat; while(!TI); TI = 0;}
// 发送电阻值到地址 0x1000void SendToDWIN(BYTE integer, BYTE decimal) { DWORD value = (DWORD)integer * 100 + decimal; UART_SendByte_DWIN(0x5A); UART_SendByte_DWIN(0xA5); UART_SendByte_DWIN(0x07); UART_SendByte_DWIN(0x82); UART_SendByte_DWIN(0x10); // 地址 0x1000 高字节 UART_SendByte_DWIN(0x00); // 地址低字节 UART_SendByte_DWIN(0x00); UART_SendByte_DWIN(0x00); UART_SendByte_DWIN((BYTE)(value >> 8)); UART_SendByte_DWIN((BYTE)(value & 0xFF));} // 发送量程信息到地址 0x1100 (用于迪文屏显示量程)void SendRangeToDWIN(BYTE range) { // 发送一个简单的数字代表量程: 1->10k, 2->100k, 3->1M // 迪文屏上对应设置文本显示或图标 UART_SendByte_DWIN(0x5A); UART_SendByte_DWIN(0xA5); UART_SendByte_DWIN(0x05); UART_SendByte_DWIN(0x82); UART_SendByte_DWIN(0x11); // 地址 0x1100 高字节 UART_SendByte_DWIN(0x00); // 地址低字节 UART_SendByte_DWIN(0x00); UART_SendByte_DWIN(range);} 4.6 主程序结构
程序采用顺序执行与状态判断结合的方式,结构清晰。
主程序伪代码:
初始化(); // 包括IO、串口、变量
while(1) {
1. 设置CD4051通道 = current_range;
2. 延时等待稳定;
3. 读取ADC = XPT2046_ReadADC()多次平均;
4. 判断新量程 = AutoRange(ADC, current_range);
if(新量程 != 当前量程) {
更新当前量程和参考电阻;
切换CD4051通道;
更新迪文屏量程显示;
continue; // 重新开始循环,用新量程测量
}
5. 计算电阻值 = CalculateResistance(ADC, current_ref_r);
6. 格式转换(分离整数、小数部分);
7. 发送电阻值至迪文屏;
8. 延时;
}
void main(void) { DWORD adc_raw, adc_sum; float resistance_kohm; BYTE int_part, dec_part; BYTE i, new_range; DWORD ref_resistors[3] = {REF_R_10K, REF_R_100K, REF_R_1M};
// 初始化 DelayMs(1000); UART_Init();
// 初始化CD4051 (默认选择10kΩ量程) CD4051_A = 0; CD4051_B = 0; CD4051_C = 0; current_range = 0; current_ref_r = ref_resistors[0]; SetCD4051Channel(0); SendRangeToDWIN(1); // 通知迪文屏当前为量程1 (10k)
// 初始化XPT2046引脚 XPT2046_CS = 1; XPT2046_DCLK = 0;
LED = 1; DelayMs(500); LED = 0; // 启动指示
while(1) { // 1. 设置当前量程对应的参考电阻通道 SetCD4051Channel(current_range); DelayMs(50); // 等待电路稳定
// 2. 读取ADC (多次平均) adc_sum = 0; for(i=0; i<16; i++) { adc_sum += XPT2046_ReadADC(); DelayMs(2); } adc_raw = adc_sum / 16;
// 3. 自动量程判断与切换 new_range = AutoRange(adc_raw, current_range); if(new_range != current_range) { // 量程需要切换 current_range = new_range; current_ref_r = ref_resistors[current_range]; SetCD4051Channel(current_range); SendRangeToDWIN(current_range + 1); // 更新迪文屏量程显示 DelayMs(100); // 切换量程后等待更长时间稳定 continue; // 跳过本次显示,重新读取新量程下的ADC值 }
// 4. 计算电阻值 resistance_kohm = CalculateResistance(adc_raw, current_ref_r);
// 5. 处理显示 if(resistance_kohm >= 10000.0) { // 超量程显示 int_part = 99; dec_part = 99; } else if(resistance_kohm < 0.1) { // 太小或无效 int_part = 0; dec_part = 0; } else { int_part = (BYTE)resistance_kohm; dec_part = (BYTE)((resistance_kohm - int_part) * 100 + 0.5); // 四舍五入 }
// 6. 发送到迪文屏 LED = 1; SendToDWIN(int_part, dec_part); LED = 0;
DelayMs(300); // 主循环延时 }}
|