AD7685 SPI 3线无BUSY 模式调试笔记 (附代码及工程)

最近项目中使用到了 AD7685 ADC 芯片,其支持最基本的 SPI 接口,但与日常我们操作的大多数标准的 SPI 又不完全相同,故在此记录下完整的调试过程,并与大家分享。文后会附上测试代码,若整个调试过程中存在疑问或个人理解的错误,恳请大家在评论区指正,如果大家有自己的调试经验,也欢迎在评论区分享。

首先分享一下 AD7685 的官网,大家可以在此找到关于该芯片的所有资料(复制链接到浏览器打开):

https://www.analog.com/cn/products/ad7685.html

官网也能够下载到 AD7685 的芯片手册,在调试开始前需要将该手册准备好,在实际调试过程中需要参考到手册中的一系列参数。

在调试开始前我们先来简单了解一下这个芯片:

AD7685 是一款16位、电荷再分配、逐次逼近型模数转换器 (ADC),采用2.3V 至 5.5V 单电源 (VDD) 供电。该器件内置一个低功耗、高速、16位无失码采样ADC、一个内部转换时钟和一个多功能串行接口端口。还集成了一个低噪声、宽带宽、短孔径延迟的采样保持电路。在CNV上升沿,该器件对 IN+ 与 IN- 之间的模拟输入电压差进行采样,范围从0V 至 REF。基准电压 (REF) 由外部提供,最高可设置为电源电压,其功耗和吞吐量呈线性变化关系。

AD7685 有以下特性:

  • 16位分辨率、无失码

  • 吞吐速率:250 kSPS

  • 积分非线性(INL):典型值±0.6 LSB,最大值±2 LSB(FSR的±0.003%)

  • 信纳比(SINAD):93.5 dB(20 kHz时)

  • 总谐波失真(THD):-110 dB(20 kHz时)

  • 伪差分模拟输入范围:0 V至 VREF(VREF最高为VDD)

  • 无流水线延迟

  • 单电源工作:2.3 V至5.5 V,逻辑接口电压:1.8 V至5 V

  • 串行接口:SPI®/QSPI™/MICROWIRE™/DSP兼容

  • 以菊花链形式连接多个ADC、忙闲指示功能

  • 功耗
    1.4 μW (2.5 V/100 SPS)
    1.35 mW (2.5 V/100 kSPS)、4 mW (5 V/100 kSPS)

  • 待机电流:1 nA

由于 SPI 最为普遍,所以大多数情况下我们都会使用 SPI 来进行采集,并且其分辨率为 16 位,意味着我们可以将 SPI 的数据长度设置为 16 位,通过 1 次数据交换就能获取到完整的采样数据。同时其吞吐速率为 250kSPS, 这就代表最快的采样间隔为 4 us。

有了以上要点,我们就得到了最基本的软件采样逻辑:

使用 SPI 接口,配置为 16 位数据长度,通过 1 次数据交换获取到采样值,两次采样的间隔需要大于 4 us。

接着我们来看采样的时序部分,此处需要注意,由于 AD7685 本身支持多种 SPI 操作方式,如 3线无BUSY,3线有BUSY,4线无BUSY和4线有BUSY 等,而在没有特殊需求的情况下,我们只需使用最简单的 3线无BUSY 形式即可,因此接下来我们会基于该模式来进行具体的调试。

3线无BUSY 模式的接线图如下:

图片

可以看到,这种模式下 AD7685 与主机只需连接三根线:CNV、SDO 与SCK,剩下的 SDI 则被拉到了高电平。其时序图如下:

图片

很明显,SCK 对应的是 SPI 的 SCK,SDO 对应的是 SPI 的 MISO,而 CNV 则对应的是 SPI 的 CS。然而这里的 CS 与我们日常操作的其他大部分标准 SPI 设备的 CS 略有不同,其在 AD7685 上代表的实际上是一个转换信号,具体的操作逻辑为:

CNV 拉高启动一次转换,经过 tCONV 的时间后转换完成,此时再次拉低 CNV 即进入数据获取阶段,主机通过 16 个时钟脉冲来得到最终的转换数值,数值的最高有效位在前。

