
简介面向STM32G431RBT6嵌入式开发者的ADC采集实践包聚焦模拟信号从直接寄存器读取到DMA自动搬运的两种实现路径适合已具备C语言与HAL库基础、希望打通多通道采样与低CPU占用数据链路的初中级工程师。包内共1254个文件以676个C源文件、275个头文件、97个汇编启动文件及若干工程配置、编译输出和说明文本为主压缩包约29.73MB覆盖ADC1/ADC2多通道配置、单次与连续转换、DMA通道链接、转换完成中断通知以及采样率与传输长度优化等关键环节。已有1978人学习下载说明该主题在传感器信号处理、电源监测和音频采集等实时场景中关注度较高。读者可据此对照理解直接读取与DMA搬运的差异掌握缓冲区设置、中断配合和性能调优思路并借助现成工程结构快速移植验证减少从寄存器配置到稳定采集的调试成本。1. 一块 G431 板子上的 ADC 抖动直采和 DMA 到底差在哪STM32G431RBT6 上最容易误判的一件事是把「采到的数不准」算到取数方式头上。前阵子拆一块用 G431 做的小信号采集板串口打出来 ADC 读数在 ±8 LSB 之间跳换成 DMA 搬运曲线看着顺了均值纹丝不动。直采和 DMA 解决的是两个不同问题直采解决「CPU 要不要陪着等」DMA 解决「连续采样搬不搬得动」两条路径都不解决模拟前端的偏移和源阻抗失配。这篇按落地顺序拆单通道轮询和中断怎么写、多通道扫描的序列怎么排、DMA 循环加半传输怎么拿到不撕裂的数据块、采样时间和源阻抗怎么算、以及我在 G431 的 ADC 上真踩过的五个坑。手上有块 G431 的板子、能点灯能串口打印就可以跟着走一遍。2. 直采链路轮询、中断与扫描模式的落地写法直采指的是 CPU 亲自去 ADC 数据寄存器里把结果取回来。它最大的价值是简单可控——不占 DMA 通道、不用担心缓冲撕裂、单次测量写三行代码就能跑。代价也直白从启动转换到读走结果这段时间CPU 要么死等要么被中断打断。我一般这样分低频、单点、事件驱动的量按键检测、电池电压巡检、NTC 温度用直采连续波形、多通道同步、要对数据做频谱或统计的直接上 DMA。判断标准不是「哪个更高级」而是「一秒采多少次」。低于 1 kHz 的采样需求硬上 DMA 只会给自己加一层调试负担。2.1 先把 ADC 时基算清楚时钟源、采样时间与转换周期G431 的 ADC 内核时钟不是自动跟着主频走的得单独选源和分频。常见做法是在时钟树里把 ADC12 的输入挂到 AHB 分频或 PLLP 上170 MHz 主频下取 AHB/4也就是 42.5 MHz。这个值决定了后面所有时间参数先定死它。一次转换的总周期数 采样周期 转换周期。12 位分辨率下转换周期固定 12.5 个 ADC 时钟采样周期从下表里挑采样时间设置采样周期12 位总周期42.5 MHz 单次耗时ADC_SAMPLETIME_2CYCLES_52.515353 nsADC_SAMPLETIME_6CYCLES_56.519447 nsADC_SAMPLETIME_12CYCLES_512.525588 nsADC_SAMPLETIME_24CYCLES_524.537871 nsADC_SAMPLETIME_47CYCLES_547.5601.41 µsADC_SAMPLETIME_92CYCLES_592.51052.47 µsADC_SAMPLETIME_247CYCLES_5247.52606.12 µs采样时间不是「填大点更保险」这么简单填太大会把吞吐压下去填太小则采样电容充不满读数系统性偏小而且偏得很有规律容易被误判成「分压电阻不准」。2.5 个周期只适合源阻抗极低运放直出、几十欧以内的场合分压电阻、热敏电阻这类源起步给 24.5 个周期。2.2 单通道直采轮询版与中断版先用轮询把通道跑通确认引脚、时钟、通道号都对再谈别的。下面这份初始化是我在 G431 上常用的骨架/* adc_direct.c —— 单通道、软件触发、轮询读取 */ static ADC_HandleTypeDef hadc1; void adc_direct_init(void) { hadc1.Instance ADC1; /* 时钟源与分频枚举名不同 HAL 版本略有差异以工程里 stm32g4xx_hal_adc.h 的定义为准这里取 AHB/4 42.5 MHz */ hadc1.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution ADC_RESOLUTION_12B; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.ScanConvMode ADC_SCAN_DISABLE; /* 单通道不扫描 */ hadc1.Init.EOCSelection ADC_EOC_SINGLE_CONV; /* 每次转换都置 EOC */ hadc1.Init.LowPowerAutoWait DISABLE; /* 打开会拖慢连续转换 */ hadc1.Init.ContinuousConvMode DISABLE; /* 单次模式 */ hadc1.Init.NbrOfConversion 1; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; /* 软件触发 */ hadc1.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_NONE; hadc1.Init.Overrun ADC_OVR_DATA_OVERWRITTEN; hadc1.Init.OversamplingMode DISABLE; if (HAL_ADC_Init(hadc1) ! HAL_OK) { Error_Handler(); } ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel ADC_CHANNEL_1; /* PA0 ADC1_IN1 */ sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLETIME_24CYCLES_5; sConfig.SingleDiff ADC_SINGLE_ENDED; sConfig.OffsetNumber ADC_OFFSET_NONE; sConfig.Offset 0; if (HAL_ADC_ConfigChannel(hadc1, sConfig) ! HAL_OK) { Error_Handler(); } /* 单端输入必须显式指定 ADC_SINGLE_ENDED漏了会返回 HAL_ERROR */ if (HAL_ADCEx_Calibration_Start(hadc1, ADC_SINGLE_ENDED) ! HAL_OK) { Error_Handler(); } } uint16_t adc_read_once(uint32_t timeout_ms) { uint16_t v 0; HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, timeout_ms) HAL_OK) { v (uint16_t)HAL_ADC_GetValue(hadc1); /* 必须在 Stop 之前取 */ } HAL_ADC_Stop(hadc1); return v; }几个参数值得单独说。EOCSelection选ADC_EOC_SINGLE_CONV时每完成一次转换就置一次 EOC适合单通道选ADC_EOC_SEQ_CONV则要等整个序列转换完才置位序列长度为 1 时两者行为一样但多通道场景下差别很大。ContinuousConvMode关掉之后必须靠HAL_ADC_Start触发这是软件触发单次采样的标准姿势。轮询版单次耗时 一次转换时间 HAL 调用的开销实际在两三微秒到十几微秒之间受编译优化和 HAL 内部状态机影响。做 1 kHz 巡检完全够用做 100 kHz 波形就别指望了。中断版把等待交给中断CPU 主循环该干什么干什么volatile uint16_t g_adc_val; volatile uint8_t g_adc_done; /* 单次转换完成回调序列模式下这个回调每次 EOC 都会进 */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { g_adc_val (uint16_t)HAL_ADC_GetValue(hadc); g_adc_done 1; } } /* 启动时用带 IT 的版本转换结束会自动回调 */ void adc_start_it(void) { g_adc_done 0; HAL_ADC_Start_IT(hadc1); }注意中断版必须把ContinuousConvMode和触发方式想清楚如果开成连续转换 单次启动回调会以最高速率反复进入几微秒一次中断开销反而比轮询还大。中断版适合「不定时来一发」的场景比如按键按下后读一次分压不是用来做高速采样的。2.3 多通道扫描Rank 顺序、缓冲索引与间断模式多通道靠扫描模式实现ScanConvMode ENABLENbrOfConversion设成通道数每个通道用Rank指定转换顺序Rank 1 最先转。G431 的规则组最多 16 个 Rank够绝大多数场合用。/* 三个通道IN1 / IN2 / IN11顺序由 Rank 决定 */ static void adc_scan_config(ADC_HandleTypeDef *h) { ADC_ChannelConfTypeDef c {0}; c.SamplingTime ADC_SAMPLETIME_47CYCLES_5; c.SingleDiff ADC_SINGLE_ENDED; c.OffsetNumber ADC_OFFSET_NONE; c.Channel ADC_CHANNEL_1; c.Rank ADC_REGULAR_RANK_1; HAL_ADC_ConfigChannel(h, c); c.Channel ADC_CHANNEL_2; c.Rank ADC_REGULAR_RANK_2; HAL_ADC_ConfigChannel(h, c); c.Channel ADC_CHANNEL_11; c.Rank ADC_REGULAR_RANK_3; HAL_ADC_ConfigChannel(h, c); }顺序一旦定下来后面所有代码都得按这个顺序解释数据DMA 缓冲区buf[0]是 Rank 1buf[1]是 Rank 2。我见过最典型的翻车是把 Rank 写反了然后在应用层用「第一个是电池电压、第二个是温度」去解读结果温度曲线跟着电池电压走排查了半天硬件。Rank 与缓冲区索引的对应关系最好写成宏或者枚举别靠记忆。另外一个容易忽略的参数是间断模式Discontinuous。打开之后一次触发只转换 N 个通道然后停下来要再触发一次才继续。它适合「分组采样」的场景比如温度不用每次都采、隔几次采一次。多通道 DMA 连续搬运的组合里这个开关必须是关的否则 DMA 每轮只能收到一部分数据长度对不上。通道数确定之后单轮序列的总耗时 各通道转换时间之和。三个通道各 47.5 个采样周期在 42.5 MHz 下就是 3 × 1.41 µs ≈ 4.2 µs也就是纯扫描的帧率上限约 236 kHz。这个数决定了后面 DMA 缓冲区该开多大、中断该多快进一次。3. DMA 搬运让 ADC 自己往内存里写DMA 这条路的本质是把「读 DR 寄存器」这件重复劳动从 CPU 手上拿走。ADC 每完成一次转换就发一个 DMA 请求DMA 控制器把 DR 里的半字搬到内存地址自增计数减一全程不惊动内核。对 G431 来说ADC1 的请求默认挂在 DMA1 的通道 1 上中间还过一层 DMAMUX配置工具里一选就行手写代码时注意别把请求号填错。真正需要想清楚的是三件事搬运长度和转换次数怎么对齐、缓冲区会不会在 CPU 读的时候被覆盖、溢出OVR之后怎么恢复。3.1 请求映射与循环模式一次配置跑到底最省心的组合是「ADC 连续转换 DMA 循环模式」。ADC 不停转DMA 不停搬指针绕一圈回到起点CPU 随便什么时候读都是最近的一批数据。/* adc_dma.c —— 三通道扫描 DMA 循环搬运 */ #define ADC_CH_NUM 3 #define ADC_BLK 128 /* 每通道每块的点数 */ /* 双半区前 128*3 个点是一块后 128*3 个点是另一块 */ static uint16_t adc_dma_buf[ADC_BLK * ADC_CH_NUM * 2]; void adc_dma_start(void) { /* 长度必须是「转换次数」不是字节数 3 个通道 3 次转换 3 个半字 */ if (HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_dma_buf, ADC_BLK * ADC_CH_NUM * 2) ! HAL_OK) { Error_Handler(); } }HAL_ADC_Start_DMA第三个参数是最容易被误解的地方。传的是转换次数HAL 内部会自己乘上数据宽度。三通道、12 位、右对齐每次转换一个半字所以传 3 表示搬 3 个半字。有人按字节数传 6结果 DMA 搬了两轮才凑够数据全错位。循环模式的配置落在 DMA 句柄上外设地址不自增、内存地址自增、半字宽度、模式 Circular、优先级中等偏高。ADC 侧则要求ContinuousConvMode ENABLE且Overrun ADC_OVR_DATA_OVERWRITTEN。溢出策略选覆盖是因为连续采样场景下丢一个点无伤大雅但 DMA 停掉就全断了选ADC_OVR_DATA_PRESERVED的话一旦溢出 HAL 会进错误回调把 DMA 关掉看起来就像「采了几百个点之后死了」。3.2 半传输中断做双缓冲拿到不撕裂的数据块循环 DMA 有个天然问题CPU 去读缓冲区的时候DMA 可能正写到一半读出来的块前半段是新的、后半段是旧的。解决办法是把缓冲区切成两半DMA 写前半区时 CPU 读后半区靠半传输中断HT和传输完成中断TC切换。static volatile uint16_t *g_ready_ptr; /* 指向「刚填满、可以读」的半区 */ static volatile uint8_t g_ready_flag; /* 前半区写满此时 DMA 正在写后半区 */ void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) { g_ready_ptr adc_dma_buf[0]; g_ready_flag 1; } /* 后半区写满此时 DMA 已经绕回前半区 */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { g_ready_ptr adc_dma_buf[ADC_BLK * ADC_CH_NUM]; g_ready_flag 1; } /* 主循环里处理注意处理时间必须小于半区填满的时间 */ void app_poll(void) { if (!g_ready_flag) return; g_ready_flag 0; const uint16_t *p (const uint16_t *)g_ready_ptr; /* 先取指针快照 */ uint32_t sum[ADC_CH_NUM] {0}; for (uint32_t i 0; i ADC_BLK; i) { for (uint32_t ch 0; ch ADC_CH_NUM; ch) { sum[ch] p[i * ADC_CH_NUM ch]; } } /* sum[] 就是这一块的累加值除以 ADC_BLK 得均值 */ }中断里只做一件事记指针、置标志。千万别在回调里做memcpy或者浮点运算半传输中断的频率是「采样率 / (2 × 半区点数)」100 kHz 采样、128 点一块的情况下是 390 Hz看着不高但一旦你在里面做几百次乘加中断执行时间就会吃掉主循环反过来拖慢其它任务。g_ready_ptr要加volatile因为它会在中断里被改写、在主循环里被读取编译器不知道这件事不加优化等级一高就把判断优化没了——这是嵌入式里最常见的一类玄学问题。还有一个边界要留意主循环的处理时间必须小于半区填满的时间。100 kHz 采样率、128 点一块、3 通道半区填满需要 128 × 3 / 100k 3.84 ms处理代码必须在这个时间内跑完否则指针会被下一次中断覆盖读到的数据块就串了。3.3 采样率与吞吐估算先把账算在纸上DMA 不会凭空提高 ADC 的转换速度它只是让 CPU 不参与搬运。真实的上限仍然是转换时间按 2.1 节的表42.5 MHz 下单通道的极限是这样采样时间总周期单次耗时单通道极限速率3 通道扫描帧率2.515353 ns2.83 Msps943 kfps6.519447 ns2.24 Msps746 kfps12.525588 ns1.70 Msps566 kfps24.537871 ns1.15 Msps382 kfps47.5601.41 µs708 ksps236 kfps注意最后两列不是「一定能达到」的还得看 DMA 带宽和内存访问。100 kHz × 3 通道 × 2 字节 600 kB/s对 AHB 总线来说毫无压力但如果同一总线上还挂着别的 DMA 搬大量数据就会有总线仲裁开销表现为偶发的数据延迟而非丢数。如果采样率要求是「精确等间隔」而不是「尽量快」那就别用连续转换改成定时器触发/* TIM3 更新事件 → TRGO → ADC1 外部触发采样率 170MHz / (PSC1) / (ARR1) */ void adc_timer_trigger_init(void) { htim3.Instance TIM3; htim3.Init.Prescaler 0; htim3.Init.CounterMode TIM_COUNTERMODE_UP; htim3.Init.Period 1699; /* 170 MHz / 1700 100 kHz */ htim3.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE; HAL_TIM_Base_Init(htim3); TIM_MasterConfigTypeDef mc {0}; mc.MasterOutputTrigger TIM_TRGO_UPDATE; mc.MasterSlaveMode TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(htim3, mc); /* ADC 侧改成外部触发连续转换关掉让定时器决定节奏 */ hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T3_TRGO; hadc1.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_RISING; HAL_ADC_Init(hadc1); HAL_TIM_Base_Start(htim3); }定时器触发的价值在于抖动极小。软件触发的采样间隔受中断延迟、主循环调度影响做 FFT 时频谱会糊硬件触发的间隔由定时器硬件保证抖动在纳秒级。做音频、振动、电流环这类需要频谱分析的场合定时器触发是标配不是可选项。顺带提一句双 ADC。G431 有两路 ADC可以在双规则同步模式下让两路同时采样同一个时刻的信号再配合 DMA 把两个结果拼成一个 32 位字搬走。它解决的是「两路信号之间要有严格相位关系」的问题普通多通道扫描做不到这一点因为扫描是分时进行的通道之间天然差着几个微秒。4. 避坑与排查G431 的 ADC 上最容易翻车的五件事下面这五条是我在 G431 上实际踩过的按「现象 → 原因 → 解决」写遇到问题时可以直接对号入座。4.1 读数整体偏小且越换大阻值越明显采样时间不够现象分压电阻测电池电压万用表量出来 12.4 VADC 换算出来 11.9 V误差稳定在 4% 左右换成阻值更大的分压网络误差更大。原因采样时间设成了 2.5 个周期ADC 内部采样电容G4 手册给的 C_ADC 在 5 pF 量级没有充足。采样保持电路本质是个 RC 充电过程源阻抗越大时间常数越大固定的采样窗口内就充不到目标电压。解决按源阻抗重新算采样时间或者加一级电压跟随器把源阻抗压到几十欧。分压电阻在几十 kΩ 量级的场合采样时间直接给到 47.5 甚至 92.5 个周期误差能回到 1 LSB 以内。加运放跟随是最彻底的办法代价是 BOM 多一颗料和一点失调。4.2 DMA 只搬了第一轮就再也不更新模式或溢出把通道关了现象串口打印adc_dma_buf的前几个点数值正常但过了几秒再看还是同一批数一点没变。原因两个高发点。一是 DMA 配成了 Normal 模式一轮搬完就停了而 ADC 还在连续转换二是Overrun设成了ADC_OVR_DATA_PRESERVED某次 CPU 处理时间偏长导致 OVR 置位HAL 的错误回调把 DMA 给关了。解决DMA 模式必须配 Circular溢出策略改成ADC_OVR_DATA_OVERWRITTEN让新数据覆盖旧数据而不是停机。同时在HAL_ADC_ErrorCallback里打一个计数器排查阶段能立刻看出是不是溢出导致的。4.3 多通道数据串位长度和 Rank 没对齐现象三通道采样温度通道的曲线跟着电池电压一起波动明显是数据错位。原因NbrOfConversion设成了 3但HAL_ADC_Start_DMA的长度传成了 6按字节算的DMA 每轮要搬 6 个半字才凑满一块于是第二块的第一个点实际上是第一块的第 7 个点整体错位一路传下去。另一种情况是 Rank 顺序和代码里的索引映射写反了。解决长度统一按「转换次数」给Rank 与缓冲区索引的对应关系写成宏或者枚举别在应用层硬编码数字。排查时先把所有通道接到同一个电压比如都接 VDDA/2 的分压看缓冲区里是不是三个一样的值这样可以立刻区分是顺序错还是数据错。4.4 校准返回错误或者校准后反而漂顺序和状态没管好现象HAL_ADCEx_Calibration_Start返回HAL_ERROR或者校准之后读数比校准前还偏。原因校准必须在 ADC 使能ADEN 置位、内部稳压器稳定之后、启动任何转换之前执行。如果先HAL_ADC_Start再校准或者中途HAL_ADC_Stop之后没有重新初始化就再次校准都会失败。另外单端输入必须显式传ADC_SINGLE_ENDED传错成差分模式也会返回错误。解决把校准放在HAL_ADC_Init之后、第一次HAL_ADC_Start*之前顺序固定需要重新校准时先HAL_ADC_Stop再HAL_ADC_Init再校准。校准时序错乱这类问题没有报错提示属于典型的黑匣子建议在初始化函数里加一句串口打印确认每次上电都走到校准那一步。4.5 直采读到的永远是上一次的值取数时机错了现象轮询单次采样输入电压改了串口打出来的值要下一次才变等于永远慢一拍。原因HAL_ADC_GetValue在HAL_ADC_Stop之后调用或者EOCSelection配成了ADC_EOC_SEQ_CONV而序列里只有一个通道导致PollForConversion提前返回、DR 里还是旧数据。解决GetValue必须在Stop之前取单通道用ADC_EOC_SINGLE_CONV。更底层的写法是直接读ADC1-DR读操作本身会清 EOC比 HAL 的层层判断快也少一层状态机。调试阶段可以用 GPIO 翻转配合示波器看「启动 → 转换完成 → 取值」三个时刻的间隔比在串口上猜快得多。5. 把有效位数榨出来校准、过采样与模拟前端12 位是分辨率不是精度。G431 的 ADC 在良好前端下能跑到 11 位左右的 ENOB前端没做好可能只有 8 位。这一章讲三个能实际提精度的动作。5.1 用出厂校准值反推 VDDA顺手把温度读出来芯片内部有个参考电压 VREFINT出厂时在 3.0 V 供电、25 ℃ 条件下测过并存进电子签名区。读它的原始码值配合出厂校准值就能反推当前实际的 VDDA——这比假设「VDDA 一定是 3.3 V」靠谱得多尤其在用电池或 USB 供电、电压本来就在飘的场合。/* 地址以数据手册 Device electronic signature 表为准 */ #define VREFINT_CAL_ADDR ((const uint16_t *)0x1FFF75AAUL) #define VREFINT_CAL_VREF 3000U /* 出厂校准时的 VDDA单位 mV */ /* VREFINT 通道采样时间必须 4 us否则读数偏低 */ static uint16_t adc_read_vrefint_raw(void) { ADC_ChannelConfTypeDef c {0}; c.Channel ADC_CHANNEL_VREFINT; c.Rank ADC_REGULAR_RANK_1; c.SamplingTime ADC_SAMPLETIME_247CYCLES_5; /* 6.1 us 42.5MHz */ c.SingleDiff ADC_SINGLE_ENDED; c.OffsetNumber ADC_OFFSET_NONE; HAL_ADC_ConfigChannel(hadc1, c); return adc_read_once(10); } float vdda_mv(void) { uint32_t raw adc_read_vrefint_raw(); if (raw 0) return 0.0f; /* 防止除零也顺便暴露配置错 */ return (float)VREFINT_CAL_VREF * (float)(*VREFINT_CAL_ADDR) / (float)raw; }拿到 VDDA 之后所有通道的换算公式都从raw * 3.3f / 4095换成raw * vdda_mv() / 1000.0f / 4095。室温下这一改测量的一致性会明显好转尤其是 USB 供电、线损压降大的板子。温度通道同理用TS_CAL1/TS_CAL2两个出厂点和对应的温度值做线性插值。G4 的手册给的校准点是 30 ℃ 和 130 ℃采样时间同样要求不小于 4 µs。片内温度传感器测的是结温不是环境温度别拿它当室温计用。5.2 硬件过采样用 16 倍换 2 位代价是什么G431 的 ADC 自带硬件过采样一次结果由多次转换累加后再右移得到全程不占 CPU。配置上只有四个参数hadc1.Init.OversamplingMode ENABLE; hadc1.Init.Oversampling.Ratio ADC_OVERSAMPLING_RATIO_16; /* 16 次累加 */ hadc1.Init.Oversampling.RightBitShift ADC_RIGHTBITSHIFT_2; /* 右移 2 位 */ hadc1.Init.Oversampling.TriggeredMode ADC_TRIGGEREDMODE_SINGLE_TRIGGER; hadc1.Init.Oversampling.OversamplingStopReset ADC_REGOVERSAMPLING_CONTINUED_MODE;有效位数 12 log2(16) - 2 14 位。这个式子值得记住累加 N 次能多出 log2(N) 位右移多少位就还回去多少位。想要满 16 位比例给 16、右移 0想要 14 位且数值不溢出uint16_t就是上面这组。缓冲区类型保持不变半字即可不用改成 32 位。代价有两层。第一是吞吐16 次转换才出一个结果采样率直接降到 1/16100 kHz 变 6.25 kHz。第二是效果有前提——过采样压的是白噪声如果干扰是 50 Hz 工频或者开关电源的固定频率纹波过采样对它几乎无效该用工频抑制和屏蔽还是得用。实际用下来过采样在「源阻抗低、信号带内噪声随机」的场合效果最明显比如用运放缓冲过的电桥信号能把有效位数从 10.5 提到 12 以上。反过来高阻分压加长线引入的干扰过采样只会把噪声平摊读数该跳还是跳。5.3 模拟前端算一下源阻抗的上限采样保持本质是 RC 充电要求 k 个时间常数内充到 0.5 LSB 以内k 与位数有关k ln( p a hrefhttps://download.csdn.net/download/qq_62725929/88837658 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p