news 2026/10/26 8:46:58

STM32G431多通道ADC采集:采样时间、DMA与溢出排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32G431多通道ADC采集:采样时间、DMA与溢出排查

简介:面向STM32G431RBT6的ADC采集工程包,适合具备一定HAL库基础的嵌入式开发者、竞赛学生与课程设计者,用于理解直接读取与DMA搬运两种采样路径的差异。包内共1254个文件,以C源码和头文件为主体,辅以汇编启动文件、工程配置、库文件及HEX、MAP等输出文件,压缩包约29.73MB,便于在Keil或IAR环境中查阅完整工程结构。已有1978人学习下载,主题在ADC多通道采样与实时数据传输学习中关注度较高。内容覆盖ADC1直接采集与ADC2 DMA采集的配置思路,涉及采样时间、分辨率、转换序列、DMA通道链接、中断配合和缓冲区设置等环节,可帮助读者对照源码梳理外设初始化完整流程、数据搬运机制与关键参数取舍,也为传感器信号采集、电源监测等常见连续采样场景提供可复用的实际工程参考。

1. 多通道下“读一次就算完”的 ADC 为什么一定会翻车

STM32G431RBT6 的 ADC 有个反直觉的地方:12 位、4 Msps 的转换速度从来不是瓶颈,真正决定你能不能拿到干净数据的,是转换完成之后那一步——谁把 ADC_DR 里的值搬走。很多人第一次做多通道采集,习惯在循环里改一次通道、启动一次、等一次 EOC、读一次 DR,单通道跑得挺顺,一上三通道、10 kHz 就原形毕露:采样间隔被中断切得七零八落,偶尔还冒出几个明显串到隔壁通道的值。

这背后是采样保持电路的电荷建立、EOC 标志的读写语义、以及 DMA 请求链路三件事没对齐。把这三层理清楚,你才会明白为什么“直接采集”在低速场景够用,而一旦通道数和速率上去,DMA 几乎是唯一不翻车的选择。

2. 采样窗口与转换时序:12 位结果是怎么被“凑”出来的

2.1 采样保持电路:C_ADC 上的电荷从哪来

ADC 内部前端不是一个理想的电压表,而是一个开关加一个采样电容 C_ADC。采样阶段开关闭合,外部信号通过源阻抗 R_src 和芯片内部的采样开关电阻 R_adc 给 C_ADC 充电;保持阶段开关断开,逐次逼近逻辑拿这个被“冻住”的电压去做 12 次比较。这意味着你读到的每一个码值,都是 C_ADC 上电荷建立到某个程度后的快照。

关键推论是:如果采样窗口太短,C_ADC 没充到目标电压,结果就会偏向“上一次残留的电压”。单通道连续采同一路时你看不出来,因为每次残留的就是同一个值;一旦切到多通道轮转,上一个通道的电压会污染下一个通道,表现出来就是通道之间串扰,且串扰程度随源阻抗升高而恶化。常见的实现思路是把采样时间按最坏情况算:误差要小于 1/2 LSB,需要的建立时间约为 (R_src + R_adc) × C_ADC × ln(2^(n+1)),12 位下这个对数项大约落在 9 到 10 个时间常数之间。

所以分压电阻选大了、或者前面串了个大阻值 RC 滤波,你必须同步把采样时间往上调,否则再怎么改代码也救不回来。

2.2 采样时间怎么算:t_SMPL 与源阻抗的乘积账

G431 的采样时间由每个通道独立的 SMP[2:0] 位控制,编码与周期的对应关系是固定的:

SMP[2:0]采样周期数60 MHz 下的采样窗口
0002.541.7 ns
0016.5108.3 ns
01012.5208.3 ns
01124.5408.3 ns
10047.5791.7 ns
10192.51541.7 ns
110247.54125 ns
111640.510675 ns

这张表是选型时最该先看的东西。举个常见的翻车场景:某开发者用两个 10 kΩ 电阻分压测电池电压,源阻抗等效 5 kΩ,采样时间随手填了 2.5 周期,读出来的值比万用表低一截,且随被测电压变化非线性。原因就是 41.7 ns 的窗口连一个时间常数都没走完,C_ADC 上只建立了很小一部分电荷。

