Maxim Integrated MAX5715 12-bit 4-channel voltage-output DAC

Dependents:   MAX5715BOB_Tester MAX5715BOB_12bit_4ch_SPI_DAC MAX5715BOB_Serial_Tester

MAX5715.cpp

Committer:
whismanoid
Date:
2019-10-30
Revision:
7:3c45adf3efe8
Parent:
5:7894decf9375

File content as of revision 7:3c45adf3efe8:

// /*******************************************************************************
// * Copyright (C) 2019 Maxim Integrated Products, Inc., All Rights Reserved.
// *
// * Permission is hereby granted, free of charge, to any person obtaining a
// * copy of this software and associated documentation files (the "Software"),
// * to deal in the Software without restriction, including without limitation
// * the rights to use, copy, modify, merge, publish, distribute, sublicense,
// * and/or sell copies of the Software, and to permit persons to whom the
// * Software is furnished to do so, subject to the following conditions:
// *
// * The above copyright notice and this permission notice shall be included
// * in all copies or substantial portions of the Software.
// *
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
// * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// * OTHER DEALINGS IN THE SOFTWARE.
// *
// * Except as contained in this notice, the name of Maxim Integrated
// * Products, Inc. shall not be used except as stated in the Maxim Integrated
// * Products, Inc. Branding Policy.
// *
// * The mere transfer of this software does not imply any licenses
// * of trade secrets, proprietary technology, copyrights, patents,
// * trademarks, maskwork rights, or any other form of intellectual
// * property whatsoever. Maxim Integrated Products, Inc. retains all
// * ownership rights.
// *******************************************************************************
// */
// *********************************************************************
// @file MAX5715.cpp
// *********************************************************************
// Device Driver file
// DO NOT EDIT; except areas designated "CUSTOMIZE". Automatically generated file.
// generated by XMLSystemOfDevicesToMBED.py
// System Name = ExampleSystem
// System Description = Device driver example

#include "MAX5715.h"

// Device Name = MAX5715
// Device Description = Ultra-Small, 12-Bit, 4-Channel, Buffered Output Voltage DAC with Internal Reference and SPI Interface
// Device DeviceBriefDescription = 12-bit 4-ch SPI VOUT DAC
// Device Manufacturer = Maxim Integrated
// Device PartNumber = MAX5715AAUD+
// Device RegValue_Width = DataWidth16bit_HL
//
// DAC NumChannels = 4
// DAC ResolutionBits = 12
//
// SPI CS = ActiveLow
// SPI FrameStart = CS
// SPI CPOL = 1
// SPI CPHA = 0
// SPI MOSI and MISO Data are both stable on Falling edge of SCLK
// SPI SCLK Idle High
// SPI SCLKMaxMHz = 50
// SPI SCLKMinMHz = 0
//
// InputPin Name = LDAC#
// InputPin Description = Dedicated Active-Low Asynchronous Load DAC.
// InputPin Function = Trigger
//
// InputPin Name = CLR#
// InputPin Description = Active-Low Clear Input.
// InputPin Function = Trigger
//
// InputPin Name = REF
// InputPin Description = Reference Voltage Input/Output.
//     Software selectable to be external reference or internal 2.048V, 2.500V, or 4.096V reference.
//     Default is external reference mode.
// InputPin Function = Reference
//
// OutputPin Name = OUTA
// OutputPin Description = Buffered Channel A DAC Output
// OutputPin Function = Analog
//
// OutputPin Name = OUTB
// OutputPin Description = Buffered Channel B DAC Output
// OutputPin Function = Analog
//
// OutputPin Name = OUTC
// OutputPin Description = Buffered Channel C DAC Output
// OutputPin Function = Analog
//
// OutputPin Name = OUTD
// OutputPin Description = Buffered Channel D DAC Output
// OutputPin Function = Analog
//
// OutputPin Name = RDY#
// OutputPin Description = SPI RDY Output. In daisy-chained applications connect RDY to the CSB of the next device in the chain.
// OutputPin Function = DaisyChain
//
// SupplyPin Name = VDD
// SupplyPin Description = Supply Voltage Input. Bypass VDD with a 0.1uF capacitor to GND.
// SupplyPin VinMax = 5.5
// SupplyPin VinMin = 2.7 (unless configured DAC VRefInternal = 4.096V, then VinMin = 4.5V)
// SupplyPin Function = Analog
//
// SupplyPin Name = VDDIO
// SupplyPin Description = Digital Interface Power-Supply Input
// SupplyPin VinMax = 5.5
// SupplyPin VinMin = 1.8
// SupplyPin Function = Digital
//