这里需要注意两点:

1. 时序图上开始转换后 CNV 存在一个短暂拉低又拉高的状态:

图片

手册中对其的描述如下:

Once a conversion is initiated, it will continue to completion irrespective of the state of CNV. For instance, it could be useful to bring CNV low to select other SPI devices, such as analog multiplexers, but CNV must be returned high before the minimum conversion time and held high until the maximum conversion time to avoid the generation of the BUSY signal indicator.


一旦启动了一次转换,无论 CNV 状态如何变化,转换都会持续进行直到转换完成。因此在这个期间就可以通过拉低 CNV 来选择 SPI 总线上的其他设备。但 CNV 必须在最短转换时间之前回到高电平,并且保持到最长转换时间后从而避免在此期间可能产生的忙信号。

由于我们不存在其他的 SPI 设备,因此这里无需关注 CNV 的拉低操作,在转换期间都保持高电平即可。图中的 tCNVH 为 CNV 转换信号(高电平)的有效保持时间,由于此处我们在整个转换期间都不会改变 CNV 的电平状态,因此同样无需关注这个参数。

2. 根据时序图,我们还可以发现数据的最高位由 CNV 的拉低信号触发,中间会有一个 tEN 的延迟,而后每一个新数据位的产生会由后续 SCK 的拉低信号触发并经过 tDSDO 的延迟输出到数据线上,并且由时序图可以看出数据在时钟的两个边沿都是有效的,即理论上可以配置为数据在 SPI 时钟上升沿有效,也可以配置为数据在 SPI 下降沿有效。这一点在手册中有这么一段描述:

The data is valid on both SCK edges. Although the rising edge can be used to capture the data, a digital host using the SCK falling edge will allow a faster reading rate provided it has an acceptable hold time.


数据在 SCK 的两个边沿都有效。不过尽管上升沿也能够捕获数据,但如果主机使用下降沿来获取数据,在数据保持时间可接受的情况下,读取速率可以更快。

这里的数据保持时间指的是时钟下降沿开始原数据位还没有更新的时间,在时序图中也就是 tHSDO。也就是说只要这个时间能够满足主机 SPI 的通讯要求,那么更推荐使用下降沿读取数据,其可以满足更快的数据读取速率。

至此我们的采集逻辑就较明确了,不过在正式开始编程前我们还需要确认上文中出现的几个重要时间参数,手册专门列了时间参数的表格,并且不同电源电压下参数也不同,这里截取 2.3V~4.5V 范围下的时间参数表:

图片

我们比较关注的是 tCONV,tCYC,tEN,tSCK 和 tHSDO。

tCONV 为转换时间,也就是 CNV 上升沿到数据有效的时间,通过表格可知这段时间最短为 0.7us,最长为 3.2us,因此为了保证数据的有效性和稳定性,实际在 CNV 拉高后会等待 > 3.2us 后再拉低进行数据读取。

tCYC 为两次转换之间的间隔,根据表格这里最短为 5us,也就是说两次采集的间隔不得少于 5us,这一点也与上文对吞吐量的分析是相近的(如果供电电压为 4.5V~5.5V,则可以达到极限 4us,这与上文吞吐量是一致的)。

tEN 为 CNV 拉低后到数据的 D15 位有效的时间,根据表格该值与 VIO 电压有关,区间为 18~25 ns,即 CNV 拉低后必须等待 18~25ns 后取的数据才是有效的,这也与 SPI 的速率有关,由于我们是时钟下降沿获取数据,这就要求 CNV 拉低和第一个时钟下降沿之间的间隔必须大于 18~25ns,当然大多数情况下我们的 SPI 速率不会很高,并且本身操作 CNV 信号与启动 SPI 传输之间也会有一定的延时,所以多数情况下这并不是什么问题。

tSCK 为时钟周期,由于我们只针对读取一个 AD7685 的情况,所以无需关注表格中的 Chain Mode,因此此处参考值就是 25ns,所以理论的最高时钟频率为 40M,当然实际我们使用 MCU 读取时也不会达到这么高的频率,因此这也不会有什么问题。