把 SMP 调到 247.5 周期后立刻正常,代价是单次转换时间从 15 个周期涨到 260 个周期,吞吐率掉了一个数量级。这就是设计取舍:源阻抗越低,你能用的采样窗口越短,能跑的速度越高。工程上的血泪经验是——先按最坏源阻抗算采样时间,再看剩下的时间预算够不够你的采样率,顺序不能反。

2.3 转换时间与吞吐率:12.5 个周期的由来

12 位逐次逼近需要 12 个比较周期,再加上最后的 0.5 个周期用于结果锁存和标志置位,所以固定开销是 12.5 个 ADC 时钟。总转换时间等于采样窗口加这 12.5 个周期:

T_conv = (SMP + 12.5) / f_ADC

f_ADC 最大 60 MHz,取 SMP = 2.5 时,T_conv = 15 / 60 MHz = 250 ns,正好对应 4 Msps 的标称吞吐率。注意这只是“转换”本身的时间,两次转换之间还有启动、序列切换、DMA 搬运的开销,实际可持续的采样率要打折。

用一个简单的 C 表达式把这笔账固定下来,避免每次改配置都靠感觉:

/* f_adc_hz: ADC 内核时钟频率;smp_cycles: 从 SMP 编码查表得到的周期数 */ static inline float adc_conv_time_ns(uint32_t f_adc_hz, uint32_t smp_cycles) { /* 12 位模式下固定附加 12.5 个周期,用于结果锁存与标志更新 */ return (smp_cycles + 12.5f) * 1e9f / (float)f_adc_hz; } /* f_adc_hz: ADC 内核时钟频率;smp_cycles: 从 SMP 编码查表得到的周期数 */ static inline float adc_max_rate_sps(uint32_t f_adc_hz, uint32_t smp_cycles) { /* 序列内每两个通道之间没有额外间隔,理论值可直接取倒数 */ return (float)f_adc_hz / ((float)smp_cycles + 12.5f); }

这两个函数的参数含义很直白:f_adc_hz 是 ADC 内核时钟,不是系统主频,G431 上它由 ADC12_CCR 的 CKMODE 位决定,可以取 AHB 分频,也可以取 PLLP 的异步时钟;smp_cycles 必须传查表得到的实际周期数,不能传 SMP 编码值,这是最容易写错的地方——把 0b010 当 2.5 传进去,算出来的吞吐率会虚高五倍。函数返回的是单通道理论值,多通道扫描时把结果除以通道数,才是每个通道的实际更新率。

2.4 时钟源、校准与参考电压:三个容易被忽略的前置条件

ADC 内核时钟的来路有两条:同步模式和异步模式。同步模式下 ADC 时钟由 AHB 时钟分频而来,好处是时序确定、不需要额外等待;异步模式用 PLLP 输出,好处是可以独立于系统主频调到一个合适频率,坏处是切换时要注意 ADC 必须处于关闭状态。G431 上 60 MHz 的上限是硬约束,PLL 配置稍不注意就会超,超了不会报错,只会让转换结果变得不可信——这类问题最难查,因为没有异常,只有噪声。

上电后必须先做一次校准。校准的前提是 ADEN = 0、ADC 处于关闭状态,置位 ADCAL 后等待硬件清零,校准因子会被自动应用到后续所有转换。跳过校准的后果是零点偏移,通常在几个 LSB 量级,看起来像“小幅抖动”,很容易被误判成电源噪声。

参考电压则是所有换算的基准。如果 VDDA 来自 USB 或电池,它就不是精确的 3.3 V,用固定 3.3 去除以 4096 会带来系统性误差。G431 内部有一个出厂校准过的参考源,其校准值烧在系统存储区的固定地址,用宏 VREFINT_CAL_ADDR 可以直接指过去,实际测量值配合它就能反推出当前 VDDA:

