
1. 项目概述与核心价值在嵌入式系统开发中模拟信号采集是一个绕不开的环节。无论是监测电池电压、读取传感器数据还是进行简单的环境参数监控都需要一个可靠的模数转换器ADC。对于许多成本极其敏感、对功耗和PCB面积有严格限制的应用比如一次性的消费电子产品、简单的智能玩具或者大批量的控制器专门增加一颗外部ADC芯片哪怕是最便宜的型号都可能成为压垮项目预算的“最后一根稻草”。这时候工程师们就得从手头的资源里“榨”出ADC功能来。基于微控制器MCU内部比较器和PWM脉宽调制的ADC方案就是一种经典的“无中生有”的智慧它利用最基础的模拟和数字外设通过巧妙的软件算法实现了模拟到数字的转换。今天要深入探讨的是基于德州仪器TICOP820CJ这款老牌8位微控制器的“可变范围PWM ADC”技术。这个方案的精妙之处远不止于“用PWM和比较器做个ADC”这么简单。它的核心创新在于“可变范围”——ADC的测量量程不再是固定的0V到VCC而是可以根据你实际要测量的信号电压范围在软件里动态调整。这就好比用一把尺子去量东西普通ADC是固定从0厘米到30厘米的尺子而可变范围PWM ADC允许你“滑动”尺子的起始和结束刻度比如只关注10厘米到20厘米这一段并把这一段等分成100份这样测量10-20厘米范围内物体的精度就远高于用0-30厘米的尺子去量。对于输入信号变化范围已知且较窄的应用例如一个温度传感器输出只在1.2V到2.8V之间变化这种技术能极大地提升有效分辨率用更少的计数获得更精细的测量结果或者用相同的计数实现更快的转换速度。我最早接触这个方案是在一些对成本“锱铢必较”的工业遥控器项目上当时主控芯片资源有限外部ADC的成本和PCB空间都无法接受。这套基于COP820CJ的PWM ADC方案仅需几个电阻电容和一对二极管就稳定可靠地完成了多路模拟量的采集其灵活性和经济性给我留下了深刻印象。下面我就结合原版应用报告AN-815和多年的实操经验为你彻底拆解这套技术的设计思路、电路细节、软件算法以及那些手册上不会写的调试技巧。2. 技术原理深度剖析从PWM到电压测量要理解可变范围PWM ADC我们得先回到最基础的PWM ADC原理。它的核心思想是利用一个RC积分电路将PWM方波的平均电压即直流分量转换成电容上的电压。MCU内部的比较器则负责判断这个电容电压何时达到外部输入电压VIN的水平。2.1 基础PWM ADC的工作流程想象一个简单的场景我们有一个由电阻R和电容C组成的低通滤波器RC电路PWM信号加在电阻一端电容另一端接地RC的连接点即电容电压Vcap接到比较器的反相输入端而同相输入端接我们要测量的VIN。初始化阶段首先MCU控制PWM输出为高电平VCC或低电平GND并持续一段时间远大于RC时间常数目的是让电容Vcap被“强制”充电或放电到与PWM输出相同的电压从而清空上一次测量的“历史”为本次测量建立一个确定的起点。这就是所谓的“初始化”或“复位”电容电压。测量阶段然后MCU开始输出一个已知占空比的PWM信号。这个PWM信号的平均电压Vavg是一个介于GND和VCC之间的值。由于RC电路的积分作用电容电压Vcap会逐渐向这个Vavg趋近。比较与计时同时MCU启动一个内部计数器开始计数。比较器持续比较Vcap和VIN。当Vcap从初始值变化到与VIN相等时比较器输出翻转。结果计算MCU在比较器翻转的瞬间停止计数器。计数器的值Ton Counts与PWM的周期、占空比以及VIN存在确定的数学关系。通过这个关系式我们就能反推出VIN的值。传统固定范围PWM ADC的局限在于它的PWM占空比是固定的比如50%这意味着Vavg是固定的0.5*VCC。为了测量0到VCC的电压Vcap的起始电压通常被初始化为0GND或VCC。如果VIN非常接近0或VCC那么Vcap需要变化很长的距离才能触碰到VIN导致计数时间很长或者为了控制总时间而牺牲分辨率。2.2 可变范围技术的核心思想可变范围PWM ADC巧妙地解决了上述问题。它不再使用单一的PWM波形而是定义了两组PWM波形低脉冲Low Pulse和高脉冲High Pulse。低脉冲其平均电压VL被设定为略低于你预期输入电压范围的最小值。高脉冲其平均电压VH被设定为略高于你预期输入电压范围的最大值。这样无论VIN在你设定的范围内何处Vcap从VL变化到VIN或从VH变化到VIN所需的电压跨度都大大减小了。这带来了两个直接好处转换速度更快电压变化距离短电容充放电到平衡点的时间自然缩短。有效分辨率更高在相同的总计数时间和时钟频率下电压变化范围变小了那么每个计数所代表的电压值即LSB也就更小分辨率自然就提高了。原文档中的例子非常典型系统VCC5V期望测量1.0V至3.3V之间的电压。他们设计了高脉冲16个周期为VCC8个周期为GND占空比 16/(168) 2/3平均电压 VH (2/3) * 5V ≈ 3.333V。低脉冲5个周期为VCC19个周期为GND占空比 5/(519) ≈ 0.2083平均电压 VL ≈ 0.2083 * 5V ≈ 1.0417V。你看VL(1.0417V)略高于范围下限1.0VVH(3.333V)略高于范围上限3.3V。这样对于范围内任意VINVcap都只需要在1.04V到3.33V这个约2.29V的区间内运动而不是传统的0-5V。他们将这个区间等分为100个计数每个计数代表约23mV。如果使用传统的0-5V全范围100计数每个计数代表50mV精度降低了一倍以上。2.3 关键电路元件的作用解析原图图1的电路虽然简单但每个元件都至关重要R1输入电阻主要作用是限流防止输入信号源过载并在一定程度上与C1构成低通滤波器抑制高频噪声。其阻值选择需要权衡太大则增大RC时间常数降低转换速度太小则可能加重信号源负担。典型值在10kΩ到100kΩ之间。R2和C1RC网络这是核心的积分网络。时间常数 τ R2 * C1 直接决定了系统的响应速度。τ需要精心选择它必须远大于PWM的单个高/低电平持续时间以确保积分效果平滑PWM但又不能太大以至于测量阶段电容电压变化太慢导致转换时间过长。通常根据PWM频率和所需转换时间来推算。背对背二极管D1和D2这是这个电路的一个精妙之处也是容易被人忽略的关键。它们的作用是“加速初始化”。在初始化阶段当MCU需要将电容电压Vcap快速拉高到VCC或拉低到GND时如果只通过R2进行由于R2阻值通常较大充电/放电会非常慢。并联上二极管后当PWM输出与Vcap电压差超过二极管导通压降约0.6-0.7V时二极管提供了一条低阻抗路径让电容电压能够迅速被钳位到PWM输出电平附近极大地缩短了初始化时间。初始化完成后在测量阶段Vcap在VL和VH之间变化电压差通常小于二极管导通电压此时二极管截止不影响RC网络的正常积分工作。实操心得这两个二极管一定要用开关速度快、反向漏电流小的型号如1N4148。我曾在一个高温环境下使用1N4007其较大的反向恢复时间和漏电流导致了测量结果的微小漂移和线性度下降。3. 系统设计与参数计算实战理解了原理下一步就是动手设计。这里我们以COP820CJ为目标平台设计一个测量1.2V到2.5V电压例如某款温度传感器的输出的可变范围PWM ADC。3.1 确定PWM波形参数这是软件算法的核心。我们的目标是让VL略低于1.2VVH略高于2.5V。假设系统VCC3.3V很多现代低功耗MCU的常用电压。选择PWM周期基础单位COP820CJ的指令周期是其时钟的倒数。为了简化我们假设使用一个定时器来产生固定周期的PWM脉冲。我们定义每个“脉冲”由固定数量的“时间单元”构成每个时间单元对应若干条指令周期。设一个脉冲周期包含N个时间单元。计算所需占空比目标 VL 1.2V - 裕量。留出约0.1V裕量设 VL_desired 1.1V。 占空比 D_L VL_desired / VCC 1.1V / 3.3V ≈ 0.3333。 假设N24则高电平时间单元数 H_L round(D_L * N) round(0.3333*24) 8。 实际 VL (8/24) * 3.3V 1.1V。完美。目标 VH 2.5V 裕量。留出约0.1V裕量设 VH_desired 2.6V。 占空比 D_H VH_desired / VCC 2.6V / 3.3V ≈ 0.7879。 高电平时间单元数 H_H round(D_H * N) round(0.7879*24) ≈ 19。 实际 VH (19/24) * 3.3V ≈ 2.6125V。符合要求。确定波形低脉冲波形在一个周期N24个单元内8个单元为高电平VCC16个单元为低电平GND。高脉冲波形在一个周期N24个单元内19个单元为高电平VCC5个单元为低电平GND。这样我们的测量范围就大致设定在1.1V到2.6125V之间完全覆盖了1.2V-2.5V的目标信号范围。3.2 计算RC网络参数RC时间常数τ的选择至关重要。它需要在“滤波效果”和“响应速度”之间取得平衡。确定PWM频率首先需要知道我们PWM波形的实际频率。假设COP820CJ的指令周期为1μs即1MHz时钟我们定义的一个“时间单元”为10条指令周期即10μs。那么一个完整的脉冲周期T_pulse N * 10μs 24 * 10μs 240μs。对应的PWM基频约为4.17kHz。确定RC时间常数原则为了有效平滑PWM波RC时间常数应远大于PWM的高电平或低电平持续时间中的较小者。从高脉冲看高电平持续1910μs190μs从低脉冲看高电平持续810μs80μs。我们取较小的80μs。 经验上τ应至少为最小脉宽的5-10倍以确保电容电压能充分跟随PWM的平均值而不是单个脉冲。取 τ_min 5 * 80μs 400μs。选择R和C的值τ R2 * C1。我们还需要考虑输入阻抗等因素。通常C1不宜过大否则物理尺寸大且漏电流可能影响精度一般选择0.1μF以下的陶瓷电容。假设我们选择 C1 0.01μF (10nF)。 则 R2 τ_min / C1 400μs / 0.01μF 40kΩ。 这是一个最小值。为了更好的滤波效果我们可以选择更大的R2比如100kΩ此时 τ 100k * 10nF 1ms。这会使系统响应稍慢但抗噪性更好。我们需要在后续的软件计时中验证这个τ是否导致测量时间过长。选择输入电阻R1R1主要起限流作用通常与信号源输出阻抗有关。如果信号源是低阻抗的如运放输出R1可以小一些如1kΩ。如果信号源阻抗较高或不确定R1可以取大一些如10kΩ或与R2同数量级100kΩ。需要注意R1和C1也会形成一个额外的高频滤波但其截止频率远高于由R2、C1决定的主积分环节影响不大。这里我们暂选 R1 10kΩ。3.3 转换算法与代码框架算法流程遵循原文档图3的流程图但我们需要用更具体的伪代码来描述。核心是公式VIN VL (VH - VL) × (Ton_Counts / Total_Counts)。假设我们已经通过定时器和GPIO输出了设计好的高、低脉冲波形。测量步骤如下// 伪代码基于COP820CJ架构思路 #define TOTAL_COUNTS 100 // 将整个电压区间等分的份数 #define VL 1.1f // 计算出的低脉冲平均电压单位V #define VH 2.6125f // 计算出的高脉冲平均电压单位V #define V_RANGE (VH - VL) // 测量电压范围 uint16_t pwm_adc_measure(void) { uint16_t timer_count 0; bool comp_output_last_state; bool comp_current_state; // 步骤1: 初始化电容电压至已知状态例如输出低脉冲波形持续多个周期 set_pwm_mode(LOW_PULSE_MODE); delay_ms(5); // 远大于RC时间常数确保电容电压稳定在VL // 步骤2: 启动计数器并开始输出高脉冲波形 set_pwm_mode(HIGH_PULSE_MODE); timer_counter_reset_and_start(); comp_output_last_state read_comparator_output(); // 读取比较器初始状态 // 步骤3: 循环检测比较器翻转 while(1) { comp_current_state read_comparator_output(); timer_count get_current_timer_count(); // 检测比较器状态变化 if (comp_current_state ! comp_output_last_state) { // 比较器翻转了 timer_counter_stop(); break; // 跳出循环 } // 安全措施防止输入电压超出范围导致永远无法翻转 if (timer_count MAX_SAFE_COUNT) { // MAX_SAFE_COUNT需根据时钟和范围计算 timer_counter_stop(); timer_count MAX_SAFE_COUNT; // 或返回一个错误码 break; } comp_output_last_state comp_current_state; } // 步骤4: 计算电压值 // Ton_Counts 就是 timer_count float measured_voltage VL (V_RANGE * ((float)timer_count / (float)TOTAL_COUNTS)); // 步骤5: 将电压值转换为需要的格式如12位ADC值或直接返回 uint16_t adc_result (uint16_t)((measured_voltage / VCC) * 4095); // 假设模拟12位ADC return adc_result; }注意事项代码中的delay_ms(5)和MAX_SAFE_COUNT是关键的安全和可靠性设计。初始化时间必须足够长确保电容电压完全稳定否则会引入系统性误差。超时判断则防止因输入电压异常开路、短路超出范围导致程序死循环。4. 核心环节实现与软件优化技巧有了算法框架我们还需要解决一些具体的实现问题并探讨如何优化性能和精度。4.1 比较器中断与定时器捕获的配合上述伪代码使用了查询方式不断读取比较器状态这会占用大量CPU时间。在实际应用中更高效的方式是使用比较器输出中断和定时器输入捕获功能如果MCU支持。配置比较器输出触发中断将比较器配置为当输出电平变化上升沿或下降沿时产生中断。配置定时器在自由运行模式定时器以一个固定的频率通常为系统时钟或分频后连续向上计数。启动测量流程初始化电容电压输出低脉冲。切换到高脉冲输出并同时使能比较器变化中断并记录下此时定时器的计数值T_start可以使用定时器捕获功能或在中断中立即读取。当比较器翻转触发中断时在中断服务程序ISR中立即读取定时器的当前计数值T_end。Ton_Counts T_end - T_start。关闭中断计算电压。这种方式几乎不占用CPU时间测量精度也更高因为捕获的是硬件触发的精确时刻。4.2 动态范围调整的实现“可变范围”的精髓在于动态调整。如何实现呢一个常见的策略是两步测量法粗测首先使用一个覆盖全电压范围或较宽范围的固定PWM ADC例如占空比50%范围0-VCC进行第一次快速、低分辨率的测量。这个结果可以告诉我们VIN的大致位置比如在1.5V左右。精测根据粗测结果动态选择或计算一组最优的VL和VH。例如粗测得到1.5V我们期望在1.5V附近获得高精度。我们可以设定VL1.3V VH1.7V范围0.4V。然后使用这组新的参数重新进行一轮高精度的可变范围PWM ADC测量。在COP820CJ上这意味着你需要预先在代码中定义多组{VL, VH, 对应的高/低脉冲波形配置}或者根据公式实时计算所需的PWM占空比。粗测和精测可以共用同一个硬件电路只需在软件中切换PWM发生器的配置即可。4.3 精度影响因素与校准这种方案的精度受到多个因素影响电源电压VCC的精度VCC的波动会直接导致VL和VH计算不准。必要时需要使用外部基准电压源或MCU内部带隙基准来校准。电阻电容的精度和温漂R2和C1的绝对值误差会影响RC时间常数但更重要的是它们的比值稳定性。如果R2和C1随温度同向变化其时间常数τ可能相对稳定。但通常使用1%精度的电阻和NPO/C0G材质的陶瓷电容可以满足大多数应用。比较器的失调电压和响应时间比较器本身存在输入失调电压这相当于在VIN上叠加了一个小误差。响应时间则会影响检测翻转的时刻精度。选择失调电压小、响应快的比较器并在软件中考虑其延迟。软件计时精度定时器的时钟精度和中断延迟是关键。校准方法可以在已知的、精确的输入电压点如使用一个精密分压电阻产生0.5VCC进行测量将测量结果与理论值对比计算出一个比例因子和偏移量在后续测量中进行软件补偿。例如VIN_calibrated (VIN_raw * Gain) Offset5. 常见问题排查与实战经验在实际部署这套方案时你几乎一定会遇到下面这些问题。这里是我踩过坑后总结的排查清单。5.1 测量结果不稳定或噪声大现象连续测量一个固定电压结果跳动较大。排查与解决检查电源首先用示波器测量MCU的VCC和GND引脚看是否有明显的纹波或噪声。PWM输出本身是噪声源可能通过电源耦合影响比较器。在VCC和GND之间靠近MCU处增加一个10μF电解电容并联一个0.1μF陶瓷电容。检查输入信号测量VIN输入点看信号本身是否干净。传感器输出可能自带噪声。可以在R1和C1节点之前增加一个RC低通滤波如1kΩ和0.1μF其截止频率应远高于你关心的信号频率但远低于PWM频率。调整RC时间常数如果τ太小PWM的纹波无法被充分滤除电容电压Vcap上会有较大的锯齿波导致比较器在阈值点附近来回抖动翻转。适当增大R2或C1的值增大τ。用示波器观察C1上的电压它应该是一条相对平滑的、趋向于平均电压的曲线而不是明显的锯齿波。检查接地模拟地AGND和数字地DGND的布局很关键。确保比较器的反相输入端接RC网络和同相输入端接VIN的接地路径干净尽量单点接地远离数字电路特别是PWM输出IO的地线回流路径。5.2 测量值线性度差或存在固定偏差现象测量结果与万用表读数对比整体偏高或偏低或者在不同电压点误差不一致。排查与解决验证VL和VH不接外部VIN将PWM分别固定输出低脉冲和高脉冲波形用高精度万用表测量C1上的电压看是否等于你计算的VL和VH。如果不符检查PWM的占空比是否准确VCC是否准确。检查二极管漏电流在高温环境下二极管的反向漏电流可能增大在测量阶段二极管应截止对电容进行充放电影响线性。尝试移除二极管此时初始化时间会很长或者更换为漏电流更小的器件。校准比较器失调将比较器的两个输入端短接在一起接到一个可调的精密电压源上。调整电压源找到比较器刚刚翻转的电压点这个点就是比较器的实际失调电压。在软件中将这个值作为偏移量进行补偿。检查公式计算确认代码中使用的VL、VH、VCC是否为浮点数计算或者使用了足够精度的定点数运算。整数运算的舍入误差在低电压下可能被放大。5.3 转换时间过长或出现超时现象测量某些电压值时程序经常走到超时分支。排查与解决确认输入电压在范围内用万用表确认VIN确实在你设定的VL和VH之间。如果VIN超出范围比较器永远不会翻转。检查初始化是否充分增大初始化阶段的延时确保电容电压有足够时间稳定到VL。可以用示波器观察初始化阶段C1上的电压看是否真的达到了稳定的VL。检查RC时间常数是否过大如果τ过大电容电压变化太慢在设定的MAX_SAFE_COUNT时间内可能无法达到VIN。需要根据公式重新估算从VL变化到VIN所需的近似时间t ≈ -τ * ln(1 - (VIN-VL)/(VH-VL))。确保在最坏情况下VIN接近VH这个时间也远小于你的超时时间。优化超时计数根据系统时钟、PWM周期和最大可能计数精确计算MAX_SAFE_COUNT避免设置得过小导致误超时或过大导致死锁响应慢。5.4 多通道测量的考虑如果需要测量多路模拟信号有几种思路模拟开关切换使用模拟多路复用器如CD4051将多路信号切换到同一个RC网络和比较器输入端。软件上每次测量前先切换通道并留出足够的稳定时间。多比较器如果MCU有多个比较器可以为每个通道配备独立的RC网络和比较器但PWM输出可能需要共用或各自独立软件复杂度增加。分时复用单路硬件这是最经济但最慢的方式。用一个IO口配合不同的电阻网络来切换输入信号每次切换后需要重新初始化RC网络。无论哪种方式都要注意通道间的串扰和建立时间。切换到新通道后必须进行完整的初始化输出对应波形足够长时间以消除上一个通道电压在电容上的残留影响。经过这些年的项目实践可变范围PWM ADC方案虽然诞生于资源受限的早期微控制器时代但其设计思想在今天依然闪烁着智慧的光芒。它教会我们在硬件的限制下通过深度的软件与系统协同设计依然能够挖掘出令人惊喜的性能。对于很多成本优先、功能简单的嵌入式产品这不仅仅是一个省钱的技巧更是一种优雅的工程解决方案。当你下次面对一个需要ADC但芯片没有、外扩又不划算的场景时不妨重新审视一下你手头MCU的比较器和定时器也许这个经典的方案能再次派上用场。