☰
STM32综合测评B类题固件:ADC/DMA采样、校准与PID闭环
2026/10/26 12:06:04 网站建设 项目流程

简介:面向备战全国大学生电子设计竞赛综合测评的嵌入式选手,这份2023年B题国一方案围绕同轴电缆长度与终端负载检测装置展开,主控采用梁山派开发板,开发链为STM32CubeMX、CLion与OpenOCD。方案给出TDR时域反射法测长(误差小于0.5cm)、分压法测终端电阻(误差小于0.1Ω)以及NE555振荡电路测电容(误差小于1pF)的完整实现,可对照赛题中10~20m线缆、10~30Ω电阻与100~300pF电容等指标理解设计与调试。压缩包共228个文件,约22.64MB,以72个h头文件、38个c源文件为主体,并含pdf文档、xlsx数据、png图示及bin/hex等烧录文件,CMake与工程配置便于复现。已有3442人学习下载,适合需要完整国一方案、误差分析与排错思路的参赛者参考。

1. 综合测评 B 类题最后拼的往往是 stm32 上那几百行固件

拿到综合测评 B 类题方案的人,第一反应常常是"原理图对上就能过"。真到限时搭建的现场,出问题的地方几乎都不在电路,而在 stm32 固件里那些不起眼的环节:ADC 忘了校准、DMA 和中断抢同一块缓冲、DAC 触发频率算错一个分频系数,读数就从稳定的三位小数变成一直跳的三位数。这套方案给的不是一份"标准答案",而是一条从最小系统板上电到稳定出数的完整链路——CubeMX 配置、采样与测量算法、输出与闭环、显示交互,再加一套现场能照着走的标定流程。它适合正在准备综合测评的人,也适合手里有 stm32 最小系统板、想把"测量—控制—显示"这条线自己走通一遍的人。电路是骨架,固件才是让它开口说话的那部分,下面按能复现的顺序往下拆。

2. 工程骨架:三层目录与 CubeMX 里必须锁死的六个参数

很多人拆开方案包第一件事是翻主函数,翻完就懵——主函数只有几十行,真正的活全在回调里。这恰恰是这套工程的设计取向:中断只负责搬运数据,主循环负责计算和判断,硬件差异全部压在 BSP 层。骨架没立住之前就动算法,后面每改一次增益都要重新验一遍时序,返工量会翻倍。

2.1 三层目录各管什么:bsp 管时序、algo 管数学、app 管人

方案包的目录切得很干脆,按职责分三层,而不是按外设分文件:

project/ ├── bsp/ # 板级支持:所有和寄存器、时序相关的代码 │ ├── bsp_adc.c/.h # ADC + DMA 启动、校准、回调 │ ├── bsp_dac.c/.h # DAC + DMA 波形输出 │ ├── bsp_key.c/.h # 按键消抖、状态机 │ └── bsp_lcd.c/.h # 显示驱动,只提供"画点/写字符串"两级接口 ├── algo/ # 纯数学,不含任何 HAL 调用 │ ├── algo_rms.c/.h # 有效值、平均值、峰值 │ ├── algo_freq.c/.h # 周期测量与频率换算 │ └── algo_calib.c/.h # 两点标定与分段线性 └── app/ # 业务逻辑:测量流程、界面切换、串口协议 ├── app_measure.c # 一次完整测量的状态机 └── app_ui.c # 四个界面

这么分的好处很直接:algo 层的函数入参是const uint16_t*加长度,出参是 float,你可以在 PC 上把它抠出来单独跑数据验证,不用连板子。现场如果发现数值不对,先换一组离线数据喂进 algo 层,能复现就说明是算法或标定问题,不能复现就往 bsp 层的时序上找,排查范围立刻砍掉一半。

代价是函数调用多一层,但综合测评这种量级完全感觉不到。我一般会把 algo 层的头文件写成不依赖任何 HAL 类型的形式,只 include<stdint.h>,这样以后换芯片系列,这一层一字不改。

2.2 CubeMX 里六个参数必须先定死

配置阶段最怕边写边改。下面这六项一旦定下来,后面所有算法的时间预算才有依据:

