【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帧:**
- [0:0] W_E_N_ 0:使能地址写入
- [1:1] R/W_ 1:选择读命令
- [3:8] Register address 将要被读出的寄存器地址
- [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帧
- [0:0] W_E_N_ 0:使能地址写入
- [1:1] R/W_ 0:选择写命令
- [3:8] Register address 将要被写入的寄存器地址
- [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];
}
}
}
|