ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

树莓派Pico ADC深度解析:从寄存器到实际应用的避坑指南

树莓派Pico ADC深度解析:从寄存器到实际应用的避坑指南 树莓派 Pico 的 ADC 是一个很有意思的外设。官方文档里只给了“12位逐次逼近型ADC”这样一句轻描淡写的描述SDK里封装好了adc_read()很多朋友调用一下就能拿到数值于是就这么用下去了。但一旦你真的拿它去做正经的数据采集比如采集音频波形、读取高阻抗传感器信号甚至只是想在多通道之间高频切换采样就会发现各种奇怪的问题采样值忽高忽低、带负载能力差、多通道串扰甚至同一管脚在不同板子上的读数都不一样。这些问题不深入底层是根本解释不了的。这篇内容我打算把 Pico 这颗 RP2040 的 ADC 模块从头到尾拆开讲一遍包括SAR型ADC的工作原理、内部模拟多路复用器的开关顺序、寄存器每一位的作用、官方SDK里adc_read()到底做了什么以及我在实际项目中踩过的一堆坑。适合刚入手 Pico、想认认真真用ADC做采集的开发者也适合想了解ARM Cortex-M0内核与模拟外设交互细节的老手。1. 先从硬件架构看起这颗12位SAR型ADC到底是怎么工作的1.1 SAR型ADC的工作原理和我们关心的关键参数RP2040 内部集成的这颗 ADC 是经典的逐次逼近型Successive Approximation RegisterSAR结构。要理解 Pico 的 ADC 为什么有这个那个脾气必须先理解这种结构本身。SAR 型 ADC 的核心是一个比较器、一个逐次逼近寄存器、一个内部 DAC 网络。采样时它先对输入电压进行采样保持然后从最高位开始一位一位地把内部 DAC 输出电压逼近到输入电压。一次转换输出 N 位数据就需要 N 个时钟周期来完成逐次比较。12位分辨率正常情况下一轮转换就是12个转换时钟周期再加上采样阶段的时间一个完整的采样周期就出来了。这颗 ADC 的关键参数在 RP2040 数据手册里写得比较分散我整理了一下参数数值说明分辨率12 bit输出寄存器实际存12位数据ADC 时钟上限48 MHz由系统时钟分频得到不能超过采样保持时间可配置 4-8 个 ADC 时钟周期通过 CS 寄存器的 SHCS 位段配置输入电压范围0 到 3.3VVREF 为 3.3V超过会损伤引脚输入通道阻抗要求源阻抗越低越好建议几kΩ以内采样电容充电时间受影响采样率上限约 50 万次每秒500 kSPS48MHz /8 12≈ 2.4 MHz 理论值实际受限于多通道切换和软件开销内置通道5路GPIO26-GPIO28 内部温度 VREF半压测试ADC3/ADC4 为内部通道那个约 500 kSPS 不是随便拍的。ADC 时钟取最高 48 MHz如果采样保持时间配置成 12 个时钟周期SHCS111 再加 4那么单次采样是 12 12 24 个 ADC 时钟48M / 24 2 MSPS。但实际还要加上软件操作寄存器的开销、多通道切换时模拟开关的稳定时间以及室温下 SAR 自身抖动恢复等等。实测下来单通道连续采样能轻轻松松跑满 1 MSPS 以上用 DMA 搬运多通道轮询就要打点折扣。1.2 输入前端从 GPIO 引脚到采样电容的这段路从物理引脚到 SAR 引擎之间并不是直接一根导线连过去的。中间隔了模拟多路复用器MUX、ESD 保护二极管还有不可避免的采样保持电容和开关导通电阻。Pico 的 ADC 只有一路 SAR 核心但前面挂了 5 个输入源靠模拟开关切换。GPIO26 对应 ADC0GPIO27 对应 ADC1GPIO28 对应 ADC2还有内部温度传感器通道和 VREF 分压检测通道。一个问题就浮现了模拟开关本身有导通电阻采样电容要从源端充电如果源阻抗过高充电时间常数RC会变大采样保持阶段电压来不及充满转换出的数值就会偏小。我在实际测试中发现用信号发生器直接驱动 GPIO26 采样 1 kHz 正弦波幅值 0.2V 到 3.1V波形还原得很好。但换成用 10 kΩ 电阻分压器给到同样幅值的信号读数直接往下掉误差从最初的 2 个 LSB 扩大到 15 个 LSB 以上。这就是典型的输入阻抗不匹配问题。所以做前端设计时要么确保源阻抗足够低要么在引脚前加一级运放做电压跟随器这是 ADC 应用的第一条铁律。1.3 参考电压与供电的关系3.3V 并不总是干净的 3.3VPico 的 ADC 参考电压直接取自 VREF 引脚而 VREF 在 Pico 板上默认接到了 3.3V 电源轨。问题在于这个 3.3V 是用板载的 RT6150B buck-boost 芯片从 USB 5V 或电池电源转换出来的纹波和噪声特性取决于负载情况。如果主板上同时驱动舵机、WiFi 模块或者大功率 LED3.3V 电源轨上的纹波会直接耦合进 ADC 参考导致转换结果表现出与负载同步的波动。最典型的场景让 Pico 一边驱动一个 8Ω 扬声器输出 PWM 音频一边用 ADC 采集麦克风信号扬声器声音大的时候采集到的直流偏移会明显跟着声音起伏。解决思路有几个一是模拟部分和数字部分尽量分开布线供电但 Pico 的引脚是固定的没法改二是给 VREF 引脚外接一个低噪声基准源比如 REF3330 输出 3.3V把板载 3.3V 和 VREF 断开飞线接基准源。实测效果立竿见影ADC 输出的底噪从原来的 ±4 LSB 降到 ±1 LSB 左右。也可以直接用内部 VREF 半压通道做软件校准测量实际参考电压值在软件里做比例修正。2. 工作模式拆解单次转换、自由运行、FIFO 与 DMA模式选择的实际考量2.1 单次转换模式按一下动一下简单但效率不高RP2040 的 ADC 最基本工作模式是单次转换模式。软件写 CS 寄存器的 START 位为 1ADC 执行一次采样和转换结果放到 RESULT 寄存器里转换完成后硬件自动把 CS 的 READY 位拉高同时可以产生中断。官方 SDK 的adc_read()函数封装的就是这个流程先配置通道然后写 START 位为 1等待 READY 位置 1最后读 RESULT。代码很简单但每次转换都需要软件参与启动和等待CPU 被占住了看门狗定时器都要小心安排不然跑着跑着就超时复位了。单次模式适合低频采样比如每秒读取几次温度、电池电压、电位器位置。数据量小逻辑简单不易出错。如果用来做音频采样一秒钟要四十几次中断打断主流程CPU 基本就干不了别的了。2.2 自由运行模式与 FIFO让 ADC 自己跑CPU 隔一会儿来取数据把 CS 寄存器的 MODE 位置为 1ADC 就进入了自由运行模式。采样转换不断重复每次转换完成结果自动推入 FIFO 或者直接覆盖 RESULT。再配合 FCS 寄存器配置 FIFO 阈值达到电平就触发中断通知 CPU 来批量取数。这样 CPU 不需要每釆一个一个等可以做别的事。FIFO 深度是 4 个 16 位条目每个条目低 12 位是转换结果高 4 位是通道标记。使能 FIFO 之后即使只用一个通道也建议把 FIFO 打开能够吸收一部分中断延迟带来的数据抖动。实际做音频采样时我通常配置 FIFO DREQ 阈值到 4配合 DMA 搬运CPU 完全不用管 ADC 这块。2.3 多通道轮询模式这是最容易踩坑的地方多通道轮询的最直观方案在自由运行模式下每次采样完成后软件切换 AINSEL 字段到下一个通道。听起来很简单但实际做出来经常发现前几个采样点的数据完全不对。原因在于模拟多路复用器的切换之后采样电容需要时间充电稳定。如果某个通道的源阻抗特别高刚切换过去的那一拍采样数据大概率偏小。我的处理办法是跳读法切换通道后把该通道的前 2-3 个采样结果直接丢弃从第 4 个数据开始使用。这样牺牲了一点速度但每个通道的数据都稳定可靠。实测用 50 kHz 采样率轮询 3 个通道每个通道实际有效采样率约 15 kHz 左右跳读法完全可行。多通道还有一个隐含问题ADC1GPIO27在 Pico 板上和板载温度传感器、I2C0 的 SDA 引脚复用了。I2C 不用时还好一用 I2C模拟开关和数字 I/O 共用焊盘信号互相干扰严重。所以同时用 ADC1 和 I2C0 是设计上的大忌优先级比你想象中还要高。通道GPIO复用风险ADC0GPIO26无比较纯净ADC1GPIO27与 I2C0 SDA 复用与温度传感器共用内部节点ADC2GPIO28与 I2C0 SCL 复用ADC3内部温度传感器ADC4内部VREF 分压测试通道VREF/2 左右2.4 DMA 与 ADC 的黄金搭档为什么高采样率必须用 DMAADC 本身的输出数据率可以到 1 MSPS也就是微秒级一个数据。CPU 用中断方式处理中断进出的开销至少要一两微秒再加上上下文保存恢复CPU 有效利用率就很低了。DMA 搬运是唯一合理的方案让 ADC 的结果直接进内存缓冲区CPU 周期性地处理内存里的数据块。Pico 的 DMA 用起来不算复杂。配置好 DMA 的读取地址为 ADC FIFO 地址0x4004c000 0x08写入地址为目标内存数组触发源选择 ADC DREQDMA_GET_DREQ_ADC传输宽度设半字16bit使能 DREQ 即可。DMA 会在 FIFO 非空时自动搬运数据到达内存缓冲区一半时触发中断CPU 处理前半部分同时 DMA 继续搬后半部分形成双缓冲流水线。我用这个方案实际跑过一段 16 kHz 音频采样循环缓冲区分 128 段CPU 平均占用不到 5%剩下 95% 的算力可以跑音频算法、接 WiFi、驱动屏幕。多说一句DMA 模式要求 ADC FIFO 始终保持非空但不能溢出FIFO 深度只有 4中断处理不能太慢否则数据会被覆盖。实际使用中双缓冲且中断处理时间控制在 50 微秒以内是安全的。3. 寄存器底层逐位拆解CS、RESULT、FIFO以及 SDK 封装下的真实执行路径3.1 寄存器地图总览RP2040 的 ADC 寄存器区块基地址是 0x4004c000总共也就十来个寄存器但真正干活的主要是下面这几个偏移地址寄存器名作用0x00CS控制状态寄存器核心开关0x04RESULT转换结果寄存器0x08FCSFIFO 控制与状态寄存器0x0cFIFOFIFO 数据读取寄存器0x10DIV采样时钟分频寄存器整数小数分频0x14INTR中断标志寄存器0x18INTE中断使能寄存器0x1cINTF中断强制寄存器0x20INTS中断状态寄存器代码里操作这些寄存器的本质就是读写内存地址和其他指针操作没有任何区别。你定义一个volatile uint32_t *指向 0x4004c000写*ptr value就完成了对 CS 寄存器的写入。这也就是为什么很多进阶教程会说“指针就是寄存器操作的钥匙”。3.2 CS 寄存器里每个位的含义以及 SDK 是怎么用它的CS 寄存器 32 位宽度实际用到的位不多但每一位都对整个 ADC 行为影响重大。我列个表把关键位段逐个拆开位段名称类型作用[31:24]RROBINRW轮询使能对应通道0-7位置1表示在自由运行模式下自动轮询[23:19]保留--[18]AINSELRW当前选择的输入通道[17]ERR_STICKYRO错误标记锁存位发生过越界则保持1[16]ERRRO本次转换是否有错误[15]READYROADC 空闲标志1 表示可以启动新转换[14:12]SHCSRW采样保持时钟周期配置实际为 (SHCS4) 个 ADC 时钟周期[11]MODERW0单次 1自由运行[10:9]保留--[8:5]START_MUXRW启动源选择0软件触发1-6PWM 触发[4:0]ENRWADC 总使能位采样前必须置1adc_init()首先把 EN 位置 1开启 ADC 电源和时钟adc_select_input(channel)把 AINSEL 设置成目标通道adc_run(true)把 MODE 置 1 开启自由运行模式adc_read()则是在单次模式下写 START 位域为 1 触发一次转换。SDK 的逻辑并不神秘就是一层薄薄的结构体封装外加用指针把寄存器地址映射到结构体字段上。采样保持时间这个参数很多新手容易忽略。SHCS 默认值是 0也就是 4 个 ADC 时钟周期的采样时间。在 48 MHz ADC 时钟下4 个周期就是约 83 纳秒这对低阻抗源完全够用但对高阻抗源就严重不足。我在前面测试10kΩ源阻抗的场景把 SHCS 调到 111采样时间拉长到 11 个周期误差从 15 LSB 缩小到 4 LSB改善非常明显。代价是最大采样率从 2 MSPS 掉到了 1.26 MSPS但在大多数传感器应用里这根本不是问题。我强烈建议有条件的项目都手动设置一下 SHCS。因为时序要求高、前级阻抗又不确定适当延长采样保持能大幅度提高测量的稳定性和准确度这是性价比极高的一次寄存器配置。3.3 FCS 寄存器与 FIFO 工作机制FCS 寄存器控制着 FIFO 的行为模式位段名称作用[31]ENFIFO 总使能[30]DREQ_EN使能 DMA 请求FIFO 达到阈值就触发 DREQ[29:24]DREQ_THRESHDMA 请求触发阈值FIFO 内条目数量达到该值时触发[20]OVER最后一次读操作是否发生过 FIFO 溢出[19]UNDER最后一次读操作时 FIFO 是否为空[18]FULLFIFO 满标志[17]EMPTYFIFO 空标志[16]DVALFIFO 中是否还有有效数据[15:4]ERR溢出错误记录位段[3:0]LEVELFIFO 内当前条目数量一个小技巧DVAL 位和 FULL/EMPTY 标志配合可以在不读 FIFO 数据的情况下先判断 FIFO 状态避免因为空读造成的 UNDER 状态锁存。官方推荐的做法是把 DREQ_EN 置 1DREQ_THRESH 设成 4即 FIFO 满了再触发一次 DMA 请求。这样 DMA 每次搬运一半是可能一次搬空整个 FIFO 的效率很高。但要注意 DMA 的突发传输字长必须和 FIFO 条目宽度匹配。ADC 结果是 16 位的12 位有效数据 4 位通道标记所以 DMA 配置为半字读取千万别配成字节或者全字。3.4 中断、DREQ 与循环采样的完整配合链路ADC 模块的 DREQ 是硬件事件当 FIFO 条目数达到阈值时自动拉高DMA 控制器检测到就开始搬运。这在时序上能保证数据不丢失如果用中断从中断触发到 CPU 真正执行读取代码中间有几十到几百个时钟周期的延迟在高采样率下很容易溢出。所以只要采样率超过 100 kSPS我的建议是打死不用中断直接上 DMA。FIFO 溢出的标志 OVER 一旦置 1如果不清除后续 DVAL 位会一直保持导致程序误认为还有数据可读。每次读取 FIFO 前做一次 OVER 位检查和清零能避免不少调试时的灵异现象。4. 误差分析与校准策略精度只有9位有效位这是很多人不知道的现实4.1 从12位到9位的落差聊聊有效位数ENOBRP2040 的 ADC 标称 12 位分辨率这句话没有撒谎但真实的有效位数受电源噪声、参考电压波动、内部比较器失调等因素影响远达不到 12 位。官方数据手册给出的典型 ENOB 大约是 9 位左右也就是说抛开外部干扰不谈ADC 自身的固有噪声水平就决定了你最多只能信任大约 9-10 位的精度。怎么理解这个差距拿一个 3.3V 满量程的 12 位 ADC 来说1 LSB 对应的电压是 3.3V / 4096 ≈ 0.8 mV。如果 ENOB 是 9 位那么实际有效可分辨的最小电压约为 3.3V / 512 ≈ 6.4 mV。换句话说你以为能分辨 1 毫伏以下的信号变化实际上只有超过 6 毫伏的变化才能被可靠地分辨出来。做精密测量时如果忽略这一点出来的数据曲线就会看起来一直在跳而且无论你怎么滤波都压不下去。4.2 噪声来源和实测验证我在面包板上做了一个简单实验将 GPIO26 接地连续采集 10000 次统计结果分布。结果显示读数在 0 到 15 之间跳动正常应为 0平均约 3标准差约 4换算成电压大约 2-3 mV 的零输入噪声。而将 GPIO26 接到 VREF 引脚3.3V读数的分布同样有几个 LSB 的波动。这个噪声主要来自哪里一是 3.3V 电源轨上的开关纹波二是 ADC 采样电容在采样瞬间对信号源的冲击导致的毛刺三是内部 SAR 比较器本身的随机噪声。前两种是主导。如果外接一个干净的参考电压源并将 VREF 从板载 3.3V 解耦零输入噪声可以降到 1 LSB 以下。这说明板载的供电质量是主要瓶颈换外部基准源的提升立竿见影。4.3 软件校准思路多点标定和线性修正知道了误差存在我们可以通过软件做校准。最朴素的做法是最小二乘线性拟合法用精密电压源给 ADC 输入几个已知电压点采集读数建立读数到实际电压的线性映射关系。以 2 点校准为例给 0V 输入记录输出码值 code0给 3.0V 输入记录输出码值 code1。实际电压 V code * (3.0V / (code1 - code0)) - offset。测量精度可以抬高到 12 位 ADC 的实际线性度以内通常 INL 在 ±3 LSB 内。如果要做 3 点以上校准可以用分段线性插值或者多项式拟合进一步提升精度。但要注意校准只在同一供电电压和同一环境温度下有效。VREF 变化后校准曲线也要重新做否则误差反而更大。4.4 过采样与求平均把有效位数字拉回来的简单办法如果采样率富余过采样加求平均是提升有效位数的通用方法。原理是把多次采样的结果平均随机噪声互相抵消信号被保留。每过采样 4 次求一次平均可以提高约 1 位有效分辨率16 次平均提高 2 位。Pico 的 ADC 最大采样率是 500 kSPS做 16 次平均还有 30 kSPS 的实际吞吐量对大多数传感器监测场景绰绰有余。我实测把采样率定在 200 kSPS每通道做 32 次采样取平均相当于每个有效点耗时 160 微秒有效数据率约 6 kSPS读取一个缓慢变化的电位器电压稳定性从 ±4 mV 改善到 ±1 mV 以内效果非常明显。C 语言里的平均值滤波可以写得极简uint32_t sum 0; for (int i 0; i 32; i) { sum adc_read(); } uint16_t avg sum 5; // 除以 32但如果数据中包含周期性噪声单纯平均而不做相位对齐效果会打折扣。更好的办法是整周期采样比如采样时钟与 PWM 同源保证采样点落在 PWM 周期的固定相位处这样能大幅消除电源纹波导致的读数跳动。5. 应用避坑实录从阻抗匹配到多通道串扰我在实际项目中踩过的问题5.1 高阻抗源无法直接驱动10kΩ 分压器实测翻车现场这是我做电池电压监测时踩的坑。用两个 10 kΩ 电阻把 4.2V 锂电池电压分压成约 2.1V直接接到 GPIO26 上采样。结果发现读到的电压值比万用表实测值低了将近 30 mV而且随着电池电压降低误差还在变化不是固定偏移。排查过程先用短粗杜邦线把 GPIO26 直接接到 VREF 引脚读数是 4095正常。又把分压电阻从 10kΩ 换成 1kΩ误差立刻缩小到 3 mV 以内。结论明确了问题出在源阻抗和采样电容充电时间常数的匹配上。最终的解决方式是在分压网络和 ADC 引脚之间加了一级 LM358 运放接成电压跟随器。运放输出阻抗是毫欧级ADC 的采样电容在微秒内就能充满误差消失了。另一个替代方案是增大 SHCS 采样时间但治标不治本若温度变化或元件老化RC 常数变了精度又会漂移。5.2 多通道串扰问题GPIO26 的读数为什么受 GPIO28 影响多通道轮询时我遇到了一个诡异的现象GPIO28 接的是 3.3VGPIO26 接的是 1V结果采样 GPIO26 时读数稳定在 1V 对应的码值但把 GPIO28 拔掉悬空后GPIO26 的读数居然上升了 20 多个 LSB。问题出在模拟 MUX 的通道隔离度上。分析原理GPIO28 悬空时其引脚电位不稳定可能存在感应噪声或漏电流。这个噪声通过 MUX 的内部寄生电容和衬底耦合串入了当前采样的通道。解决办法有几个路径一是所有模拟通道都保证低阻抗驱动悬空的模拟引脚不管用不用都接地或接固定电压二是切换通道后多跳过几个采样点等串扰衰减后再取数三是在 PCB 布线上把模拟通道之间做隔离地线处理。5.3 ADC 与 GPIO 功能冲突启用了 ADC 的引脚还能当数字 IO 用吗GPIO26 可以配置成数字输入输出也可以配置成 ADC 输入但不能同时使用两种功能。analog_pin_init()之后GPIO 的输出驱动器和输入缓冲器会被禁用引脚只作为模拟输入。如果后面想把这个引脚切换回数字功能需要重新执行gpio_init()和gpio_set_dir()恢复配置。我在调试时犯过这样一个错误GPIO26 同时打算读 ADC 和检测按键按下状态。结果发现gpio_get()始终返回 0折腾了半天才意识到 ADC 配置把数字输入缓冲器关掉了。正确的做法是外部分时切换模拟数字通道或者在 GPIO 和 ADC 之间加模拟开关从硬件上隔离。5.4 温度传感器通道的实测曲线和校准公式ADC4 通道内部温度传感器是个很有意思的存在。它直接输出一个与芯片温度相关的电压在常温附近大约有 2mV/°C 的斜率。用官方手册给出的公式就可以换算温度V code * (3.3 / 4096) T 27 - (V - 0.706) / 0.001721但实测下来这颗传感器受芯片自身功耗影响很大。芯片满负荷运行时比如跑 WiFi 或复杂计算温度读数会明显偏高比实际环境温度高出 10°C 以上因为板载传感器测的是芯片结温不是环境温度。所以用它做环境温度监测是误导的但做芯片过热保护却很合适。如果一定要测环境温度建议外接 NTC 热敏电阻或者温湿度传感器。5.5 一个让我最头疼的问题DMA 循环缓冲区的欠载和覆盖最后分享一个运行时序方面的问题。DMA 双缓冲模式跑着跑着音频数据偶尔出现几毫秒的卡顿和爆音。排查过程最终定位到 DMA 传输完成中断没有及时清除标志位导致上一次数据块还没处理完DMA 已经开始写下一个缓冲区内存写乱了。修复方式在 DMA 中断处理函数中先读取并清除所有相关中断标志位再处理数据。同时把缓冲区分成三段三缓冲DMA 写一段CPU 处理上一段中间留一段作为缓冲余量彻底杜绝了竞态问题。这个思路也适用于其他嵌入式平台尤其是中断密集、数据率高、处理耗时的采集场景。6. 从寄存器操作到应用性能的优化建议一些值得长期沿用的实践经验6.1 直接寄存器操作和 SDK 到底应该怎么选官方 SDK 的封装很薄但每次adc_read()都要做通道选择、启动转换、等待完成、读取结果四步如果只是单通道连续采样用 SDK 是没问题的。但如果追求极限性能比如中断上下文里快速读出 FIFO 数据直接操作寄存器能省掉函数调用的栈开销和若干冗余判断。直接寄存器操作并不复杂。一个单通道连续采样 DMA 搬运用到的寄存器操作加起来也就十几行代码编译出来的目标代码比 SDK 版本少三分之一左右。对于慢速应用我建议直接用 SDK可读性好维护容易对于音频采样这类硬实时、高吞吐的任务直接寄存器操作几乎是必需的。6.2 采集中断服务程序ISR的时序分析到底要留多少余量在写 ISR 时我给自己定了一条经验法则ISR 总执行时间必须小于最短采样间隔的 70%剩下 30% 留给内核中断处理和紧急情况。以 100 kSPS 采样率为例采样间隔是 10 微秒。ISR 中可能做的操作包括读取 FIFO、累加求和、计算均值、拷贝数据到应用缓冲区。这些操作加起来如果超过 7 微秒就要考虑把计算搬到主循环里异步完成。实际测下来在 133 MHz 主频下一条普通指令约 7.5 ns几千条指令执行时间就在几十微秒了所以不能贪多ISR 里只做最重要的数据读取。6.3 分频时钟和采样时序的定量计算DIV 寄存器控制着 ADC 时钟分频。它的值由系统时钟默认 48 MHz可以更高到 133 MHz 但 ADC 内核上限 48M 不能破和期望的采样率共同决定。假设系统时钟 48 MHz需要单通道 100 kSPS 连续采样那么每个采样周期消耗的系统时钟数 48 MHz / 100 kSPS 480 个假设 SHCS 设为 4448 个 ADC 时钟周期做采样保持加上 12 个转换时钟周期一共 20 个 ADC 时钟周期那么 ADC 时钟频率需要 100 kSPS × 20 2 MHz所以 DIV 的整数部分 48 MHz / 2 MHz 24小数部分为 0。写入 DIV 寄存器的值是 (整数部分 8) | 小数部分即 24 8 0x1800。反过来如果直接设置 DIV 0ADC 时钟全速 48 MHz那么单次转换周期是 41216 个 ADC 时钟周期对应最大采样率约 3 MSPS。但这种情况下 SHCS04 周期对高阻抗源不友好而且 FIFO 很快会被灌满DMA 压力很大实际使用很少跑这么激进。提示ADC 时钟一定不要超过 48 MHz否则转换线性度会剧烈恶化读数非线性失真严重。6.4 软硬件协同设计的调试技巧逻辑分析仪抓 DREQ、用 DMA 计数器验证时序最后分享一个我一直在用的调试三板斧。第一板斧是逻辑分析仪抓 GPIO。写一个测试程序在 ADC 完成一笔 DMA 传输后翻转一个 GPIO用逻辑分析仪比如售价几十块的 24MHz 8 通道逻辑分析仪测出实际中断频率和数据传输节奏对比理论计算值能快速定位配置错误。第二板斧是 DMA 控制块里的传输计数寄存器TRANS_COUNT。传输启动后读取它的值和期望值对比如果传输结束前就变成 0说明实际传输数据量比预期少大概率是 DREQ 信号没对齐或者 FIFO 配置有问题。第三板斧是打印校准值。先用一个已知电压源实测 ADC 读数生成软件校准参数之后再测其他传感器信号把读数和校准曲线打印出来对比快速发现线性度问题和通道串扰。这三种手段配合起来基本能在几分钟内定位绝大多数 ADC 应用层面问题。我自己在调试中最大的感受是ADC 的问题往往不是芯片本身的问题而是前端电路、电源质量、时序配置三方共同作用的结果。把这三方面逐一排查清楚树莓派 Pico 的 ADC 在大多数场景下是完全够用的甚至在一些要求不高的音频项目里配合过采样和校准可以做出超出预期的效果。如果你正准备用 Pico 做传感器数据采集建议一开始就把输入阻抗、供电噪声、采样保持时间这三个因素考虑进去后边能省掉大量调试时间。这三点做好了Pico 的 ADC 就是一颗可靠的小神器。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进