ad5764的spi菊花链通信

有一个模拟板子上有4个dac芯片,2颗ad5762和2颗ad5764,最终使用了mcu的硬件spi做菊花链通信。
dac原理图
dac原理图
mcu原理图
可以看到mcu的spi sdo进入到u29 ad5762的sdin后,u29 ad5762的sdo进入到u57 ad5762的sdi,依次类推,最后从u69 ad5764的sdo进入到mcu的sdin中,完成一个环。而ad5762与ad5764的所有LDAC都是并联的,公用mcu上的一个pin脚。
spi菊花链原理图
菊花链的部分还是要多看芯片手册。
LDAC的作用
从图中可以看到spi第一个发送的bit是db23,也就是MSB在前,同时SDO线上标明了INPUT WORD FOR DAC N,是什么意思呢?板子上有4颗dac芯片,就是说spi第1次发送的数据其实是给第4颗芯片的,spi第4次发送的数据才是给第1颗芯片的。第几颗芯片跟原理图的连线有关系,需要自己根据实际情况判断。
菊花链时序
这里可以看到有一个t15时间,极短,但是很重要。还有LDAC的t10时间,也非常重要。在芯片手册中会有对时间的最大最小值说明。
关键时间
首先初始化芯片的reset pin和ldac pin

static void dac_init_ldac_reset_pin(void)
{
    // dac reset pin
    GPIO_SetMode(PA, BIT14, GPIO_PMD_OUTPUT);
    SYS->GPA_MFP &= (~(1 << 14));
    // dac ldac pin
    GPIO_SetMode(PA, BIT15, GPIO_PMD_OUTPUT);
    SYS->ALT_MFP &= (~(1 << 9));
    SYS->GPA_MFP &= (~(1 << 15));
    LDAC = PIN_HIGH;
}

紧接着对mcu的spi接口进行初始化,并对ad5762、ad5764的寄存器进行初始化,相关寄存器就是量程、offset与gain。这其中offset与gain的值直接影响了dac的输出精度。在我之前的文章中有提到如何校准没有offset和gain寄存器的adc芯片。如何设置offset与gain的方法在芯片手册写的很详细了,在此就不做说明了。

void hw_ad5764_init(void)
{
    dac_init_ldac_reset_pin();
    CLK_SetModuleClock(SPI1_MODULE, CLK_CLKSEL1_SPI1_S_HCLK, MODULE_NoMsk);
    CLK_EnableModuleClock(SPI1_MODULE);
    // PC8 -> CS
    SYS->GPC_MFP |= (1 << 8);
    SYS->ALT_MFP1 &= (~(1 << 23));
    // PC9 -> SCLK
    SYS->GPC_MFP |= (1 << 9);
    // PC10 -> MISO
    SYS->GPC_MFP |= (1 << 10);
    // PC11 -> MOSI
    SYS->GPC_MFP |= (1 << 11);
    RSTIN = 0;
    delay_100ms(1);
    RSTIN = 1;
    SYS_ResetModule(SPI1_RST);
    SPI_Open(SPI1, SPI_MASTER, SPI_MODE_1, 24, 4000000); // send 24bit once, spi frequence 4MHz
    SPI1->CNTRL &= (~(0x0f << 12));
    SPI_DisableFIFO(SPI1);
    SPI_DisableAutoSS(SPI1);    // nuc123 spi autoss error
    init_dac_range_reg(spi_ad5764.send_array, DAC_A);
    init_dac_range_reg(spi_ad5764.send_array, DAC_B);
    init_dac_range_reg(spi_ad5764.send_array, DAC_C);
    init_dac_range_reg(spi_ad5764.send_array, DAC_D);
	init_dac_offset_reg(&t1, &spi_ad5764);
    init_dac_gain_reg(&t1, &spi_ad5764);
    init_dac_data_reg(spi_ad5764.send_array, 0x8000);
    memset(spi_ad5764.send_array, 0, sizeof(spi_ad5764.send_array));
}