MAX5715::MAX5715(SPI &spi, DigitalOut &cs_pin, // SPI interface
                 DigitalOut &LDACb_pin, // Digital Trigger Input to MAX5715 device
                 DigitalOut &CLRb_pin, // Digital Trigger Input to MAX5715 device
                 // AnalogOut &REF_pin, // Reference Input to MAX5715 device
                 // AnalogIn &OUTA_pin, // Analog Output from MAX5715 device
                 // AnalogIn &OUTB_pin, // Analog Output from MAX5715 device
                 // AnalogIn &OUTC_pin, // Analog Output from MAX5715 device
                 // AnalogIn &OUTD_pin, // Analog Output from MAX5715 device
                 // DigitalIn &RDYb_pin, // Digital DaisyChain Output from MAX5715 device
                 MAX5715_ic_t ic_variant)
    : m_spi(spi), m_cs_pin(cs_pin), // SPI interface
    m_LDACb_pin(LDACb_pin), // Digital Trigger Input to MAX5715 device
    m_CLRb_pin(CLRb_pin), // Digital Trigger Input to MAX5715 device
    // m_REF_pin(REF_pin), // Reference Input to MAX5715 device
    // m_OUTA_pin(OUTA_pin), // Analog Output from MAX5715 device
    // m_OUTB_pin(OUTB_pin), // Analog Output from MAX5715 device
    // m_OUTC_pin(OUTC_pin), // Analog Output from MAX5715 device
    // m_OUTD_pin(OUTD_pin), // Analog Output from MAX5715 device
    // m_RDYb_pin(RDYb_pin), // Digital DaisyChain Output from MAX5715 device
    m_ic_variant(ic_variant)
{
    // SPI CS = ActiveLow
    // SPI FrameStart = CS
    m_SPI_cs_state = 1;
    m_cs_pin = m_SPI_cs_state;

    // SPI CPOL = 1
    // SPI CPHA = 0
    // SPI MOSI and MISO Data are both stable on Falling edge of SCLK
    // SPI SCLK Idle High
    m_SPI_dataMode = 2; //SPI_MODE2; // CPOL=1,CPHA=0: Falling Edge stable; SCLK idle High
    m_spi.format(8,m_SPI_dataMode);         // int bits_must_be_8, int mode=0_3 CPOL=0,CPHA=0

    // SPI SCLKMaxMHz = 50
    // SPI SCLKMinMHz = 0
    //#define SPI_SCLK_Hz 48000000 // 48MHz
    //#define SPI_SCLK_Hz 24000000 // 24MHz
    //#define SPI_SCLK_Hz 12000000 // 12MHz
    //#define SPI_SCLK_Hz 6000000 // 6MHz
    //#define SPI_SCLK_Hz 4000000 // 4MHz
    //#define SPI_SCLK_Hz 2000000 // 2MHz
    //#define SPI_SCLK_Hz 1000000 // 1MHz
    m_SPI_SCLK_Hz = 12000000; // 12MHz; MAX5715 limit is 50MHz
    m_spi.frequency(m_SPI_SCLK_Hz);

    //
    // LDACb Trigger Input to MAX5715 device
    m_LDACb_pin = 1; // output logic high -- initial value in constructor
    //
    // CLRb Trigger Input to MAX5715 device
    m_CLRb_pin = 1; // output logic high -- initial value in constructor
    //
    // REF Reference Input to MAX5715 device
    //
    // OUTA Analog Output from device
    //
    // OUTB Analog Output from device
    //
    // OUTC Analog Output from device
    //
    // OUTD Analog Output from device
    //
    // RDYb DaisyChain Output from device
}

MAX5715::~MAX5715()
{
    // do nothing
}

/// set SPI SCLK frequency
void MAX5715::spi_frequency(int spi_sclk_Hz)
{
    m_SPI_SCLK_Hz = spi_sclk_Hz;
    m_spi.frequency(m_SPI_SCLK_Hz);
}

// Assert SPI Chip Select
// SPI chip-select for MAX5715
//
void MAX5715::SPIoutputCS(int isLogicHigh)
{
    m_SPI_cs_state = isLogicHigh;
    m_cs_pin = m_SPI_cs_state;
}

