Write to a daisy chained array of MCP4822 DAC chips, with latching.
Dependents: MCP4822_demo MCP4822_SinewaveV2
Diff: MCP4822A.cpp
- Revision:
- 0:fcd6f2777ddd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MCP4822A.cpp Tue Feb 22 17:23:08 2011 +0000 @@ -0,0 +1,274 @@ +/* + * MCP4822A - DAC array library. + * + * Copyright (c) 2011 Steven Beard, UK Astronomy Technology Centre. + * + * 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 THE + * AUTHORS OR COPYRIGHT HOLDERS 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. + */ + +#include "mbed.h" +#include "MCP4822A.h" + +using namespace mbed; + +/*+ + * Interface to an array of MCP4822 12-bit dual-output DACs daisy chained + * on an SPI bus and selected using DigitalOut pins. + * + * For a detailed API description see MCP4822.h. + *- + */ +// Constructor +MCP4822A::MCP4822A(int ndacs, PinName mosi, PinName sclk, PinName ncslist[], PinName nldac) : _spi(mosi, NC, sclk) { + + // Create an array of DigitalOut objects connected to each NCS pin. + int i; + _ndacs = (ndacs > 0) ? ndacs : 1; + _ncs_array = new DigitalOut*[ _ndacs ]; + for (i=0; i<_ndacs; i++) { + _ncs_array[i] = new DigitalOut( ncslist[i] ); + } + + // The LDAC pin is optional. + if (nldac == NC) { + // LDAC is not connected for this DAC. + _latched = 0; + _nldac = NULL; + } else { + // LDAC is connected - create a DigitalOut object connected to it. + _latched = 1; + _nldac = new DigitalOut( nldac ); + } + + // Initialise the DAC SPI interface. + _init(); +} + +// Destructor +MCP4822A::~MCP4822A() { + + // Before destroying the object, shut down all the chips. + shdn_all(); + + // Delete all the NCS DigitalOut objects and the array pointing to + // them. + int i; + for (i=0; i<_ndacs; i++) { + delete _ncs_array[i]; + } + delete [] _ncs_array; + + // Delete the LDAC DigitalOut object if it exists. + if (_latched ) delete _nldac; +} + +// Initialise SPI interface. +void MCP4822A::_init() { + + // Set up the SPI for 16-bit values (12-bit + 4 command bits) and mode 0. + _spi.format(16, 0); + + // Start with all the CS and LDAC signals high (disabled) + int i; + for (i=0; i<_ndacs; i++) { + _ncs_array[i]->write(1); + } + + if (_latched ) _nldac->write(1); + return; +} + +// Set SPI clock frequency. +void MCP4822A::frequency( int freq ) { + + // Set the SPI interface clock frequency in Hz. + _spi.frequency( freq ); + return; +} + +/* + * Note: There is a lot of code in common between the following 4 functions. + * The code is kept in line to keep it efficient. Could the functions have + * been written as templates? + */ +// Write to DAC channel A with gain 1. +void MCP4822A::writeA1(int dac, int value) { + + // Set up the command register with the appropriate value. + // For efficiency, the caller is assumed to have checked dac. + int reg; + reg = (value & 0x0FFF) | MCP4822_REG_A1; + + // Select the DAC chip, write to its command register and + // then unselect the DAC chip. + _ncs_array[dac]->write(0); + _spi.write(reg); + _ncs_array[dac]->write(1); + return; +} + +// Write to DAC channel A with gain 2. +void MCP4822A::writeA2(int dac, int value) { + + // Set up the command register with the appropriate value. + // For efficiency, the caller is assumed to have checked dac. + int reg; + reg = (value & 0x0FFF) | MCP4822_REG_A2; + + // Select the DAC chip, write to its command register and then + // unselect the DAC chip. + _ncs_array[dac]->write(0); + _spi.write(reg); + _ncs_array[dac]->write(1); + return; +} + +// Write to DAC channel B with gain 1. +void MCP4822A::writeB1(int dac, int value) { + + // Set up the command register with the appropriate value. + // For efficiency, the caller is assumed to have checked dac. + int reg; + reg = (value & 0x0FFF) | MCP4822_REG_B1; + + // Select the DAC chip, write to its command register and then + // unselect the DAC chip. + _ncs_array[dac]->write(0); + _spi.write(reg); + _ncs_array[dac]->write(1); + return; +} + +// Write to DAC channel B with gain 2. +void MCP4822A::writeB2(int dac, int value) { + + // Set up the command register with the appropriate value. + // For efficiency, the caller is assumed to have checked dac. + int reg; + reg = (value & 0x0FFF) | MCP4822_REG_B2; + + // Select the DAC chip, write to its command register and then + // unselect the DAC chip. + _ncs_array[dac]->write(0); + _spi.write(reg); + _ncs_array[dac]->write(1); + return; +} + +// Write an array of values to the DACs. +void MCP4822A::write(int nchans, int values[], int gain, int latch) { + + // nchans must be at least 1 but less than or equal to ndacs x 2. + if (nchans < 1) nchans = 1; + const int maxchans = _ndacs * 2; + if (nchans > maxchans) nchans = maxchans; + + if (latch && _latched) + latch_disable(); + + int i, dac; + if ( gain == 2 ) { + + for (i=0; i<nchans;) { + dac = i/2; + writeA2(dac, values[i]); + i++; + if (i < nchans) { + writeB2(dac, values[i]); + i++; + } else break; + } + } else { + + for (i=0; i<nchans;) { + dac = i/2; + writeA1(dac, values[i]); + i++; + if (i < nchans) { + writeB1(dac, values[i]); + i++; + } else break; + } + } + + // Automatically latch the new voltages if the latch flag is 1. + if (latch && _latched) + latch_enable(); + return; +} + +// Convert a voltage into a 12-bit value. +int MCP4822A::voltage2value( float voltage, int gain ) { + + int value = int(4096000.0 * abs(voltage) / float(MCP4822_VREF * gain)); + if ( value > 0x0FFF ) value = 0x0FFF; + return value; +} + +// Convert a 12-bit value into a voltage. +float MCP4822A::value2voltage( int value, int gain ) { + + float voltage = value * MCP4822_VREF * gain / 4096000.0; + return voltage; +} + +// Set latch signal to "enable". +void MCP4822A::latch_enable() { + + // Latch all chips. There should be a delay of at least T_LS=40 + // nanoseconds between the last CS rising edge and the LDAC falling + // edge. The software function calls seem to be sufficient to + // introduce that delay. A delay may be inserted here if this + // software is ported to a faster processor. + if (_latched) _nldac->write(0); + // The LDAC pulse width must be at least T_LD=100 nanoseconds long. + // A delay can be inserted here if necessary, but so far this has + // not been needed (see above). + return; +} + +// Set latch signal to "disable". +void MCP4822A::latch_disable() { + + // Disable latch for all chips. + if (_latched) _nldac->write(1); + return; +} + +// Shut down one chip. +void MCP4822A::shdn( int dac ) { + + // Shut down one particular chip + _ncs_array[dac]->write(0); + _spi.write(MCP4822_REG_SHDN); + _ncs_array[dac]->write(1); + return; +} + +// Shut down all chips. +void MCP4822A::shdn_all( ) { + + // Shut down all chips + int dac; + for (dac=0; dac<_ndacs; dac++) { + shdn( dac ); + } + latch_disable(); + return; +}