这里我使用的mcu是新唐公司的NUC123SD4AN0,spi接口没有使用中断模式,根据芯片手册可以知道,ad5762、ad5764的寄存器是24bit的,那么在初始化spi接口时,需要将一次发送的bit长度设置为24,但是spi接口发送的变量一定是32bit的,也就是uint32_t类型,这并不妨碍spi接口一次只发送24bit,这一点在NUC123.h文件中有说明。
TX寄存器说明
spi接口的速度我设置的是4MHz,已经很快了,而ad5762和ad5764的最大时钟是16MHz,目前我还用不到。
在使用NUC123SD4AN0的时候,我发现AutoSS(将片选pin交给硬件管理)并不能正常通信,当软件控制片选pin的时候则是正常的,这个bug我并未解决。
最后需要实现spi的读写函数,对ad5762和ad5764来说,我们只需要往里写值让dac芯片输出就行了,其实返回值是可以不要的。

void hw_spi_read_write(uint32_t *send_data, uint32_t *receice_data, uint8_t size)
{
    SPI_SET_SS0_LOW(SPI1);
    for (uint8_t i = 0; i < size; i++)
    {
        SPI_WRITE_TX0(SPI1, send_data[i]);
        SPI_TRIGGER(SPI1);
        while (SPI_IS_BUSY(SPI1));
        receice_data[i] = SPI_READ_RX0(SPI1);
    }
    SPI_SET_SS0_HIGH(SPI1);
    LDAC = PIN_LOW;
    Delay(1);
    LDAC = PIN_HIGH;
}

这里,在函数中拉低和拉高SS0 pin(也就是片选pin)。LDAC pin的拉低拉高中间要放一个延时,这一点在前面说菊花链时序的时候已经做了说明。
在ladc pin初始化函数中,把LDAC拉高了,有一句话是LDAC = PIN_HIGH,因此在spi的读写函数中,LDAC第一次一定是拉低,让spi发送到dac芯片的数据生效,然后延时,最后把LDAC拉高,让dac寄存器的值保持到下次更新之前。这一点可以从菊花链的时序中看的很清楚。在之前的博文中有说到不使用菊花链模式,也就是读写单颗芯片,那么LDAC就不需要经常拉低拉高了,在mcu初始化LDAC pin之后,一直拉低就行了,这样每次写入到ad5762数据寄存器的值就立即生效了。
这里因为板子上有4颗dac芯片,2颗ad5762和2颗ad5764,所以spi一次发送的数据是4*24bit,也就是说send_data数组的长度是4。

typedef struct DAC_SEND_ARRAY
{
    uint32_t send_array[4];//AB
    uint32_t send_cd_array[4];//CD
    uint32_t receive_array[4];
}ad_5764_spi;

至此,菊花链通信的逻辑就完成了。下面是一些封装函数。

uint32_t daisy_chain_writecoarsegainregister(uint32_t dac_ch, uint8_t range)
{
    return WRITE + REG_COARSE_GAIN + dac_ch + range;
}
uint32_t daisy_chain_writefinegainregister(enum lcc_ch_num ch, enum lcc_ch_abcd abcd, LCC6_EQUIP_T *t)
{
    uint32_t result = 0;
    switch (ch)
    {
    case ch1:
        if (abcd == a)
        {
            result = WRITE + REG_FINE_GAIN + DAC_A + t->r1[0].gain_A;
        }
        else if (abcd == b)
        {
            result = WRITE + REG_FINE_GAIN + DAC_B + t->r1[0].gain_B;
        }
        break;
    case ch2:
        if (abcd == a)
        {
            result = WRITE + REG_FINE_GAIN + DAC_A + t->r1[1].gain_A;
        }
        else if (abcd == b)
        {
            result = WRITE + REG_FINE_GAIN + DAC_B + t->r1[1].gain_B;
        }
        break;
    case ch3:
        if (abcd == a)
        {
            result = WRITE + REG_FINE_GAIN + DAC_A + t->r2[0].gain_A;
        }
        else if (abcd == b)
        {
            result = WRITE + REG_FINE_GAIN + DAC_B + t->r2[0].gain_B;
        }
        break;
    case ch4:
        if (abcd == c)
        {
            result = WRITE + REG_FINE_GAIN + DAC_C + t->r2[0].gain_C;
        }
        else if (abcd == d)
        {
            result = WRITE + REG_FINE_GAIN + DAC_D + t->r2[0].gain_D;
        }
        break;
    case ch5:
        if (abcd == a)
        {
            result = WRITE + REG_FINE_GAIN + DAC_A + t->r2[1].gain_A;
        }
        else if (abcd == b)
        {
            result = WRITE + REG_FINE_GAIN + DAC_B + t->r2[1].gain_B;
        }
        break;
    case ch6:
        if (abcd == c)
        {
            result = WRITE + REG_FINE_GAIN + DAC_C + t->r2[1].gain_C;
        }
        else if (abcd == d)
        {
            result = WRITE + REG_FINE_GAIN + DAC_D + t->r2[1].gain_D;
        }
        break;
    default:
        break;
    }
    return result;
}