/* vrefint_cal: 出厂校准值,对应 3.0V 供电时的参考源读数 */ /* vrefint_now: 当前配置下读到的参考源原始码值 */ static inline float vdda_from_vrefint(uint16_t vrefint_cal, uint16_t vrefint_now) { /* 参考源电压不随供电变化,用比例关系反推实际供电电压 */ return 3.0f * (float)vrefint_cal / (float)vrefint_now; }

这段换算的前提是读参考源时用的采样时间足够长(参考源内阻较高,建议 247.5 周期以上),且读的是同一个分辨率配置下的原始码值。拿到 vdda 之后再去换算外部通道,误差能从百分之几压到千分之几。顺带一提,同一套两点校准的思路也适用于内部温度传感器,出厂给了两个温度点的码值,线性插值就能用,比查表靠谱。

3. 轮询与中断直接采集:CPU 亲自搬数的代价

3.1 ADC_DR 的读写语义:读一次清一个标志

直接采集的核心动作是“等 EOC,读 DR”。这里的语义细节决定了你写的循环到底对不对:EOC 在转换完成后由硬件置位,而读取 ADC_DR 会同时清掉 EOC;如果在读 DR 之前又发生了一次转换完成,新结果会覆盖 DR,同时置位溢出标志。也就是说,DR 本质上是一个深度为 1 的寄存器,不是队列,它没有任何缓冲能力。

更细的一层是 EOC 的置位时机由 EOCS 位控制。置为“每次转换后”时,扫描序列里每转换完一个通道都会置 EOC;置为“序列结束后”时,只有整个序列跑完才置一次。前者适合逐通道搬运,后者适合整块搬运。DMA 场景下这个位尤其关键,选错了会出现“只搬了第一个通道就没了”或者“每个序列只搬一次”的怪现象。

轮询代码看起来简单,但要写得不出错,至少要有超时保护:

/* ch: 规则组第一个转换的通道号;返回原始码值,0xFFFF 表示超时 */ static uint16_t adc_read_poll(uint32_t ch) { /* 改序列前必须确保当前没有转换在跑,否则写入被忽略 */ if (ADC1->CR & ADC_CR_ADSTART) { ADC1->CR |= ADC_CR_ADSTP; while (ADC1->CR & ADC_CR_ADSTP) { } } ADC1->SQR1 = (ADC1->SQR1 & ~ADC_SQR1_SQ1) | (ch << ADC_SQR1_SQ1_Pos); ADC1->CR |= ADC_CR_ADSTART; /* 软件触发一次转换 */ uint32_t guard = 0; while (!(ADC1->ISR & ADC_ISR_EOC)) { /* 忙等转换完成 */ if (++guard > 200000u) return 0xFFFF; /* 超时返回,绝不死等 */ } return (uint16_t)ADC1->DR; /* 读 DR 会顺带清 EOC */ }

这段代码里 guard 的阈值要按你的 ADC 时钟和采样时间估:单次转换最多几百个 ADC 周期,对应到 CPU 循环也就几千次,20 万次是很宽松的上限。真正需要警惕的是写 SQR1 的时机——ADSTART 置位期间写序列寄存器是无效的,必须先停。很多“通道号没生效”的 bug 就出在这一步,代码不报错,只是读回来永远是上一个通道。

3.2 轮询模式的时序账:什么时候 EOC 已经晚了

轮询的致命伤不是慢,而是不确定。CPU 在 while 里空转时,采样间隔由循环体的指令数决定;一旦有 SysTick、串口接收这类中断插进来,这一次的间隔就被拉长,下一次又恢复正常。如果你的应用只是“偶尔读一下电池电压”,这点抖动完全无所谓;但如果你在做 50 Hz 工频信号的采样、或者要靠采样间隔做频谱分析,抖动会直接变成频谱泄漏。

还有一个更隐蔽的问题:忙等期间 CPU 什么也干不了。三通道 10 kHz 意味着每秒 3 万次转换,每次转换 250 ns 加轮询开销算 1 µs,CPU 就被吃掉 3%。看起来不多,但这 3% 是不可被打断的忙等,会和其它实时任务抢时间片。数量级再往上走,比如 12 位 100 kHz 三通道,轮询就彻底不可行了。

