875|2

574

帖子

11

TA的资源

一粒金砂(高级)

楼主
 

【DigiKey创意大赛】多通道微型气相色谱采集单元-3. AD7606C-18驱动开发 [复制链接]

  本帖最后由 sunduoze 于 2024-1-6 16:13 编辑

AD7606C-18驱动开发

        首先,先了解一下AD7606C内部的结构。框图的左上角部分为模拟输入部分,输入具有±21V的钳位保护,接着为全差分输入放大器FDA,同时兼有可编程增益PG功能(内部是通过MUX切换不同的反馈电阻R_F来进行信号衰减或放大),接下来进入低通滤波器送入SAR ADC内核,实际参考AD7606来分析,经过低通滤波器后再进入采样保持器S/H,然后再通过MUX切入到ADC内核中。框图左下方为基准部分,包含了通过REF SELECT控制切换外部和内部基准的功能,里边的放大器(同相端、反相端符号标记反了?我表示很难理解。如果反过来,一个同相比例放大倒是能说的通)将2.5V基准电压放大到4.4V再送到ADC内核,实际鉴于板卡集成了优于内部的低噪声的外部基准ADR4525B,甚好甚好。整个框图右侧为数字核心,包含了通讯接口等等配置,此处不进行赘述。
接下来,阐述一下目前的硬件配置。
OSx: all high -> software mode ;ADC mode and register mode are available
RANGE: high -> hardware mode config as ±10 Single-Ended
P_A_R/SER: high -> Serial mode
W_R_:high
S_T_B_Y_: high -> normal mode
DB2-DB4: AGND
DB14-DB17: AGND
VDRIVE: Logic Power Supply Input,与MCU等IO相同电平,拉MCU的VDD到此
REF SELECT: low->外部
----------------------------------------------------------------------------------------------------
RESET: 接MCU I/O, 默认给低
CONVST: 接MCU I/O, _|-当CONVST引脚从低电平转换为高电平时,ADC对模拟输入进行采样
BUSY: 接MCU I/O, ADC转换完成时拉低
FRSTDATA: 接MCU I/O, 指示第一个通道V1正在回读(第18个时钟边沿后拉低)
SPI配置为:模式2 or 模式0
R_D_/SCLK: SPI-SCLK
DB9/DoutA:SPI-MISO
DB13/SDI:  SPI-MOSI
C_S_:       SPI-CS

 

前期调试过程中为了硬件连接相对简单,用了尽可能少的引脚,故采用SPI进行串行通讯,注意:SPI配置为:模式2 or 模式0,并且使用单DOUTx口串行接口的寄存器模式 (P.41)
于是需要写一下CONFIG寄存器来让它切换为1个DOUTx pin
读取ADC数据结果时序 (P.45)
/**
 * @brief   Blocking conversion start and data read.
 *
 * This function performs a conversion start and then proceeds to reading
 * the conversion data.
 *
 * @param data       - Pointer to location of buffer where to store the data.
 *
 * @return   ret - return code.
 *                  0 - No errors encountered.
 */
int32_t AD7606C_Serial::read(int32_t *data)
{
    int32_t ret;
    uint8_t busy;
    uint32_t timeout = tconv_max[AD7606_OSR_256];
    ipulse(_CONVST);
    /* Wait for BUSY falling edge */
    while (timeout)
    {
        if (digitalRead(_BUSY) == 0)
            break;
        delayMicroseconds(1);
        timeout--;
    }
    if (timeout == 0)
    {
        Serial.printf("[error]timeout\r\n");
        return 1;
    }
    return data_read(data);
}
/**
 * @brief Read conversion data.
 *
 * If the status is enabled in device settings, each sample of data will contain
 * status information in the lowest 8 bits.
 *
 * The output buffer provided by the user should be as wide as to be able to
 * contain 1 sample from each channel since this function reads conversion data
 * across all channels.
 *
 * @param data       - Pointer to location of buffer where to store the data.
 *
 * @return ret       - return code.
 *         Example: 1 - xxx error.
 *                  0 - No errors encountered.
 */