uint32_t daisy_chain_writeoffsetregister(enum lcc_ch_num ch, enum lcc_ch_abcd abcd, LCC6_EQUIP_T *t)
{
    uint32_t result = 0;
    switch (ch)
    {
    case ch1:
        if (abcd == a)
        {
            result = WRITE + REG_OFFSET + DAC_A + t->r1[0].offset_A;
        }
        else if (abcd == b)
        {
            result = WRITE + REG_OFFSET + DAC_B + t->r1[0].offset_B;
        }
        break;
    case ch2:
        if (abcd == a)
        {
            result = WRITE + REG_OFFSET + DAC_A + t->r1[1].offset_A;
        }
        else if (abcd == b)
        {
            result = WRITE + REG_OFFSET + DAC_B + t->r1[1].offset_B;
        }
        break;
    case ch3:
        if (abcd == a)
        {
            result = WRITE + REG_OFFSET + DAC_A + t->r2[0].offset_A;
        }
        else if (abcd == b)
        {
            result = WRITE + REG_OFFSET + DAC_B + t->r2[0].offset_B;
        }
        break;
    case ch4:
        if (abcd == c)
        {
            result = WRITE + REG_OFFSET + DAC_C + t->r2[0].offset_C;
        }
        else if (abcd == d)
        {
            result = WRITE + REG_OFFSET + DAC_D + t->r2[0].offset_D;
        }
        break;
    case ch5:
        if (abcd == a)
        {
            result = WRITE + REG_OFFSET + DAC_A + t->r2[1].offset_A;
        }
        else if (abcd == b)
        {
            result = WRITE + REG_OFFSET + DAC_B + t->r2[1].offset_B;
        }
        break;
    case ch6:
        if (abcd == c)
        {
            result = WRITE + REG_OFFSET + DAC_C + t->r2[1].offset_C;
        }
        else if (abcd == d)
        {
            result = WRITE + REG_OFFSET + DAC_D + t->r2[1].offset_D;
        }
        break;
    default:
        break;
    }
    return result;
}

uint32_t daisy_chain_writedataregister(uint32_t dac_ch, uint16_t data)
{
    return WRITE + REG_DATA + dac_ch + data;
}

static void init_dac_range_reg(uint32_t *array, uint32_t ch)
{
    array[0] = daisy_chain_writecoarsegainregister(ch, RANGE_10V);
    array[1] = array[0];
    if (ch == DAC_A || ch == DAC_B)
    {
        array[2] = array[0];
        array[3] = array[0];
    }
    else
    {
        array[2] = 0;
        array[3] = 0;
    }
    hw_spi_read_write(array, spi_ad5764.receive_array, 4);
}

static void init_dac_offset_reg(LCC6_EQUIP_T *t, ad_5764_spi *array)
{
    array->send_array[3] = daisy_chain_writeoffsetregister(ch1, a, t);
    array->send_array[2] = daisy_chain_writeoffsetregister(ch2, a, t);
    array->send_array[1] = daisy_chain_writeoffsetregister(ch3, a, t);
    array->send_array[0] = daisy_chain_writeoffsetregister(ch5, a, t);
    hw_spi_read_write(array->send_array, array->receive_array, 4);
    array->send_array[3] = daisy_chain_writeoffsetregister(ch1, b, t);
    array->send_array[2] = daisy_chain_writeoffsetregister(ch2, b, t);
    array->send_array[1] = daisy_chain_writeoffsetregister(ch3, b, t);
    array->send_array[0] = daisy_chain_writeoffsetregister(ch5, b, t);
    hw_spi_read_write(array->send_array, array->receive_array, 4);
    array->send_array[3] = 0;
    array->send_array[2] = 0;
    array->send_array[1] = daisy_chain_writeoffsetregister(ch4, c, t);
    array->send_array[0] = daisy_chain_writeoffsetregister(ch6, c, t);
    hw_spi_read_write(array->send_array, array->receive_array, 4);
    array->send_array[3] = 0;
    array->send_array[2] = 0;
    array->send_array[1] = daisy_chain_writeoffsetregister(ch3, d, t);
    array->send_array[0] = daisy_chain_writeoffsetregister(ch5, d, t);
    hw_spi_read_write(array->send_array, array->receive_array, 4);
}

