简介一套面向AD7172-2、AD7175-2、AD7177-2等AD717X系列多路复用模数转换器的C语言驱动源码适合嵌入式系统开发者、仪器仪表及工业采集类项目工程师直接移植使用。驱动以寄存器读写为核心提供初始化、通道配置、数据读取等基础函数并覆盖多路复用输入切换与不同分辨率型号的适配逻辑。压缩包内共5个文件其中3个头文件用于接口声明与寄存器映射2个C文件实现上电配置、SPI通信及数据解析等底层逻辑整体仅12KB结构精简便于移植到STM32、DSP或FPGA等主控平台。这套源码已受到1809人关注学习代码注释清晰按模块封装可直接参考其寄存器操作流程省去查阅英文数据手册的时间同时也可作为学习高精度ADC驱动开发、时序配合与多通道采集系统的实际范例帮助快速打通从硬件配置到有效取数的完整链路。1. AD717X-X 驱动到底解决什么问题多路复用模数转换的 C 源码包干过工业采集的人都知道AD7177-2、AD7175-2 这一族 24/32 位 Σ-Δ ADC 性能没问题麻烦都在驱动上。第一次拿到 AD717X-X 的 C 源码包时我先确认了三件事型号适配、多路复用切换、SPI 读数据时序。这份资源解决的正是这三件事——不用你从 datasheet 一页页抠寄存器写好 SPI 底层后配置通道、改速率、读转换结果都调用包内函数。适合正在做称重、压力、振动或 PLC 模拟量采集手上还没有一套能跑通的 AD717X 驱动的朋友。2. 先看懂 AD717X-X 寄存器家族型号差异、多路复用机制与驱动文件结构2.1 AD7172-2 / AD7175-2 / AD7177-2 怎么选寄存器同源配置参数不同AD717X-X 不是一颗芯片而是一族采用相同 SPI 寄存器框架的 Σ-Δ ADC。这份 C 源码包把驱动做成一个中间层型号差异靠宏开关和 ID 校验隔离。只要板子上是 AD7172-2、AD7175-2、AD7177-2 中的任意一颗同一份ad717x.c都可以编译不需要维护三套驱动。型号标称分辨率常见输出数据率上限典型场景AD7172-224 位250 kSPS 档高速采集、低功耗仪表AD7175-224 位250 kSPS 档动态应力、振动、高速电压测量AD7177-232 位10 kSPS 档称重、计量、低带宽高分辨率测量这里要提醒一句数据率上限不是让你直接跑满。驱动在连续转换模式下切通道、等稳定、丢样本都要时间实际可用通道数打五折甚至三折都很正常。我一般会把目标速率定在 datasheet 上限的一半以内先把每一路数据读干净再谈榨干带宽。寄存器同源是这套驱动能一做多用的前提。ID、ADC_MODE、IF_MODE、DATA 这些核心寄存器的地址在三个型号里基本一致差异集中在滤波器的位字段、通道数量以及个别校准位。所以源码包里最常见的结构是ad717x.h型号枚举、寄存器地址宏、API 声明ad717x.cSPI 通信、复位、通道配置、数据读取ad717x_port.c平台相关的 SPI/GPIO/延时实现ad717x_example.c上电初始化和多路读取示例如果你用的不是 STM32而是 Zynq、GD32、瑞萨或者其他 MCU只需要重写ad717x_port.c里的三个回调函数片选、SPI 收发、微秒延时。核心代码尽量不要动动了后面难维护。2.2 多路复用到底怎么切内部 MUX、使能位和转换次序很多新人把“多路 ADC”理解成同时采样多路模拟量这是本资源里最需要纠正的概念。AD717X-X 的输入级只有一个 Σ-Δ 调制器和一路采样保持所谓多路是靠输入 MUX 轮询切换实现的。同一时刻只能转换一路但因为输出速率高在多路之间快速切换后宏观上看起来像在同时采集。通道配置的核心是每个通道寄存器里的两个字段AINP 选择正输入端AINN 选择负输入端再加上一个使能位。用代码表示就是static void ad717x_channel_select(ad717x_dev_t *dev, uint8_t ch, uint8_t ainp, uint8_t ainn) { uint16_t reg AD717X_CH_ENABLE | ((uint16_t)ainp AD717X_SHIFT_AINP) | ((uint16_t)ainn AD717X_SHIFT_AINN); ad717x_write_reg16(dev, AD717X_REG_CH0 ch, reg); }AD717X_CH_ENABLE是使能位不使能的通道不会参与扫描AD717X_SHIFT_AINP和AD717X_SHIFT_AINN是位偏移。具体偏移要看包内头文件因为单端和差分模式对 AINN 的编码不一样直接跨型号抄容易翻车。如果你用的是不支持自动扫描的型号驱动里一般会提供一个ad717x_next_channel函数每读完一个通道就手动切换一次通道寄存器。这种软件轮询比硬件自动扫描慢但控制更直接。做多路采集时我通常会让每个通道至少停留两个转换周期第一个周期只用来让内部滤波器和前端 RC 稳定第二个周期才把数据记进队列。2.3 驱动包文件结构和四个核心入口拿到这份 C 源码包后我建议你先浏览一遍头文件而不是先看.c文件。头文件里的宏定义就是这块芯片的“地图”。你真正要调用的入口其实只有四个ad717x_init复位、读 ID、设置工作模式ad717x_set_channel配置通道的输入引脚和使能状态ad717x_set_filter设置输出数据率和滤波器类型ad717x_read_data读取当前通道的转换结果这四个函数覆盖了“初始化—配置—轮询/中断—读数据”的完整闭环。剩下的读寄存器、写寄存器、校验 CRC 都属于内部细节正常业务代码不要到处调。一个常见误用是把ad717x_init写在主循环里反复调用。初始化函数里包含复位时序和 500us 延时放在主循环里会拖慢整个系统还会把 ADC 的寄存器打回默认值。正确做法是上电后只初始化一次后续只切通道和读数据。提示如果你的项目里有看门狗初始化阶段的 500us 延时通常不会造成喂狗超时但如果初始化里面还加了校准校准时间可能到几十毫秒这时必须在初始化前先喂一次狗。3. 把驱动跑起来SPI 初始化、复位、ID 校验和通道配置3.1 SPI 模式和回调封装先把底层做干净写这类 ADC 驱动我一般先把底层封装成函数指针。原因很简单你手头现在是 STM32 的板子下一个项目可能换国产 MCU如果不把 SPI 收发抽象出来到时候改驱动等于重写。typedef void (*spi_xfer_fn)(const uint8_t *tx, uint8_t *rx, uint32_t len); typedef void (*gpio_write_fn)(uint8_t level); typedef void (*delay_us_fn)(uint32_t us); typedef struct { spi_xfer_fn xfer; gpio_write_fn cs; gpio_write_fn ext_mux_a0; gpio_write_fn ext_mux_a1; delay_us_fn delay_us; } ad717x_port_t;xfer负责 SPI 全双工收发tx或rx可以为 NULL比如只发不读。cs控制片选建议用普通 GPIO 而不是 SPI 硬件 NSS因为 AD717X 读多字节寄存器时需要把 CS 全程拉低硬件 NSS 容易在字节间隙自动翻转。SPI 模式上AD717X 的 datasheet 一般会写支持 CPOL0/CPHA0 或 CPOL1/CPHA1。我习惯先按 mode 3 配也就是 CPOL1、CPHA1。读 ID 如果返回 0xFF 或 0x00再切到 mode 0 验证。SCLK 频率先降到 1MHz等代码通了再慢慢提上去不要一上来就 10MHz很多飞线板子的 SPI 问题都是时钟太快导致的。3.2 复位和 ID 校验上电后不能直接读数据AD717X 上电后寄存器不一定处于可用状态驱动的第一个动作应该是复位。最常见的复位方式是把 DIN 拉高连续给 64 个 SCLK相当于往 SPI 里塞 8 个字节的 0xFFstatic int ad717x_reset(ad717x_dev_t *dev) { uint8_t tx[8] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; uint8_t rx[8] {0}; dev-port-cs(0); dev-port-xfer(tx, rx, sizeof(tx)); dev-port-cs(1); dev-port-delay_us(500); return ad717x_read_reg(dev, AD717X_REG_ID, dev-id, 1); }这段代码的逻辑是8 字节0xFF等于 64 个高电平数据位让芯片内部逻辑全部复位到默认值。500us延时是给内部上电稳定留的时间不要太短。ad717x_read_reg读回来的 ID 要跟包里的预期值比较ID 不对直接返回错误避免后面用错误的寄存器配置去读数据。写寄存器函数的关键是命令字格式注意最高位是读写方向int ad717x_read_reg(ad717x_dev_t *dev, uint8_t addr, uint8_t *buf, uint8_t len) { uint8_t cmd AD717X_CMD_READ | (addr 0x3F); dev-port-cs(0); dev-port-xfer(cmd, NULL, 1); dev-port-xfer(NULL, buf, len); dev-port-cs(1); return 0; } int ad717x_write_reg(ad717x_dev_t *dev, uint8_t addr, const uint8_t *buf, uint8_t len) { uint8_t cmd addr 0x3F; dev-port-cs(0); dev-port-xfer(cmd, NULL, 1); dev-port-xfer(buf, NULL, len); dev-port-cs(1); return 0; }AD717X_CMD_READ在位定义里就是读标志。addr 0x3F是寄存器地址的低 6 位地址上限足够了。这里要注意读多字节寄存器时 CS 一定要在整个读过程中保持拉低如果每发一个字节就拉一次 CS读回来的数据顺序全是乱套的。3.3 配置通道并开始连续转换一个能跑通的初始化序列配置通道最常见的错误是上来就把 ADC_MODE 配成连续转换然后不管通道寄存器。AD717X 在连续转换模式下会扫描所有使能的通道如果通道没配好扫描结果全是无效值。一个稳定的初始化序列是这样int ad717x_setup_channels(ad717x_dev_t *dev) { uint8_t ch; for (ch 0; ch dev-active_channels; ch) { ad717x_channel_select(dev, ch, ch, AD717X_AIN_COM); ad717x_enable_channel(dev, ch, 1); } ad717x_write_reg8(dev, AD717X_REG_ADC_MODE, AD717X_MODE_CONT_CONV); ad717x_write_reg8(dev, AD717X_REG_IF_MODE, 0x00); return 0; }这里的AD717X_AIN_COM是单端模式下的公共端不是 GND。如果你做差分测量第二参数就要改成对应差分负端引脚不能继续用 COM。配完通道后再写 ADC_MODE顺序反了可能出现第一帧数据没有通道归属。数据读取的推荐写法是等 RDYwhile (1) { if (ad717x_data_ready(dev, 10) 0) { int32_t raw; ad717x_read_data(dev, raw); printf(ch%u raw%ld\n, dev-current_ch, raw); ad717x_next_channel(dev); } }ad717x_data_ready会判断 DOUT/RDY 引脚电平或者轮询 STATUS 寄存器。如果板子上没引出 DOUT/RDY只能轮询 STATUS但要注意轮询本身会占用 SPI 总线数据率一高就容易漏帧。能用 RDY 中断就不要用轮询。4. 把数据率、滤波器和参考电压调到能测准配置顺序与参数关联4.1 输出数据率和滤波器是一组关联参数不能单独改Sigma-Delta ADC 的输出数据率不是简单写一个分频值而是和滤波器类型、抽取率绑在一起。AD717X-X 的滤波器寄存器一旦配置就把输出速率、建立时间、带外抑制一起定下来了。驱动包里的ad717x_set_filter一般会内置一张配置表const ad717x_filter_cfg_t ad717x_filters[AD717X_NUM_FILTERS] { {AD717X_FILTER_SINC5, 10, AD717X_FILTER_VAL_10SPS}, {AD717X_FILTER_SINC5, 50, AD717X_FILTER_VAL_50SPS}, {AD717X_FILTER_SINC3, 1000, AD717X_FILTER_VAL_1KSPS}, };结构体里的第三个字段是滤波寄存器的可编程值这个值来自 datasheet 的滤波器表不要自己算。你必须理解一件事滤波寄存器不是只决定速率它还决定群延迟。Sinc5 滤波在 50SPS 下稳定时间接近 60ms也就是说切换通道后至少等 60ms 才能得到完整幅值。如果你在 1ms 后就读数据读到的只是一个过渡值。应用输出速率滤波器倾向切换通道注意称重/压力10~50 SPSSinc5带市电抑制切通道后丢 2~3 个样本温度/应变100~500 SPSSinc3低延迟用 RDY 中断别固定延时动态振动1kSPS 以上低延迟滤波器每通道单独验证稳定时间这张表里的数值不是绝对标准但它代表一类常见做法。你自己调参时我建议用“配置后回读寄存器”来确认写入生效。很多寄存器位在写错后会静默失效回读是成本最低的验证手段。4.2 参考电压、PGA 增益和单端/差分配置先定满量程再定通道参考电压直接决定满量程。AD717X-X 可以用内部参考也可以用外部基准驱动里的 setup 寄存器会有一个 REF 源选择位。外部基准要特别注意 REF 和 REF- 的滤波电容至少放一个 10uF 和一个 100nF 并联REF- 要接到模拟地附近不能拉一条细线到数字地。PGA 增益建议按“信号幅度 / 满量程”来选不要盲目开高增益。增益越高有效分辨率越好但输入噪声和线性误差也跟着放大。驱动里的设置一般是ad717x_set_setup(dev, 0, AD717X_REF_EXT); ad717x_set_pga(dev, 0, AD717X_PGA_1); ad717x_set_setup(dev, 1, AD717X_REF_EXT); ad717x_set_pga(dev, 1, AD717X_PGA_8);这里ad717x_set_pga的第二个参数是 PGA 倍率不同通道可以用不同 setup 寄存器所以通道 0 接 0~10V 调理板通道 1 接称重桥完全没有问题。只要配合好各自通道寄存器的 AINP/AINN 映射关系即可。单端输入最容易犯的错是直接拿 GND 当地线。AD717X 的公共端 COM 引脚是专门为单端测量设计的AINN 要选 COM外部再让 COM 靠近模拟地回流才能避免单端负半周读数乱跳。差分输入则要把桥式传感器的高端接 AINP低端接 AINN中间不要有多余走线分叉。4.3 外部多路开关和内部 MUX稳定时间和样本丢弃策略如果你要接的是 16 路甚至 32 路传感器单靠 AD717X-X 内部 MUX 不够前面往往还要加外部模拟开关。比如 ADG707、ADG706 这类 8/16 选 1 开关控制引脚用 GPIO 拉就行但稳定时间必须由驱动处理。外部通道切换后前端 RC 和开关导通电阻会形成新的充电回路第一帧数据通常是上一通道的残余电压。我一般会在驱动里做三件事先延时再读几帧丢掉最后才取有效数据。void select_ext_channel(ad717x_dev_t *dev, uint8_t ext_ch) { dev-port-ext_mux_a0(ext_ch 1); dev-port-ext_mux_a1((ext_ch 1) 1); dev-port-delay_us(AD717X_EXT_MUX_SETTLE_US); for (uint8_t i 0; i AD717X_DISCARD_SAMPLES; i) { int32_t dummy; ad717x_read_data(dev, dummy); } }AD717X_EXT_MUX_SETTLE_US是外部 RC 的建立时间我一般先按 1ms 起步。AD717X_DISCARD_SAMPLES是丢弃帧数Sinc5 滤波下至少丢 3 个样本Sinc3 可以少丢一个。这个参数不是越小越好要是切通道后波形还没稳住丢少了读回来的数据方差会很大。注意外部多路开关不要选导通电阻太大的型号。电桥源阻抗几千欧时开关电阻本身还会引入温漂和误差最好选低漏电、低导通电阻的模拟开关必要时在每个通道输入前并联一个 10nF 到 100nF 的电容。5. AD717X-X 调试避坑五个让数据“看起来正常但实际是废数”的案例5.1 读 ID 返回 0x00 或 0xFFSPI 通信完全不通现象上电后调用ad717x_init读 ID 寄存器永远是 0x00 或者 0xFF写什么都像没写进去。 原因SPI 模式不对、SCLK 太快、复位没完成或者片选被硬件 NSS 干扰。 解决把 SCLK 降到 1MHzSPI 模式在 mode 0 和 mode 3 之间切换试程序里加 500us 复位后延时。片选改成 GPIO 控制CS 拉低后整个读寄存器期间不允许松开。5.2 切换通道后第一帧数据是上一通道的电压现象通道 0 接 1V通道 1 接 3V从通道 0 切到通道 1 后读到的第一个值还是 1V 附近第二帧才变成 3V。 原因在 ADC 还没完成当前转换时改了通道寄存器或者滤波器群延迟还没结束第一帧数据和新的输入通道不匹配。 解决切换通道后先丢弃至少一帧或者等 DOUT/RDY 拉低再读。低速 Sinc5 滤波下丢帧数要提到 2~3 帧不能只看一次转换时间。5.3 数据率改了实际输出点数一点没变现象把滤波器寄存器的速率档从 50SPS 改成 10SPS示波器看 DOUT/RDY 的频率还是原来的速率。 原因AD717X 部分寄存器配置需要写同步寄存器 REG_UPDATE 才会生效。只写了滤波器寄存器芯片内部没有重新加载配置。 解决改完滤波器后按资源包里的说明写一次 REG_UPDATE然后回读滤波器寄存器确认值。如果回读出来还是旧值说明写命令的字节序或者地址发错了。5.4 单端输入负半周读数全错看起来像噪声现象输入接 0~5V 的单端信号正电压没问题接近 0V 时读数不稳定负半周直接变成大码。 原因AINN 接了 GND没有接 AD717X 的 COM 公共端导致共模电压不在芯片允许范围内。 解决通道寄存器里 AINN 选AD717X_AIN_COM外部把 COM 引脚就近连到模拟地和 REF- 之间的低阻点不要和数字地单点相连。5.5 低速采样时数据每隔一段就跳一个大码现象称重应用 10SPS 下数据稳定一段时间后突然跳十几到几十个码然后又恢复正常。 原因主循环没有真正等 RDY而是用固定延时读数据。数据寄存器正好在更新时被读读到的是新旧数据拼接的半帧。 解决改用 DOUT/RDY 引脚中断或者轮询 STATUS 寄存器确认数据更新完成后再发读取命令。如果资源包支持 GPIO 中断直接接 MCU 的 EXTI不要在中断里做打印和滤波。6. 进阶用 DOUT/RDY 中断把多路采样率跑满并给每路数据打时间戳6.1 GPIO 中断里只搬数据不做运算多路采集最容易出现的问题不是读数错而是数据顺序乱。高数据率下用轮询经常漏掉 RDY 下降沿一漏就导致后续通道错位。正确做法是把 DOUT/RDY 接 MCU 的下降沿中断中断里只做一件事读数据、放队列、切下一路。typedef struct { uint32_t tick; int32_t raw; uint8_t ch; } adc_sample_t; #define RING_SIZE 256 static adc_sample_t ring[RING_SIZE]; static uint32_t wr_idx 0; static uint8_t current_ch 0; void EXTI0_IRQHandler(void) { uint32_t tick timer_get_us(); int32_t raw; ad717x_read_data(adc, raw); ring[wr_idx].tick tick; ring[wr_idx].raw raw; ring[wr_idx].ch current_ch; wr_idx (wr_idx 1) (RING_SIZE - 1); current_ch (current_ch 1) % active_ch_cnt; ad717x_enable_channel(adc, current_ch); }timer_get_us()返回当前微秒计时ring是一个环形缓冲区wr_idx到 255 后回卷。中断里尽量不要调用printf否则一次打印可能吃掉几十微秒下一路 RDY 来了只能排队。时间戳是后来加上的它解决了一个很隐蔽的问题当 SPI 时钟和数据率接近时切换通道的代码执行时间会被 SPI 延迟挤掉通道号和时间对不上。6.2 用时间戳验证多路数据有没有串路拿到时间戳后我有一个固定验证套路把所有通道接到同一个稳定电压连续读 1000 帧按通道分组统计均值和标准差。如果标准差明显偏大或者某一帧的原始值突然跳到另一个电压区间就能立刻定位是通道切换时序还是外部 MUX 稳定时间不够。如果是外部 MUX还要把每帧的tick差值拉出来看。正常情况下 RDY 间隔应该接近理论值如果某两帧之间差了一倍说明中断被更高优先级任务堵过那一帧的时间戳会比真实采样时间晚很多。此时要么提高中断优先级要么把环形缓冲改深一点。从那以后我每次拿到 AD717X 驱动不管任务多急都强制走一遍三件事查 ID、丢样本、打时间戳。这三个动作能挡住绝大部分“数据怪怪的”问题。这份 C 源码包适合拿来当底板上面的坑都是真金白银换来的希望帮到你。本文还有配套的精品资源点击获取