所以轮询的适用边界很清晰:单通道、低速、对采样间隔没有严格要求的场合。它的价值在于简单,出问题时能一眼看到底,调试阶段先用它把通道和量程跑通,再换 DMA,是一个很务实的路径。

3.3 中断模式的失控点:ISR 延迟与 OVR

中断模式把“等”换成了“通知”,CPU 不用空转,但引入了新的不确定性:从中断触发到 ISR 里读走 DR,中间隔着中断响应延迟、可能有更高优先级的抢占、可能有临界区。这段时间如果超过了下一次转换的时间,DR 就被覆盖了。

后果分两种,取决于 OVRMOD 位怎么配。配成“保留旧数据”时,新结果被丢弃,你拿到的是上一次的值,表现出来是数据“卡住”;配成“覆盖”时,新结果覆盖旧值,你拿到的是最新的,但中间丢了一个样本,表现出来是波形上偶发跳变。两种都不好受,而且都很难复现,属于典型的那种“跑一晚上出一次”的玄学问题。

更麻烦的是溢出之后的状态恢复。溢出标志置位后,ADC 会停止产生新的 DMA 请求,直到软件显式清掉标志。如果你的 ISR 里只读 DR 不清标志,第一次溢出之后整个采集就静默停摆了——现象是数据不再更新,但代码看起来还在正常跑。这个问题在中断和 DMA 两种模式下都存在,排查时第一件事就是看溢出标志有没有被清。

中断模式真正适合的场景是“通道少、速率中等、能容忍偶发丢样”的采集,比如按键扫描时的电压检测。如果要它承担连续高速采集,就得在 ISR 里做软件缓冲、做丢样统计,代码复杂度很快会超过直接上 DMA。

4. DMA 采集:把 EOC 变成一次内存写入

4.1 请求链路:ADC EOC → DMAMUX → DMA 通道

DMA 采集的本质,是把“CPU 读 DR”这个动作换成一个硬件请求。转换完成、EOC 置位的同时,ADC 向 DMA 控制器发一次请求;DMA 控制器读走 ADC_DR 并写到内存,这个读动作同样会清掉 EOC,形成闭环。整条链路是:ADC 转换完成 → 产生 DMA 请求 → DMAMUX 把请求路由到指定 DMA 通道 → DMA 通道完成一次“外设寄存器到内存”的搬运。

G431 上这条链路多了一层 DMAMUX。DMA1 和 DMA2 各有 6 个通道,但请求源远多于通道数,所以每个 DMA 通道前面都有一个复用器,由 DMAMUX 通道的 CCR 寄存器选定具体请求号。这一步是纯配置,写错了不会报错,只会表现为 DMA 完全不触发——排查时先确认 DMAMUX 的请求号,再怀疑别的地方。

配置代码大致长这样:

#define ADC_BUF_LEN 3 static volatile uint16_t adc_buf[ADC_BUF_LEN]; void adc_dma_start(void) { RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; DMAMUX1_Channel0->CCR = ADC1_DMA_REQ; /* 请求号查 DMAMUX 映射表 */ DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR; /* 外设端固定为数据寄存器 */ DMA1_Channel1->CMAR = (uint32_t)adc_buf; /* 内存端为数组首地址 */ DMA1_Channel1->CNDTR = ADC_BUF_LEN; /* 一次循环搬运的元素个数 */ DMA1_Channel1->CCR = DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0 /* 双方都是 16 位 */ | DMA_CCR_MINC /* 内存地址递增 */ | DMA_CCR_CIRC /* 循环模式 */ | DMA_CCR_HTIE | DMA_CCR_TCIE; /* 半满与全满中断 */ DMA1_Channel1->CCR |= DMA_CCR_EN; ADC1->CFGR |= ADC_CFGR_DMAEN | ADC_CFGR_DMACFG; /* 循环 DMA 请求 */ ADC1->CR |= ADC_CR_ADSTART; }

参数上三个点必须对齐:CPAR 指向 ADC1->DR,不能指到别的寄存器;CNDTR 填的是缓冲区元素个数而不是字节数,因为 MSIZE 和 PSIZE 都设成了 16 位,每个元素两个字节;DMACFG 置位表示循环模式,ADC 会持续发请求,清零则是单次模式,一个序列搬完就停。这三处任意一处配错,现象都是“只搬了一次”或者“搬出来的值不对”。

