ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

正余弦编码器采样台阶问题:多通道ADC采样保持残留电荷与多看一拍解法

正余弦编码器采样台阶问题:多通道ADC采样保持残留电荷与多看一拍解法 写这篇工程手记之前先交代一下背景我在做一套正余弦编码器的角度解算电路MCU 自带 14 位 ADC前端经过运放调理正余弦信号双双送进 ADC 的两个输入通道。调试了几天之后我盯着串口打印出来的码值曲线足足愣了半分钟——那条曲线不是平滑的楼梯而是一段一段跳着走的台阶每级台阶恰好是 8 个 LSB。更邪门的是这种台阶只在正余弦信号过零附近出现其他区段反而挺正常。最开始我怀疑是自己滤波函数写错了后来确认算法没问题又怀疑是参考电压纹波结果换来换去一整天问题依然健在。直到我做了“同一相位连续采样两次”的实验才把真正的凶手揪出来采样保持电容里欠着上一笔的“债”正余弦通道切换后没有多等一拍导致第一次转换读数带着残留电压。这篇文章就把完整的定位过程和“多看一拍”的解决方案记录下来给同样在 14 位 ADC 上做正余弦采样的人当个避坑参考。1. 从一张“难看”的码值曲线说起1.1 正余弦过零段的异常平台先描述一下现场。电路本身不复杂旋转编码器输出差分正余弦信号经过仪表放大器转单端然后分别接到 MCU 的采样通道。我让信号源输出 0.5 Hz 的低频正弦波同时给余弦通道一个 90° 相位偏移这样两个通道都能看到完整的过零过程。理想情况下14 位 ADC 的结果应该是一个码一个码往上爬就算有量化误差最多也就是 ±1 LSB 的抖动。但我看到的实际序列是下面这个样子输入电压折算到 ADC 输入理想码值实际读取码值-1.60 mV-8-8-1.40 mV-7-8-1.20 mV-6-8-1.00 mV-5-8-0.80 mV-40-0.60 mV-30-0.40 mV-20-0.20 mV-100.00 mV000.20 mV100.40 mV200.60 mV300.80 mV481.00 mV581.20 mV681.40 mV781.60 mV88可以看出码值不是逐 1 LSB 变化而是在一段区间内完全不变然后瞬间跳 8 个 LSB。平台期持续大约 0.4 mV 的输入范围也就是 8 LSB 的“台阶”。这种表现和普通量化噪声完全不同更像是有个东西把 ADC 的输入“粘住”了等那里的电压累积到足够大才猛然释放。1.2 14 位 ADC 的量化底子与我的预期14 位 ADC 的量化分辨力其实已经很细了以 3.3V 参考电压为例1 LSB 3.3V / 2^14 ≈ 201µV也就是说理想情况下电路里任何小于 201µV 的电压变化都不可能反映到输出码值上。8 个 LSB 对应的电压大约是 1.61 mV这在精密信号链路里不算小数目。对于正余弦编码器来说如果信号幅度是 1.65V 峰值那么 1.61 mV 的误差在过零附近折算成角度大约是θ_error ≈ arcsin(1.61mV / 1650mV) ≈ 0.056°看起来不大但如果是用在精密转台或者伺服反馈上面0.056° 的台阶足以让电流环和速度环抖出明显噪声。更让人难受的是这个台阶不是固定的偏移而是和信号变化方向、通道切换顺序都有关系想用常规校准把它消除都不容易。1.3 最先排除的三个常规嫌疑我一开始按照常规排查顺序依次怀疑了三个东西。第一个是电源纹波。我用示波器看 ADC 参考电压引脚直流纹波只有不到 0.5 mVpp而且明显不到 8 LSB 的等效电压。把参考电压退耦电容从 1µF 加到 10µF 后台阶纹丝不动——嫌疑排除。第二个是基准源动态响应。如果基准电压芯片在 ADC 内部 DAC 抽电流时建立不住也会出现进位误差。我换了更低输出阻抗的基准芯片结果用高分辨率示波器观察基准引脚在转换期间的波动确认建立时间足够快而且台阶位置和输入电压强相关与转换时钟关系不大——嫌疑变轻。第三个是我自己写的软件滤波。我用串口打印原始码值关掉了所有均值和中值滤波直接看单次转换结果——台阶依然存在。这说明问题在模拟链路或者 ADC 自己的采样机制里不在数据处理环节。连续排除三个嫌疑后我把注意力放到了 ADC 内部的采样保持电路上。2. 8 LSB 台阶背后的两个物理机制2.1 量化台阶与“假台阶”的区别ADC 有量化误差是天然的。逐次逼近型 ADC 每次比较器的分辨力有限加上 DAC 电容阵列的失配个别码位偶尔偏几个 LSB 也很常见。但是那种误差一般是随机分布的或者表现为 INL/DNL 曲线上的个别峰谷而不会出现“一段区间完全锁死然后突跳 8 个 LSB”的规律性台阶。一个最直观的验证方法把输入信号改成缓慢三角波同时降低转换速率观察码值序列。如果是真的量化误差码值会随机地在理想值附近抖动因为信号是单调上升的输出码值虽然可能忽大忽小但总体趋势不会出现长时间停留。而我们看到的台阶是在一个输入区间内输出始终是同一个值然后到某个临界点后直接跨过好几个码值这更像“迟滞”或者“电荷残留”。2.2 采样保持电容的残留电压前一次采样欠下的“债”SAR ADC 的内部结构里采样阶段要闭合采样开关让外部信号给内部电容阵列充电。转换阶段里电容阵列和比较器共同完成逐位逼近。当一次转换结束后电容阵列内部仍然保留着刚刚那组开关状态对应的电荷。下一次采样开始时外部信号需要把电容阵列重新充到新的输入电压。问题就出在这个重新充电的速度上。如果外部信号源的内阻太大或者走线滤波电容太大外部源在采样窗口内来不及把内部电容阵列“纠正”到新电压那么 ADC 采到的实际上是“旧电压残余 新电压变化量的一部分”。用时间常数的思路来算一笔账假设 ADC 的内部采样电容大约是 20 pF外部串联电阻加开关电阻合计 10 kΩ那么时间常数 τ 200 ns。14 位 ADC 需要约 10 个时间常数才能建立到 1/2^14 的精度也就是大约 2µs。如果采样时间只有 800ns则建立误差大约是V_error ≈ ΔV * e^(-t/τ)其中 ΔV 是上一次和当前输入之间的电压差。如果 ΔV 有 1.65V 那么夸张t/τ4 时误差已经是输入差值的 1.8%约 30 mV远超 8 LSB。而在正余弦过零附近信号本身变化不快但两个通道的信号差值可能很大——比如上一拍采的是数值接近 1.65V 的 SIN这一拍要采的是接近 0V 的 COS差值近 1.65V残留误差很容易超过 8 LSB。这就是“债务”的根源ADC 总是带着上一次采样的残留电荷进入下一次采样如果外部电路没有足够能力把它冲掉台阶就会一直存在。2.3 通道切换时电荷注入的叠加效应很多 ADC 采样系统会在信号源和 ADC 之间放置一个多路复用器或者利用 MCU 内部的多通道扫描。通道切换本身就是一次“台阶信号”MUX 输出从上一通道的电平瞬间跳变到当前通道的电平这会向后续滤波电容注入一个电流脉冲。如果 MUX 后跟随了一个较大的滤波电容比如 1nF那么通道切换后电容上的电压需要从上一通道的电平逐渐建立到当前通道的电平。此时如果立即启动 ADC 采样采样保持电容和这个滤波电容之间会发生电荷分享。由于滤波电容上的电压还没完全稳定ADC 采到的就是两个电容电荷再分配后的中间值。一个典型的表现是如果上一通道电压比当前通道高当前通道的转换结果会偏高如果上一通道电压比当前通道低转换结果会偏低。这种误差与两个通道的电压差值成比例会让正余弦信号在交替采样时出现“你欠我、我欠你”的台阶。我这次遇到的过零台阶恰好就是 COS 通道切换在 SIN 通道之后时出现的——因为 SIN 在过零附近数值较大会给 COS 留下明显的残余。所以“8 LSB 台阶”不是单纯的量化问题而是电荷残留 通道串扰 建立时间不足三者叠加的结果。3. 模拟前端验证双采样实验定位残留电荷3.1 用慢速三角波做迟滞对比为了确认残留电荷方向与信号变化方向的关系我做了这样一个实验用极慢的三角波0.05 Hz作为 VIN1 输入VIN2 接地ADC 在两个通道之间循环采样。然后分别记录三角波上升段和下降段中 VIN1 通道的码值。结果非常明显上升段时VIN1 通道的码值总体偏低下降段时VIN1 通道的码值总体偏高。换句话说VIN1 的读数总是被 VIN2 的上一拍电压“拉”着走。上升时 VIN2 是 0V把 VIN1 的读数往下拉下降时 VIN2 还是 0V但 VIN1 的自身残留又让它偏高。这个现象完美解释了正余弦过零台阶的方向性。正余弦信号同时变化SIN 大时 COS 小SIN 小时 COS 大两个通道互相拖拽导致每个通道都在接近过零时被另一个通道“按住”直到差值积累到超过残留电荷的保持能力才猛地跳出去。3.2 同一相位连续采样两次第二次才正常接下来做了整个排查中最重要的实验让 ADC 保持同一个输入通道不变但在极短时间间隔内连续触发两次转换把两次码值都打出来。如果残留电荷理论成立那么第一次转换应该带着上一周期的残留第二次转换应该更接近真实值因为第一次转换后的“债务”已经被当前输入电平冲抵了一部分。结果没有让我失望。以 COS 通道过零前的某一次采样为例第一次转换码值 0第二次转换码值 -6理想码值约 -6。第二次转换的码值基本就等于正确值第一次转换却躲在 0 的平台上。这是因为第一次转换前ADC 内部电容阵列还记着 SIN 通道留下的高电压采样窗口里虽然开始向 COS 的 0V 靠近但没来得及走完结果就被判成了 0。等第一次转换做完电容阵列又被强制重新充满过一次这次才真正贴近 COS 的当前电压。我把这个实验重复了 200 组每组都丢第一个转换结果、只保留第二个转换结果。码值曲线立刻恢复了清晰的楼梯形态台阶消失。这个结果基本确立了“多看一拍”的解决方向。3.3 示波器探头引出的岔路输入节点电压为什么反而升高在排查过程中我还踩过一个探头的坑这里顺带提一句免得有人走弯路。我用普通 10× 探头直接点到 ADC 输入引脚上发现引脚电压比信号发生器设定值偏高了几十毫伏。起初以为电路板漏电后来才意识到是探头本身的输入电容大约 15 pF和 ADC 采样保持电容在采样瞬间发生了电荷再分配。当 ADC 采样开关闭合时内部电容阵列会从外部抽取电荷如果外部源阻抗较高采样瞬间引脚电压会下降而普通示波器探头会引入额外电容改变这个压降的幅度和恢复时间。换句话说示波器一接上去观察到的是被探头发改之后的“系统扰动”而不是真实工作状态下的波形。所以后面所有临界时序的验证我都改用高阻差分探头或者干脆用 ADC 自身的读数来反推不再用普通探头单独判断 ADC 输入引脚上的瞬态电压。4. “多看一拍”怎么落地4.1 数字侧时序调整通道切换后强制插入 dummy 转换定位到残留电荷问题后我做的第一项修正就是在多通道采样序列中插入“空转换”。这套思路非常简单第 1 拍启动转换转换结果是包含上一通道残留的“坏值”丢弃第 2 拍启动转换这时候电容阵列已经被“纠正”到当前通道电平附近转换结果是可用的“好值”采样完成后才切换通道。这里的“多看一拍”本质上就是给 ADC 一次额外的重采样机会让内部电容阵列先从上一通道的欠债里恢复过来。如果你只用 MCU 的扫描模式没法在两个通道之间插入 dummy 采样那就改用手动触发模式或者把 N 个通道的扫描序列扩展为 2N 个槽位偶数位放真正需要的通道奇数位强制接到同样的输入通道或者干脆短接到地。FPGA 里实现更简单维护一个采样状态机状态切换顺序为 IDLE - SETTLE - CONVERT - HANDLE。SETTLE 状态只有一个目的——等待 MUX 切换完成、外部滤波电容建立、内部保持电容进行足够的预充电。这个状态下不做比较只是把采样开关闭合一段时间。等 SETTLE 完成后才开始真正的逐次逼近转换。实用参数参考若外部滤波电阻为 1 kΩ滤波电容为 470 pF那么 SETTLE 时间至少需要 10 × 1kΩ × 470pF 4.7 µs。如果 ADC 的采样时间配置不够就用 dummy 转换来补足。我系统里最终选择的是在 MUX 切换后先插入一个 10µs 的 SETTLE再启动一次 dummy 转换最后再启动有效转换整体采样周期从原来的 20µs 拉长到 40µs完全够用。4.2 增加采样建立时间的寄存器配置除了在逻辑上多插一拍硬件寄存器里也要把采样时间配长一些。很多 MCU 的 ADC 都支持可编程采样时间比如 1.5 个 ADCCLK 到 239.5 个 ADCCLK。如果你原来用的是 1.5 cycles现在建议至少翻到 7.5 cycles 或更高。采样时间的关键作用是直接控制采样开关闭合的持续时间。采样开关闭合时间越长外部电压就越有机会把内部电容阵列充到正确值。需要注意的是采样时间也不是越长越好太长的采样时间会增加整个系统的延迟对高频正余弦信号会造成采样时刻偏移。一般建议根据信号频率计算允许的最大相位误差再倒推采样时间。对于 1 kHz 以内的正余弦信号采样时间在 1µs 到 10µs 之间通常不会引入明显相位滞后。我最终的配置是ADCCLK 设为 18 MHz采样时间为 14.5 cycles算下来大约是 0.8µs同时在外围增加软件 dummy 转换。这样既保证了建立精度又没有把采样周期拖得太离谱。4.3 算法侧补刀阶梯抑制与相位同步判断硬件改为“多看一拍”之后8 LSB 的大台阶已经基本消失。但为了保险起见我在算法里再加了一重保护专门对付漏网的小台阶和边缘毛刺。思路就是利用正余弦信号本身的光滑性角度解算不可能瞬间突跳 8 个以上码值除非真的发生了极高速的运动。做法很简单维护一个历史码值缓冲区每次读入新采样值时先与缓冲区最后一个有效值比较。如果差值绝对值在 2 个 LSB 以内正常更新如果差值在 3 到 8 个 LSB 之间就按照当前信号斜率方向做线性插值而不是直接采信这个跳变如果差值超过 8 个 LSB就认为可能是真实的高速变化或者丢码用多次中值滤波确认后再更新。这套保护只会在异常瞬间起作用对真实信号的影响极小因为正常采样下正余弦信号相邻两拍的变化量远小于 8 LSB。它作为最后一道防线让我在后续调高运动速度时也不至于因为偶发残留电荷导致角度解算出现毛刺。5. 正余弦采样还有哪些隐藏坑5.1 采样点与激励源相位未同步造成的“游动台阶”解决掉通道切换问题之后我又碰到了一个新的台阶位置还会随着电机转速变化而变化。后来分析发现那是采样时刻与正余弦编码器的激励信号相位没对齐导致的。许多正余弦编码器需要外部激励信号典型频率在 1 kHz 到 20 kHz 之间。ADC 每次采样的时刻如果在激励周期里不是固定的那么相邻两次采集的 sin/cos 瞬时值就可能差出好几个 LSB。比如激励频率 10 kHz 时信号斜率每秒变化 2π×10k×A ≈ 62831×A V/s。如果采样时刻抖动 1µs瞬时电压偏差可达 0.063 VA 取 1V 时远超 8 LSB。所以正余弦采样一定要尽量做到和激励源同源触发用激励信号的过零检测或者 PWM 同步信号作为 ADC 触发源保证每次采样点都落在激励周期的同一相位上。我最后改成了由 PWM 中心对齐事件触发 ADC 采样台阶问题才彻底消失。5.2 差分增益/失调不对称让 sin 和 cos 台阶高度不同如果你用的是单端 ADC 采样差分正余弦信号那么前端运放的增益误差和共模抑制能力也会被“放大”成不同高度的台阶。我的板子上 SIN 通道和 COS 通道各用了一个仪表放大器两片芯片的增益匹配误差大约 0.3%这在高速旋转时会转化为幅度误差。更麻烦的是两片芯片的失调电压不同会让两个通道的零点错开于是同一个机械角度在 sin 和 cos 通道上的码值比例发生变化atan2 解算出来的角度会出现周期性非线性。建议做一个出厂自校准在无激励或者激励固定幅值时短接运算放大器输入记录两个通道的零位偏移量然后用一个实测幅度值标定两通道增益比例。软件里采用一点校正和两点校正结合的方式把这个误差从系统里抠掉。我做完之后在空载低速下测量到的 LSB 台阶基本只剩下 ±1 的随机量化抖动。5.3 我目前的工程清单与一段完整可用的采集伪代码最后把我这次调试沉淀下来的工程清单整理如下检查项推荐做法通道切换切换后至少等待 10 个外部 RC 时间常数再启动采样采样时间不低于 10 个内部采样 RC 时间常数宁长勿短dummy 转换每个真实采样之前插入一次结果丢弃的预转换触发源尽量使用 PWM 同步触发采样点相对激励相位固定模拟前端仪表放大器增益匹配 0.1% 以内失调电压低于 0.1 mV保护算法相邻采样差值超过 3 LSB 时启用插值/中值确认下面是一段适用于任督二脉已经打通、却想快速在 MCU 上实现“多看一拍”的伪代码可以直接改成 C 或者 Veriloguint16_t adc_read_with_settle(adc_channel_t ch) { // 1. 切换模拟 MUX 到目标通道 mux_select(ch); // 2. 等待外部 RC 建立 delay_us(SETTLE_US); // 3. 启动第一次转换结果丢弃 adc_start_conversion(); adc_wait_conversion_done(); (void)adc_read_result(); // 4. 立即启动第二次有效转换 adc_start_conversion(); adc_wait_conversion_done(); return adc_read_result(); } void sample_sin_cos(uint16_t *sin_code, uint16_t *cos_code) { *sin_code adc_read_with_settle(ADC_CH_SIN); *cos_code adc_read_with_settle(ADC_CH_COS); // 如果需要更高精度这里可以再做一次第二拍采样并取平均 }这段伪代码看起来平凡但重点在于第二次转换才是真正的读数。如果你把这个模式套在自己的代码里大概率也能复现我在标题里写的那句“多看一拍”的效果。至于为什么标题里特别强调“正余弦采样要多看一拍”因为正余弦信号天然存在两个通道交替读取的问题。只要你是交替采样 sin 和 cos无论内部 ADC 还是外部 MUX都要面对残留电荷。不要指望靠 PCB 走线或者加去耦电容就能彻底避开因为问题的根源在 ADC 采样开关和电容阵列本身你能做的就是把每一次采样之间的“欠债”用额外的时间补上。我自己的体会是碰到 ADC 台阶先别急着怪芯片精度不够很多情况下是我们没给 ADC 足够的时间“忘掉过去”。14 位 ADC 的分辨力已经足够细腻真正决定系统高不高端的往往是这些没有人写在数据手册里的细节。
返回列表