ATmega328p does not provide any Digital/Analog conversion channels (although the D/A conversion can be simulated by means of measuring the PWM output). We can easily add up to eight D/A channels using either a LTC1665 (8 bit) or LTC1660 (10 bit) from Linear Technology.

What makes LTC1665/LTC1660 attractive is that the D/A converters addressable via the serial interface. And thus, controlling the eight D/A channels requires relatively few connections with ATmega328p.

The schematic below illustrates how to utilize a D/A channel with input from ATmega328p. Pin IN, SCK, CSLD, CLR can be connected to any pins that accept digital input. And the pin labeled Analog Out outputs the conversion result.


The output voltage from the function output() is determined by:
\[Vout = \frac{Vref \times val}{256}\]
for LTC1665 (8 bit), and
\[Vout = \frac{Vref \times val}{1024}\]
for LTC1660 (10 bit). In my example above, Vref is connected to Vcc and is typically around 5V.

The following code uses Arduino to drive the DAC clock. To test the conversion result, the analog output is connected with an Arduino analog input pin.

const int pinIN = 8;
const int pinSCK = 7;
const int pinCSLD= 3;
const int pinCLR =12;

const int pinAnalogOut = 5;

void setup()
  output(1, 64);

void pinInit()
  pinMode(pinIN, OUTPUT);
  pinMode(pinSCK, OUTPUT);
  pinMode(pinCSLD, OUTPUT);
  pinMode(pinCLR, OUTPUT);
  digitalWrite(pinCLR, HIGH);
  digitalWrite(pinSCK, LOW);
  digitalWrite(pinCSLD, HIGH);

//Performs D/A conversion
void output(long chn, long val)
  //the control word is 16 bits
  //the high 4 bits defines the output channel
  long t = chn << 12;
  //the lower 4 bits are don't cares so we make
  //them zeros.
  t = t | val << 4;
  //for LTC1660 it has 10bit resolution this line will
  //need to change to:
  //t = t | val << 2;
  digitalWrite(pinCSLD, LOW);
  for (long i = 15; i >= 0; i--)
    long b = (t >> i) & 1;
    digitalWrite(pinIN, b);
    digitalWrite(pinSCK, HIGH);
    digitalWrite(pinSCK, LOW);  
  digitalWrite(pinCSLD, HIGH);

void loop()

The bit resolution difference between LTC1665 and LTC1660 is handled by the code within the output() function.


You can also drive LTC1665 using hardware SPI. The code is listed below:

void setDAC(int address, int value) 
    int h = address << 4 | value >> 4;
    int l = value << 4;
Be Sociable, Share!

4 Thoughts on “A Library for LTC1665/LTC1660”

  • I have been testing LTC1660 with a PIC micro, but when i load an analog port, the rest lose their settings and show 0 volts. When i send the address b1111, all ports are set correctly but again, when i load one port individually the rest lose their settings.

    Have you experienced this behavior?

    I have pin CLR always tied to Vcc.


  • Here’s a fast version of the output function. It’s about 5 times faster:

    //Performs D/A conversion
    void output(int8_t chn, uint16_t val) {
    //the control word is 16 bits
    //the high 4 bits defines the output channel
    uint16_t t = chn << 12;
    t |= val << 2;

    PORTD &= ~(1 <= 0; i–){
    byte b = (t >> i) & 1;
    PORTB = (PORTB & 0xFE) | b; //pinIN
    PORTD |= (1 << pinSCK); //HIGH
    PORTD &= ~(1 << pinSCK); //LOW
    PORTD |= (1 << pinCSLD); //HIGH

Leave a Reply

Your email address will not be published. Required fields are marked *