// SPI write 24 bits
// SPI interface to MAX5715 shift 24 bits mosiData into MAX5715 DIN
//
void MAX5715::SPIwrite24bits(int8_t mosiData8_FF0000, int16_t mosiData16_00FFFF)
{
    size_t byteCount = 3;
    static char mosiData[3];
    static char misoData[3];
    mosiData[0] = mosiData8_FF0000;
    mosiData[1] = (char)((mosiData16_00FFFF >> 8) & 0xFF); // MSByte
    mosiData[2] = (char)((mosiData16_00FFFF >> 0) & 0xFF); // LSByte
    //
    // Arduino: begin critical section: noInterrupts() masks all interrupt sources; end critical section with interrupts()
    //~ noInterrupts();
    //
    //~ digitalWrite(Scope_Trigger_Pin, LOW); // diagnostic Scope_Trigger_Pin
    //
    unsigned int numBytesTransferred = m_spi.write(mosiData, byteCount, misoData, byteCount);
    //~ SPI.transfer(mosiData8_FF0000);
    //~ SPI.transfer(mosiData16_00FF00);
    //~ SPI.transfer(mosiData16_0000FF);
    //
    //~ digitalWrite(Scope_Trigger_Pin, HIGH); // diagnostic Scope_Trigger_Pin
    //
    // Arduino: begin critical section: noInterrupts() masks all interrupt sources; end critical section with interrupts()
    //~ interrupts();
    // Optional Diagnostic function to print SPI transactions
    if (onSPIprint)
    {
        onSPIprint(byteCount, (uint8_t*)mosiData, (uint8_t*)misoData);
    }
    //
    // VERIFY: SPIwrite24bits print diagnostic information
    //cmdLine.serial().printf(" MOSI->"));
    //cmdLine.serial().printf(" 0x"));
    //Serial.print( (mosiData8_FF0000 & 0xFF), HEX);
    //cmdLine.serial().printf(" 0x"));
    //Serial.print( (mosiData16_00FF00 & 0xFF), HEX);
    //cmdLine.serial().printf(" 0x"));
    //Serial.print( (mosiData16_0000FF & 0xFF), HEX);
    // hex dump mosiData[0..byteCount-1]
#if HAS_MICROUSBSERIAL
    cmdLine_microUSBserial.serial().printf("\r\nSPI");
    if (byteCount > 7) {
        cmdLine_microUSBserial.serial().printf(" byteCount:%d", byteCount);
    }
    cmdLine_microUSBserial.serial().printf(" MOSI->");
    for (unsigned int byteIndex = 0; byteIndex < byteCount; byteIndex++)
    {
        cmdLine_microUSBserial.serial().printf(" 0x%2.2X", mosiData[byteIndex]);
    }
    // hex dump misoData[0..byteCount-1]
    cmdLine_microUSBserial.serial().printf("  MISO<-");
    for (unsigned int byteIndex = 0; byteIndex < numBytesTransferred; byteIndex++)
    {
        cmdLine_microUSBserial.serial().printf(" 0x%2.2X", misoData[byteIndex]);
    }
    cmdLine_microUSBserial.serial().printf(" ");
#endif
#if HAS_DAPLINK_SERIAL
    cmdLine_DAPLINKserial.serial().printf("\r\nSPI");
    if (byteCount > 7) {
        cmdLine_DAPLINKserial.serial().printf(" byteCount:%d", byteCount);
    }
    cmdLine_DAPLINKserial.serial().printf(" MOSI->");
    for (unsigned int byteIndex = 0; byteIndex < byteCount; byteIndex++)
    {
        cmdLine_DAPLINKserial.serial().printf(" 0x%2.2X", mosiData[byteIndex]);
    }
    // hex dump misoData[0..byteCount-1]
    cmdLine_DAPLINKserial.serial().printf("  MISO<-");
    for (unsigned int byteIndex = 0; byteIndex < numBytesTransferred; byteIndex++)
    {
        cmdLine_DAPLINKserial.serial().printf(" 0x%2.2X", misoData[byteIndex]);
    }
    cmdLine_DAPLINKserial.serial().printf(" ");
#endif
    // VERIFY: DIAGNOSTIC: print MAX5715 device register write
    // TODO: MAX5715_print_register_verbose(mosiData8_FF0000, mosiData16_00FFFF);
    //
    // int misoData16 = (misoData16_FF00 << 8) | misoData16_00FF;
    // return misoData16;
}

// Assert MAX5715 LDAC pin : High = inactive, Low = load DAC.
//
void MAX5715::LDACboutputValue(int isLogicHigh)
{
    // m_LDACb_pin.output(); // only applicable to DigitalInOut
    m_LDACb_pin = isLogicHigh;
}

// Assert MAX5715 CLR pin : High = inactive, Low = clear DAC.
//
void MAX5715::CLRboutputValue(int isLogicHigh)
{
    // m_CLRb_pin.output(); // only applicable to DigitalInOut
    m_CLRb_pin = isLogicHigh;
}

//----------------------------------------
// Menu item '!'
// Initialize device
// @return 1 on success; 0 on failure
uint8_t MAX5715::Init(void)
{
    
    //----------------------------------------
    // Initialize device
          //----------------------------------------
          // Perform Software Reset
          if (!SW_RESET()) {
            return 0; // failure
          }
          //----------------------------------------
          // Turn on the reference output pin
          REF(REF_AlwaysOn_2V500);
          //
          //~ Serial.println(F(" success"));
          //~ return;
          return 1; // success
}

//----------------------------------------
// Return the DAC register value corresponding to physical voltage.
// Does not perform any offset or gain correction.
//
// @pre VRef = Voltage of REF input, in Volts
// @param[in] voltage = physical voltage in Volts
// @return raw 12-bit MAX5715 code (right justified).
uint16_t MAX5715::DACCodeOfVoltage(double voltageV)
{
    
    //----------------------------------------
    // Linear map min and max endpoints
    const double MaxScaleVoltage = VRef; // voltage of maximum code 0x0fff
    const double MinScaleVoltage = 0.0; // voltage of minimum code 0x000
    const uint16_t FULL_SCALE_CODE_12BIT = 0x0fff;
    const uint16_t MaxCode = FULL_SCALE_CODE_12BIT;
    const uint16_t MinCode = 0x000;
    double codeFraction = (voltageV - MinScaleVoltage) / (MaxScaleVoltage - MinScaleVoltage);
    double dacRegValueIdeal = ((codeFraction * (double)(MaxCode - MinCode + 1)) + MinCode + 0.5);
    uint16_t dacRegValue = (uint16_t)dacRegValueIdeal;
    if (dacRegValueIdeal > MaxCode)
    {
        dacRegValue = MaxCode;
    } else if (dacRegValueIdeal < MinCode)
    {
        dacRegValue = MinCode;
    }
    return dacRegValue;
}