4.2 循环模式、半满中断与双缓冲

循环模式让 CNDTR 在减到 0 后自动重装,DMA 永不停歇地绕圈写。配合半满中断和全满中断,就得到了一套天然的双缓冲:缓冲区前半部分装满时触发一次中断,后半部分装满时再触发一次。CPU 在中断里处理“已经稳定的那一半”,DMA 同时写另一半,两边互不干扰。

这个结构的关键在于“处理滞后一个半缓冲区”。中断触发时你拿到的是刚写完的那一半,但另一半可能正在被写,绝对不能碰。所以典型写法是在全满中断里处理前半段、在半满中断里处理后半段,指针和长度按半缓冲区大小算。这样即使 CPU 处理某一段花的时间略长,也还有半个缓冲区的时间余量。

延迟的代价也要算清楚:从第一个样本被写入,到它被 CPU 处理,最长要等一个完整的缓冲区周期。缓冲区设得越大,抗抖动能力越强,处理延迟也越大。做闭环控制时这个延迟会直接吃掉相位裕度,所以缓冲区长度不是越大越好,通常按“一个控制周期内的样本数”来定。

还有一点容易被忽略:半满与全满中断里应该只做搬移和置标志这类短操作,把浮点换算、滤波这些重活留给主循环。在中断里做长计算,等于人为延长了响应延迟,前面费劲搭的双缓冲就没意义了。

4.3 数据宽度与地址对齐:ADC_DR 是 32 位寄存器的陷阱

ADC_DR 名义上是 32 位寄存器,但 G431 的 12 位结果只占低 16 位(右对齐时占低 12 位),高 16 位是保留的。如果你在 DMA 配置里把 PSIZE 设成 32 位、MSIZE 设成 16 位,DMA 会读走完整的 32 位再截断一半写进内存,结果要么全 0 要么错位;反过来把 PSIZE 设成 8 位,就只能拿到低字节,高 4 位数据丢失,表现为数值在 0 到 255 之间跳。

最稳的配法是两边都是 16 位,内存端用 uint16_t 数组,地址自然对齐。如果用左对齐模式(把 12 位结果顶到 16 位的高位),好处是可以直接把 16 位值当无符号数处理,方便做定点运算;坏处是换算时要右移 4 位,忘了移就会得到一个 16 倍大的数。这类错误非常显眼——所有通道都顶到满量程,反而好查。

数组的对齐也值得提一句。uint16_t 数组天然两字节对齐,但如果你为了省事把缓冲区定义成 uint8_t 再强转,或者用结构体混排,就可能出现非对齐访问,轻则效率下降,重则在某些配置下触发总线错误。定义成static volatile uint16_t adc_buf[N]是最省心的写法,volatile 是必须的,因为在中断和主循环之间共享。

4.4 启动顺序与溢出恢复:两个能让 DMA 静默失效的点

启动顺序错了,第一次转换的结果就会丢。正确顺序是先配好 DMA 通道并使能,再置位 ADC 的 DMAEN 和 DMACFG,最后才启动 ADC。反过来做的话,ADC 先跑起来产生了 EOC,而 DMA 还没准备好接收,这一次请求就丢了;如果此时又没开溢出处理,后续行为就取决于 OVRMOD 的配置,很容易演变成“第一个样本永远是 0”。

溢出恢复则是长期运行必须处理的。一旦发生溢出,ADC 会停止发 DMA 请求,即使你清了标志位,DMA 通道的计数器状态也可能已经和数据流对不上了。稳妥的恢复流程是:停 ADC、停 DMA、清溢出标志、重置 CNDTR 和内存地址、重新使能 DMA、重新启动 ADC。这套动作放在错误处理里,比试图“原地修好”可靠得多。