项目取值为什么是这个值
系统时钟72 MHz(F1 系列)/ 168 MHz(F4 系列)留足浮点运算余量,同时不挑战供电与布线
ADC 触发源TIM3 更新事件,20 kHz20 kHz 采样率对 50 Hz~5 kHz 信号有 4 倍以上余量
ADC 采样时间71.5 周期(源阻抗 < 10 kΩ 时)采样时间太短,高阻信号源会采不准,表现为读数偏低且抖
DMA 模式循环模式,半字宽度,半满+全满双中断半满中断给算法留出半个缓冲的处理窗口
DAC 触发源TIM6 更新事件,与 TIM3 同源不同频输出和采样共用一套时基思路,频率换算不用换公式
串口115200 8N1,仅用于标定与日志现场标定靠它,不占用显示资源

采样时间这一项值得多说一句。综合测评的题面经常给一个带运放跟随的输出,源阻抗看着不高,但如果你把采样时间设成 1.5 周期,ADC 内部的采样电容根本充不满,读数会系统性地偏低 3%~8%,而且温度一变就漂。这类误差不是"玄学",是采样保持电路的时间常数问题,把采样时间拉长到 71.5 或 239.5 周期,同样的电路读数立刻就稳了。

2.3 主循环与中断的分工:中断只置标志,计算全放主循环

这是整套工程最核心的一条约定,也是我见过翻车最多的地方。ADC 的 DMA 半满/全满回调里只做一件事——置标志位:

#define ADC_CH_NUM 2 /* 通道数:CH0 被测信号,CH1 基准分压 */ #define ADC_BLOCK 256 /* 每通道每块的采样点数,必须是 2 的幂 */ volatile uint16_t g_adc_raw[ADC_CH_NUM * ADC_BLOCK]; volatile uint8_t g_half_ready = 0; volatile uint8_t g_full_ready = 0; /* DMA 半满:前 128 点已经搬完,可以开始算 */ void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { g_half_ready = 1; /* 只置位,不做任何浮点运算 */ } } /* DMA 全满:后 128 点搬完 */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { g_full_ready = 1; } }

逻辑说明:DMA 在循环模式下把 ADC 结果连续写到g_adc_raw,写到一半触发半满回调,写满触发全满回调。两个回调把缓冲区切成前后两半,主循环处理前一半时,DMA 正在填后一半,天然形成双缓冲,不会出现"算到一半数据被覆盖"的情况。

参数说明:ADC_CH_NUM * ADC_BLOCK是总长度,DMA 的传输长度必须和它一致,否则会出现只搬了半个缓冲就不再触发的怪现象;ADC_BLOCK取 256 是因为后面做 256 点有效值计算时可以直接对齐,取 100 这种非 2 的幂也能跑,但半满中断的切分点会变得不好算。

主循环里对应的处理长这样:

while (1) { if (g_half_ready) { g_half_ready = 0; app_process_block(&g_adc_raw[0], ADC_BLOCK); /* 前半区 */ } if (g_full_ready) { g_full_ready = 0; app_process_block(&g_adc_raw[ADC_BLOCK], ADC_BLOCK); /* 后半区 */ } app_ui_task(); /* 显示刷新,内部限速 50 ms */ app_key_task(); /* 按键扫描,10 ms 周期 */ }

为什么非要把浮点运算从回调里赶出来?72 MHz 的核,256 点浮点乘加大约要 30~60 微秒,而 20 kHz 采样意味着每 50 微秒就有一次 DMA 请求。你在回调里算 60 微秒,DMA 请求就会被压住,表现为采样点丢失、缓冲区错位,最后反应在显示上就是"数值偶尔蹦一下"。这种偶发问题现场极难定位,所以干脆从结构上避免——回调只置标志,谁也别想在里面多待一个指令周期。

3. 采样链路:从 12 位码值到有效值的四步处理

采样链路的每一级都会引入误差,关键是知道误差从哪来、能不能在线消掉。这套方案的思路是:能用硬件比值消掉的,绝不留给软件标定;必须靠标定的,做成参数存起来,现场可以重标。整条链路拆成四步,每一步都有独立的验证点。

3.1 定时器触发加双通道扫描的启动顺序

启动顺序错了,前几十个采样点一定是废的。正确的顺序是:先校准,再启动 DMA,最后启动定时器触发。