//----------------------------------------
// Return the physical voltage corresponding to DAC register.
// Does not perform any offset or gain correction.
//
// @pre VRef = Voltage of REF input, in Volts
// @param[in] value_u12: raw 12-bit MAX5715 code (right justified).
// @return physical voltage corresponding to MAX5715 code.
double MAX5715::VoltageOfCode(uint16_t value_u12)
{
    
    //----------------------------------------
    // Linear map min and max endpoints
    double MaxScaleVoltage = VRef; // voltage of maximum code 0x0fff
    double MinScaleVoltage = 0.0; // voltage of minimum code 0x000
    const uint16_t FULL_SCALE_CODE_12BIT = 0x0fff;
    const uint16_t MaxCode = FULL_SCALE_CODE_12BIT;
    const uint16_t MinCode = 0x000;
    double codeFraction = ((double)value_u12 - MinCode) / (MaxCode - MinCode + 1);
    return MinScaleVoltage + ((MaxScaleVoltage - MinScaleVoltage) * codeFraction);
}

//----------------------------------------
// CMD_1000_0000_dddd_dddd_dddd_0000_CODEall
//
// Writes data to all CODE registers
// @post updates g_MAX5815_device.Shadow_0010_nnnn_CODE[0..3]
// @post updates g_MAX5815_device.CODE[0..3]
void MAX5715::CODEall(uint16_t dacCodeLsbs)
{
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_1000_0000_dddd_dddd_dddd_0000_CODEall;
    uint16_t regValue = (dacCodeLsbs << 4); // left-align dddd_dddd_dddd_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
    
    //----------------------------------------
    // shadow of write-only register CODE 0010_nnnn[channel_0_3]
    // Each bit of channels_bitmask_DCBA maps to an index of g_MAX5815_device.Shadow_0010_nnnn_CODE[0..3]
    if (channels_bitmask_DCBA & (1 << 0)) {
        CODE[0] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[0] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 1)) {
        CODE[1] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[1] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 2)) {
        CODE[2] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[2] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 3)) {
        CODE[3] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[3] = regValue;
    }
}

//----------------------------------------
// CMD_0000_nnnn_dddd_dddd_dddd_0000_CODEn
//
// Writes data to the selected CODE register(s)
//
// @param[in] channel_0_3 = DAC Selection: 0=OUTA, 1=OUTB, 2=OUTC, 3=OUTD, 4..15=ALL OUTA,OUTB,OUTC,OUTD
// @post updates g_MAX5815_device.Shadow_0010_nnnn_CODE[0..3]
// @post updates g_MAX5815_device.CODE[0..3]
void MAX5715::CODEn(uint8_t channel_0_3, uint16_t dacCodeLsbs)
{
    
    //----------------------------------------
    // update channel selection from channel_0_3
    channelNumber_0_3 = channel_0_3;
    switch (channelNumber_0_3) {
      case 0: 
        channels_bitmask_DCBA = 0x01; // OUTA only
        break;
      case 1: 
        channels_bitmask_DCBA = 0x02; // OUTB only
        break;
      case 2: 
        channels_bitmask_DCBA = 0x04; // OUTC only
        break;
      case 3: 
        channels_bitmask_DCBA = 0x08; // OUTD only
        break;
      default: 
        channels_bitmask_DCBA = 0x0F; // ALL OUTA,OUTB,OUTC,OUTD
    }
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_0000_nnnn_dddd_dddd_dddd_0000_CODEn | (channel_0_3 & 0x0F);
    uint16_t regValue = (dacCodeLsbs << 4); // left-align dddd_dddd_dddd_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
    
    //----------------------------------------
    // shadow of write-only register CODE 0010_nnnn[channel_0_3]
    // Each bit of channels_bitmask_DCBA maps to an index of g_MAX5815_device.Shadow_0010_nnnn_CODE[0..3]
    if (channels_bitmask_DCBA & (1 << 0)) {
        CODE[0] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[0] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 1)) {
        CODE[1] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[1] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 2)) {
        CODE[2] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[2] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 3)) {
        CODE[3] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[3] = regValue;
    }
}

//----------------------------------------
// CMD_1000_0010_dddd_dddd_dddd_0000_CODEallLOADall
//
// Simultaneously writes data to all CODE registers while updating all DAC registers
// @post updates g_MAX5815_device.Shadow_0010_nnnn_CODE[0..3]
// @post updates g_MAX5815_device.CODE[0..3]
void MAX5715::CODEallLOADall(uint16_t dacCodeLsbs)
{
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_1000_0010_dddd_dddd_dddd_0000_CODEallLOADall;
    uint16_t regValue = (dacCodeLsbs << 4); // left-align dddd_dddd_dddd_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
    
    //----------------------------------------
    // shadow of write-only register CODE 0010_nnnn[channel_0_3]
    // Each bit of channels_bitmask_DCBA maps to an index of g_MAX5815_device.Shadow_0010_nnnn_CODE[0..3]
    CODE[0] = dacCodeLsbs;
    Shadow_0010_nnnn_CODE[0] = regValue;
    CODE[1] = dacCodeLsbs;
    Shadow_0010_nnnn_CODE[1] = regValue;
    CODE[2] = dacCodeLsbs;
    Shadow_0010_nnnn_CODE[2] = regValue;
    CODE[3] = dacCodeLsbs;
    Shadow_0010_nnnn_CODE[3] = regValue;
}