实践中还有一个省事的做法:把 OVRMOD 配成覆盖模式,并确保 DMA 带宽足够,让溢出根本不会发生。溢出是否发生可以用一个计数器统计,长期跑下来如果这个计数一直是 0,说明缓冲区长度和采样率匹配得当;如果偶有增长,就该考虑加长缓冲区或者降低采样率了。这个计数器比任何调试器都直观。

5. 排查清单:五类“ADC 不准、不更新、乱序”的现场问题

5.1 数值类异常:跳变、串扰、满量程

现象一:多通道数据互相污染,通道之间能看到彼此的电压。原因基本可以锁定在采样时间不足。切换通道后,采样电容上残留着上一个通道的电荷,如果新的采样窗口太短,残留电荷来不及被新信号替换,读到的就是两者的加权平均。解决方法是按最高源阻抗那个通道来配 SMP,所有通道统一用最长的采样时间;如果各通道源阻抗差异很大,可以给每个通道单独配 SMP,让低阻抗通道跑快一点。

现象二:读数整体偏小且非线性,越接近满量程偏差越大。这是典型的“没充满”而不是“比例错”。除了采样时间,还要检查信号源到引脚之间的串联电阻,以及引脚有没有配置成模拟模式。G431 的 GPIO 如果留在默认的数字输入状态,引脚上的施密特触发器会漏电流,对高阻抗信号源的影响非常明显。初始化时把对应引脚配成模拟模式,这一步漏掉的人不少。

现象三:所有通道都读到 4095 或者 0,怎么改都不变。先看数据对齐。左对齐模式下 12 位结果被顶到高 12 位,如果按右对齐去解析,就会得到 16 倍大的数,超过量程后一律截到满量程。再看参考电压有没有接、VDDA 是否正常。这两种情况都会让所有通道同时异常,反而好定位。

5.2 流程类异常:只搬一次、卡死、顺序错乱

现象四:DMA 只搬了第一轮就不动了,数据停在初始值。两种可能。一是 DMACFG 没置位,ADC 工作在单次 DMA 模式,一个序列搬完就不再发请求;二是发生了溢出但没清标志,ADC 停发请求。排查顺序是:先读溢出标志,再看 DMACFG,最后确认 DMAMUX 的请求号。这三步能覆盖绝大多数“DMA 不触发”的场景。

现象五:缓冲区里的数据顺序和通道顺序对不上,或者从中间开始错位。根因通常是序列配置和缓冲区下标没有一一对应。规则组的转换顺序由 SQR 寄存器里的 SQ 序列决定,DMA 严格按转换完成的顺序写入,所以缓冲区下标 0 对应的是序列里的第一个通道,不是通道号最小的那个。另一个常见原因是中途重启过 ADC 而没有重置 DMA 的 CNDTR 和内存地址,导致从缓冲区中间继续写。解决方法是把“停 ADC → 停 DMA → 重置指针 → 重配序列 → 启动”封装成一个函数,任何重配都走这条路,不要零散地改寄存器。

注意:这五类问题里有四类的第一现场都是溢出标志和配置寄存器,养成“先读状态寄存器再改代码”的习惯,能省掉大量猜测。

6. 设计取舍:DMA 不是默认答案

把三种方式摆在一起看,选择其实很清楚:

方式CPU 占用采样间隔抖动缓冲区典型适用
轮询读 DR高,忙等期间无法做别的事由循环体指令数决定无单通道、低速、调试阶段
EOC 中断中,每个样本一次 ISR受中断响应延迟影响单点少量通道、中速、能容忍丢样
DMA 循环 + 半满中断低,每半个缓冲区一次 ISR由触发源决定,与 CPU 无关环形多通道、连续、高速

DMA 的代价不在配置复杂度,而在两处隐性成本。一是内存:环形缓冲区要占用 SRAM,G431 的 32 KB 看着不少,但同时跑 USB、串口缓冲、算法中间变量之后就很紧张了。二是延迟:双缓冲结构天然有一个缓冲区周期的处理滞后,做闭环控制时这个滞后会折算成相位损失,缓冲区设得越大越明显。所以在低频、单点的场景里,轮询反而是更诚实的选择——代码短、时序透明、出问题一眼能看到。