uint8_t AD7606C_Serial::data_read(int32_t *data)
{
    uint8_t ret;
    uint32_t sz;
    int32_t i;
    uint16_t crc, icrc;
    uint8_t bits = 18;
    uint8_t sbits = data_inc_status ? 8 : 0;
    uint8_t nchannels = 8;
    uint32_t data_temp[DATA_SIZE];
    uint8_t data_buf[DATA_SIZE] = {0x00};
    sz = nchannels * (bits + sbits);
    /* Number of bits to read, corresponds to SCLK cycles in transfer.
     * This should always be a multiple of 8 to work with most SPI's.
     * With this chip family this holds true because we either:
     *  - multiply 8 channels * bits per sample
     *  - multiply 4 channels * bits per sample (always multiple of 2)
     * Therefore, due to design reasons, we don't check for the
     * remainder of this division because it is zero by design.
     */
    sz /= 8; // 不包含status时为18个
    memset(data_buf, 0, sz);
    digitalWrite(hspi->pinSS(), LOW); // pull SS slow to prep other end for transfer
    portDISABLE_INTERRUPTS();
    for (uint8_t i = 0; i < sz; i++)
    {
        data_buf[i] = hspi->transfer(0x00);
    }
    portENABLE_INTERRUPTS();
    digitalWrite(hspi->pinSS(), HIGH); // pull ss high to signify end of data transfer
    switch (bits)
    {
    case 18:
        if (data_inc_status)
        {
            ret = cpy26b32b(data_buf, sz, data_temp);
            // TODO:
        }
        else
        {
            ret = cpy18b32b(data_buf, sz, data_temp);
            convert_18bit_to_32bit((int32_t *)data_temp, 8, data);
        }
        if (ret)
            return ret;
        break;
    default:
        ret = 1;
        break;
    };
    return ret;
}

 

串行寄存器模式下 读取寄存器数据时序(P.47)
 
 
read command读取命令包含2组16bits帧
**第1帧:**
  1. [0:0] W_E_N_ 0:使能地址写入
  2. [1:1] R/W_ 1:选择读命令
  3. [3:8] Register address 将要被读出的寄存器地址
  4. [9:15] 没卵用
**第2帧:**
寄存器内容
* 如果AD7606C-18处于 ADC mode,Doutx线将在[9:16]送出ADC数据,并且切换为Register mode 寄存器模式
* 如果AD7606C-18处于 Register mode,无论前一帧是读取还是写入命令,Doutx线读回之前寻址寄存器的内容。通过保持SDI拉低16 SCLK cycle将退出Register mode寄存器模式(图111,图示8个cycle貌似就可以退出)
#define NO_OS_BIT(x) (1 << (x))
#define AD7606_RD_FLAG_MSK(x) (NO_OS_BIT(6) | ((x) & 0x3F))
#define AD7606_WR_FLAG_MSK(x) ((x) & 0x3F)
/**
 * @brief Read a device register via SPI.
 *
 * This function performs CRC8 computation and checking if enabled in the device.
 *
 * @param reg_addr   - Register address in device memory.
 * @param reg_val    - Pointer to the location where to store the register value.
 *
 * @return ret - return code.
 * @note   ad7606c-18 ds P.47
 */
uint8_t AD7606C_Serial::read_reg(uint8_t reg_addr, uint8_t *reg_val)
{
    static uint16_t data_temp;
    static uint8_t ret;
    // data_temp = (0x2 | (reg_addr & 0x3F) << 2) << 8;// 0x3F is mask 0bxx111111 shit!!!
    data_temp = AD7606_RD_FLAG_MSK(reg_addr) << 8;
    // Serial.printf("[debug]data_temp:0x%x\r\n", data_temp);
    hspi->beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE2));
    digitalWrite(hspi->pinSS(), LOW);  // pull SS slow to prep other end for transfer
    hspi->transfer16(data_temp);       // 发送第1帧
    digitalWrite(hspi->pinSS(), HIGH); // pull ss high to signify end of data transfer
    delayMicroseconds(1);
    digitalWrite(hspi->pinSS(), LOW);              // pull SS slow to prep other end for transfer
    *reg_val = hspi->transfer16(data_temp) & 0xFF; // 发送第2帧(与第1帧内容相同) 并且取低8位为寄存器内容
    digitalWrite(hspi->pinSS(), HIGH);             // pull ss high to signify end of data transfer
    hspi->endTransaction();
    // Serial.printf("[debug]data_temp:0x%4x\r\n", debug);
    return ret;
}

 

串行寄存器模式下 写入寄存器数据(P.48)
 
写入寄存器序列需要退出ADC模式(读取任意寄存器),write command写入命令包含1组16bits帧
  1. [0:0] W_E_N_ 0:使能地址写入
  2. [1:1] R/W_ 0:选择写命令
  3. [3:8] Register address 将要被写入的寄存器地址
  4. [9:15] 被写入地址的寄存器的内容(**SDI的数据是在SCLK的下降沿送入的;DoutA的数据是在SCLK的上升沿送出的**)