//----------------------------------------
// CMD_0010_nnnn_dddd_dddd_dddd_0000_CODEnLOADall
//
// Simultaneously writes data to the selected CODE register(s) while updating all DAC registers.
//
// @param[in] channel_0_3 = DAC Selection: 0=OUTA, 1=OUTB, 2=OUTC, 3=OUTD, 4..15=ALL OUTA,OUTB,OUTC,OUTD
// @post updates g_MAX5815_device.Shadow_0010_nnnn_CODE[0..3]
// @post updates g_MAX5815_device.CODE[0..3]
void MAX5715::CODEnLOADall(uint8_t channel_0_3, uint16_t dacCodeLsbs)
{
    
    //----------------------------------------
    // update channel selection from channel_0_3
    channelNumber_0_3 = channel_0_3;
    switch (channelNumber_0_3) {
      case 0: 
        channels_bitmask_DCBA = 0x01; // OUTA only
        break;
      case 1: 
        channels_bitmask_DCBA = 0x02; // OUTB only
        break;
      case 2: 
        channels_bitmask_DCBA = 0x04; // OUTC only
        break;
      case 3: 
        channels_bitmask_DCBA = 0x08; // OUTD only
        break;
      default: 
        channels_bitmask_DCBA = 0x0F; // ALL OUTA,OUTB,OUTC,OUTD
    }
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_0010_nnnn_dddd_dddd_dddd_0000_CODEnLOADall | (channel_0_3 & 0x0F);
    uint16_t regValue = (dacCodeLsbs << 4); // left-align dddd_dddd_dddd_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
    
    //----------------------------------------
    // shadow of write-only register CODE 0010_nnnn[channel_0_3]
    // Each bit of channels_bitmask_DCBA maps to an index of g_MAX5815_device.Shadow_0010_nnnn_CODE[0..3]
    if (channels_bitmask_DCBA & (1 << 0)) {
        CODE[0] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[0] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 1)) {
        CODE[1] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[1] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 2)) {
        CODE[2] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[2] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 3)) {
        CODE[3] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[3] = regValue;
    }
}

//----------------------------------------
// CMD_0011_nnnn_dddd_dddd_dddd_0000_CODEnLOADn
//
// Simultaneously writes data to the selected CODE register(s) while updating selected DAC register(s)
//
// @param[in] channel_0_3 = DAC Selection: 0=OUTA, 1=OUTB, 2=OUTC, 3=OUTD, 4..15=ALL OUTA,OUTB,OUTC,OUTD
// @post updates g_MAX5815_device.Shadow_0010_nnnn_CODE[0..3]
// @post updates g_MAX5815_device.CODE[0..3]
void MAX5715::CODEnLOADn(uint8_t channel_0_3, uint16_t dacCodeLsbs)
{
    
    //----------------------------------------
    // update channel selection from channel_0_3
    channelNumber_0_3 = channel_0_3;
    switch (channelNumber_0_3) {
      case 0: 
        channels_bitmask_DCBA = 0x01; // OUTA only
        break;
      case 1: 
        channels_bitmask_DCBA = 0x02; // OUTB only
        break;
      case 2: 
        channels_bitmask_DCBA = 0x04; // OUTC only
        break;
      case 3: 
        channels_bitmask_DCBA = 0x08; // OUTD only
        break;
      default: 
        channels_bitmask_DCBA = 0x0F; // ALL OUTA,OUTB,OUTC,OUTD
    }
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_0011_nnnn_dddd_dddd_dddd_0000_CODEnLOADn | (channel_0_3 & 0x0F);
    uint16_t regValue = (dacCodeLsbs << 4); // left-align dddd_dddd_dddd_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
    
    //----------------------------------------
    // shadow of write-only register CODE 0010_nnnn[channel_0_3]
    // Each bit of channels_bitmask_DCBA maps to an index of g_MAX5815_device.Shadow_0010_nnnn_CODE[0..3]
    if (channels_bitmask_DCBA & (1 << 0)) {
        CODE[0] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[0] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 1)) {
        CODE[1] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[1] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 2)) {
        CODE[2] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[2] = regValue;
    }
    if (channels_bitmask_DCBA & (1 << 3)) {
        CODE[3] = dacCodeLsbs;
        Shadow_0010_nnnn_CODE[3] = regValue;
    }
}

//----------------------------------------
// CMD_0110_0000_0000_dcba_0000_0000_CONFIGn_LATCHED
//
// Sets the DAC Latch Mode of the selected DACs.
// Only DACS with a 1 in the selection bit are updated by the command.
// LD_EN = 0: DAC latch is operational (LOAD and LDAC controlled)
//
// @param[in] channels_bitmask_DCBA = channel select bitmap
//     bit 1000 = channel D
//     bit 0100 = channel C
//     bit 0010 = channel B
//     bit 0001 = channel A
void MAX5715::CONFIGn_LATCHED(uint8_t channels_bitmask_DCBA)
{
    
    //----------------------------------------
    // update channel selection from channels_bitmask_DCBA
    channels_bitmask_DCBA = channels_bitmask_DCBA;
    if (channels_bitmask_DCBA == 0x0F) {
        channelNumber_0_3 = 0x0F; // ALL OUTA,OUTB,OUTC,OUTD
    }
    else if ((channels_bitmask_DCBA & 0x01) != 0) {
        channelNumber_0_3 = 0x00; // OUTA only
    }
    else if ((channels_bitmask_DCBA & 0x02) != 0) {
        channelNumber_0_3 = 0x01; // OUTB only
    }
    else if ((channels_bitmask_DCBA & 0x04) != 0) {
        channelNumber_0_3 = 0x02; // OUTC only
    }
    else {
        channelNumber_0_3 = 0x03; // OUTD only
    }
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_0110_0000_0000_dcba_0000_0000_CONFIGn_LATCHED;
    uint16_t regValue = ((channels_bitmask_DCBA & 0x0F) << 8); // align field 0000_dcba_0000_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
}

