摘要:本文介绍在 ATtiny26 这款 8 位 MCU 上,利用其内置 10 位 ADC 通过过采样技术实现 14 位 ADC 高精度转换的完整方案。核心思路是连续采样 18 次、去除极值后累加剩余 16 次结果,再经十六分之一二阶滤波器平滑,将有效精度提升至约 14 位。文章依次讲解 ADC 配置与采样流程、过采样与数字滤波原理、偏移校准与增益校准方法、完整 C 语言实战示例、常见问题排查以及工程注意事项,帮助读者在成本敏感且信号变化缓慢的场景下,以纯软件方式获得更高精度的 ADC 测量结果。
关键词:ATtiny26;STM32;10位ADC;过采样;去极值平均;二阶滤波;有效位数(ENOB);高精度转换;嵌入式系统;8位MCU;数字滤波。
ATtiny26上实现14位ADC高精度转换的方案
10位ADC连续采样18次,去极值后,余下16次数值,累加起来,其累加值再进十六分之一二阶滤波,后输出14位ADC值,再用这个值进行以后的运算。
这是一种简单的二阶滤波器数字表达式:
Yn = a*Xn + b*Yn-1 + c*Yn-2其中系数满足a + b + c = 2^N(N 为右移位数),这样整个滤波过程就可以通过一次右移运算完成,避免浮点除法,非常适合在 ATtiny26 这类 8 位 MCU 上高效实现。
以a = 1,b = 6,c = 9为例,此时 a + b + c = 16 = 2^4(N = 4),滤波表达式可写为:
Yn = (Xn + 6*Yn-1 + 9*Yn-2) >> 4由于右移 4 位等价于除以 16,因此该滤波器可简称为十六分之一二阶滤波器。它利用前两次输出值 Yn-1、Yn-2 对当前结果进行平滑,能有效抑制高频噪声,同时保持对缓慢变化信号的跟随能力。
1. ADC配置与采样流程
ATtiny26 内置 10 位逐次逼近型 ADC,支持 11 路模拟输入(8 路外部引脚 + 内部参考电压等)。在开始采样前,需要先完成 ADC 的时钟、参考电压、通道选择和触发方式等配置。
1.1 ADC 寄存器配置
ATtiny26 的 ADC 相关寄存器主要包括 ADCSR(ADC 控制与状态寄存器)、ADMUX(多路复用选择寄存器)以及 ADCH/ADCL(数据寄存器)。核心配置步骤如下:
- 设置 ADC 时钟分频:通过 ADCSR 的 ADPS2:ADPS0 位设置分频系数。为保证转换精度,ADC 时钟应保持在 50kHz~200kHz 之间,例如系统时钟 1MHz 时可选择 8 分频。
- 选择参考电压:通过 ADMUX 的 REFS 位选择参考源,常用内部 2.56V 参考或 VCC 作为参考。
- 选择输入通道:通过 ADMUX 的 MUX 位选择对应的模拟输入引脚。
- 使能 ADC 并启动转换:置位 ADCSR 中的 ADEN 使能 ADC,置位 ADSC 启动一次转换。
1.2 采样流程
本方案采用连续采样 18 次、去除极值后累加剩余 16 次的策略,具体流程如下:
- 初始化 ADC 寄存器,配置时钟分频、参考电压和输入通道。
- 连续采样 18 次,每次等待转换完成后读取 10 位结果。
- 对 18 个采样值进行排序,去掉 1 个最大值和 1 个最小值。
- 将剩余 16 个采样值累加,得到累加和。
- 将累加和送入十六分之一二阶滤波器进行平滑处理,最终输出 14 位精度的 ADC 值。
下面给出 ADC 初始化与单次采样的 C 语言示例:
void ADC_Init(void) { // 选择通道 0,内部 2.56V 参考 ADMUX = (1 << REFS0) | 0x00; // 使能 ADC,时钟 8 分频 ADCSR = (1 << ADEN) | (1 << ADPS1) | (1 << ADPS0); } unsigned int ADC_Read(unsigned char ch) { ADMUX = (ADMUX & 0xF0) | (ch & 0x0F); // 选择通道 ADCSR |= (1 << ADSC); // 启动转换 while (!(ADCSR & (1 << ADIF))); // 等待转换完成 ADCSR |= (1 << ADIF); // 清除标志 return ADC; // 返回 10 位结果 }完成 18 次采样后,即可进入去极值、累加和滤波阶段,这部分内容将在下一节详细展开。
2. 提升精度的方法
在 ATtiny26 这类 8 位 MCU 上,仅靠单次 10 位 ADC 转换很难获得更高精度。要提升到约 14 位有效精度,需要从硬件布局、采样策略、数字滤波和校准补偿等多个维度综合入手。下面介绍几种常用且行之有效的方法。
2.1 过采样与求平均
过采样(Oversampling)是提升 ADC 有效位数的经典手段。对同一信号连续采样多次并累加求平均,可以抑制随机噪声,等效提高分辨率。本方案采用连续采样 18 次、去除 1 个最大值和 1 个最小值后,对剩余 16 次结果累加,正是过采样思想的体现。
需要说明的是,单纯累加 16 个 10 位值最多得到 14 位的结果(16 = 2^4,相当于左移 4 位),但前提是采样噪声足够大且随机,才能让低位产生有效抖动。若信号本身非常稳定、噪声极小,过采样提升精度的效果会受限。
2.2 数字滤波平滑
累加后的结果仍可能带有周期性或脉冲性噪声,此时需要数字滤波进一步平滑。本方案采用十六分之一二阶滤波器:
Yn = (Xn + 6*Yn-1 + 9*Yn-2) >> 4该滤波器利用前两次输出对当前结果进行加权平滑,能有效抑制高频噪声,同时保持对缓慢变化信号的跟随能力。由于系数之和为 16,整个滤波过程只需一次右移运算,非常适合在 8 位 MCU 上高效实现。
2.3 偏移校准
ADC 的偏移误差会导致输入为零时输出不为零,从而影响低端测量精度。偏移校准的思路是:将输入端短接到地(或参考地),读取若干次采样值并求平均,得到偏移量 offset。之后每次测量结果都减去该偏移量:
adc_corrected = adc_raw - offset;偏移校准通常在系统上电初始化时执行一次,也可定期在空闲时段重新校准,以应对温漂带来的偏移变化。
2.4 增益校准
增益误差会使 ADC 的转换曲线斜率偏离理想值,导致满量程附近测量偏差较大。增益校准需要借助一个已知精度的参考电压 Vref_cal:
// 读取参考电压对应的 ADC 原始值 unsigned int adc_ref = ADC_Read(REF_CHANNEL); // 计算增益校正系数(定点数,放大 256 倍) unsigned int gain = (unsigned int)((Vref_cal * 256UL) / adc_ref); // 应用增益校正 unsigned long adc_scaled = (unsigned long)adc_corrected * gain; adc_final = (unsigned int)(adc_scaled >> 8);实际应用中,可先做偏移校准再做增益校准,顺序不能颠倒,否则会引入交叉误差。
下面给出一个完整的偏移校准与增益校准的 C 语言实战代码示例,包含校准参数存储(使用 EEPROM)、校准执行流程(先偏移后增益)以及校准后测量结果的修正计算:
#include <avr/io.h> #include <avr/eeprom.h> // 校准参数在 EEPROM 中的存储地址 #define EEPROM_OFFSET_ADDR 0x00 // 偏移量存储地址 #define EEPROM_GAIN_ADDR 0x02 // 增益系数存储地址(2 字节) // 校准标志地址:用于判断 EEPROM 中是否已保存有效校准参数 #define EEPROM_CAL_FLAG_ADDR 0x04 #define CAL_FLAG_VALID 0xA5 // 有效标志值 // 校准相关常量 #define CAL_SAMPLE_COUNT 16 // 校准采样次数 #define REF_CHANNEL 1 // 参考电压输入通道(接已知精密电压) #define GND_CHANNEL 0 // 接地通道(用于偏移校准) // 全局校准参数(从 EEPROM 加载后保存在 RAM 中,避免频繁读 EEPROM) static unsigned int cal_offset = 0; // 偏移量(10 位 ADC 原始值) static unsigned int cal_gain = 256; // 增益校正系数(定点数,放大 256 倍,默认 1.0) // 读取 EEPROM 中的校准标志,判断是否已有有效校准参数 unsigned char CalParams_IsValid(void) { return eeprom_read_byte((uint8_t *)EEPROM_CAL_FLAG_ADDR) == CAL_FLAG_VALID; } // 从 EEPROM 加载校准参数到 RAM void CalParams_Load(void) { cal_offset = eeprom_read_word((uint16_t *)EEPROM_OFFSET_ADDR); cal_gain = eeprom_read_word((uint16_t *)EEPROM_GAIN_ADDR); } // 将校准参数保存到 EEPROM(带有效标志) void CalParams_Save(void) { eeprom_write_word((uint16_t *)EEPROM_OFFSET_ADDR, cal_offset); eeprom_write_word((uint16_t *)EEPROM_GAIN_ADDR, cal_gain); eeprom_write_byte((uint8_t *)EEPROM_CAL_FLAG_ADDR, CAL_FLAG_VALID); } // 对指定通道连续采样多次并求平均,用于校准 unsigned int ADC_ReadAverage(unsigned char ch, unsigned char count) { unsigned long sum = 0; unsigned char i; for (i = 0; i < count; i++) { sum += ADC_Read(ch); // 复用已有的单次采样函数 } return (unsigned int)(sum / count); } // 偏移校准:将输入端短接到地,读取平均值作为偏移量 void Calibrate_Offset(void) { // 将 ADC 输入通道切换到接地引脚(或外部短接到地) cal_offset = ADC_ReadAverage(GND_CHANNEL, CAL_SAMPLE_COUNT); } // 增益校准:借助已知精度的参考电压计算增益校正系数 // 注意:必须先完成偏移校准,再执行本函数 void Calibrate_Gain(void) { unsigned int adc_ref_raw; // 参考电压对应的原始 ADC 值 unsigned long vref_cal_mv; // 已知参考电压(单位:mV) unsigned long expected_raw; // 理论期望的 ADC 原始值 // 已知精密参考电压,例如 1.250V = 1250mV vref_cal_mv = 1250UL; // 读取参考电压对应的 ADC 原始值(10 位) adc_ref_raw = ADC_ReadAverage(REF_CHANNEL, CAL_SAMPLE_COUNT); // 先减去偏移量,得到去除偏移后的原始值 if (adc_ref_raw > cal_offset) { adc_ref_raw -= cal_offset; } else { adc_ref_raw = 0; } // 计算理论期望值:参考电压 / (参考电压满量程) * 1023 // 内部 2.56V 参考,满量程 2560mV 对应 1023 expected_raw = (vref_cal_mv * 1023UL) / 2560UL; // 计算增益校正系数(定点数,放大 256 倍) // gain = 期望值 / 实际值 * 256 if (adc_ref_raw != 0) { cal_gain = (unsigned int)((expected_raw * 256UL) / adc_ref_raw); } else { cal_gain = 256; // 异常保护:实际值为 0 时恢复默认增益 1.0 } } // 完整校准流程:先偏移后增益,最后保存到 EEPROM void Calibrate_All(void) { Calibrate_Offset(); // 第一步:偏移校准 Calibrate_Gain(); // 第二步:增益校准(依赖偏移校准结果) CalParams_Save(); // 第三步:将校准参数写入 EEPROM 持久保存 } // 应用校准参数修正测量结果 // 输入:raw 为 10 位 ADC 原始值 // 输出:修正后的 14 位高精度结果 unsigned long ADC_ApplyCalibration(unsigned int raw) { unsigned long corrected; // 第一步:减去偏移量,消除零点误差 if (raw > cal_offset) { corrected = (unsigned long)(raw - cal_offset); } else { corrected = 0; // 低于偏移量时钳位到 0 } // 第二步:乘以增益校正系数(定点数,放大 256 倍) corrected = corrected * cal_gain; // 第三步:右移 8 位还原增益系数(除以 256) corrected = corrected >> 8; return corrected; } // 使用示例:主循环中读取 ADC 并应用校准 void main(void) { unsigned int raw_value; unsigned long final_value; ADC_Init(); // 上电时检查 EEPROM 中是否已有有效校准参数 if (CalParams_IsValid()) { CalParams_Load(); // 有则直接加载 } else { Calibrate_All(); // 无则执行完整校准流程并保存 } while (1) { // 读取通道 0 的 10 位原始 ADC 值 raw_value = ADC_Read(0); // 应用偏移校准和增益校准,得到修正后的结果 final_value = ADC_ApplyCalibration(raw_value); // final_value 即为修正后的测量值,可用于后续运算或显示 } }上述代码的核心设计要点如下:
- 校准参数持久化:偏移量和增益系数保存在 EEPROM 中,并附带一个有效标志字节。系统上电时先检查标志,若已有有效校准参数则直接加载,避免每次开机都重新校准,节省时间并保证一致性。
- 校准执行顺序:完整校准流程严格按照「先偏移、后增益」的顺序执行。增益校准在计算校正系数前会先减去偏移量,确保斜率校正在零点已修正的基础上进行,避免交叉误差。
- 定点数增益系数:增益系数放大 256 倍后以整数形式存储,修正计算时先乘后右移 8 位,全程使用整数运算,避免浮点运算在 8 位 MCU 上的开销。
- 异常保护:增益校准中若参考电压读取值为 0(如通道接错或信号异常),增益系数恢复默认值 256(即 1.0),防止除零错误导致校准参数异常。
- 修正计算流程:每次测量先减偏移量消除零点误差,再乘增益系数并右移还原,两步修正共同保证全量程的线性度和精度。
2.5 硬件与布局优化
软件算法之外,硬件层面的优化同样关键:
- 参考电压稳定:为 VREF 引脚增加去耦电容,避免参考电压随负载波动。
- 模拟地与数字地分离:在 PCB 布局上尽量将模拟地与数字地单点连接,减少数字噪声耦合。
- 输入阻抗匹配:ATtiny26 的 ADC 采样电容需要足够的充电时间,若信号源阻抗过高,应增加缓冲器或降低 ADC 时钟频率。
- 屏蔽与滤波:对长距离模拟信号线使用屏蔽线,并在输入端增加 RC 低通滤波,抑制高频干扰。
2.6 采样时序与时钟优化
ADC 时钟频率直接影响转换精度。ATtiny26 要求 ADC 时钟保持在 50kHz~200kHz 之间,过高的时钟会降低转换精度。此外,每次转换前应保证采样保持电容有足够的充电时间,必要时可在启动转换前插入一小段延时,确保信号稳定。
为帮助读者快速选择合适的方法,下表对过采样求平均、数字滤波、偏移校准、增益校准四种方法进行对比:
| 方法 | 原理 | 适用场景 | 精度提升效果 | 实现复杂度 |
|---|---|---|---|---|
| 过采样与求平均 | 对同一信号连续采样多次,去除极值后累加求平均,利用随机噪声让低位产生有效抖动,等效提高分辨率。 | 信号变化缓慢、噪声足够大且随机;适合温度、压力、电池电压等低频测量。 | 采样 16 次累加可提升约 4 位,从 10 位提升 |