static void init_dac_gain_reg(LCC6_EQUIP_T *t, ad_5764_spi *array)
{
    array->send_array[3] = daisy_chain_writefinegainregister(ch1, a, t);
    array->send_array[2] = daisy_chain_writefinegainregister(ch2, a, t);
    array->send_array[1] = daisy_chain_writefinegainregister(ch3, a, t);
    array->send_array[0] = daisy_chain_writefinegainregister(ch5, a, t);
    hw_spi_read_write(array->send_array, array->receive_array, 4);
    array->send_array[3] = daisy_chain_writefinegainregister(ch1, b, t);
    array->send_array[2] = daisy_chain_writefinegainregister(ch2, b, t);
    array->send_array[1] = daisy_chain_writefinegainregister(ch3, b, t);
    array->send_array[0] = daisy_chain_writefinegainregister(ch5, b, t);
    hw_spi_read_write(array->send_array, array->receive_array, 4);
    array->send_array[3] = 0;
    array->send_array[2] = 0;
    array->send_array[1] = daisy_chain_writefinegainregister(ch4, c, t);
    array->send_array[0] = daisy_chain_writefinegainregister(ch6, c, t);
    hw_spi_read_write(array->send_array, array->receive_array, 4);
    array->send_array[3] = 0;
    array->send_array[2] = 0;
    array->send_array[1] = daisy_chain_writefinegainregister(ch3, d, t);
    array->send_array[0] = daisy_chain_writefinegainregister(ch5, d, t);
    hw_spi_read_write(array->send_array, array->receive_array, 4);
}

void init_dac_data_reg(uint32_t *array, uint16_t data)
{
    array[0] = daisy_chain_writedataregister(DAC_A, data);
    for (uint8_t i = 1; i < 4; i++)
    {
        array[i] = array[0];
    }
    hw_spi_read_write(array, spi_ad5764.receive_array, 4);
    array[0] = daisy_chain_writedataregister(DAC_B, data);
    for (uint8_t i = 1; i < 4; i++)
    {
        array[i] = array[0];
    }
    hw_spi_read_write(array, spi_ad5764.receive_array, 4);
    array[0] = daisy_chain_writedataregister(DAC_C, data);
    array[1] = array[0];
    array[2] = 0;
    array[3] = 0;
    hw_spi_read_write(array, spi_ad5764.receive_array, 4);
    array[0] = daisy_chain_writedataregister(DAC_D, data);
    array[1] = array[0];
    hw_spi_read_write(array, spi_ad5764.receive_array, 4);
}

如果还是不明白,请下载我上传的源码吧。链接如下:
https://download.csdn.net/download/cp_srd/13199314