//----------------------------------------
// CMD_0110_0001_0000_dcba_0000_0000_CONFIGn_TRANSPARENT
//
// Sets the DAC Latch Mode of the selected DACs.
// Only DACS with a 1 in the selection bit are updated by the command.
// LD_EN = 1: DAC latch is transparent
//
// @param[in] channels_bitmask_DCBA = channel select bitmap
//     bit 1000 = channel D
//     bit 0100 = channel C
//     bit 0010 = channel B
//     bit 0001 = channel A
void MAX5715::CONFIGn_TRANSPARENT(uint8_t channels_bitmask_DCBA)
{
    
    //----------------------------------------
    // update channel selection from channels_bitmask_DCBA
    channels_bitmask_DCBA = channels_bitmask_DCBA;
    if (channels_bitmask_DCBA == 0x0F) {
        channelNumber_0_3 = 0x0F; // ALL OUTA,OUTB,OUTC,OUTD
    }
    else if ((channels_bitmask_DCBA & 0x01) != 0) {
        channelNumber_0_3 = 0x00; // OUTA only
    }
    else if ((channels_bitmask_DCBA & 0x02) != 0) {
        channelNumber_0_3 = 0x01; // OUTB only
    }
    else if ((channels_bitmask_DCBA & 0x04) != 0) {
        channelNumber_0_3 = 0x02; // OUTC only
    }
    else {
        channelNumber_0_3 = 0x03; // OUTD only
    }
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_0110_0001_0000_dcba_0000_0000_CONFIGn_TRANSPARENT;
    uint16_t regValue = ((channels_bitmask_DCBA & 0x0F) << 8); // align field 0000_dcba_0000_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
}

//----------------------------------------
// CMD_0110_1000_0000_0000_0000_0000_CONFIGall_LATCHED
//
// Sets the DAC Latch Mode of all DACs.
// LD_EN = 0: DAC latch is operational (LOAD and LDAC controlled)
void MAX5715::CONFIGall_LATCHED(void)
{
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_0110_1000_0000_0000_0000_0000_CONFIGall_LATCHED;
    uint16_t regValue = 0; // 0000_0000_0000_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
}

//----------------------------------------
// CMD_0110_1001_0000_0000_0000_0000_CONFIGall_TRANSPARENT
//
// Sets the DAC Latch Mode of all DACs.
// LD_EN = 1: DAC latch is transparent
void MAX5715::CONFIGall_TRANSPARENT(void)
{
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_0110_1001_0000_0000_0000_0000_CONFIGall_TRANSPARENT;
    uint16_t regValue = 0; // 0000_0000_0000_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
}

//----------------------------------------
// CMD_1000_0001_0000_0000_0000_0000_LOADall
//
// Updates all DAC latches with current CODE register data
void MAX5715::LOADall(void)
{
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_1000_0001_0000_0000_0000_0000_LOADall;
    uint16_t regValue = 0; // 0000_0000_0000_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
}

//----------------------------------------
// CMD_0001_nnnn_0000_0000_0000_0000_LOADn
//
// Transfers data from the selected CODE register(s) to the selected DAC register(s).
//
// @param[in] channel_0_3 = DAC Selection: 0=OUTA, 1=OUTB, 2=OUTC, 3=OUTD, 4..15=ALL OUTA,OUTB,OUTC,OUTD
void MAX5715::LOADn(uint8_t channel_0_3)
{
    
    //----------------------------------------
    // update channel selection from channel_0_3
    channelNumber_0_3 = channel_0_3;
    switch (channelNumber_0_3) {
      case 0: 
        channels_bitmask_DCBA = 0x01; // OUTA only
        break;
      case 1: 
        channels_bitmask_DCBA = 0x02; // OUTB only
        break;
      case 2: 
        channels_bitmask_DCBA = 0x04; // OUTC only
        break;
      case 3: 
        channels_bitmask_DCBA = 0x08; // OUTD only
        break;
      default: 
        channels_bitmask_DCBA = 0x0F; // ALL OUTA,OUTB,OUTC,OUTD
    }
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_0001_nnnn_0000_0000_0000_0000_LOADn | (channel_0_3 & 0x0F);
    uint16_t regValue = (0 << 4); // left-align dddd_dddd_dddd_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
}

