简介:基于国产SWM181CBT6-50(Cortex-M0内核)单片机设计的ADC评估板完整工程资料,面向嵌入式硬件工程师、电子爱好者和相关专业学生,既可作为学习国产MCU评估板设计的样例,也适合在此基础上进行二次开发或产品预研。压缩包大小约40.73MB,内含Altium Designer原理图与PCB文件、软件代码及应用文档;硬件为97x58mm的双面2层板布局布线,涉及LDO低压差线性稳压、可调升压、可调基准电压、按键输入、LED指示等常见功能模块设计。其中给出了从电源电路到信号调理的完整器件选型参考,如贴片电阻电容及翠绿指示灯等,并配有相应软件代码,帮助理解硬件与固件的协同工作方式。目前已有314人学习,适合需要快速上手SWM181系列ADC应用开发的工程师参考。所有工程均可用Altium Designer直接打开或修改,便于对照原理图、PCB和代码进行系统性学习,为实际产品设计提供可靠参考。
1. 一块 SWM181CBT6-50 的 ADC 评估板,真正难住人的是采样数据怎么才不漂
调试这种评估板时,最常遇到的画面是:面板上的电位器拧到固定位置,万用表测出 1.650V,板上 SWM181CBT6-50 的串口却反复跳 1.610~1.720V,12 位 ADC 的理论分辨率不到 1mV,实测波动却大到没法用。问题往往不在芯片本身,而在原理图上的前端 RC、PCB 上的 guard ring 和软件里的采样周期上。这个评估板资料包把这几样凑齐了:Altium 工程里是完整的 ADC 采集前端与板级布局,软件 code 里是初始化、滤波、DMA 和校准例程,应用文档记录调试方法。下面顺着这三块内容,讲清楚怎么从原理图一直推到校准后的稳定读数,适合正在做信号采集、传感器接口或电池监测的单片机工程师。
2. SWM181CBT6-50 的 ADC 资源与 Altium 原理图外围设计
拿到资料先打开原理图,不要急着看 PCB。SWM181CBT6-50 内部是一个 12 位逐次逼近型 ADC,采样保持电容通常在几 pF 到十几 pF,对外接口看起来像高阻,但一旦采样开关导通,瞬间电流由外部电路提供。因此原理图上每个 ADC 输入引脚前面的阻容网络,决定了实际转换精度。
2.1 12 位 SAR ADC 的采样原理与通道分配
逐次逼近型 ADC 的采样过程是短暂的:采样开关闭合,内部电容充电,然后断开进行逐位比较。如果外部源阻抗过大,RC 充电时间不够,采样到的电压就不是输入电压实际值,表现为“高阻抗通道读数偏低、随采样周期变化”。这就是 adc 采样原理里最容易被忽略的一点。
评估板一般会引出 4 到 8 个模拟通道。下面是一份常见的通道分配表,具体网络名以原理图里的 net 为准:
| ADC 通道 | 板载功能 | 网络名(示例) | 输入范围 | 备注 |
|---|---|---|---|---|
| AIN0 | 板上电位器分压 | VR_ADC | 0 ~ 3.3V | 用于自检 |
| AIN1 | 外部电流转电压 | EXT_4_20MA | 0.4 ~ 2V | 250Ω 采样电阻 |
| AIN2 | 电池电压分压 | BAT_ADC | 0 ~ 3.0V | 分压比 1/3 |
| AIN3 | 温度传感器 | NTC_ADC | 0 ~ 3.0V | NTC + 上拉 |
分配通道时要注意相邻通道的串扰:如果 AIN0 走线紧挨着一路 PWM 输出的 GPIO,切换采集时容易在转换结果里看到固定跳变。SWM181 的 ADC 采样周期寄存器,一般以 ADC 时钟为单位,范围从几个到几十个时钟。增大采样周期可以直接补偿高源阻抗造成的采保误差,但代价是总吞吐率下降。
2.2 原理图里需要优先确认的前端 RC 参数
每个 ADC 输入前建议都加一节 RC 低通。RC 的作用一是滤除高频噪声,二是在采样瞬间由电容 C 提供电荷。常用做法是串联 100Ω~1kΩ,并联 1nF~10nF。R 和 C 的乘积决定时间常数 τ,选择时考虑两个约束:
- 截止频率要高于信号最高频率,避免想采的信号被衰减。对于 10Hz 温度的 NTC,1kΩ + 1μF 的 τ=1ms,截止频率 159Hz,够用。
- τ 要远小于 ADC 采样周期,否则采样电容充电还没稳定,开关就断开了。一般要求 τ < Tsamp/20。
下面的表给出几组常用参数,方便抄作业时做取舍:
| R | C | τ_rc | 截止频率 | 适合场景 |
|---|---|---|---|---|
| 100Ω | 10nF | 1 μs | 159 kHz | 低速直流电压采集 |
| 1kΩ | 10nF | 10 μs | 15.9 kHz | 传感器信号 |
| 10kΩ | 100nF | 1 ms | 159 Hz | 大时间常数抗混叠 |
注意,R 过大会带来直流压降吗?ADC 输入漏电流很小,但在高温高湿下不能忽略。评估板一般把前级运放输出端直接接这个 RC,以运放的低输出阻抗驱动 RC 网络,这是更稳妥的做法。前端 rc 滤波设计里还要看参考电压,SWM181CBT6-50 的 VREF 引脚不能直接挂在 3.3V 电源上,板上通常会放一个 1μF 钽电容加 100nF 陶瓷电容,如果应用对绝对精度有要求,会换低温漂基准源,并把 100nF 放在离 VREF 引脚最近的位置。
2.3 用 Python 检查网络表,避免原理图引脚接错
拿到 Altium 的原理图,导出 Netlist 后,不要只肉眼对引脚。可以用一段几十行的 Python 脚本把 ADC 通道的网络名提取出来,和分配表对照,防止通道悬空或者接错电源。下面是解析 Protel 格式网表的例子:
import re netlist_file = "SWM181_ADCB_V1.0.net" pin_map = {} with open(netlist_file, "r", errors="ignore") as f: lines = f.read().splitlines() cur_net = None for line in lines: m = re.match(r"\[(.*)\]", line.strip()) if m: cur_net = m.group(1).strip() continue if cur_net and re.match(r"^SWM181", line.strip()): # 行末括号里是引脚号,这里按实际格式调整 pin_map.setdefault(cur_net, []).append(line.strip()) for net in ["VR_ADC", "EXT_4_20MA", "BAT_ADC", "NTC_ADC"]: print(net, pin_map.get(net, []))这个脚本不复杂,核心是按括号分行识别网络,再按器件前缀筛选。跑完能看到每个模拟网络接在 SWM181 的哪个引脚上。如果某一路网络同时出现在两处 ADC 输入或没出现在目标引脚,可以立刻发现。实际应用中,Altium 导出的网表格式可能带更多转义字符,正则要按自己的格式微调,思路不变。
3. 软件 code:采样周期、滤波函数和四通道 DMA 搬运
硬件只是基础,软件上最容易栽跟头的是初始化顺序和滤波时机的选择。很多人直接把 ADC_Init 放在 while(1) 外面,然后循环读,得到的值没有经过滤波就直接用于 PID 或通信,结果上机就跑飞。先把周期、DMA、滤波放在一起看。
3.1 主频与 ADC 时钟的匹配,sampleCycle 该怎么调
SWM181CBT6-50 主频为 50MHz 时,ADC 外设时钟还需要分频。示例代码中常见的是配置 ADC 时钟为 12.5MHz 或 25MHz,然后设置采样周期寄存器。下面是基于官方寄存器风格改写的初始化代码,函数名以实际 SDK 为准:
void ADC_SampleCycle_Init(void) { // 1. 使能 ADC 时钟前先复位,避免上一次配置残留 SYS_ClockEnable(SYS_CLK_ADC); ADC_InitTypeDef cfg; cfg.channel = ADC_CHANNEL0; // 单通道 cfg.sampleCycle = ADC_SAMPLE_24CLK; // 采样 24 个 ADC 时钟 cfg.convMode = ADC_CONV_SOFTWARE; // 软件触发 cfg.vrefSel = ADC_VREF_EXTERNAL; // 使用板上外部基准 ADC_Init(&cfg); ADC_ChannelEnable(ADC_CHANNEL0); ADC_Start(); }sampleCycle 参数单位是 ADC 时钟个数。对 50MHz 主频、2 分频得到 25MHz ADC 时钟,24 个时钟就是 960ns 采样时间,足以应付源阻抗 10kΩ 以下的输入。如果源阻抗是 100kΩ,需要把 sampleCycle 提到 64 甚至 128。对应关系可以做成一张速查表:
| 源阻抗范围 | ADC 时钟分频 | sampleCycle | 等效采样时间 |
|---|---|---|---|
| <1kΩ | 4 分频 | 12 | 480ns |
| 1kΩ ~ 10kΩ | 2 分频 | 24 | 960ns |
| 10kΩ ~ 100kΩ | 1 分频 | 100 | 2μs |
注意:采样周期加大会降低最大采样率。如果项目既要高阻抗输入又要高同步速率,更实际的做法是加一级运放跟随器,而不是一味拉长采样周期。外部基准的精度和温漂同样会影响结果,片上内部参考电压温度系数一般够用但不够好,评估板上留有外部基准焊盘就是为校准留余地。
3.2 c 语言 adc 值滤波函数:滑动平均与中值组合
评估板资料里的软件 code,通常不会只给一个裸读函数。对工业采集场景,一个稳定的 adc 值滤波函数比多几路通道更有用。我一般推荐先做一次中值滤波剔除毛刺,再做滑动平均平滑。
#define ADC_MEDIAN_WIN 5 #define ADC_AVG_WIN 8 static uint16_t median_buf[ADC_MEDIAN_WIN]; static uint16_t avg_buf[ADC_AVG_WIN]; static uint8_t avg_index = 0; uint16_t ADC_MedianFilter(uint16_t new_value) { for (int i = 0; i < ADC_MEDIAN_WIN - 1; i++) median_buf[i] = median_buf[i + 1]; median_buf[ADC_MEDIAN_WIN - 1] = new_value; // 拷贝并排序 uint16_t tmp[ADC_MEDIAN_WIN]; for (int i = 0; i < ADC_MEDIAN_WIN; i++) tmp[i] = median_buf[i]; for (int i = 0; i < ADC_MEDIAN_WIN - 1; i++) for (int j = i + 1; j < ADC_MEDIAN_WIN; j++) if (tmp[j] < tmp[i]) { uint16_t t = tmp[i]; tmp[i] = tmp[j]; tmp[j] = t; } return tmp[ADC_MEDIAN_WIN / 2]; } uint16_t ADC_MovingAverage(uint16_t filtered) { avg_buf[avg_index] = filtered; avg_index = (avg_index + 1) % ADC_AVG_WIN; uint32_t sum = 0; for (int i = 0; i < ADC_AVG_WIN; i++) sum += avg_buf[i]; return (uint16_t)(sum / ADC_AVG_WIN); }中值窗口选 5,适合电力工频干扰和偶然尖峰;滑动平均的窗口选 8,是 2 的幂,可以直接移位。两者合在一起后,数据流看起来平滑,但阶跃响应会慢,用于保护逻辑时要额外判断原始值。adc 数据漂移还有一个常见来源:参考电源随温度缓慢变化,滤波函数解决不了,只能在硬件上选低温漂基准,或者用第 5 章的三点校准补偿。
3.3 adc 四通道使用 dma 的初始化与缓冲切换
当需要连续扫描多通道时,轮询模式会卡在等待转换结束上,比较合适的是 adc 四通道使用 dma。DMA 把 ADC 数据寄存器按顺序搬到内存缓冲区,不占用 CPU。这里的关键是缓冲区的长度设计:
#define ADC_DMA_CH_COUNT 4 #define ADC_DMA_BUF_LEN 64 uint16_t adc_dma_buf[ADC_DMA_CH_COUNT * ADC_DMA_BUF_LEN]; uint16_t adc_dma_buf_backup[ADC_DMA_CH_COUNT * ADC_DMA_BUF_LEN]; volatile uint8_t dma_active_buf = 0; void ADC_Setup_DMA_4Ch(void) { ADC_InitTypeDef cfg; cfg.channelMask = ADC_CH_MASK0 | ADC_CH_MASK1 | ADC_CH_MASK2 | ADC_CH_MASK3; cfg.convMode = ADC_CONV_DMA; cfg.sampleCycle = ADC_SAMPLE_24CLK; ADC_Init(&cfg); DMA_ConfigTypeDef dma_cfg; dma_cfg.periphAddr = (uint32_t)&ADC->DATA; dma_cfg.memAddr = (uint32_t)adc_dma_buf; dma_cfg.length = ADC_DMA_CH_COUNT * ADC_DMA_BUF_LEN; dma_cfg.loopMode = DMA_LOOP_ENABLE; DMA_Init(ADC_DMA_CH, &dma_cfg); DMA_Start(ADC_DMA_CH); } void ADC_DMA_IRQHandler(void) { if (DMA_GetFlag(DMA_CH_TRANSFER_DONE)) { DMA_ClearFlag(DMA_CH_TRANSFER_DONE); // 双缓冲切换 if (dma_active_buf == 0) { DMA_SetMemAddr(ADC_DMA_CH, (uint32_t)adc_dma_buf_backup); dma_active_buf = 1; } else { DMA_SetMemAddr(ADC_DMA_CH, (uint32_t)adc_dma_buf); dma_active_buf = 0; } } }代码里的长度是4 * 64,等于每次 DMA 中断处理 256 次转换结果。ADC_DMA_BUF_LEN最好按采样率和中断频率取整,避免在中断里做除法。双缓冲切换能防止数据处理期间缓冲被覆盖。若串口还要发 modbus 帧,处理完的adc_dma_buf[]再交给上层的帧发送函数,不要在中断里直接调用串口发送,否则长帧会拖长中断时间,导致下一次 DMA 切换不及时。
4. PCB 布局:guard ring、铺铜挖空和 Altium 的 DRC 规则
原理图和代码只是让 ADC 工作的第一步,实际稳定度大部分由 PCB 决定。SWM181CBT6-50 的 ADC 属于逐次逼近型,对参考电压、时钟走线和地平面敏感。这一章把评估板里最值得抄的三个布局手段拆开来说。
4.1 规避时钟抖动与电源噪声的 3 个 pcb 布局要点
第一个要点:ADC 参考电压的 VREF 引脚不能直接接在主电源铜皮上。评估板一般用单独的参考电压芯片或从 AGND 分出来的星型网络。VREF 的 1μF 和 100nF 去耦电容要放在距离 VREF 引脚不超过 3mm 的位置,GND 过孔垂直落到模拟地平面。
第二个要点:时钟线、PWM 线和 ADC 输入线不能在相邻层平行超过 5mm。SWM181 的 ADC 时钟来自内部 PLL 或外部晶振路径,如果在 Top 层把晶振走线从 ADC 输入旁边穿过,时钟抖动会耦合进采样电容,转换结果低位持续跳。
第三个要点:模拟地和数字地单点连接,且连接点选在 ADC 地引脚附近。评估板通常用 0Ω 电阻或磁珠把 AGND 和 DGND 隔开,连接点下方不要铺整片铜,而是露出一小块铜皮,让返回电流唯一路径。下面这张表可以贴在工位上看:
| 检查项 | 推荐做法 | 判定标准 |
|---|---|---|
| VREF 去耦距离 | 1μF+100nF 靠近 VREF 引脚 | < 3mm |
| ADC 输入线宽 | 0.2mm~0.3mm,尽量短 | 避开高频总线 |
| 模拟地分界 | 单点 0Ω/磁珠连接 | 连接点靠近 ADC 地 |
| 采样电容位置 | 靠近 MCU 引脚放置 | 控制 RC 回路面积 |
4.2 Altium 里加 guard ring 的操作步骤与常见误区
“guard ring” 不是简单画一圈线,它的作用是让模拟输入引脚外围被同电位的地包围,减小表面漏电流和耦合电流。Altium 里加 guard ring 的常见做法是:
- 在 Top Layer 用 Track 沿 ADC 输入网络外围画一圈闭合图形,图形距信号线至少 0.3mm。
- 把这圈 Track 的网络改成 AGND,宽度设 0.4mm 左右。
- 在环上间隔 0.5mm 放置过孔,过孔网络同样设为 AGND,让环和地层形成低阻屏蔽。
- 如果环内有 3.3V 电源走线穿过,需要将电源走线移出,Guard Ring 内只允许模拟信号和地。
需要提醒:Guard ring 不能同时作为多路 ADC 输入间的隔离带。多路模拟输入之间更应该用独立的接地走线分开,而不是把所有信号都包在一个大环里,否则会引入通道间寄生电容。另一个常见误区是把环放在 Keep-Out 层,Keep-Out 只影响布线,铜箔层上的 guard ring 还是要在 Top/Bottom 层绘制。对于已经铺了大面积 AGND 的情况,可以用 Polygon Pour Cutout 在 ADC 输入信号周围挖出一道狭窄的隔离带,避免数字返回电流从模拟输入下方穿过,这种铺铜挖空手法在混合信号板里比单纯画线更可靠。
4.3 用 AD20 的 DRC 报告脚本统计规则冲突
在 Altium 里跑 DRC 只是第一步,报告密密麻麻,眼睛看容易漏。建议直接导出drc_report.txt,再用 Python 按规则名统计。DRC 规则本身也要预先设置:在 PCB 编辑器的 Design Rules 里,把 Clearance 设为 0.2mm,信号线宽最小 0.2mm,电源铜皮线宽最小 0.5mm。AD20 默认的规则偏保守,但模拟区域与数字走线的间距可以单独加一条规则强制到 0.5mm。我一般用下面这个脚本快速核对:
import re, collections rule_counter = collections.Counter() with open("drc_report.txt", "r", errors="ignore") as fp: for line in fp: # 典型行:Clearance Constraint -> 12 violation(s) m = re.search(r"^([^:]+?)\s+->\s+(\d+)\s+violation", line) if m: rule_counter[m.group(1).strip()] += int(m.group(2)) for rule, total in rule_counter.most_common(): print(f"{rule}: {total}")实际导出报告格式可能含有制表符,这里用->做分隔符,读出来就是每个规则对应错误数。比如看到Silk To Solder Mask: 5,就说明丝印压焊盘,这类问题对 ADC 性能没影响,可以最后处理;如果Clearance Constraint计数高,则要先查 AGND 环路是否足够宽。
5. 用外部 adc 三点校准把数据漂移压到 0.1% 以内
校准是对 ADC 的偏移、增益和非线性做一次“就地修正”。用高精度电压源分别输入三个标准电压,记下对应原始码,然后分段线性插值。选点时不要选 0% 和 100%,而选量程的 10%、50%、90%,避免基准饱和区。SWM181CBT6-50 的 ADC 量程常用 0~3.3V。
5.1 三点校准的选点和分段线性插值实现
| 标准点 | 输入电压 | 预期原始码 | 实际原始码 |
|---|---|---|---|
| 低点 | 0.330V | 409 | 405 |
| 中点 | 1.650V | 2047 | 2040 |
| 高点 | 2.970V | 3684 | 3670 |
实现时用一个结构体保存校准表,计算阶段直接把原始码映射到电压,不做线性拟合再反推,省一步浮点运算。
typedef struct { uint16_t raw[3]; float volt[3]; } ADC_TripleCal_t; float ADC_TripleCal(ADC_TripleCal_t *cal, uint16_t raw) { if (raw <= cal->raw[0]) return cal->volt[0]; if (raw >= cal->raw[2]) return cal->volt[2]; for (int i = 0; i < 2; i++) { if (raw >= cal->raw[i] && raw <= cal->raw[i + 1]) { float k = (cal->volt[i + 1] - cal->volt[i]) / (float)(cal->raw[i + 1] - cal->raw[i]); return cal->volt[i] + k * (raw - cal->raw[i]); } } return cal->volt[0]; }代码核心是两段斜率,注意(float)强制转换要放在差值上,别放在整数除法里,否则斜率直接变成 1。校准最多能做三段,适合实际曲线“S”形比较明显的器件。如果系统不允许浮点运算,可以把斜率放大 1024 倍存成 int,最后右移 10 位,代价是精度损失不到 0.1%。
验证方法:输入 1.000V 和 2.000V 两个校验点,分别采集 100 次,最大值减最小值除以中值,如果小于 0.1%,就说明采样周期、PCB 和校准这三层都对了。如果还差一点,可以先把滤波窗口加回中值+滑动平均,再跑一次同一个校验点。整个评估板的调试顺序就是:先看原理图 RC,再调采样周期,然后守好 PCB 布局,最后校准。
本文还有配套的精品资源,点击获取