Write to a daisy chained array of MCP4822 DAC chips, with latching.

Dependents:   MCP4822_demo MCP4822_SinewaveV2

Files at this revision

API Documentation at this revision

Comitter:
ukatcsmb
Date:
Tue Feb 22 17:23:08 2011 +0000
Commit message:
First published

Changed in this revision

MCP4822A.cpp Show annotated file Show diff for this revision Revisions of this file
MCP4822A.h Show annotated file Show diff for this revision Revisions of this file
--- /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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MCP4822A.h	Tue Feb 22 17:23:08 2011 +0000
@@ -0,0 +1,253 @@
+/*
+ * 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"
+
+#ifndef MCP4822_H
+#define MCP4822_H
+
+/* Reference: Microchip Technology (2005), MCP4821/MCP4822 DAC Data Sheet. */
+
+// MCP4822 reference voltage.
+#define MCP4822_VREF     2048    // Reference voltage (mV)
+
+/* Define possible combinations of 16-bit command register bits */
+#define MCP4822_REG_A1   0x3000  // Channel A gain 1
+#define MCP4822_REG_A2   0x1000  // Channel A gain 2
+#define MCP4822_REG_B1   0xB000  // Channel B gain 1
+#define MCP4822_REG_B2   0x9000  // Channel B gain 2
+#define MCP4822_REG_SHDN 0x0000  // Output power down
+
+/*+
+ * Interface to an array of MCP4822 12-bit dual-output DACs daisy chained
+ * on an SPI bus, with each DAC selected using an array of DigitalOut pins.
+ * All the DACs may be latched together using a common nLDAC pin.
+ *
+ *       +-----------------------------------+
+ *       |+------------------------+         |
+ *       ||+-------------+         |         |
+ *       |||            nCS1      nCS2      nCS3
+ *    +--+++-+           |         |         |
+ *    |      |         +-+----+  +-+----+  +-+----+
+ *    | mbed +---SPI---+ DAC1 +--+ DAC2 +--+ DAC3 +--etc...
+ *    |      |         +-+----+  +-+----+  +-+----+
+ *    +---+--+           |         |         |
+ *        |            nLDAC     nLDAC     nLDAC
+ *        +--------------+---------+---------+
+ *-
+ */
+class MCP4822A {
+public:
+    /*+
+     * Constructor: MCP4822A
+     *
+     * Description:
+     *    A class which describes an array of MCP4822 DACs connected in
+     *    a daisy chain with an SPI interface. Each DAC is selected using
+     *    its own separate "not CS" pin and all the DACs may be latched
+     *    together using a single "not LDAC" pin.
+     *
+     * Parameters:
+     *    ndacs, int
+     *        The number of DAC chips included in the array. Must be at
+     *        least 1. (Limited by the number of "not CS" pins available.)
+     *    mosi, PinName
+     *        The SPI data out pin.
+     *    sclk, PinName
+     *        The SPI clock pin.
+     *    ncslist, PinName[]
+     *        An array of "not chip select" ("not CS") pins - one per DAC
+     *        chip. Each pin is held low to make the DAC respond to SPI
+     *        input. The array must contain at least ndacs elements (only
+     *        the first ndacs elements will be used).
+     *    nldac, PinName (optional)
+     *        The "not latch DAC" ("not LDAC") pin. Setting this pin low
+     *        causes each DAC to apply the preset A and B voltages to its
+     *        outputs. The pin is optional. The DAC will apply new voltages
+     *        immediately if the "not LDAC" pin is held permanently to
+     *        ground. This parameter is optional, and if not given will be
+     *        assumed NC (not connected).
+     *-
+     */
+    MCP4822A ( int ndacs, PinName mosi, PinName sclk, PinName ncslist[], PinName nldac=NC );
+
+    /*+
+     * Destructor: MCP4822A
+     *-
+     */
+    ~MCP4822A();
+
+    /*+
+     * frequency: Set the SPI bus clock frequency in Hz.
+     *
+     * Parameters:
+     *    freq, int
+     *        The SPI bus frequency in Hz. Must be within the range
+     *        supported by both the SPI interface and the DAC chips
+     *        (~10 KHz to 20 MHz).
+     *-
+     */
+    void frequency( int freq );
+
+    /*+
+     * writeA1: Write 12-bit data to channel A with gain set to 1.
+     * writeA2: Write 12-bit data to channel A with gain set to 2.
+     * writeB1: Write 12-bit data to channel B with gain set to 1.
+     * writeB2: Write 12-bit data to channel B with gain set to 2.
+     *
+     * These functions will automatically select the chip with the "not CS"
+     * signal. If the "not LDAC" pin is used, it is up to the caller to
+     * enable or disable that signal. For example:
+     *
+     *     latch_disable();
+     *     writeA2( 1500 );
+     *     latch_enable();
+     *
+     * Choose these functions when high performance is most important.
+     *
+     * Parameters:
+     *    dac, int
+     *        The DAC chip to be addressed, corresponding to the elements
+     *        of the ncslist[] array used to create the MCP4822A object.
+     *        Must be in the range 0 to ndacs-1.
+     *    value, int
+     *        The number to be written to the 12 data bits of the A or B
+     *        register of the DAC chip. At a gain setting of 2, this value
+     *        corresponds to a voltage demand in millivolts. Only the first
+     *        12 bits are used, so the value will wrap around after 4095.
+     *        (Note: Strictly this should be an unsigned int, but the SPI
+     *        interface library expects an int.)
+     *-
+     */
+    void writeA1( int dac, int value );
+    void writeA2( int dac, int value );
+    void writeB1( int dac, int value );
+    void writeB2( int dac, int value );
+
+    /*+
+     * write: Write an array of 12-bit register values to multiple DAC channels.
+     *
+     * Choose this function when flexibility and convenience are most important.
+     *
+     * Parameters:
+     *    nchans, int
+     *        The number of channels to be written. There are two channels
+     *        per DAC, so this value must lie between 2 and ndacs x 2.
+     *    values, int[]
+     *        An array of values to be written to the 12 data bits of the DAC
+     *        registers, in the order dac/chan = 0/A, 0/B, 1/A, 1/B, etc...
+     *        The array must contain at least nchans elements (but only the
+     *        first nchans elements will be used for longer arrays).
+     *        At a gain setting of 2, these values corresponds to a voltage
+     *        demand in millivolts. They will wrap back to 0 above 4095.
+     *        (Note: Strictly this should be an unsigned int array, but the SPI
+     *        interface library expects an int.)
+     *    gain, int (optional)
+     *        The required gain setting (1 or 2). If not specified, a default
+     *        of 2 will be assumed.
+     *    latch, int (optional)
+     *        A flag set to 1 if the "not LDAC" signal should be latched after
+     *        setting up the array of voltages. If not specified, a default
+     *        of 1 will be assumed.
+     *-
+     */
+    void write( int nchans, int values[], int gain=2, int latch=1 );
+
+    /*+
+     * voltage2value - convert a voltage into a 12-bit data value.
+     *
+     * Parameters:
+     *    voltage, float
+     *        The voltage to be converted (in volts). The sign is ignored.
+     *    gain, int (optional)
+     *        The gain setting required (1 or 2). If not specified, a default
+     *        of 2 will be assumed.
+     *
+     * Returns:
+     *    value, int.
+     *        The 12-bit data value corresponding to the given
+     *        voltage. 4095 will be returned for out of range
+     *        voltages.
+     *-
+     */
+    int voltage2value( float voltage, int gain=2 );
+
+    /*+
+     * value2voltage - convert a 12-bit data value into a voltage.
+     *
+     * Parameters:
+     *    value, int
+     *        The 12-bit data value to be converted to a voltage.
+     *    gain, int (optional)
+     *        The gain setting required (1 or 2). If not specified, a default
+     *        of 2 will be assumed.
+     *
+     * Returns:
+     *    voltage, float.
+     *        The voltage (in volts) corresponding the the given 12-bit
+     *        data value at the given gain setting.
+     *-
+     */
+    float value2voltage( int value, int gain=2 );
+
+    /*+
+     * latch_enable: enable the latch signal - the DAC(s) will apply
+     *     voltage demands.
+     *
+     * latch_disable: disable the latch signal - the DAC(s) will not
+     *     apply voltage demands.
+     *
+     * These functions only apply for objects constructed with the
+     * optional nldac parameter assigned to a pin.
+     *-
+     */
+    void latch_enable();
+    void latch_disable();
+
+    /*+
+     * shdn: Shut down a DAC. It will sleep until woken by a new command
+     * over the SPI bus.
+     *-
+     */
+    void shdn(int dac);
+    void shdn_all();
+
+private:
+    // Make copy constructor private to prevent the object being copied.
+    // The pins are available to one and only one object.
+    MCP4822A( const MCP4822A& rhs );
+
+    /* Initialise the DAC interface. */
+    void _init();
+
+    int _ndacs;              // The number of DACS in the array
+    int _latched;            // Is the "not LDAC" pin used (1=yes; 0=no)?
+    SPI _spi;                // SPI bus object for communicating with DAC.
+    DigitalOut** _ncs_array; // Array of pointers to DigitalOut objects
+    // connected to "not CS" pins.
+    DigitalOut* _nldac;      // Pointer to DigitalOut object connected
+    // to "not LDAC" pin (if any - NULL if none).
+};
+
+#endif