tHSDO 为 SCK 下降沿后的数据保持时间,由于我们是下降沿读数据,因此需要保证我们的采集主机能够接受这个时间,此处表格列出的时间为 5ns,也就意味着主机需要在下降沿后的 5ns 之前完成数据的采集,否则得到的数据就会错位,这一点可能各主机间会存在差异,而我所使用的 GD32F310 实测是满足条件的,如果所选主机不满足这一点,则需要修改 SPI 数据为时钟的上升沿采集模式。

最后,我们的采集逻辑就很清晰了:

使用 SPI 接口,配置为 16 位数据长度,时钟空闲为低电平,第二个时钟沿有效,数据传输为 MSB 模式。CNV 初始为高电平,读取数据时先将其拉低再拉高创造一个上升沿,进入转换模式,延时 > 3.2us 后将其拉低进入数据获取模式,接着通过 SPI 接口读取一个数据单位(16位)的数据,最后将 CNV 拉高结束一次数据读取操作。

整体驱动测试代码如下(基于 GD32F310,内部时钟72M,SPI 9M):

/***************************************************************
 * @file           ad7685.c
 * @brief
 * @author         WKJay
 * @Version
 * @Date           2024-05-13
 ***************************************************************/
#include "gd32f3x0.h"
uint16_t ad7685_data;

void ad7685_spi_rcu_config(void) {
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_SPI0);
}

void ad7685_spi_gpio_config(void) {
    // SPI0 GPIO config: SCK/PA5, MISO/PA6
    gpio_af_set(GPIOA, GPIO_AF_0, GPIO_PIN_5 | GPIO_PIN_6);
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_5 | GPIO_PIN_6);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_6);

    // SPI0 GPIO config: CNV/PA4
    gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_4);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
    gpio_bit_reset(GPIOA, GPIO_PIN_4);

    // SPI0 GPIO config: MOSI/PA7 保持高电平
    gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_7);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
    gpio_bit_set(GPIOA, GPIO_PIN_7);
}

void ad7685_spi_config(void) {
    spi_parameter_struct spi_init_struct;

    spi_i2s_deinit(SPI0);
    spi_struct_para_init(&spi_init_struct);

    spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
    spi_init_struct.device_mode = SPI_MASTER;
    spi_init_struct.frame_size = SPI_FRAMESIZE_16BIT;
    spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_2EDGE;
    spi_init_struct.nss = SPI_NSS_SOFT;
    spi_init_struct.prescale = SPI_PSC_4;
    spi_init_struct.endian = SPI_ENDIAN_MSB;

    spi_init(SPI0, &spi_init_struct);
}

void ad7685_spi_init(void) {
    ad7685_spi_rcu_config();
    ad7685_spi_gpio_config();
    ad7685_spi_config();
    spi_enable(SPI0);
}

void ad7685_read_data(void) {
    int i = 0;
    // 启动转换, PA4: CNV
    gpio_bit_reset(GPIOA, GPIO_PIN_4);
    gpio_bit_set(GPIOA, GPIO_PIN_4);
    for (i = 0; i < 70; i++) {
        // 不要开优化,此处约为 4 us
    }
    gpio_bit_reset(GPIOA, GPIO_PIN_4);

    // 数据读取
    spi_i2s_data_transmit(SPI0, 0xFFFF);
    while (RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE));
    while (RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE));
    ad7685_data = spi_i2s_data_receive(SPI0);

    gpio_bit_set(GPIOA, GPIO_PIN_4);
}

实际运行的时序波形如下:

图片

读到的数据为 0x7E3C,对应十进制为 32316,测到的模拟量为最大量程的 0.49左右,与我实际的电压大小是一致的,因此可以判断数据读取是正常的。

完整可编译运行的工程已上传到 Gitee,链接如下:

https://gitee.com/wangjunjie997/ad7685_gd32f310

感兴趣的小伙伴可以自行下载源码研究。

—— The  End ——

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WKJay_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值