一、电路特性 本电路为高速、高精度、同步采样模数转换应用提供电流隔离,如图1所示。16位PulSAR ADC AD7685 是一款多功能器件,支持通过菊花链监控多个通道。基于运算放大器 AD8615的输入电路对±10 V工业信号进行电平转换、衰减和缓冲,以满足ADC的输入要求。这个灵活的电路包括一个精密基准电压源ADR391 和两个四通道数字隔离器ADuM1402 ,以便为常见的工业数据采集应用提供紧凑且具成本效益的解决方案。 图1 二、电路描述 AD7685是一款16位、电荷再分配、逐次逼近型(SAR)模数转换器(ADC),采用2.3 V至5.5 V单电源(VDD)供电。它内置一个低功耗、高速、16位无失码、无流水线延迟的采样ADC、一个内部转换时钟和一个多功能串行接口端口,还集成了一个低噪声、宽带宽、短孔径延迟的采样保持电路。在CNV上升沿,它对IN+与IN-之间的模拟输入电压差进行采样,范围从0 V至VREF。基准电压VREF由外部提供,范围从0.5 V至VDD。图1所示电路采用4.5 V基准电压。 AD7685的功耗与采样速率成线性比例关系。VDD = 5 V且采样速率为250 kSPS时,最大功耗为15 mW。AD7685采用10引脚MSOP封装或10引脚QFN(LFCSP)封装,工作温度范围为−40°C至+85°C。SPI兼容串行接口还能够利用SDI输入,将几个ADC以菊花链形式连结到单条三线式总线上,或提供一个可选的繁忙(BUSY)指示功能。它采用独立的VIO电源引脚,与1.8V、2.5V、3V或5V逻辑兼容。 完整的模拟信号链采用5 V单电源供电。低压差2.5 V基准电压源ADR391和U13轨到轨CMOS运算放大器AD8615,产生ADC所用的4.5 V基准电压。4.5 V基准电压在U13的输出端提供0.5 V裕量,因此,在5 V电源的标称变化范围内,运算放大器始终保持在线性区域内工作。ADR391 2.5 V输出由U13的噪声增益(1 + R4/R5)放大。对于所选的R4和R5值,噪声增益为1.8,因此基准电压为1.8 × 2.5 V = 4.5 V。 U4和U14运算放大器AD8615提供0.225的信号增益(由R1与R2和R19与R20的比值设置),将20 V p-p的输入信号幅度降至ADC输入端的4.5 V p-p。对于0 V输入,U4和U14的输出端需要2.25 V偏移,这就要求U4和U14同相输入端的共模电压为1.84 V,该电压由电阻分压器R3-R6产生。 U4和U14输出端的R-C网络(33 Ω、2.7 nF)形成一个带宽为1.8 MHz的单极点噪声滤波器。 AD8615是一款CMOS轨到轨输入和输出、单电源放大器,具有极低失调电压、宽信号带宽以及低输入电压和电流噪声等特性。该器件采用DigiTrim:registered:专利调整技术,无需激光调整便可达到出色的精度。AD8615采用2.7 V至5 V单电源供电。 20 MHz以上的带宽、低失调、低噪声和低输入偏置电流特性的结合,使该放大器适合各种应用。滤波器、积分器、光电二极管放大器和高阻抗传感器等器件均可受益于这些特性组合。宽带宽和低失真特性则有益于交流应用。在DigiTrim系列中,AD8615提供最高的输出驱动能力,是音频线路驱动器和其它低阻抗应用的理想选择。 该器件的具体应用包括:便携式和低功耗仪器仪表、便携式设备的音频放大、便携式电话耳机、条形码扫描器以及多极点滤波器。它还具有轨到轨输入与输出摆幅能力,因而设计人员可以在单电源系统中缓冲CMOS ADC、DAC、ASIC及其它宽输出摆幅器件。 ADR391是一款微功耗、低压差基准电压源,可利用仅比输出电压高出300 mV的电源提供稳定的输出电压。先进的设计无需外部电容,可进一步节省电路板空间、降低成本。ADR391精密基准电压源具有低功耗、小尺寸和易于使用的特点,非常适合电池供电应用。该器件利用ADI公司的温度漂移曲率校正专利技术,可在TSOT封装中实现9 ppm/°C的低温漂特性。 ADuM1402是一款采用ADI公司iCoupler:registered:技术的四通道数字隔离器。该隔离器件将高速CMOS与单芯片空芯变压器技术融为一体,具有优于光耦合器等替代器件的出色性能特征。 iCoupler 器件不用LED和光电二极管,因而不存在一般与光耦合器相关的设计困难。简单的iCoupler数字接口和稳定的性能特征,可消除光耦合器通常具有的电流传输比不确定、非线性传递函数以及温度和使用寿命影响等问题。 这些iCoupler产品不需要外部驱动器和其它分立器件。此外,在信号数据速率相当的情况下,iCoupler器件的功耗只有光耦合器的1/10至1/6。 ADuM1402隔离器提供四个独立的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值