void bsp_adc_start(void) { /* 1. 上电校准,消掉内部零点偏移,F1/F4 系列必做 */ HAL_ADCEx_Calibration_Start(&hadc1); /* 2. 先挂好 DMA,缓冲区地址和长度必须与 CubeMX 里一致 */ HAL_ADC_Start_DMA(&hadc1, (uint32_t *)g_adc_raw, ADC_CH_NUM * ADC_BLOCK); /* 3. 最后才开定时器,让触发源开始产生更新事件 */ HAL_TIM_Base_Start(&htim3); }

逻辑说明:HAL_ADC_Start_DMA只是把 ADC 置于"等待触发"状态,真正开始转换要等 TIM3 的更新事件。如果先开定时器再启动 DMA,那么在 DMA 挂好之前的几个触发事件会被丢掉,缓冲区开头会出现几个 0 或者上一轮的残留值。停采样时的顺序反过来:先关定时器,再停 DMA。

参数说明:(uint32_t *)这个强制转换是因为 HAL 的函数原型要求 32 位指针,而缓冲区是 16 位数组,转换本身不改变地址,但缓冲区必须 4 字节对齐,否则在某些系列上会进 HardFault。稳妥做法是在数组声明前加__attribute__((aligned(4)))。

校准这一步在新片子上做一次就够,但如果现场换了供电或者长时间高温运行,建议每次上电都做一遍,耗时只有几十微秒,比事后排查零点漂移划算得多。

3.2 码值到电压:先做比值,再做两点标定

从 12 位码值到物理量,中间有三级换算,顺序不能乱。

第一级是把码值变成电压。很多人直接写v = code * 3.3f / 4095.0f,问题在于 3.3 V 这个数是"标称值",实际板上的 LDO 输出可能是 3.28 V,也可能是 3.34 V,而且随负载和温度变化。方案里用了一个更稳的办法:第二通道接一个精密分压,测已知电压,用实测值反推参考电压。

/* 用参考通道反推实际 VDDA,消除 LDO 误差 */ static float get_vdda(const uint16_t *ref_buf, uint16_t n) { uint32_t sum = 0; for (uint16_t i = 0; i < n; i++) sum += ref_buf[i]; float code = (float)sum / n; /* REF_KNOWN 是分压后的已知电压,例如 1.200 V */ return code * REF_KNOWN / (4095.0f - code * 0.0f) * 1.0f; }

逻辑说明:参考通道接的是固定分压,其电压值由两个精密电阻决定,与 VDDA 成正比。测出这个通道的码值,就能反解出当前实际的 VDDA,再用它去换算被测通道,LDO 的偏差就被消掉了。

参数说明:REF_KNOWN必须实测,不要用电阻标称值算——1% 精度的电阻两个串起来,误差最大能到 2%,直接把这个误差带进所有测量结果里。做法是用六位半表量一次分压点电压,把实测值写进宏定义。这段代码里那个* 1.0f是占位,实际工程中应当按vref = code / 4095 * VDDA_nominal反解,写成VDDA = REF_KNOWN * 4095 / code,具体形式取决于你分压的是 VDDA 还是内部基准。

第二级是两点标定,第三级才是分段线性。这两步放到最后一章细说,这里先记住一个原则:两点标定只能修正增益和偏置,也就是y = kx + b,如果传感器的非线性明显,两点标定之后残差仍然是弯的,必须加分段。

3.3 有效值与频率:两套算法各有各的适用边界

综合测评里最常见的两个量就是有效值和频率,它们对采样条件的要求完全不同。

有效值用去直流后的平方和开方:

float algo_rms(const uint16_t *buf, uint16_t n, float scale, float offset) { float sum = 0.0f, sum_sq = 0.0f; float v; /* 第一遍:求均值,用于去直流 */ for (uint16_t i = 0; i < n; i++) { sum += (float)buf[i] * scale + offset; } float mean = sum / n; /* 第二遍:去直流后平方累加 */ for (uint16_t i = 0; i < n; i++) { v = (float)buf[i] * scale + offset - mean; sum_sq += v * v; } return sqrtf(sum_sq / n); /* 真有效值 */ }

逻辑说明:两遍循环是必要的,先求均值再平方,才能把直流分量彻底去掉。如果只做一遍、边减边平方,减掉的均值本身还在变,低频信号会引入明显误差。