另一个值得考虑的维度是触发源。软件触发下,采样时刻取决于代码执行到哪一行,抖动是天然的;如果对采样间隔有要求,应该用定时器的 TRGO 去触发规则组,让采样时刻由硬件决定,这时 DMA 才真正发挥出“CPU 完全不参与”的优势。用软件触发配 DMA,只是把搬运的活交出去了,时序精度并没有提升。

还有一条进阶路线:G431 的 ADC 支持硬件过采样,在 CFGR2 里配置过采样率和移位,可以在不增加 CPU 负担的前提下把有效分辨率往上提,代价是吞吐率按过采样倍数下降。这和 DMA 是互补的——DMA 负责把数据流搬走,过采样负责在搬走之前把精度做上去。两者叠加时要注意缓冲区元素的数据宽度会变,别还按 12 位去解析。

我自己的习惯是:任何 ADC 项目,先用轮询把通道、量程、参考电压全部验证一遍,写个小脚本把每个通道接到已知电压上核对换算公式;确认无误后再切到 DMA,切完第一件事是加溢出计数器并跑满一小时。从那以后我每次上电初始化都强制走一遍“关 ADC → 校准 → 配序列 → 配 DMA → 启动”的完整流程,不再图省事跳过校准。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 8:46:58

专科生AI论文高效检索与阅读工具指南

1. 专科生如何高效获取AI论文资源作为一名在人工智能领域摸爬滚打多年的从业者&#xff0c;我深知专科生在学术资源获取上的困境。今天要分享的这个工具&#xff0c;正是为了解决这个痛点而生——千笔写作工具&#xff0c;一个专为专科生设计的AI论文资源平台。记得我刚入行时&…

作者头像 李华
网站建设 2026/10/26 8:46:57

LoRA微调技术:大模型高效适配的突破性方案

1. LoRA微调技术概述 LoRA&#xff08;Low-Rank Adaptation&#xff09;是当前大模型微调领域最具突破性的技术之一。我在实际项目中验证过&#xff0c;相比传统全参数微调&#xff0c;LoRA能减少90%以上的显存占用&#xff0c;同时保持95%以上的模型性能。这项技术的核心思想是…

作者头像 李华
网站建设 2026/10/26 8:46:41

Janus-Pro-R1:视觉理解与生成协同的强化学习框架

1. 项目概述&#xff1a;Janus-Pro-R1的视觉协同革命 2025年NIPS会议即将发布的Janus-Pro-R1项目&#xff0c;正在重新定义机器视觉的理解与生成边界。这个代号源自罗马双面神的名字&#xff0c;暗示着系统同时具备观察世界和创造内容的能力。不同于传统单向的计算机视觉模型&a…

作者头像 李华
网站建设 2026/10/26 8:46:41

AI权限管理:风险、误区与最佳实践

1. 当AI获得过高权限时会发生什么 最近三年&#xff0c;我参与过47家企业AI系统的安全审计工作&#xff0c;其中32家都存在AI权限过大的问题。最典型的案例是某电商平台的智能客服系统&#xff0c;原本只被授权访问客户服务数据库&#xff0c;却因为一个配置错误获得了整个用户…

作者头像 李华
网站建设 2026/10/26 8:44:57

WinHex定位FAT32文件首扇区:从MBR/DBR到起始簇号换算

简介&#xff1a;这是一份面向IT运维、数据恢复与安全分析初学者的PPT课件&#xff0c;聚焦用WinHex查看磁盘底层数据并定位文件首个扇区。内容围绕MBR与分区表、DBR、FAT32根目录及目录项展开&#xff0c;串联分区起始扇区、FAT表位置、簇号与扇区号换算等关键概念&#xff0c…

作者头像 李华
网站建设 2026/10/26 8:44:43

工业能源管控系统:80%效率提升的数字化转型实践

1. 项目背景与核心价值 在工业生产领域&#xff0c;能源消耗一直是企业运营成本的重要组成部分。传统能源管理方式往往存在数据采集不完整、分析滞后、决策依据不足等问题&#xff0c;导致能源浪费严重。某大型制造企业通过部署行列视&#xff08;RCV&#xff09;能源管控系统&…

作者头像 李华