向设备连续写入数据时,DoutA上显示的数据来自上一帧写入的寄存器地址。
在寄存器模式下,不会输出 ADC 数据,因为 DOUTx 线路用于时钟输出寄存器内容。写入所有所需寄存器后,保持 SDI 线路低电平16个 SCLK 周期,AD7606C-18 将返回 ADC 模式,此时 ADC 数据再次通过 DOUTx 线路时钟输出。
在软件模式下,打开 CRC 时,每个帧会有八个额外的位在每个帧上进行时钟输入和输出。因此,需要24位帧。
/**
 * @brief Write a device register via SPI.
 *
 * This function performs CRC8 computation and checking if enabled in the device.
 *
 * @param reg_addr   - Register address in device memory.
 * @param reg_data   - Value to write to register.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -ENOTSUP - Device not in software mode.
 *                  -EBADMSG - CRC computation mismatch.
 *                  0 - No errors encountered.
 */
uint8_t AD7606C_Serial::write_reg(uint8_t reg_addr, uint8_t reg_val)
{
    static uint16_t data_temp;
    static uint8_t ret;
    static uint8_t debug;
    /* Dummy read to place the chip in register mode. */
    read_reg(reg_addr, &debug);
    data_temp = AD7606_WR_FLAG_MSK(reg_addr) << 8 | (reg_val & 0xFF);
    // Serial.printf("[debug]data_temp:0x%x\r\n", data_temp);
    hspi->beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE2));
    digitalWrite(hspi->pinSS(), LOW);           // pull SS slow to prep other end for transfer
    debug = hspi->transfer16(data_temp) & 0xFF; // 发送第1帧
    digitalWrite(hspi->pinSS(), HIGH);          // pull ss high to signify end of data transfer
    hspi->endTransaction();
    // Serial.printf("[debug]data_temp:0x%4x\r\n", debug); // debug 为连续写入过程中上一次寄存器的地址
    return ret;
}

 

逻辑分析仪抓取时序:
不包含状态位的数据读取:
 
### AD7606C-18 极限速度模式(在busy状态下读取数据)
SPI:
SPI串行通讯, only DoutA output data
SPI时钟2MHz
ADC:
OS_PAD = 0
OSR = 256(max)
busy = 255.6us
不包含状态位
 
 
其他麻烦事儿
由于实际使用的限制,导致不同的通道有的配置成单极性模式,有些配置成双极性模式,转换电压时还需要区分不同的配置模式来用不同的转换方式
 
这里给出这部分转换用的代码(真的fan 。。。又耦合了)
void AD7606C_Serial::convert_18bit_to_32bit(int32_t *unsigned_val, int32_t srcsz, int32_t *pdst)
{
    unsigned int i;
    for (i = 0; i < srcsz; i++)
    {                             // #Twos Complement Output Coding
                                  // Bipolar Analog Input Ranges
        if (channel_mode[i] == 0) // BIPOLAR_MODE
        {
            pdst[i] = (unsigned_val[i] & 0x00020000) ? (unsigned_val[i] | 0xFFFC0000) : unsigned_val[i];
        }
        else // UNIPOLAR_MODE
        {
            pdst[i] = unsigned_val[i];
        }
    }
}

 

 

AD7606.zip

5.37 KB, 阅读权限: 5, 下载次数: 4

AD7616C-18 时序.dsl

5.41 KB, 阅读权限: 1, 下载次数: 4

可通过DsView软件打开

最新回复

大佬这作品非常牛,花了不少心思,分享的进度也非常多。   详情 回复 发表于 2024-1-6 18:01
点赞 关注
 
 

回复
举报

574

帖子

11

TA的资源

一粒金砂(高级)

沙发
 

对了,这里补充一下下,实际由于ESP32中跑了FreeRTOS,同时还有其他很多任务,ADC采集过程中偶尔出现数据异常,后来通过在SPI读取数据时,退出临界区(Platform IO下其FreeRTOS有点奇怪,临时使用portDISABLE_INTERRUPTS();来替代)来解决。如果有高手还有其他更好的解决方案帮忙给出,谢谢!

 
 
 

回复

6841

帖子

11

TA的资源

版主

板凳
 

大佬这作品非常牛,花了不少心思,分享的进度也非常多。

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表