参数说明:scale是码值到电压的换算系数,等于VDDA / 4095;offset是运放调理电路引入的偏置,通常是 1.65 V 左右的虚拟地。n的选取有个硬约束:采样窗口必须覆盖整数个信号周期,否则会有截断误差。50 Hz 信号在 20 kHz 采样率下,一个周期 400 点,取 256 点就是 0.64 个周期,误差能到 5% 以上。解决办法是让 TIM3 的触发频率跟着被测频率走,或者把窗口拉到 400 点、800 点这种整数倍。

频率测量则走另一条路,用输入捕获测周期:

/* TIM2 输入捕获中断:上升沿捕获,记录两次捕获的差值 */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { static uint32_t last = 0; uint32_t now = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); if (last != 0) { uint32_t diff = (now > last) ? (now - last) : (0xFFFF - last + now); /* 72 MHz / 预分频 72 = 1 MHz 计数,diff 即为微秒数 */ g_period_us = diff; } last = now; }

逻辑说明:把定时器预分频设成 71,计数频率就是 1 MHz,捕获值的差直接就是微秒数,省掉一次除法。now > last的判断处理计数器回绕,溢出时用0xFFFF - last + now补齐。

参数说明:捕获频率上限取决于信号质量,方波可以直接测,正弦波必须先过比较器整形,否则过零点抖动会让周期值跳变几个微秒。方案里对周期值做了 8 次中值滤波再取平均,这一步不是可选项——单次捕获的抖动在实际电路上很容易到 ±2%,滤波之后能压到 ±0.2%。

4. 输出链路与闭环:波形表、PID 与界面状态机

输入链路解决"测得准",输出链路解决"给得对"。综合测评里 DAC 通常有两个用途:一是产生一个可调的频率/幅值信号,二是作为闭环的执行端。两种用途对配置的要求不一样,混在一起写很容易互相干扰。

4.1 用脚本生成正弦表,而不是手写

波形表建议用脚本生成,一是避免手算出错,二是改点数、改幅值时不用重新数数组。生成脚本本身也是方案的一部分:

import math N = 256 # 每周期点数,取 2 的幂方便 DMA 循环 MID = 2048 # 12 位 DAC 的中点 AMP = 2047 # 幅值留 1 个码的余量,防止削顶 with open("sine256.h", "w", encoding="utf-8") as f: f.write("/* 自动生成,勿手改 */\n") f.write("static const uint16_t g_sine256[%d] = {\n" % N) for i in range(N): v = int(round(MID + AMP * math.sin(2 * math.pi * i / N))) v = max(0, min(4095, v)) # 双重保险,卡在 12 位范围内 f.write(" %4d," % v) if (i + 1) % 8 == 0: f.write("\n") f.write("};\n")

逻辑说明:MID + AMP * sin(...)把双极性正弦搬到 0~4095 的单极性区间,这是单电源 DAC 输出交流信号的常规做法。幅值取 2047 而不是 2048,是为了让峰值落在 4095 以内——取 2048 时峰值正好是 4096,会被截断成 4095,波形顶部出现一个码值的平台,频谱上就是一层谐波。

参数说明:N = 256决定了输出频率和定时器触发频率的关系,f_out = f_trigger / N。想输出 1 kHz,触发频率就得是 256 kHz;如果触发频率算出来不是整数,频率误差会直接体现为输出频率偏差,这时候要么改 N,要么改定时器的分频组合。math.sin的参数用2*pi*i/N而不是角度制,是为了避免一次额外的换算。

DAC 端的启动顺序和 ADC 对称:

void bsp_dac_start(void) { HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t *)g_sine256, 256, DAC_ALIGN_12B_R); HAL_TIM_Base_Start(&htim6); /* 触发源最后开 */ }

这里的DAC_ALIGN_12B_R表示 12 位右对齐,和上面的表格生成方式对应。如果改成左对齐,同样的数组会输出完全错误的波形,这一点在 CubeMX 里改配置时特别容易漏。

4.2 闭环 PID:三个参数按什么顺序整定

闭环部分用的是位置式 PID 加抗积分饱和,采样周期 1 ms,由 TIM7 中断驱动:

typedef struct { float kp, ki, kd; float integ; /* 积分累加项 */ float integ_max; /* 抗饱和限幅,一般取 out_max 的 60%~80% */ float last_err; float out_max; } pid_t; float pid_step(pid_t *p, float target, float fb, float dt) { float err = target - fb; /* 积分先累加再限幅,避免长时间偏差把积分项顶死 */ p->integ += p->ki * err * dt; if (p->integ > p->integ_max) p->integ = p->integ_max; if (p->integ < -p->integ_max) p->integ = -p->integ_max; /* 微分用误差差分,不直接用反馈差分,避免设定值突变时冲击 */ float d = (err - p->last_err) / dt; p->last_err = err; float out = p->kp * err + p->integ + p->kd * d; if (out > p->out_max) out = p->out_max; if (out < -p->out_max) out = -p->out_max; return out; }

逻辑说明:积分项累加后立刻限幅,是抗积分饱和最简单有效的做法。没有这一步,当目标值阶跃变化时积分会一路累加到很大,等反馈追上来需要很长时间才能退饱和,表现就是"超调之后迟迟不回"。

参数说明:dt是调用周期,单位秒,这里是 0.001。integ_max一般取out_max的 60%~80%,太小会让系统始终存在静差,太大则抗饱和失效。整定顺序是先把ki、kd清零,只加kp到系统开始等幅振荡,再回退到 60% 左右,然后加ki消静差,最后视情况加一点kd压超调。综合测评这种场合,kd很多时候直接给 0 就行,因为反馈信号本身的噪声会被微分放大。

4.3 显示与按键:用状态机代替一堆 if

界面部分最容易被写成意大利面。方案里用了四状态的状态机,每个状态只管自己的刷新内容和按键响应:

typedef enum { UI_MAIN, UI_SET_FREQ, UI_SET_AMP, UI_CALIB } ui_state_t; static ui_state_t s_state = UI_MAIN; static uint32_t s_last_refresh = 0; void app_ui_task(void) { uint32_t now = HAL_GetTick(); if (now - s_last_refresh < 50) return; /* 限速 20 Hz,防闪烁 */ s_last_refresh = now; switch (s_state) { case UI_MAIN: ui_draw_measure(); /* 只重绘数值区,不整屏刷新 */ if (key_pressed(KEY_ENTER)) s_state = UI_SET_FREQ; break; case UI_SET_FREQ: ui_draw_freq_edit(); if (key_pressed(KEY_ENTER)) s_state = UI_SET_AMP; if (key_pressed(KEY_BACK)) s_state = UI_MAIN; break; /* 其余状态同理 */ } }

逻辑说明:刷新限速 50 ms 有两个作用,一是避免 LCD 整屏刷新占用太多时间挤掉采样处理,二是数值看起来稳定,不会因为每位都在变而显得一直在跳。ui_draw_measure内部只重绘数值所在的矩形区域,不整屏清,这能把一次刷新的耗时从毫秒级压到百微秒级。

参数说明:按键消抖在bsp_key.c里做,采样周期 10 ms,连续 2 次读到相同电平才确认,等效消抖窗口 20 ms。这个值不要设太大,否则快速连按会被吞掉;也不要设太小,机械按键的抖动通常在 5~10 ms。

5. 避坑与排查:现场最容易翻车的四个瞬间

这一节写的都是实际调试里反复出现的现象,按"现象 → 原因 → 解决"走,遇到对得上的直接照做,不要先怀疑芯片坏了。

5.1 现象:ADC 读数系统性偏低 3%~8%,且随温度漂

原因:采样时间设得太短,ADC 内部采样保持电容没有充分充电,源阻抗越高偏差越明显。另一个常见原因是 VDDA 用标称值 3.3 V 直接换算,而实际 LDO 输出偏低。

解决:把采样时间从 1.5 周期改到 71.5 或 239.5 周期,同时在信号源和 ADC 引脚之间加一级运放跟随器,把源阻抗压到 1 kΩ 以下。换算时用参考通道反推实际 VDDA,不要写死 3.3。改完之后用同一路已知电压复测,偏差应该能进 0.5% 以内。

5.2 现象:数值偶尔蹦一下,蹦的时机没有规律

原因:九成是在 DMA 回调里做了浮点运算。回调执行时间超过采样周期,DMA 请求被压住,缓冲区里的数据出现错位,算出来的有效值自然跳。也有一种情况是主循环处理前半区时,DMA 已经写到了后半区的边界,两边抢同一个索引。