//----------------------------------------
// CMD_0100_0000_0000_dcba_0000_0000_POWERn_Normal
// CMD_0100_0001_0000_dcba_0000_0000_POWERn_PD1k
// CMD_0100_0010_0000_dcba_0000_0000_POWERn_PD100k
// CMD_0100_0011_0000_dcba_0000_0000_POWERn_PDHiZ
//
// Sets the power mode of the selected DACs
// (DACs selected with a 1 in the corresponding DACn bit are updated,
// DACs with a 0 in the corresponding DACn bit are not impacted)
//
// @param[in] channels_bitmask_DCBA = channel select bitmap
//     bit 1000 = channel D
//     bit 0100 = channel C
//     bit 0010 = channel B
//     bit 0001 = channel A
// @param[in] powerValue = power configuration for selected channel
void MAX5715::POWER(uint8_t channels_bitmask_DCBA, MAX5715_POWER_enum_t powerValue)
{
    
    //----------------------------------------
    // update channel selection from channels_bitmask_DCBA
    channels_bitmask_DCBA = channels_bitmask_DCBA;
    if (channels_bitmask_DCBA == 0x0F) {
        channelNumber_0_3 = 0x0F; // ALL OUTA,OUTB,OUTC,OUTD
    }
    else if ((channels_bitmask_DCBA & 0x01) != 0) {
        channelNumber_0_3 = 0x00; // OUTA only
    }
    else if ((channels_bitmask_DCBA & 0x02) != 0) {
        channelNumber_0_3 = 0x01; // OUTB only
    }
    else if ((channels_bitmask_DCBA & 0x04) != 0) {
        channelNumber_0_3 = 0x02; // OUTC only
    }
    else {
        channelNumber_0_3 = 0x03; // OUTD only
    }
    
    //----------------------------------------
    // select command_regAddress based on condition
    uint8_t command_regAddress = CMD_0100_0000_0000_dcba_0000_0000_POWERn_Normal; // diagnostic
    uint16_t regValue = ((channels_bitmask_DCBA & 0x0F) << 8); // 0000_dcba_0000_0000
    // select command_regAddress from list of 4 values, based on condition
    if (powerValue == POWERn_Normal) {
        command_regAddress = CMD_0100_0000_0000_dcba_0000_0000_POWERn_Normal;
    }
    if (powerValue == POWERn_PD1k) {
        command_regAddress = CMD_0100_0001_0000_dcba_0000_0000_POWERn_PD1k;
    }
    if (powerValue == POWERn_PD100k) {
        command_regAddress = CMD_0100_0010_0000_dcba_0000_0000_POWERn_PD100k;
    }
    if (powerValue == POWERn_PDHiZ) {
        command_regAddress = CMD_0100_0011_0000_dcba_0000_0000_POWERn_PDHiZ;
    }
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
}

//----------------------------------------
// CMD_0111_0000_0000_0000_0000_0000_REF_EXT
// CMD_0111_0001_0000_0000_0000_0000_REF_2V500
// CMD_0111_0010_0000_0000_0000_0000_REF_2V048
// CMD_0111_0011_0000_0000_0000_0000_REF_4V096
// CMD_0111_0100_0000_0000_0000_0000_REF_AlwaysOn_EXT
// CMD_0111_0101_0000_0000_0000_0000_REF_AlwaysOn_2V500
// CMD_0111_0110_0000_0000_0000_0000_REF_AlwaysOn_2V048
// CMD_0111_0111_0000_0000_0000_0000_REF_AlwaysOn_4V096
//
// Sets the reference operating mode.
// REF Power (B18): 0 = Internal reference is only powered if at least one DAC is powered
// 1 = Internal reference is always powered
void MAX5715::REF(MAX5715_REF_enum_t ref)
{
    
    //----------------------------------------
    // select command_regAddress based on condition
    uint8_t command_regAddress = CMD_0111_0110_0000_0000_0000_0000_REF_AlwaysOn_2V048; // diagnostic
    uint16_t regValue = 0; // 0000_0000_0000_0000
    // select command_regAddress from list of 8 values, based on condition
    if (ref == REF_EXT) {
        command_regAddress = CMD_0111_0000_0000_0000_0000_0000_REF_EXT;
    }
    if (ref == REF_2V500) {
        command_regAddress = CMD_0111_0001_0000_0000_0000_0000_REF_2V500;
        VRef = 2.500;
    }
    if (ref == REF_2V048) {
        command_regAddress = CMD_0111_0010_0000_0000_0000_0000_REF_2V048;
        VRef = 2.048;
    }
    if (ref == REF_4V096) {
        command_regAddress = CMD_0111_0011_0000_0000_0000_0000_REF_4V096;
        VRef = 4.096;
    }
    if (ref == REF_AlwaysOn_EXT) {
        command_regAddress = CMD_0111_0100_0000_0000_0000_0000_REF_AlwaysOn_EXT;
    }
    if (ref == REF_AlwaysOn_2V500) {
        command_regAddress = CMD_0111_0101_0000_0000_0000_0000_REF_AlwaysOn_2V500;
        VRef = 2.500;
    }
    if (ref == REF_AlwaysOn_2V048) {
        command_regAddress = CMD_0111_0110_0000_0000_0000_0000_REF_AlwaysOn_2V048;
        VRef = 2.048;
    }
    if (ref == REF_AlwaysOn_4V096) {
        command_regAddress = CMD_0111_0111_0000_0000_0000_0000_REF_AlwaysOn_4V096;
        VRef = 4.096;
    }
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
    
    //----------------------------------------
    // shadow of write-only register REF CMD_0111_0rrr
    Shadow_0111_0rrr_REF = regValue;
}

