
简介AD7689 是一颗高精度 16 位 ADC属于逐次逼近型结构支持多通道采样常用于工业控制、医疗仪器等对精度和噪声要求较高的采集场景。这份压缩包围绕 AD7689 芯片开发同时涉及 AD7705 与 AD9880 的协同应用面向需要编写底层驱动的嵌入式开发者或硬件工程师。资源包共 155 个文件大小 3.01MB包含 C 源码、H 头文件、Keil uVision 工程uvproj以及编译生成的 .hex、.axf 等整体是一个基于 STM32F10x 平台的可运行工程代码覆盖 SPI 通信、ADC 采样、LCD 显示、定时器和基础外设配置能直接对照学习并提供了可烧录的 .hex 文件以便验证功能。通过源码可以了解器件寄存器配置、采样率和输入范围设置、转换启动与结果读取流程也能学习与 AD7705/AD9880 类似芯片的协同处理、中断与状态寄存器读取以及系统设计思路。资源已有 977 人学习下载适合需要快速搭建高精度采集系统、调试 ADC 驱动或做课程设计和毕业设计的人群。1. AD7689 芯片开发源码包先别急着编译看到“AD7689.zip”这个包名时通常意味着你拿到的是 ADI 三颗代表性芯片的驱动集合AD7689 负责 16 位多通道 SAR ADC 采集AD7705 负责低速率高精度测量AD9880 则出现在 HDMI 视频前端。这个包真正的价值不在 zip 里那几行寄存器赋值而在把 SPI 时序、通道序列、DMA 搬运和上层数据校验串成一条完整链路的工程经验。如果你是按关键词搜到这里多半已经下载了某个资源站打包的源码却发现注释全是乱码或寄存器写法太杂。接下来以 AD7689 为主线把驱动源码拆开讲透再带上 AD7705 的迁移手法和 AD9880 的信号链配合让这套代码不只在现成板子上跑通也能落到你自己画的 PCB 上。2. 从 AD7689 的寄存器配置到 SPI 时序先把采集链路跑通2.1 AD7689 的 SPI 引脚是半双工还是全双工接线决定后面代码怎么改AD7689 与 MCU 之间通常只有四根信号线CNV、SDI、SDO、SCLK。很多人第一眼把它当成标准四线 SPI直接照搬 51 单片机程序源码里的CS_LOW(); SPI_Exchange(); CS_HIGH();流程结果发现读回来的码值要么固定全 1要么每个通道错位一格。问题在于 AD7689 的 SPI 并不是普通的“CS 片选 全双工交换”结构。它的 CNV 引脚同时承担转换启动和传输触发的角色CNV 的上升沿先锁定输入采样之后 SDO 按 SCLK 逐位输出上一次转换结果同一组 SCLK 脉冲里SDI 上写入的新数据又会被移入内部移位寄存器作为下一次转换的配置命令。也就是说读结果和写通道命令发生在同一个 SPI 事务里SDI 与 SDO 在同一时刻分时工作而不是像 AD7705 那样有独立的通信寄存器和状态寄存器可以分开操作。接线上常见的做法有下面两种我在实际项目中都验证过接线方式SDO 与 SDI 是否共用适用场景驱动注意点四线独立否各接一个 MCU 引脚普通 MCUSPI 外设资源充足用全双工 SPI 即可注意 CNV 单独控制三线半双工是共用一个双向 IO引脚紧张的 ARM 或 FPGA发送命令后要切换 IO 方向时序损耗大四线接法更推荐因为 STM32、NXP 这类 MCU 的 SPI 外设天然支持同时收发省去切换 IO 方向的额外开销。SCLK 频率不要一开始就拉到数据手册标称的 50MHz先用 1MHz 把链路打通再用逻辑分析仪去验证边沿关系。CNV 建议单独接一个 GPIO不要和硬件 CS 引脚绑在一起后面调 DMA 时会方便很多。2.2 初始化寄存器要写两次CFG 与 CH 分开改AD7689 内部有两个 16 位寄存器CFG 控制带宽、参考电压、输入配置和顺序扫描模式CH 寄存器负责选择具体参与转换的通道。上电后必须把这两个寄存器都正确写入否则只会按默认配置跑单通道。一个常见的坑是只写一次 16 位数据就以为配置完成。AD7689 的命令和数据在同一个 16 位帧里传输写 CFG 和写 CH 各需要一次完整的 SPI 交换。下面这段是裸机环境下的初始化片段/* ad7689_write_reg: 通过 SPI 写 16 位寄存器命令 */ static void ad7689_write_reg(uint16_t cmd) { gpio_cs_low(); /* CNV 保持低电平等待命令写入 */ spi_write_16bit(cmd); /* 16 个 SCLKMSB 先移出 */ gpio_cs_high(); /* 拉高 CNV命令被锁存并启动转换 */ } void ad7689_init(void) { /* CFG 寄存器取伪差分输入、使能内部参考缓冲、允许顺序扫描 */ uint16_t cfg 0x2003; /* 具体位按手册寄存器映射设置 */ ad7689_write_reg(cfg); /* CH 寄存器让 CH0~CH7 全部进入转换序列 */ uint16_t ch 0x00FF; ad7689_write_reg(ch); }这段代码的核心逻辑是先把 CFG 命令写入 SDI再用 CNV 上升沿锁存。CNV 拉高的瞬间器件开始采样此时如果 SCLK 还在跑SDO 上就会出现上一次转换的结果。cfg变量里的具体位值要根据自己的板子调整比如外部参考源时要把 REF 位置 0单端输入时要改 INCC 字段。不要直接复制网上的现成值先对照数据手册的 CFG 寄存器表逐位确认。CH 寄存器写0x00FF的意思是 8 个通道都参与顺序转换。如果只需要采集两路改成0x0003可以减少每轮转换耗时。这里还有个细节CH 寄存器写入后不会立即生效要等当前那一轮转换完成后才切换所以第一次读回的数据有时还是旧通道的值丢弃第一帧是做 AD7689 驱动时的默认习惯。2.3 在 Linux 下用 spidev 先读一帧数据验证接线有没有反做驱动移植时我习惯先在嵌入式 Linux 环境里把链路验证一遍再回头写裸机代码。i.MX、瑞萨 RZ 或树莓派这类平台的内核通常已经带了 spidev 驱动在设备树里使能 SPI 控制器后可以直接用用户态 Python 读回原始码值。import spidev spi spidev.SpiDev() spi.open(0, 0) # /dev/spidev0.0 spi.max_speed_hz 1000000 # 先低速避免信号质量干扰 spi.mode 0b00 # CPOL0, CPHA0 # 写 4 个字节前两个字节是 CFG 配置命令后两个用来读回结果 # 第 1 次交换读到的还是上电后的旧数据所以连读两次 out [0x20, 0x03, 0x00, 0x00] rx spi.xfer2(out) # 第二次交换才是真正携带有效结果的帧 out2 [0x00, 0x00, 0x00, 0x00] rx2 spi.xfer2(out2) raw (rx2[0] 8) | rx2[1] print(fAD7689 raw: {raw:#06x})这个脚本里连续执行两次xfer2是典型的 AD7689 回读模式。第一次 SPI 交换写入 CFG 命令SDO 返回的是上一个状态的无关数据第二次交换时芯片已经按新配置跑完一次转换数据才是可用的。如果打印出的 raw 恒定在 0xFFFF 附近优先检查 SDO 对应主控 MISO 引脚是不是虚焊如果数值在 0x0000 和 0xFFFF 之间跳变但没有规律多半是 SCLK 极性与芯片要求的采样边沿不一致把spi.mode改成0b01再试。这种用户态验证方式能排除大量 PCB 和引脚复用问题之后再进内核态写正式驱动会节省很多时间。3. 多通道连续采集通道切换、DMA 搬运与丢码的几个根源3.1 通道切换的本质是往 SDI 写下一轮命令而不是遍历寄存器很多初次接触 AD7689 的人会这样理解多通道采集配置好 CH 寄存器然后每次读一个通道读之前再去改 CH 寄存器。这在逻辑上没错但实现上绕了远路。AD7689 的通道切换其实是跟随 SPI 数据流自动完成的每次转换完成后SDI 上写入的 16 位命令除了包含配置位还包含下一次要转换的通道序号。也就是说你在读第 N 个通道结果的同时已经把第 N1 个通道的转换命令写进去了。正因如此驱动里不需要维护一个通道遍历的for循环只需要在每帧数据对应的命令字里更新通道号。下面是一个典型的裸机轮询采集流程uint16_t current_ch 0; uint16_t ad7689_read_channel(uint8_t ch) { uint16_t cmd 0x0000; /* command 的高字节填入 CFG 内容 */ cmd | (uint16_t)ch 4; /* 通道号放在数据字段的指定位置 */ uint16_t result spi_exchange(cmd | READ_FLAG); /* 同时读回旧结果 */ current_ch ch; return result; } void ad7689_scan_all_channels(uint16_t *out_buf) { for (uint8_t i 0; i 8; i) { out_buf[i] ad7689_read_channel(i); } }这个代码里spi_exchange完成一次 16 位全双工交换返回的是上一轮转换结果写入的是当前准备启动的通道命令。第一次调用读回来的通道 0 数据往往是电源刚上电的毛刺值实际产品里会在启动后先执行一次空读丢弃。通道号在命令中的具体位偏移参考数据手册里 CH 寄存器的通道选择字段来设置不同版本器件会有细微差别。3.2 DMA 环形缓冲区怎么绕半满中断才不容易丢点当采样率跑到 AD7689 的最大值 250kSPS 时每 4 微秒就要处理一帧数据靠 CPU 中断逐点搬运会占掉大量主循环时间。更合理的方案是让 SPI 外设在 DMA 模式下自行搬运CPU 只在缓冲区半满和全满时处理数据。用 STM32 HAL 库搭这套逻辑时需要把 SPI 的发送内存地址指到一个固定的命令缓冲区接收内存地址指到一个环形结果缓冲区两个缓冲区大小一致DMA 循环模式同时开启。#define ADC_DMA_BUF_SIZE 2048 uint16_t adc_cmd_buf[ADC_DMA_BUF_SIZE]; /* 预置好的通道命令序列 */ uint16_t adc_data_buf[ADC_DMA_BUF_SIZE]; /* 接收到的转换结果 */ volatile uint8_t adc_half_flag 0; void adc_dma_start(void) { /* 预填 2048 条命令通道号按 0~7 重复 */ for (int i 0; i ADC_DMA_BUF_SIZE; i) { adc_cmd_buf[i] 0x2003 | ((uint16_t)(i % 8) 4); } HAL_SPI_TransmitReceive_DMA(hspi2, (uint8_t *)adc_cmd_buf, (uint8_t *)adc_data_buf, ADC_DMA_BUF_SIZE); } void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI2) { adc_half_flag 1; /* 前半段数据已收完可处理 adc_data_buf[0..1023] */ } }这里把命令预填充成 8 通道循环序列DMA 会按固定节奏向 SPI 发送命令同时从 SDO 读回结果。由于命令序列是静态数组CPU 完全不用干预每次交换的内容处理数据的周期是整段缓冲区传输完成时而不是每个点。有一点要注意半满回调触发后到下一次半满之前CPU 必须处理完前 1024 个数据点否则环形缓冲区会覆盖。处理不过来时把缓冲区加大到 8192或者改成乒乓双缓冲而不是降低采样率。3.3 连续采集丢码的四个检查点DMA 跑起来后如果出现偶发丢码优先查这四个位置命中率非常高现象可能原因排查手段固定通道读到 0xFFFFSDO 引脚浮空芯片未完成复位量 REF 引脚电压正常应等于 VREF通道号整体偏移一位序列起点不对第一帧命令未对齐命令缓冲区头部加一个 dummy 帧DMA 半满后连续丢两个点SPI 时钟太快FIFO 溢出SCLK 降到 20MHz 以下后再测数据偶发重复上一帧CNV 脉冲间隔小于 tCONV用逻辑分析仪量两次 CNV 上升沿间隔丢码问题最难查的是第一种因为 0xFFFF 也可能是真实满量程输入。判断方法很简单把 ADC 输入接到 GND如果读回值不是接近 0 的码值而是 0xFFFF说明 SDO 数据通路根本没建立起来跟输入信号无关。接到 VREF 后如果读回值接近满码才说明链路正常。4. AD7705 与 AD7689 的 16 位 ADC 迁移比较AD9880 负责另一条链路4.1 采样原理不同驱动风格完全是两套写法AD7705 和 AD7689 虽然都是 16 位但一个是 Σ-Δ 型一个是逐次逼近型这决定了驱动代码的骨架完全不同。AD7705 内部有数字滤波器输出数据率由滤波器配置决定通信寄存器结构复杂需要先写通信寄存器再写数据寄存器每次操作还要等 DRDY 引脚拉低才知道数据准备好。AD7689 则简单很多转换速度由外部 CNV 脉冲决定结果在 SPI 交换完成后立即可用没有 DRDY 等待过程。对比项AD7705AD7689转换架构Σ-Δ带数字滤波SAR逐次逼近典型输出数据率20Hz ~ 500Hz最大 250kSPS通道数2 路差分8 路伪差分/单端数据就绪信号DRDY 引脚拉低SPI 交换期间自动输出驱动复杂度通信寄存器 时钟分频单一 16 位命令字适用场景应变片、温度、称重多通道动态信号、电流检测如果下载的源码包里同时有 AD7705 和 AD7689 两份代码千万不要以为换掉 SPI 读写函数就能共用。AD7705 驱动里最核心的是数字滤波器的建立时间计算修改输出速率后要等几次转换周期才能读到稳定数据AD7689 驱动里最核心的则是命令字构造和 CNV 时序这两者的代码结构几乎没有重叠。4.2 从 AD7705 迁移到 AD7689 时要改的五个地方把 AD7705 的驱动改造成 AD7689最省事的方式不是逐行翻译而是按下面五个层面重写第一删除 DRDY 相关代码。AD7705 的轮询和中断都是围绕 DRDY 引脚展开的AD7689 没有这个信号所有等待逻辑都变成多余。第二把寄存器访问方式从“通信寄存器 目标寄存器”改成单命令字写入。AD7689 每个命令帧本身就包含目标和数据不需要两阶段操作。第三重新设置 SCLK 上限。AD7705 通常建议 SCLK 不超过 2MHzAD7689 可以跑到更高频率但高速时要用四线制接法保证信号完整性。第四检查 VREF 驱动能力。AD7689 内部参考缓冲可以直连外部基准源不像 AD7705 在 REF 引脚上需要额外去耦电容的严苛要求。第五回调接口从转换完成变成 DMA 批量完成。一个很容易被忽略的细节是数据格式。AD7705 的转换结果在高位对齐某些配置下低两位是无效位AD7689 的结果是 16 位满位有效直接右移即可得到实际码值。迁移后建议先接已知电压源做满量程校正避免把旧驱动的掩码逻辑带进来。4.3 AD9880 芯片开发规范跟 ADC 完全不同别混进 SPI 驱动标题包里出现的 AD9880 常常让新手困惑因为它虽然叫芯片开发却和 AD7689、AD7705 是两个世界。AD9880 是 HDMI 接收端的模拟前端芯片负责把 HDMI 差分信号转换成并行数字视频流配置走 I2C数据输出走专用像素时钟和 DE/HSYNC/VSYNC 同步信号。它的初始化代码通常是一长串 I2C 寄存器配置表跟 SPI ADC 驱动没有任何调用关系。在整合这套源码时我一般会把 AD9880 独立编译成一个ad9880.c只提供ad9880_init()和ad9880_get_timing()两个对外接口。视频数据流用 DMA 直接搬运到帧缓冲由显示控制器负责读取不会进入 ADC 的中断回调。这样分层之后AD7689 的采集链路和 AD9880 的视频链路互不干扰调试时可以单独屏蔽一路。如果源码包里的 AD9880 代码用了 i2c-dev 或类似机制访问总线说明它依赖具体内核接口移植到裸机时要手工实现 I2C 时序函数。5. 用差分输入做噪声直方图量化 AD7689 的 ENOB 是否够用5.1 搭建静态测试平台把输入接到地或精密电压源驱动跑通之后真正见真章的是 ADC 的噪声指标。把 AD7689 的两个差分输入短接后接到 PCB 的地平面通过串口或 DMA 缓冲区收集 4096 个采样点。理想情况下码值应集中在一个很小的范围内波动幅度直接反映系统噪声水平。测试时要注意 CNV 触发频率建议用固定频率的定时器产生触发脉冲而不是在主循环里随机调用这样得到的直方图才有统计意义。5.2 用 Python 计算 RMS 噪声和 ENOB 的快速脚本采集完成后把数据通过串口导出为 CSV 文件用下面这段脚本做噪声分析import numpy as np # 从串口保存的 4096 个码值替换为实际采集数据 samples np.loadtxt(adc_samples.csv, dtypenp.uint16) codes samples.astype(np.float64) # 去除直流分量后求 RMS 噪声 noise codes - np.mean(codes) rms_noise np.sqrt(np.mean(noise ** 2)) # ENOB 16 - log2(RMS 噪声) enob 16 - np.log2(rms_noise) print(fRMS noise: {rms_noise:.2f} LSB) print(fENOB: {enob:.2f} bit)计算逻辑是先用平均码值作为直流基准把采样值减去均值得到纯噪声再求均方根。ENOB 的换算公式假定噪声是量化噪声和热噪声的合成16 位理想 ADC 的 RMS 量化噪声约 0.29 LSB所以测出 0.29 附近说明性能接近理想值。如果 ENOB 低于 13bit优先检查 VREF 去耦电容是否靠近引脚以及 ADC 附近的数字信号线有没有串扰到模拟输入。5.3 四个必调的边界参数最后再检查一次最后一个落点是把驱动里的几个边界参数按生产环境重新校一遍。第一个是 SCLK 频率量产板 PCB 走线长度与样板不同标称 50MHz 不一定能稳定跑通常最终定在 20MHz 到 33MHz 区间。第二个是 CNV 脉冲最小间隔把定时器重载值设到略大于 tCONV 的周期留出 10% 余量避免温漂后丢触发。第三个是参考源建立时间首次上电后要延时至少 100ms 再启动采集否则 VREF 未稳定会导致前几帧数据整体偏差。第四个是 SPI 时钟空闲电平部分 MCU 会把 CPOL 默认设为 1穿到 AD7689 上会直接导致转换结果全错复位配置时务必重新设置 SPI mode 而不是沿用 GPIO 复用后的默认值。本文还有配套的精品资源点击获取