简介: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 引脚 | 普通 MCU,SPI 外设资源充足 | 用全双工 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 个 SCLK,MSB 先移出 */ 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 # CPOL=0, CPHA=0 # 写 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(f"AD7689 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 个通道结果的同时,已经把第 N+1 个通道的转换命令写进去了。
正因如此,驱动里不需要维护一个通道遍历的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 跑起来后如果出现偶发丢码,优先查这四个位置,命中率非常高:
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| 固定通道读到 0xFFFF | SDO 引脚浮空,芯片未完成复位 | 量 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 等待过程。
| 对比项 | AD7705 | AD7689 |
|---|---|---|
| 转换架构 | Σ-Δ,带数字滤波 | 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 不超过 2MHz,AD7689 可以跑到更高频率,但高速时要用四线制接法保证信号完整性。第四,检查 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", dtype=np.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(f"RMS noise: {rms_noise:.2f} LSB") print(f"ENOB: {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 复用后的默认值。
本文还有配套的精品资源,点击获取