工程师总是希望从电路中获得最大效益,这一点对于模数转换器(ADC)也是如此。为了最大限度地提高ADC的性能,可能最需要关注的规格就是分辨率。然而,令人遗憾的是,一旦我们选定了ADC及其参考电压源,我们就定义了最大的单次读取分辨率。例如,假设我们使用一个10位ADC和一个5V参考电压源。这意味着我们的读数分辨率为5V/(1023),即每个数字步长4.888mV。但是,如果我们只有这个ADC系统,并且必须将其应用于输出为0至1V的传感器,该怎么办?ADC分辨率是4.888mV,但这也就意味着只有1V/4.888ms,或约205个可用步长,因此实际上我们是将传感器的分辨率降低至205分之1。
如果我们设计一个设备,用于测量通过串联电阻施加直流阶跃电压时电感两端的电压,结果会怎样?如下图(图1)的曲线所示,在最初的几秒钟内,我们可能会得到不错的数据点读数,但之后,由于斜率较平缓,许多数据点的值都会相同。换句话说,相对误差会很高。
图1:电路中电感器电压随时间变化的示例曲线,该电路通过串联电阻施加直流阶跃电压以测量电感器两端的电压。请注意,3秒后斜率趋于平缓,这将增加测量的相对误差。
有两种最简单的方法可以改变这种情况:
(还有一些更奇特的方法可以改变分辨率,例如使用Δ-Σ转换器。)改变位数意味着需要使用外部ADC或其他微控制器。那么,如果我们设计一个带有可调参考电压的系统会怎么样?这种分辨率可以根据需要自动调整——我们可以称之为自适应分辨率。
让我们先来看一个简单的方法。Microchip ATMEGA328P的内部ADC参考电压有三种设置选项:Vcc电压、内部1.1V参考电压和外部参考引脚(REF)。因此,为了演示,最简单的设置是使用Arduino Nano,它使用了ATMEGA328P芯片。
该demo使用其中一个模拟输入引脚(可连接到10位ADC)来测量电压或传感器输出。这里的关键技巧是将参考电压设置为Vcc(本设计中为+5V),并读取模拟输入的ADC读数。
如果读数转换为电压后大于1.1V,则使用该值作为测量值。如果不大于1.1V,则将参考电压更改为内部1.1V电压,然后重新读取读数。现在,假设您的传感器或测量电压相对于采样率变化较慢,那么您获得的读数分辨率将高于使用5V参考电压时获得的读数分辨率。
参照我们的电感器示例,图2说明了自适应分辨率如何随着电压下降而变化。
图2:使用Microchip ATMEGA328P内部ADC时自适应分辨率的变化,参考Vcc电压为5V,内部参考电压为1.1V。
下面是一段C语言代码,用于演示自适应分辨率的概念。
[题外话:作为测试,我使用Copilot AI编写了基础代码,它的表现出奇地好,变量名和注释都很好,而且布局也很简洁。它还能正确地将ADC数字信号转换为模拟电压。当我尝试让Copilot添加一些逻辑更改时,代码就会变得比较混乱,所以当时我手动编写了修改和清理代码。]
// Define the analog input pin
const int analogPin = A0;
// Variable to store the reference voltage (in mV)
const float referenceVoltage5V = 4753.0; // Enter the actual mv value here
const float referenceVoltage1p1V = 1099.0; // Enter the actual mv value here
// Types and variable to track reference state
enum ReferenceState {Ref_5, Ref_1p1};
ReferenceState reference = Ref_5;
void setup() {
// Initialize serial communication at 9600 bits per second
Serial.begin(9600);
// Set the analog reference to 5V (default)
analogReference(DEFAULT);
reference = Ref_5; // Set reference state
}
void loop() {
int sensorValue = 0;
int junk = 0;
float voltage = 0;
sensorValue = analogRead(analogPin); // Take a reading using the current reference
if (reference == Ref_5) {
voltage = (sensorValue / 1023.0) * referenceVoltage5V; //Convert reading
if (voltage < 1100) { // Check if the voltage is less than 1.1V
// Change the ref voltage to 1.1v and take a new reading
analogReference(INTERNAL); // Set the analog reference to 1.1V (internal)
reference = Ref_1p1; // Set reference state
junk = analogRead(analogPin); // Take a throw-away read after ref change
sensorValue = analogRead(analogPin); // Take a new reading using 1.1v ref
voltage = (sensorValue / 1023.0) * referenceVoltage1p1V; //Convert reading
}
}
else // Reference is currently set to 1.1v
voltage = (sensorValue / 1023.0) * referenceVoltage1p1V; //Convert reading
if (sensorValue == 1023) { // Check if the ADC is at maximum (>= 1.1v)
// Voltage is 1.1 volts or greater, so change to 5v ref and reread
analogReference(DEFAULT); // Set the analog reference to 5V (default)
reference = Ref_5; // Set reference state
junk = analogRead(analogPin); // Take a throw-away read after reference change
sensorValue = analogRead(analogPin); // Take a reading using the 5v reference
voltage = (sensorValue / 1023.0) * referenceVoltage5V; //Convert reading
}
// Print the analog value and voltage to the serial monitor
if (reference == Ref_5) Serial.print("Analog value with 5V reference: ");
else Serial.print("Analog value with 1.1V reference: ");
Serial.print(sensorValue);
Serial.print(", Voltage: ");
Serial.println(voltage / 1000,4);
// Delay for a moment before the next loop
delay(1000);
}
这段代码持续读取连接到模拟引脚A0的ADC电压。它首先使用Vcc(~5V)作为ADC的参考电压。如果读数小于1.1V,则ADC参考电压切换至1.1V内部参考电压。此参考电压将被持续使用,直到ADC返回其最大二进制值1023,这意味着A0电压必须为1.1V或更高,因此,在这种情况下,参考电压会再次切换至Vcc。在获取有效电压读数后,代码将输出与读数一同使用的参考电压值。
为了获得准确的读数,5V和1.1V参考电压在使用前必须进行校准。校准应该使用一个性能良好的电压表(我使用了一个经过校准的5½位万用表),然后可以将这些测量到的电压输入到代码中。
请注意,在代码顶部,当A0上的输入大于1.1V时,5V参考电压变量(“referenceVoltage5V”)会被设置为在Arduino Nano的REF引脚上测得的实际电压。当A0电压小于1.1V时,也应通过测量REF引脚上的电压来设置1.1V参考电压变量(“referenceVoltage1p1V”)。下图3说明了这一概念。
图3:此代码持续读取连接到模拟引脚A0的ADC电压。如果A0电压<1.1V,则ADC参考电压切换至1.1V。如果A0>1.1V,则ADC参考电压切换至Vcc。
以下几项数据显示了此自适应分辨率带来的改进:在1.1V左右,参考5V的读数分辨率误差可能高达0.41%,而参考1.1V的读数误差可能只有0.10%。在100mV时,参考5V的读数误差可能高达4.6%,而参考1.1V的读数误差可能只有1.1%。当输入信号达到10mV时,参考5V的读数误差可能高达46%,而参考1.1V的读数误差只有10.7%或更低。
如果需要,可以扩展此概念以添加更多参考级,但由于收益递减,我不会在10位ADC上使用超过3或4个参考级,以下是一些如何实现此操作的示例。
第一种方案是使用一个DAC,将其自身参考电压源连接到Nano的REF引脚。然后,可以调整Nano控制的DAC,使其提供所需的参考电压值。例如,MAX5362 DAC支持I2C控制(尽管其内部基准电压源为0.9xVcc,因此最大读数约为4.5V)。在此设计中,Nano的REF引脚应设置为“EXTERNAL”。请参见下方图4以获得更清晰的说明。
图4:使用Arduino Nano控制的外部DAC(MAX5362)提供更多参考级。
另一种创建多个参考电压的方法是使用Arduino Nano的PWM输出。这需要使用高频PWM和非常出色的滤波电路,以获得与5V参考电压成比例的良好平坦直流信号。您需要约1mV(-74dB)或更低的纹波电压才能获得干净、可用的输出。此外,还需要测量输出以便在代码中进行校准。这种方法所需的元件数量极少,但可以提供多种不同级别的参考电压。图5显示了此概念的框图。
图5:使用Arduino Nano的PWM输出和低通滤波器获取所需的直流信号作为电压参考。
另一种实现可调参考电压的方案是使用梯形电阻网络和一个模拟开关来选择梯形电阻网络中的不同节点。例如,TI TMUX1204可能就适合这种方案。您可以根据自己的参考电压需求选择梯形电阻网络的阻值。图6显示,Nano的两个数字输出也用于选择梯形电阻网络中的节点位置。
图6:使用梯形电阻网络和模拟开关(例如TI TMUX1204)选择梯子上的不同节点来生成所需的电压参考值。
或许还有其他方法可以构建参考电压,但您应该明白我想表达的意思了,这里的核心是使用多个参考电压来提高电压读数的分辨率。
(原文刊登于EDN美国版,参考链接:Adaptive resolution for ADCs,由Ricardo Xie编译)