├── .gitignore ├── LICENSE ├── Makefile ├── README.org ├── datasheet └── AD5724R_5734R_5754R.pdf ├── examples ├── Interactive │ └── Interactive.ino ├── MultiChip │ └── MultiChip.ino ├── Sawtooth │ └── Sawtooth.ino └── SimultaneousUpdate │ └── SimultaneousUpdate.ino ├── library.properties ├── platformio.ini └── src ├── AD57X4R.h └── AD57X4R └── AD57X4R.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.idea 3 | /build 4 | /bin 5 | /lib 6 | /sftp-config.json 7 | .tags 8 | .tags_sorted_by_file 9 | .pio 10 | .venv 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Janelia Open-Source Software (3-clause BSD License) 2 | 3 | Copyright 2023 Howard Hughes Medical Institute 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation and/or 13 | other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its contributors 16 | may be used to endorse or promote products derived from this software without 17 | specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: venv 2 | venv: 3 | rm -rf .venv;\ 4 | mkdir .venv;\ 5 | python3 -m venv .venv;\ 6 | source .venv/bin/activate;\ 7 | pip install platformio 8 | 9 | .PHONY: clean 10 | clean: 11 | rm -rf .pio 12 | 13 | firmware: clean 14 | . .venv/bin/activate; pio run -e teensy41 15 | 16 | .PHONY: upload 17 | upload: clean 18 | . .venv/bin/activate; pio run -e teensy41 --target upload --upload-port /dev/ttyACM0 19 | 20 | .PHONY: monitor 21 | monitor: 22 | . .venv/bin/activate; pio device monitor 23 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: AD57X4R 2 | #+AUTHOR: Peter Polidoro 3 | #+EMAIL: peter@polidoro.io 4 | 5 | * Library Information 6 | - Name :: AD57X4R 7 | - Version :: 5.0.1 8 | - License :: BSD 9 | - URL :: https://github.com/janelia-arduino/AD57X4R 10 | - Author :: Peter Polidoro 11 | - Email :: peter@polidoro.io 12 | 13 | ** Description 14 | 15 | Provides an SPI based interface to the AD5724R, AD5734R, and the AD5754R Quad 16 | 12-/14-/16-Bit Unipolar/Bipolar Voltage Output DACs. 17 | 18 | The outputs can be set two ways, either by setting the analog value or by 19 | setting the voltage directly. The analog values are integers with the range 20 | determined by the resolution and whether the output is set to unipolar or 21 | bipolar. This is similar to Arduino analogWrite and this method is provided as 22 | an alternative name for convenience. Alternatively, the output voltages can be 23 | set directly. Voltages are floats with the minimum and maximum values 24 | determinded by the output range. By default, the outputs are updated 25 | immediately after they are set, but the outputs can be updated synchronously 26 | by using the simultaneous update methods. 27 | 28 | * Wiring 29 | 30 | | PIN | CONNECTION | 31 | |-----------+-------------------------------------------------------------------------------| 32 | | SDIN | connect to processor MOSI pin or SDO of previous daisy-chained chip | 33 | | SDO | connect to processor MISO pin or SDIN of next daisy-chained chip | 34 | | SCLK | connect to processor SCK pin | 35 | | SYNC | connect to processor CS pin and set in constructor or setChipSelectPin method | 36 | | BIN2SCOMP | GND | 37 | | LDAC | GND or connect to processor pin and use setLoadDacPin method | 38 | | CLR | DVCC or connect to processor pin and use setClearPin method | 39 | -------------------------------------------------------------------------------- /datasheet/AD5724R_5734R_5754R.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janelia-arduino/AD57X4R/69b244e9090b13ab8d4612529c15a397abcd66d0/datasheet/AD5724R_5734R_5754R.pdf -------------------------------------------------------------------------------- /examples/Interactive/Interactive.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | const size_t CHIP_SELECT_PIN = 10; 6 | const size_t LOAD_DAC_PIN = 3; 7 | const size_t CLEAR_PIN = 4; 8 | const long BAUD = 115200; 9 | 10 | const size_t CHANNEL_COUNT = 4; 11 | const double VOLTAGE_MIN = -10.8; 12 | const double VOLTAGE_MAX = 10.8; 13 | const long ANALOG_MIN = -32768; 14 | const long ANALOG_MAX = 65536; 15 | 16 | AD57X4R dac = AD57X4R(CHIP_SELECT_PIN); 17 | 18 | char input_buffer[128]; 19 | uint8_t idx = 0; 20 | bool input_complete = false; 21 | char *argv[8]; 22 | int arg1, arg2, arg3; 23 | const size_t CHANNEL_MIN = 0; 24 | const size_t CHANNEL_MAX = CHANNEL_COUNT-1; 25 | 26 | uint8_t parse(char *line, char **argv, uint8_t max_args) 27 | { 28 | uint8_t arg_count = 0; 29 | while (*line != '\0') 30 | { /* if not the end of line ....... */ 31 | while (*line == ',' || *line == ' ' || *line == '\t' || *line == '\n') 32 | { 33 | *line++ = '\0'; /* replace commas and white spaces with 0 */ 34 | } 35 | *argv++ = line; /* save the argument position */ 36 | arg_count++; 37 | if (arg_count == max_args-1) 38 | { 39 | break; 40 | } 41 | while (*line != '\0' && *line != ',' && *line != ' ' && 42 | *line != '\t' && *line != '\n') 43 | { 44 | line++; /* skip the argument until ... */ 45 | } 46 | } 47 | **argv = '\0'; /* mark the end of argument list */ 48 | return arg_count; 49 | } 50 | 51 | void setup() 52 | { 53 | // PC communications 54 | Serial.begin(BAUD); 55 | 56 | // Initialize DAC 57 | dac.setLoadDacPin(LOAD_DAC_PIN); 58 | dac.setClearPin(CLEAR_PIN); 59 | dac.setup(AD57X4R::AD5754R); 60 | size_t channel = 0; 61 | dac.setOutputRange(channel,AD57X4R::BIPOLAR_10V); 62 | channel = 1; 63 | dac.setOutputRange(channel,AD57X4R::UNIPOLAR_10V); 64 | channel = 2; 65 | dac.setOutputRange(channel,AD57X4R::UNIPOLAR_5V); 66 | channel = 3; 67 | dac.setOutputRange(channel,AD57X4R::BIPOLAR_10V8); 68 | } 69 | 70 | void loop() 71 | { 72 | if (input_complete) 73 | { 74 | uint8_t arg_count = parse((char*)input_buffer, argv, sizeof(argv)); 75 | if (strcmp(argv[0], "setVoltage") == 0) 76 | { 77 | if ((arg_count == 3) && (0 < strlen(argv[1])) && (0 < strlen(argv[2]))) 78 | { 79 | size_t channel = atoi(argv[1]); 80 | double voltage = atof(argv[2]); 81 | dac.setVoltage(channel,voltage); 82 | long analog_value = dac.voltageToAnalogValue(channel,voltage); 83 | Serial << "setVoltage CHANNEL: " << channel << ", VOLTAGE: " << voltage << ", ANALOG_VALUE: " << analog_value << "\n"; 84 | } 85 | else 86 | { 87 | Serial << "setVoltage , CHANNEL = {" << CHANNEL_MIN << ".." << CHANNEL_MAX << "}" << ", VOLTAGE = {" << VOLTAGE_MIN << ".." << VOLTAGE_MAX << "}" << endl; 88 | } 89 | } 90 | else if (strcmp(argv[0], "setAnalogValue") == 0) 91 | { 92 | if ((arg_count == 3) && (0 < strlen(argv[1])) && (0 < strlen(argv[2]))) 93 | { 94 | size_t channel = atoi(argv[1]); 95 | long analog_value = atoi(argv[2]); 96 | dac.setAnalogValue(channel,analog_value); 97 | double voltage = dac.analogValueToVoltage(channel,analog_value); 98 | Serial << "setAnalogValue CHANNEL: " << channel << ", ANALOG_VALUE: " << analog_value << ", VOLTAGE: " << voltage << "\n"; 99 | } 100 | else 101 | { 102 | Serial << "setAnalogValue , CHANNEL = {" << CHANNEL_MIN << ".." << CHANNEL_MAX << "}" << ", ANALOG_VALUE = {" << ANALOG_MIN << ".." << ANALOG_MAX << "}" << endl; 103 | } 104 | } 105 | else if (strcmp(argv[0], "channelPoweredUp") == 0) 106 | { 107 | if (0 < strlen(argv[1])) 108 | { 109 | size_t channel = atoi(argv[1]); 110 | channel = constrain(channel,CHANNEL_MIN,CHANNEL_MAX); 111 | bool channel_powered_up = dac.channelPoweredUp(channel); 112 | Serial << "channel " << channel << " powered up = " << channel_powered_up << endl; 113 | } 114 | else 115 | { 116 | Serial << "channelPoweredUp , CHANNEL = {" << CHANNEL_MIN << ".." << CHANNEL_MAX << "}" << endl; 117 | } 118 | } 119 | else 120 | { 121 | Serial.println("setVoltage , setAnalogValue , channelPoweredUp "); 122 | } 123 | 124 | input_complete = false; 125 | } 126 | } 127 | 128 | void serialEvent() 129 | { 130 | while (Serial.available()) 131 | { 132 | uint8_t in_byte; 133 | in_byte = Serial.read(); 134 | if ((in_byte == '\n') || (in_byte == '\r')) 135 | { 136 | input_buffer[idx] = 0; 137 | idx = 0; 138 | input_complete = true; 139 | } 140 | else if (((in_byte == '\b') || (in_byte == 0x7f)) && (idx > 0)) 141 | { 142 | idx--; 143 | } 144 | else if ((in_byte >= ' ') && (idx < sizeof(input_buffer) - 1)) 145 | { 146 | input_buffer[idx++] = in_byte; 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /examples/MultiChip/MultiChip.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | const size_t CHIP_SELECT_PIN = 10; 5 | const size_t LOAD_DAC_PIN = 3; 6 | const size_t CLEAR_PIN = 4; 7 | 8 | const size_t CHIP_COUNT = 2; 9 | 10 | const size_t VOLTAGE_MULTIPLIER = 1; 11 | 12 | const int DELAY = 500; 13 | 14 | AD57X4R dac = AD57X4R(CHIP_SELECT_PIN); 15 | 16 | void setup() 17 | { 18 | // Initialize DAC 19 | dac.setLoadDacPin(LOAD_DAC_PIN); 20 | dac.setClearPin(CLEAR_PIN); 21 | dac.setup(AD57X4R::AD5724R, CHIP_COUNT); 22 | 23 | dac.setAllOutputRanges(AD57X4R::UNIPOLAR_10V); 24 | } 25 | 26 | void loop() 27 | { 28 | dac.beginSimultaneousUpdate(); 29 | for (size_t channel=0; channel 2 | 3 | 4 | const size_t CHIP_SELECT_PIN = 10; 5 | const size_t LOAD_DAC_PIN = 3; 6 | const size_t CLEAR_PIN = 4; 7 | 8 | const long ANALOG_CHANNEL = 0; 9 | 10 | const int LOOP_DELAY = 10; 11 | const size_t ANALOG_VALUE_INC = 1000; 12 | 13 | AD57X4R dac = AD57X4R(CHIP_SELECT_PIN); 14 | long analog_value = 0; 15 | 16 | void setup() 17 | { 18 | // Initialize DAC 19 | dac.setLoadDacPin(LOAD_DAC_PIN); 20 | dac.setClearPin(CLEAR_PIN); 21 | dac.setup(AD57X4R::AD5754R); 22 | dac.setOutputRange(ANALOG_CHANNEL,AD57X4R::UNIPOLAR_5V); 23 | } 24 | 25 | void loop() 26 | { 27 | analog_value += ANALOG_VALUE_INC; 28 | if (analog_value > dac.getAnalogValueMax(ANALOG_CHANNEL)) 29 | { 30 | analog_value = dac.getAnalogValueMin(ANALOG_CHANNEL); 31 | } 32 | dac.analogWrite(ANALOG_CHANNEL,analog_value); 33 | delay(LOOP_DELAY); 34 | } 35 | -------------------------------------------------------------------------------- /examples/SimultaneousUpdate/SimultaneousUpdate.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | const size_t CHIP_SELECT_PIN = 10; 5 | const size_t LOAD_DAC_PIN = 3; 6 | const size_t CLEAR_PIN = 4; 7 | 8 | const long ANALOG_CHANNEL_0 = 0; 9 | const long ANALOG_CHANNEL_1 = 1; 10 | 11 | const int LOOP_DELAY = 1000; 12 | 13 | AD57X4R dac = AD57X4R(CHIP_SELECT_PIN); 14 | bool is_min_value = true; 15 | 16 | void setup() 17 | { 18 | // Initialize DAC 19 | dac.setLoadDacPin(LOAD_DAC_PIN); 20 | dac.setClearPin(CLEAR_PIN); 21 | dac.setup(AD57X4R::AD5754R); 22 | dac.setOutputRange(ANALOG_CHANNEL_0,AD57X4R::UNIPOLAR_5V); 23 | dac.setOutputRange(ANALOG_CHANNEL_1,AD57X4R::BIPOLAR_10V); 24 | } 25 | 26 | 27 | void loop() 28 | { 29 | if (is_min_value) 30 | { 31 | dac.beginSimultaneousUpdate(); 32 | dac.setAnalogValue(ANALOG_CHANNEL_0,dac.getAnalogValueMin(ANALOG_CHANNEL_0)); 33 | delay(LOOP_DELAY/2); 34 | dac.setAnalogValue(ANALOG_CHANNEL_1,dac.getAnalogValueMin(ANALOG_CHANNEL_1)); 35 | dac.simultaneousUpdate(); 36 | delay(LOOP_DELAY/2); 37 | } 38 | else 39 | { 40 | dac.setAnalogValue(ANALOG_CHANNEL_0,dac.getAnalogValueMax(ANALOG_CHANNEL_0)); 41 | delay(LOOP_DELAY/2); 42 | dac.setAnalogValue(ANALOG_CHANNEL_1,dac.getAnalogValueMax(ANALOG_CHANNEL_1)); 43 | delay(LOOP_DELAY/2); 44 | } 45 | is_min_value = !is_min_value; 46 | } 47 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=AD57X4R 2 | version=5.0.1 3 | author=Peter Polidoro 4 | maintainer=Peter Polidoro 5 | sentence=Provides an SPI based interface to the AD5724R, AD5734R, and the AD5754R Quad 12-/14-/16-Bit Unipolar/Bipolar Voltage Output DACs. 6 | paragraph=Like this project? Please star it on GitHub! 7 | category=Signal Input/Output 8 | url=https://github.com/janelia-arduino/AD57X4R.git 9 | architectures=* 10 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | [platformio] 2 | ; src_dir = examples/Interactive 3 | ; src_dir = examples/MultiChip 4 | ; src_dir = examples/Sawtooth 5 | src_dir = examples/SimultaneousUpdate 6 | lib_dir = src 7 | 8 | [common_env_data] 9 | build_flags = 10 | -Isrc 11 | lib_deps_builtin = 12 | SPI 13 | lib_deps_external = 14 | https://github.com/janelia-arduino/Streaming.git#6.1.1 15 | lib_deps_local = 16 | src/AD57X4R 17 | 18 | [env] 19 | lib_ldf_mode = off 20 | build_flags = 21 | ${common_env_data.build_flags} 22 | ; monitor_flags = 23 | ; --echo 24 | ; --eol 25 | ; CRLF 26 | monitor_filters = 27 | send_on_enter 28 | colorize 29 | monitor_speed = 115200 30 | lib_deps = 31 | ${common_env_data.lib_deps_builtin} 32 | ${common_env_data.lib_deps_external} 33 | ${common_env_data.lib_deps_local} 34 | 35 | [env:teensy41] 36 | platform = teensy 37 | framework = arduino 38 | board = teensy41 39 | upload_protocol = teensy-cli 40 | 41 | ; pio run 42 | ; pio run -e teensy41 --target upload --upload-port /dev/ttyACM0 43 | ; pio device monitor 44 | -------------------------------------------------------------------------------- /src/AD57X4R.h: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // AD57X4R.h 3 | // 4 | // Provides an SPI based interface to the AD57X4R 5 | // Complete, Quad, 12-/14-/16-Bit, Serial Input, 6 | // Unipolar/Bipolar Voltage Output DACs. 7 | // 8 | // Authors: 9 | // Peter Polidoro peter@polidoro.io 10 | // ---------------------------------------------------------------------------- 11 | #ifndef AD57X4R_H 12 | #define AD57X4R_H 13 | #include 14 | 15 | 16 | class AD57X4R 17 | { 18 | public: 19 | enum Resolution { 20 | AD5724R, 21 | AD5734R, 22 | AD5754R}; 23 | enum Range { 24 | UNIPOLAR_5V, 25 | UNIPOLAR_10V, 26 | UNIPOLAR_10V8, 27 | BIPOLAR_5V, 28 | BIPOLAR_10V, 29 | BIPOLAR_10V8}; 30 | 31 | AD57X4R(); 32 | AD57X4R(size_t chip_select_pin); 33 | 34 | void setChipSelectPin(size_t pin); 35 | void setLoadDacPin(size_t pin); 36 | void setClearPin(size_t pin); 37 | 38 | void setup(Resolution resolution=AD5754R, 39 | uint8_t chip_count=1); 40 | 41 | uint8_t getChipCount(); 42 | size_t getChannelCount(); 43 | 44 | void setOutputRange(size_t channel, 45 | Range range); 46 | void setAllOutputRanges(Range range); 47 | 48 | long getAnalogValueMin(size_t channel); 49 | long getAnalogValueMax(size_t channel); 50 | 51 | void setAnalogValue(size_t channel, 52 | long analog_value); 53 | void setAllAnalogValues(long analog_value); 54 | void analogWrite(size_t channel, 55 | long analog_value); 56 | 57 | double getVoltageMin(size_t channel); 58 | double getVoltageMax(size_t channel); 59 | 60 | void setVoltage(size_t channel, 61 | double voltage); 62 | void setAllVoltages(double voltage); 63 | 64 | double analogValueToVoltage(size_t channel, 65 | long analog_value); 66 | long voltageToAnalogValue(size_t channel, 67 | double voltage); 68 | 69 | bool channelPoweredUp(size_t channel); 70 | bool referencePoweredUp(uint8_t chip); 71 | bool thermalShutdown(uint8_t chip); 72 | bool channelOverCurrent(size_t channel); 73 | 74 | void beginSimultaneousUpdate(); 75 | void simultaneousUpdate(); 76 | 77 | private: 78 | size_t chip_select_pin_; 79 | size_t ldac_pin_; 80 | bool simultaneous_update_enabled_; 81 | size_t clr_pin_; 82 | 83 | int resolution_; 84 | 85 | const static uint8_t CHANNEL_COUNT_PER_CHIP = 4; 86 | 87 | const static int CHIP_ALL = -1; 88 | 89 | const static size_t CHIP_COUNT_MIN = 1; 90 | const static size_t CHIP_COUNT_MAX = 8; 91 | uint8_t chip_count_; 92 | 93 | const static size_t CHANNEL_MIN = 0; 94 | const static size_t CHANNEL_COUNT_MAX = CHIP_COUNT_MAX*CHANNEL_COUNT_PER_CHIP; 95 | 96 | const static uint32_t SPI_CLOCK = 8000000; 97 | const static uint8_t SPI_BIT_ORDER = MSBFIRST; 98 | const static uint8_t SPI_MODE = SPI_MODE2; 99 | 100 | // Datagrams 101 | const static uint8_t DATAGRAM_SIZE = 3; 102 | 103 | // Datagram 104 | union Datagram 105 | { 106 | struct 107 | { 108 | uint32_t data : 16; 109 | uint32_t channel_address : 3; 110 | uint32_t reg : 3; 111 | uint32_t space0 : 1; 112 | uint32_t rw : 1; 113 | uint32_t space1 : 8; 114 | }; 115 | uint32_t bytes; 116 | }; 117 | 118 | // Read/Write Bit 119 | const static uint8_t RW_READ = 1; 120 | const static uint8_t RW_WRITE = 0; 121 | 122 | // Register Bits 123 | const static uint8_t REGISTER_DAC = 0b000; 124 | const static uint8_t REGISTER_OUTPUT_RANGE = 0b001; 125 | const static uint8_t REGISTER_POWER_CONTROL = 0b010; 126 | const static uint8_t REGISTER_CONTROL = 0b011; 127 | 128 | // Channel Address Bits 129 | const static uint8_t CHANNEL_ADDRESS_A = 0b000; 130 | const static uint8_t CHANNEL_ADDRESS_B = 0b001; 131 | const static uint8_t CHANNEL_ADDRESS_C = 0b010; 132 | const static uint8_t CHANNEL_ADDRESS_D = 0b011; 133 | const static uint8_t CHANNEL_ADDRESS_ALL = 0b100; 134 | const static uint8_t CHANNEL_ADDRESS_POWER_CONTROL = 0b000; 135 | 136 | // Control Register Functions 137 | const static uint8_t CONTROL_ADDRESS_NOP = 0b000; 138 | const static uint8_t CONTROL_ADDRESS_CLEAR = 0b100; 139 | const static uint8_t CONTROL_ADDRESS_LOAD = 0b101; 140 | 141 | // Power Control Register 142 | const static uint16_t POWER_CONTROL_DAC_A = 0b1; 143 | const static uint16_t POWER_CONTROL_DAC_B = 0b10; 144 | const static uint16_t POWER_CONTROL_DAC_C = 0b100; 145 | const static uint16_t POWER_CONTROL_DAC_D = 0b1000; 146 | const static uint16_t POWER_CONTROL_REF = 0b10000; 147 | const static uint16_t POWER_CONTROL_THERMAL_SHUTDOWN = 0b100000; 148 | const static uint16_t POWER_CONTROL_OVERCURRENT_A = 0b10000000; 149 | const static uint16_t POWER_CONTROL_OVERCURRENT_B = 0b100000000; 150 | const static uint16_t POWER_CONTROL_OVERCURRENT_C = 0b1000000000; 151 | const static uint16_t POWER_CONTROL_OVERCURRENT_D = 0b10000000000; 152 | 153 | // Output Ranges 154 | const static uint8_t OUTPUT_RANGE_UNIPOLAR_5V = 0b000; 155 | const static uint8_t OUTPUT_RANGE_UNIPOLAR_10V = 0b001; 156 | const static uint8_t OUTPUT_RANGE_UNIPOLAR_10V8 = 0b010; 157 | const static uint8_t OUTPUT_RANGE_BIPOLAR_5V = 0b011; 158 | const static uint8_t OUTPUT_RANGE_BIPOLAR_10V = 0b100; 159 | const static uint8_t OUTPUT_RANGE_BIPOLAR_10V8 = 0b101; 160 | 161 | Range range_[CHANNEL_COUNT_MAX]; 162 | 163 | void initialize(); 164 | uint8_t channelToChip(size_t channel); 165 | uint8_t channelToChannelAddress(size_t channel); 166 | void enableChipSelect(); 167 | void disableChipSelect(); 168 | void spiBeginTransaction(); 169 | void spiEndTransaction(); 170 | void initializeMosiDatagramArray(Datagram datagram_array[]); 171 | void writeMosiDatagramToChip(int chip, 172 | Datagram mosi_datagram); 173 | Datagram readMisoDatagramFromChip(int chip); 174 | void powerUpAllDacs(); 175 | void setOutputRangeOnChip(int chip, 176 | uint8_t channel_address, 177 | Range range); 178 | void setAnalogValueOnChip(int chip, 179 | uint8_t channel_address, 180 | long data); 181 | void load(int chip); 182 | uint16_t readPowerControlRegister(uint8_t chip); 183 | bool rangeIsBipolar(Range range); 184 | }; 185 | #endif 186 | -------------------------------------------------------------------------------- /src/AD57X4R/AD57X4R.cpp: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // AD57X4R.cpp 3 | // 4 | // Provides an SPI based interface to the AD57X4R 5 | // Complete, Quad, 12-/14-/16-Bit, Serial Input, 6 | // Unipolar/Bipolar Voltage Output DACs. 7 | // 8 | // Authors: 9 | // Peter Polidoro peter@polidoro.io 10 | // ---------------------------------------------------------------------------- 11 | #include "AD57X4R.h" 12 | 13 | 14 | AD57X4R::AD57X4R() 15 | { 16 | initialize(); 17 | } 18 | 19 | AD57X4R::AD57X4R(size_t chip_select_pin) 20 | { 21 | initialize(); 22 | setChipSelectPin(chip_select_pin); 23 | } 24 | 25 | void AD57X4R::setChipSelectPin(size_t pin) 26 | { 27 | pinMode(pin,OUTPUT); 28 | digitalWrite(pin,HIGH); 29 | chip_select_pin_ = pin; 30 | } 31 | 32 | void AD57X4R::setLoadDacPin(size_t pin) 33 | { 34 | pinMode(pin,OUTPUT); 35 | digitalWrite(pin,LOW); 36 | ldac_pin_ = pin; 37 | simultaneous_update_enabled_ = true; 38 | } 39 | 40 | void AD57X4R::setClearPin(size_t pin) 41 | { 42 | pinMode(pin,OUTPUT); 43 | digitalWrite(pin,HIGH); 44 | clr_pin_ = pin; 45 | } 46 | 47 | void AD57X4R::setup(Resolution resolution, 48 | uint8_t chip_count) 49 | { 50 | if ((chip_count >= CHIP_COUNT_MIN) && (chip_count <= CHIP_COUNT_MAX)) 51 | { 52 | chip_count_ = chip_count; 53 | } 54 | else 55 | { 56 | chip_count_ = CHIP_COUNT_MIN; 57 | } 58 | resolution_ = resolution; 59 | SPI.begin(); 60 | powerUpAllDacs(); 61 | setAllOutputRanges(UNIPOLAR_5V); 62 | setAllAnalogValues(0); 63 | } 64 | 65 | uint8_t AD57X4R::getChipCount() 66 | { 67 | return chip_count_; 68 | } 69 | 70 | size_t AD57X4R::getChannelCount() 71 | { 72 | return chip_count_*CHANNEL_COUNT_PER_CHIP; 73 | } 74 | 75 | void AD57X4R::setOutputRange(size_t channel, 76 | Range range) 77 | { 78 | size_t channel_constrained = constrain(channel, 79 | CHANNEL_MIN, 80 | getChannelCount()-1); 81 | uint8_t chip = channelToChip(channel_constrained); 82 | uint8_t channel_address = channelToChannelAddress(channel_constrained); 83 | range_[channel_constrained] = range; 84 | setOutputRangeOnChip(chip,channel_address,range); 85 | } 86 | 87 | void AD57X4R::setAllOutputRanges(Range range) 88 | { 89 | uint8_t chip = CHIP_ALL; 90 | uint8_t channel_address = CHANNEL_ADDRESS_ALL; 91 | for (size_t channel=0; channel= getChannelCount()) 102 | { 103 | return analog_value_min; 104 | } 105 | if (rangeIsBipolar(range_[channel])) 106 | { 107 | switch (resolution_) 108 | { 109 | case AD5724R: 110 | analog_value_min = -2048; 111 | break; 112 | case AD5734R: 113 | analog_value_min = -8192; 114 | break; 115 | case AD5754R: 116 | analog_value_min = -32768; 117 | break; 118 | } 119 | } 120 | return analog_value_min; 121 | } 122 | 123 | long AD57X4R::getAnalogValueMax(size_t channel) 124 | { 125 | long analog_value_max = 0; 126 | if (channel >= getChannelCount()) 127 | { 128 | return analog_value_max; 129 | } 130 | if (rangeIsBipolar(range_[channel])) 131 | { 132 | switch (resolution_) 133 | { 134 | case AD5724R: 135 | analog_value_max = 2047; 136 | break; 137 | case AD5734R: 138 | analog_value_max = 8191; 139 | break; 140 | case AD5754R: 141 | analog_value_max = 32767; 142 | break; 143 | } 144 | } 145 | else 146 | { 147 | switch (resolution_) 148 | { 149 | case AD5724R: 150 | analog_value_max = 4095; 151 | break; 152 | case AD5734R: 153 | analog_value_max = 16383; 154 | break; 155 | case AD5754R: 156 | analog_value_max = 65535; 157 | break; 158 | } 159 | } 160 | return analog_value_max; 161 | } 162 | 163 | void AD57X4R::setAnalogValue(size_t channel, 164 | long analog_value) 165 | { 166 | size_t channel_constrained = constrain(channel, 167 | CHANNEL_MIN, 168 | getChannelCount()-1); 169 | long analog_value_constrained = constrain(analog_value, 170 | getAnalogValueMin(channel_constrained), 171 | getAnalogValueMax(channel_constrained)); 172 | uint8_t chip = channelToChip(channel_constrained); 173 | uint8_t channel_address = channelToChannelAddress(channel_constrained); 174 | setAnalogValueOnChip(chip,channel_address,analog_value_constrained); 175 | } 176 | 177 | void AD57X4R::setAllAnalogValues(long analog_value) 178 | { 179 | for (size_t channel=0; channel= getChannelCount()) 195 | { 196 | return voltage_min; 197 | } 198 | switch (range_[channel]) 199 | { 200 | case UNIPOLAR_5V: 201 | voltage_min = 0.0; 202 | break; 203 | case UNIPOLAR_10V: 204 | voltage_min = 0.0; 205 | break; 206 | case UNIPOLAR_10V8: 207 | voltage_min = 0.0; 208 | break; 209 | case BIPOLAR_5V: 210 | voltage_min = -5.0; 211 | break; 212 | case BIPOLAR_10V: 213 | voltage_min = -10.0; 214 | break; 215 | case BIPOLAR_10V8: 216 | voltage_min = -10.8; 217 | break; 218 | default: 219 | voltage_min = 0.0; 220 | break; 221 | } 222 | return voltage_min; 223 | } 224 | 225 | double AD57X4R::getVoltageMax(size_t channel) 226 | { 227 | double voltage_max = 0.0; 228 | if (channel >= getChannelCount()) 229 | { 230 | return voltage_max; 231 | } 232 | switch (range_[channel]) 233 | { 234 | case UNIPOLAR_5V: 235 | voltage_max = 5.0; 236 | break; 237 | case UNIPOLAR_10V: 238 | voltage_max = 10.0; 239 | break; 240 | case UNIPOLAR_10V8: 241 | voltage_max = 10.8; 242 | break; 243 | case BIPOLAR_5V: 244 | voltage_max = 5.0; 245 | break; 246 | case BIPOLAR_10V: 247 | voltage_max = 10.0; 248 | break; 249 | case BIPOLAR_10V8: 250 | voltage_max = 10.8; 251 | break; 252 | default: 253 | voltage_max = 0.0; 254 | break; 255 | } 256 | return voltage_max; 257 | } 258 | 259 | void AD57X4R::setVoltage(size_t channel, 260 | double voltage) 261 | { 262 | // Wastes resolution, need to change algorithm 263 | size_t channel_constrained = constrain(channel, 264 | CHANNEL_MIN, 265 | getChannelCount()-1); 266 | uint8_t chip = channelToChip(channel_constrained); 267 | uint8_t channel_address = channelToChannelAddress(channel_constrained); 268 | long analog_value = voltageToAnalogValue(channel,voltage); 269 | setAnalogValueOnChip(chip,channel_address,analog_value); 270 | } 271 | 272 | void AD57X4R::setAllVoltages(double voltage) 273 | { 274 | for (size_t channel=0; channel= getChannelCount()) 333 | { 334 | return false; 335 | } 336 | uint8_t chip = channelToChip(channel); 337 | uint8_t channel_address = channelToChannelAddress(channel); 338 | uint16_t data = readPowerControlRegister(chip); 339 | 340 | bool channel_powered_up = false; 341 | switch (channel_address) 342 | { 343 | case CHANNEL_ADDRESS_A: 344 | channel_powered_up = data & POWER_CONTROL_DAC_A; 345 | break; 346 | case CHANNEL_ADDRESS_B: 347 | channel_powered_up = data & POWER_CONTROL_DAC_B; 348 | break; 349 | case CHANNEL_ADDRESS_C: 350 | channel_powered_up = data & POWER_CONTROL_DAC_C; 351 | break; 352 | case CHANNEL_ADDRESS_D: 353 | channel_powered_up = data & POWER_CONTROL_DAC_D; 354 | break; 355 | } 356 | return channel_powered_up; 357 | } 358 | 359 | bool AD57X4R::referencePoweredUp(uint8_t chip) 360 | { 361 | if (chip >= chip_count_) 362 | { 363 | return false; 364 | } 365 | uint16_t data = readPowerControlRegister(chip); 366 | 367 | bool reference_powered_up = data & POWER_CONTROL_REF; 368 | return reference_powered_up; 369 | } 370 | 371 | bool AD57X4R::thermalShutdown(uint8_t chip) 372 | { 373 | if (chip >= chip_count_) 374 | { 375 | return false; 376 | } 377 | uint16_t data = readPowerControlRegister(chip); 378 | 379 | bool thermal_shutdown = data & POWER_CONTROL_THERMAL_SHUTDOWN; 380 | return thermal_shutdown; 381 | } 382 | 383 | bool AD57X4R::channelOverCurrent(size_t channel) 384 | { 385 | if (channel >= getChannelCount()) 386 | { 387 | return false; 388 | } 389 | uint8_t chip = channelToChip(channel); 390 | uint8_t channel_address = channelToChannelAddress(channel); 391 | uint16_t data = readPowerControlRegister(chip); 392 | 393 | bool channel_over_current = false; 394 | switch (channel_address) 395 | { 396 | case CHANNEL_ADDRESS_A: 397 | channel_over_current = data & POWER_CONTROL_OVERCURRENT_A; 398 | break; 399 | case CHANNEL_ADDRESS_B: 400 | channel_over_current = data & POWER_CONTROL_OVERCURRENT_B; 401 | break; 402 | case CHANNEL_ADDRESS_C: 403 | channel_over_current = data & POWER_CONTROL_OVERCURRENT_C; 404 | break; 405 | case CHANNEL_ADDRESS_D: 406 | channel_over_current = data & POWER_CONTROL_OVERCURRENT_D; 407 | break; 408 | } 409 | return channel_over_current; 410 | } 411 | 412 | void AD57X4R::beginSimultaneousUpdate() 413 | { 414 | if (simultaneous_update_enabled_) 415 | { 416 | digitalWrite(ldac_pin_,HIGH); 417 | } 418 | } 419 | 420 | void AD57X4R::simultaneousUpdate() 421 | { 422 | if (simultaneous_update_enabled_) 423 | { 424 | digitalWrite(ldac_pin_,LOW); 425 | } 426 | } 427 | 428 | // private 429 | void AD57X4R::initialize() 430 | { 431 | simultaneous_update_enabled_ = false; 432 | } 433 | 434 | uint8_t AD57X4R::channelToChip(size_t channel) 435 | { 436 | uint8_t chip = channel / CHANNEL_COUNT_PER_CHIP; 437 | return chip; 438 | } 439 | 440 | uint8_t AD57X4R::channelToChannelAddress(size_t channel) 441 | { 442 | uint8_t chip_channel = channel % CHANNEL_COUNT_PER_CHIP; 443 | uint8_t channel_address; 444 | switch (chip_channel) 445 | { 446 | case 0: 447 | channel_address = CHANNEL_ADDRESS_A; 448 | break; 449 | case 1: 450 | channel_address = CHANNEL_ADDRESS_B; 451 | break; 452 | case 2: 453 | channel_address = CHANNEL_ADDRESS_C; 454 | break; 455 | case 3: 456 | channel_address = CHANNEL_ADDRESS_D; 457 | break; 458 | } 459 | return channel_address; 460 | } 461 | 462 | void AD57X4R::enableChipSelect() 463 | { 464 | digitalWrite(chip_select_pin_,LOW); 465 | } 466 | 467 | void AD57X4R::disableChipSelect() 468 | { 469 | digitalWrite(chip_select_pin_,HIGH); 470 | } 471 | void AD57X4R::spiBeginTransaction() 472 | { 473 | enableChipSelect(); 474 | SPI.beginTransaction(SPISettings(SPI_CLOCK,SPI_BIT_ORDER,SPI_MODE)); 475 | } 476 | 477 | void AD57X4R::spiEndTransaction() 478 | { 479 | SPI.endTransaction(); 480 | disableChipSelect(); 481 | } 482 | 483 | void AD57X4R::initializeMosiDatagramArray(AD57X4R::Datagram datagram_array[]) 484 | { 485 | Datagram mosi_datagram; 486 | mosi_datagram.bytes = 0; 487 | mosi_datagram.rw = RW_WRITE; 488 | mosi_datagram.reg = REGISTER_CONTROL; 489 | mosi_datagram.channel_address = CONTROL_ADDRESS_NOP; 490 | for (size_t chip_n=0; chip_n=0) && (chip < chip_count_)) 508 | { 509 | initializeMosiDatagramArray(mosi_datagram_array); 510 | mosi_datagram_array[chip] = mosi_datagram; 511 | } 512 | 513 | spiBeginTransaction(); 514 | for (int chip_n=(chip_count_ - 1); chip_n>=0; --chip_n) 515 | { 516 | Datagram mosi_datagram_n = mosi_datagram_array[chip_n]; 517 | for (int byte_n=(DATAGRAM_SIZE - 1); byte_n>=0; --byte_n) 518 | { 519 | uint8_t byte_write = (mosi_datagram_n.bytes >> (8*byte_n)) & 0xff; 520 | SPI.transfer(byte_write); 521 | } 522 | } 523 | spiEndTransaction(); 524 | } 525 | 526 | AD57X4R::Datagram AD57X4R::readMisoDatagramFromChip(int chip) 527 | { 528 | Datagram mosi_datagram_array[chip_count_]; 529 | initializeMosiDatagramArray(mosi_datagram_array); 530 | 531 | Datagram miso_datagram_array[chip_count_]; 532 | 533 | spiBeginTransaction(); 534 | for (int chip_n=(chip_count_ - 1); chip_n>=0; --chip_n) 535 | { 536 | miso_datagram_array[chip_n].bytes = 0; 537 | for (int byte_n=(DATAGRAM_SIZE - 1); byte_n>=0; --byte_n) 538 | { 539 | uint8_t byte_write = (mosi_datagram_array[chip_n].bytes >> (8*byte_n)) & 0xff; 540 | uint8_t byte_read = SPI.transfer(byte_write); 541 | miso_datagram_array[chip_n].bytes |= ((uint32_t)byte_read) << (8*byte_n); 542 | } 543 | } 544 | spiEndTransaction(); 545 | 546 | Datagram miso_datagram; 547 | miso_datagram.bytes = 0; 548 | if ((chip >=0) && (chip < chip_count_)) 549 | { 550 | miso_datagram = miso_datagram_array[chip]; 551 | } 552 | 553 | return miso_datagram; 554 | } 555 | 556 | void AD57X4R::powerUpAllDacs() 557 | { 558 | uint16_t data = (POWER_CONTROL_DAC_A | 559 | POWER_CONTROL_DAC_B | 560 | POWER_CONTROL_DAC_C | 561 | POWER_CONTROL_DAC_D | 562 | POWER_CONTROL_REF); 563 | 564 | Datagram mosi_datagram; 565 | mosi_datagram.bytes = 0; 566 | mosi_datagram.rw = RW_WRITE; 567 | mosi_datagram.reg = REGISTER_POWER_CONTROL; 568 | mosi_datagram.channel_address = CHANNEL_ADDRESS_POWER_CONTROL; 569 | mosi_datagram.data = data; 570 | int chip = CHIP_ALL; 571 | writeMosiDatagramToChip(chip,mosi_datagram); 572 | } 573 | 574 | void AD57X4R::setOutputRangeOnChip(int chip, 575 | uint8_t channel_address, 576 | Range range) 577 | { 578 | uint16_t data; 579 | switch (range) 580 | { 581 | case UNIPOLAR_5V: 582 | data = OUTPUT_RANGE_UNIPOLAR_5V; 583 | break; 584 | case UNIPOLAR_10V: 585 | data = OUTPUT_RANGE_UNIPOLAR_10V; 586 | break; 587 | case UNIPOLAR_10V8: 588 | data = OUTPUT_RANGE_UNIPOLAR_10V8; 589 | break; 590 | case BIPOLAR_5V: 591 | data = OUTPUT_RANGE_BIPOLAR_5V; 592 | break; 593 | case BIPOLAR_10V: 594 | data = OUTPUT_RANGE_BIPOLAR_10V; 595 | break; 596 | case BIPOLAR_10V8: 597 | data = OUTPUT_RANGE_BIPOLAR_10V8; 598 | break; 599 | default: 600 | data = OUTPUT_RANGE_UNIPOLAR_5V; 601 | break; 602 | } 603 | Datagram mosi_datagram; 604 | mosi_datagram.bytes = 0; 605 | mosi_datagram.rw = RW_WRITE; 606 | mosi_datagram.reg = REGISTER_OUTPUT_RANGE; 607 | mosi_datagram.channel_address = channel_address; 608 | mosi_datagram.data = data; 609 | writeMosiDatagramToChip(chip,mosi_datagram); 610 | } 611 | 612 | void AD57X4R::setAnalogValueOnChip(int chip, 613 | uint8_t channel_address, 614 | long data) 615 | { 616 | Datagram mosi_datagram; 617 | mosi_datagram.bytes = 0; 618 | mosi_datagram.rw = RW_WRITE; 619 | mosi_datagram.reg = REGISTER_DAC; 620 | mosi_datagram.channel_address = channel_address; 621 | switch (resolution_) 622 | { 623 | case AD5754R: 624 | mosi_datagram.data = data; 625 | break; 626 | case AD5734R: 627 | mosi_datagram.data = data << 2; 628 | break; 629 | case AD5724R: 630 | mosi_datagram.data = data << 4; 631 | break; 632 | } 633 | writeMosiDatagramToChip(chip,mosi_datagram); 634 | } 635 | 636 | void AD57X4R::load(int chip) 637 | { 638 | Datagram mosi_datagram; 639 | mosi_datagram.bytes = 0; 640 | mosi_datagram.rw = RW_WRITE; 641 | mosi_datagram.reg = REGISTER_CONTROL; 642 | mosi_datagram.channel_address = CONTROL_ADDRESS_LOAD; 643 | writeMosiDatagramToChip(chip,mosi_datagram); 644 | } 645 | 646 | uint16_t AD57X4R::readPowerControlRegister(uint8_t chip) 647 | { 648 | Datagram mosi_datagram; 649 | mosi_datagram.bytes = 0; 650 | mosi_datagram.rw = RW_READ; 651 | mosi_datagram.reg = REGISTER_POWER_CONTROL; 652 | mosi_datagram.channel_address = CHANNEL_ADDRESS_POWER_CONTROL; 653 | writeMosiDatagramToChip(chip,mosi_datagram); 654 | 655 | Datagram miso_datagram = readMisoDatagramFromChip(chip); 656 | return miso_datagram.data; 657 | } 658 | 659 | bool AD57X4R::rangeIsBipolar(AD57X4R::Range range) 660 | { 661 | bool range_is_bipolar = false; 662 | if ((range == BIPOLAR_5V) || (range == BIPOLAR_10V) || (range == BIPOLAR_10V8)) 663 | { 664 | range_is_bipolar = true; 665 | } 666 | return range_is_bipolar; 667 | } 668 | --------------------------------------------------------------------------------