想法是尽量简化ADC 采样值换算到真实电压的过程,最好是不涉及浮点运算,整数乘除法成本比较低。
对于使用了分压电阻采样输入电压的情形,电路大概是这样:
分压比例为:
K = R 1 + R 2 R 1 (1) K = \frac{R1 + R2}{R1} \tag{1} K=R1R1+R2(1)
ADC 换算出来的电压还要再乘 K K K 才是输入电压。设ADC 的参考电压是Vref,分辨率10 位,最大值1023,也就是把Vref 一共分成1023 份。那么从ADC 值计算输入电压就是这样:
V i n = V r e f 1023 × K × A D C (2) V_{in} = \frac{V_{ref}}{1023} × K × ADC \tag{2} Vin=1023Vref×K×ADC(2)
要简化计算,就是把ADC 值要乘的系数变成整数,也就是要令:
V r e f K 1023 (3) \frac{V_{ref} K}{1023} \tag{3} 1023VrefK(3)
这个结果是整数。要注意,整体除出来的结果是整数,并不代表里面每个因子都是整数。
单片机供电电压5V,所以参考电压不能超过5V,最大值就定为4.8V 好了。分压比例K 不变时,参考电压越小,能测量的外部输入电压越小。另外,参考电压越小,那么ADC 每一级对应的电压也越小,理论上精度越高,但是对噪声也越敏感。所以参考电压不能太小,就随便把最小值定为3.3V。
参考电压通过TL431 产生:
参考电压的计算公式是:
A r e f = 2.5 V × R 1 + R 2 R 2 (4) A_{ref} = 2.5V × \frac{R1 + R2}{R2} \tag{4} Aref=2.5V×R2R1+R2(4)
限制一下组合的数量,规定只能用两个电阻设置TL431 的电压。那么按照E24 数系,R1 和R2 的值只能在这些数字里面选:
e24 = [1.0, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7,
3.0, 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8,
7.5, 8.2, 9.1]
限制条件是:最大电压4.8,最小3.3,写个脚本来搜索所有能拿两个电阻组合出来的电压:
# unit 是电压单位对应的放大倍数
def find_v(v_min, v_max, unit):
print('v, r1, r2')
v_list = []
r_min = v_min / 2.5
r_max = v_max / 2.5
for r2 in e24:
for r1 in e24:
k = (r1 + r2) / r2
if(k >= r_min and k <= r_max):
v = 2.5 * k * unit
print(f'{v}, {r1}, {r2}')
v_list.append(v)
return v_list
计算中,电压的单位选择微伏(μV),因为如果用V 做单位,除非参考电压达到了上千伏,不然除以1023 肯定都不是整数。所以unit
参数用1e6
,运行:
vref = find_v(3.3, 4.8, 1e6)
输出的电压有几百种,用CSV 格式,大概是这样:
v, r1, r2
4772727.2727272725, 1.0, 1.1
4583333.333333334, 1.0, 1.2
4791666.666666666, 1.1, 1.2
4423076.923076922, 1.0, 1.3
4615384.615384616, 1.1, 1.3
4166666.666666667, 1.0, 1.5
4333333.333333334, 1.1, 1.5
4500000.0, 1.2, 1.5
4666666.666666666, 1.3, 1.5
4062500.0, 1.0, 1.6
4218750.0, 1.1, 1.6
4374999.999999999, 1.2, 1.6
4531250.000000001, 1.3, 1.6
3888888.8888888885, 1.0, 1.8
4027777.7777777775, 1.1, 1.8
4166666.666666666, 1.2, 1.8
4305555.555555555, 1.3, 1.8
4583333.333333333, 1.5, 1.8
4722222.222222222, 1.6, 1.8
3750000.0, 1.0, 2.0
...
里面可能有重复的,但是不影响,之后要综合分压比例K,从里面选一个。
分压电阻的选择也有限制,就是分压比例不能太大。很简单,太大的话输入到ADC 的电压太低,测量精度下降。预定输入电压最大30V 左右,所以分压比例定为6 倍以上,满足输入电压范围的前提下越小越好。
分压比例大于2,R2 肯定大于下拉电阻R1,R2 的值可以比R1 大一个数量级。比如,若K = 10,R1 = 1k,那么R2 是10k,所以R2 的取值范围要增加一倍。
bigger = e24 + [r * 10 for r in e24]
bigger
是R2 的取值范围。然后再写一段脚本,暴力搜索两个分压电阻和参考电压的组合,让式3 的结果是整数:
def find_k(vref_list):
print('k, r1, r2, d, vref')
for r2 in e24:
for r1 in bigger:
if(r1 < r2):
continue
k = (r1 + r2) / r2
for v in vref_list:
d = v * k / (2 ** 10 - 1)
if(abs(int(d) - d) < 1e-8):
print(f'{k}, {r1}, {r2}, {int(d)}, {v}')
运行:
find_k(vref)
这下满足要求的结果就不太多了:
k, r2, r1, d, vref
6.6, 5.6, 1.0, 25000, 3875000.0
31.0, 30.0, 1.0, 125000, 4125000.0
31.0, 30.0, 1.0, 100000, 3300000.0000000005
31.0, 33.0, 1.1, 125000, 4125000.0
31.0, 33.0, 1.1, 100000, 3300000.0000000005
31.000000000000004, 36.0, 1.2, 125000, 4125000.0
31.000000000000004, 36.0, 1.2, 100000, 3300000.0000000005
30.999999999999996, 39.0, 1.3, 100000, 3300000.0000000005
46.5, 91.0, 2.0, 187500, 4125000.0
46.5, 91.0, 2.0, 150000, 3300000.0000000005
4.125, 7.5, 2.4, 15625, 3875000.0
6.2, 39.0, 7.5, 25000, 4125000.0
6.2, 39.0, 7.5, 20000, 3300000.0000000005
一共就上面这些。从第一列开始,k 是分压比例;r2 和r1 是对应的电阻取值;d 是ADC 要乘的系数,单位是微伏;vref 是选中的参考电压。运气不错,刚好有很接近6 的比值:
k, r2, r1, d, vref
6.6, 5.6, 1.0, 25000, 3875000.0
6.2, 39.0, 7.5, 25000, 4125000.0
第一组参考电压3.875V,分压比例6.6,ADC 系数是25mV,也就是ADC 每一级对应的输入电压是25mV。第二组参考电压4.125V,比例6.2,ADC 系数相同,不知道是单纯巧合,还是有什么数学原理。虽然第二组似乎更理想,比例更接近6,但是我选了第一组。有两点原因:
如果考虑上两个电阻串联、并联当一个电阻用的情况,组合的规模估计会增加好几个数量级,有可能过滤出全部由常用阻值构成的组合,但是我太懒了。顺便,对应3.875V,TL431 的两个电阻分别是1.1k 和2k。
这么整能成的前提是电阻和参考电压的精度都比较高,所以程序里可以直接用25mV 作为理想值来计算;或者测量精度要求不高,但也不那么低。如果用了外部基准电压来校准ADC 系数,这么整就完全白费力气。