解决:回调里只置标志位,所有运算搬到主循环;缓冲区严格按前后半区切分,处理前半区时绝不去读后半区。验证方法是把app_process_block的耗时用 GPIO 翻转打出来,用示波器看一眼,处理时间必须明显小于半个采样窗口。

5.3 现象:DAC 输出频率和算出来的对不上,差一个固定倍数

原因:定时器触发频率算错。常见错误是只改了 ARR 没改 PSC,或者把f_out = f_trigger / N记成了f_out = f_trigger * N。还有一种情况是 CubeMX 里 DAC 的触发源选成了软件触发,结果 DMA 一转完就停,波形只出来一个周期。

解决:先把 TIM6 的触发频率用示波器在引脚上翻出来验证(配一个 GPIO 在更新中断里翻转),确认是 256 kHz 之后再接 DAC。触发源必须选定时器更新事件,DMA 模式必须是循环。三个条件缺一个,波形就不对。

5.4 现象:上电后偶发不启动,复位一下又好了

原因:初始化顺序错了。最常见的是在 ADC 校准之前就启动了 DMA,或者 DAC 的 DMA 在时钟使能之前就调用了启动函数。这类问题冷启动时概率高,热复位反而正常,特别容易误判成"电源不稳"。

解决:把初始化顺序固化成一条链——先MX_xxx_Init()使能所有外设时钟,再校准 ADC,再挂 DMA,最后统一启动定时器。启动函数集中放在main里一个bsp_start_all()中,不要在各自的初始化函数里零散调用。这样即使以后加了新外设,顺序也不会乱。

6. 分段线性校准与上电自检:把误差压进 ±0.5% 的收尾做法

两点标定能修好增益和偏置,但修不了弯曲。实测中,调理电路在量程两端往往会有 1%~2% 的非线性,这时候再怎么做两点标定,中间段和两端总有一头不准。方案里给的做法是分段线性:把量程切成 4~6 段,每段单独存一组k、b,运行时先判断落在哪一段,再套对应的系数。

typedef struct { float x0, x1, k, b; } seg_t; /* 按实测数据填,x0/x1 是分段边界,k/b 是该段的拟合系数 */ static const seg_t g_seg[] = { { 0.00f, 0.50f, 1.0021f, -0.0013f }, { 0.50f, 1.20f, 1.0008f, -0.0007f }, { 1.20f, 2.10f, 0.9989f, 0.0016f }, { 2.10f, 3.00f, 0.9976f, 0.0041f }, }; float calib_apply(float raw) { for (uint8_t i = 0; i < sizeof(g_seg)/sizeof(g_seg[0]); i++) { if (raw >= g_seg[i].x0 && raw < g_seg[i].x1) { return raw * g_seg[i].k + g_seg[i].b; } } return raw; /* 越界时原样返回,不猜 */ }

系数怎么来?用可调源在量程内均匀取 11 个点,每个点稳定后读 20 次取平均,把"标准值—实测值"两列丢进表格软件做分段拟合,每段用最小二乘算k、b。分段边界不要等距切,切在曲线拐点附近,段内残差会更均匀。段数也不是越多越好,超过六段之后每段的样本点太少,拟合出来的系数反而不可靠,还会引入段与段之间的跳变。

配套的上电自检只需要三件事:ADC 校准是否完成、参考通道读数是否落在合理区间、DAC 输出一个已知码值后回读是否一致。任意一项不过就在屏幕上打一个明确的错误码,而不是继续跑——现场最耗时间的就是"数值看着不对但不知道哪一环坏了",有了错误码,排查直接从出错的那一环开始。

血泪经验是标定参数一定要能在现场重标,而不是烧死在固件里。做法是把那几组k、b放在 Flash 的最后一个扇区,串口发一条固定帧格式的指令就能覆盖,标完自动校验和。我曾经在一次限时测试里因为换了根信号线,整条链路的偏置变了 30 mV,如果参数是编译进去的,那次就只能干瞪眼;能现场重标,五分钟就追回来了。

校准这件事值得留一份后悔药:每次上电先自检、再读参数、最后才进主循环,参数校验和不对就退回默认值并在屏幕角上打个标记。从那以后我每次改完调理电路,都会先跑一遍 11 点标定把系数刷进去再开始调算法,不再凭"看起来差不多"下判断。希望这套流程能帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询