//----------------------------------------
// CMD_0101_0000_0000_0000_0000_0000_SW_CLEAR
//
// Software Clear
// All CODE and DAC registers cleared to their default values.
//
// @return 1 on success; 0 on failure
uint8_t MAX5715::SW_CLEAR(void)
{
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_0101_0000_0000_0000_0000_0000_SW_CLEAR;
    uint16_t regValue = 0; // 0000_0000_0000_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
    
    //----------------------------------------
    // after successful SW_RESET, update shadow registers
    // assume SPI write was successful
    if (1)
    {
      // shadow of write-only register CODE[channel_0_3] CMD_0010_nnnn
      Shadow_0010_nnnn_CODE[0] = 0x0000;
      Shadow_0010_nnnn_CODE[1] = 0x0000;
      Shadow_0010_nnnn_CODE[2] = 0x0000;
      Shadow_0010_nnnn_CODE[3] = 0x0000;
      //
      // shadow of write-only register POWER[channel_0_3] CMD_0100_00pp
      //Shadow_0100_00pp_POWER[0] = POWERn_Normal;
      //Shadow_0100_00pp_POWER[1] = POWERn_Normal;
      //Shadow_0100_00pp_POWER[2] = POWERn_Normal;
      //Shadow_0100_00pp_POWER[3] = POWERn_Normal;
      //
      // shadow of write-only register CONFIG[channel_0_3] CMD_0110_a00t
      //Shadow_0110_a00t_CONFIG[0] = 0; // normal (latched, not transparent)
      //Shadow_0110_a00t_CONFIG[1] = 0; // normal (latched, not transparent)
      //Shadow_0110_a00t_CONFIG[2] = 0; // normal (latched, not transparent)
      //Shadow_0110_a00t_CONFIG[3] = 0; // normal (latched, not transparent)
      //
      // shadow of write-only register REF CMD_0111_0rrr
      //Shadow_0111_0rrr_REF = REF_EXT;
      //
      // shadow of CODE field of write-only register CODE[channel_0_3] CMD_0010_nnnn
      CODE[0] = 0x0000;
      CODE[1] = 0x0000;
      CODE[2] = 0x0000;
      CODE[3] = 0x0000;
      //
    }
    
    //----------------------------------------
    // success
    return 1;
}

//----------------------------------------
// CMD_0101_0001_0000_0000_0000_0000_SW_RESET
//
// Software Reset
// All CODE, DAC, and control registers returned to their default values,
// simulating a power cycle reset.
//
// @return 1 on success; 0 on failure
uint8_t MAX5715::SW_RESET(void)
{
    
    //----------------------------------------
    // Define command code
    uint8_t command_regAddress = CMD_0101_0001_0000_0000_0000_0000_SW_RESET;
    uint16_t regValue = 0; // 0000_0000_0000_0000
    
    //----------------------------------------
    // SPI write 8-bit regAddress and 16-bit regValue
    // int16_t mosiData16 = ((command_regAddress << 8) & 0xFF00) | ((regValue >> 8) & 0xFF);
    // int8_t mosiData8_0000FF = (regValue & 0xFF);
    SPIoutputCS(0);
    SPIwrite24bits(command_regAddress, regValue);
    SPIoutputCS(1);
    
    //----------------------------------------
    // after successful SW_RESET, update shadow registers
    // assume SPI write was successful
    if (1)
    {
      // shadow of write-only register CODE[channel_0_3] CMD_0010_nnnn
      Shadow_0010_nnnn_CODE[0] = 0x0000;
      Shadow_0010_nnnn_CODE[1] = 0x0000;
      Shadow_0010_nnnn_CODE[2] = 0x0000;
      Shadow_0010_nnnn_CODE[3] = 0x0000;
      //
      // shadow of write-only register POWER[channel_0_3] CMD_0100_00pp
      Shadow_0100_00pp_POWER[0] = POWERn_Normal;
      Shadow_0100_00pp_POWER[1] = POWERn_Normal;
      Shadow_0100_00pp_POWER[2] = POWERn_Normal;
      Shadow_0100_00pp_POWER[3] = POWERn_Normal;
      //
      // shadow of write-only register CONFIG[channel_0_3] CMD_0110_a00t
      Shadow_0110_a00t_CONFIG[0] = 0; // normal (latched, not transparent)
      Shadow_0110_a00t_CONFIG[1] = 0; // normal (latched, not transparent)
      Shadow_0110_a00t_CONFIG[2] = 0; // normal (latched, not transparent)
      Shadow_0110_a00t_CONFIG[3] = 0; // normal (latched, not transparent)
      //
      // shadow of write-only register REF CMD_0111_0rrr
      Shadow_0111_0rrr_REF = REF_EXT;
      //
      // shadow of CODE field of write-only register CODE[channel_0_3] CMD_0010_nnnn
      CODE[0] = 0x0000;
      CODE[1] = 0x0000;
      CODE[2] = 0x0000;
      CODE[3] = 0x0000;
      //
    }
    
    //----------------------------------------
    // success
    return 1;
}


// End of file