MCP4725 Digital to Analog Converter – Part 3

My plan was always to try to use my MCP4725 board with Mozzi eventually, but there is one thing I came to realize that has stopped that ambition.  You can’t talk I2C from an interrupt routine using the standard Arduino Wire libraries.  Mozzi uses an interrupt routine to output the audio samples at the required 32kHz sample rate.

So I set off in search of an alternative way to talk I2C without using interrupts (spoiler: this postulate turned out not to be true – actually the issue is time spent waiting around in the code rather than using interrupts, but more on that in a future post), and found one in the shape of of the I2C Master Library by Peter Fleury:

This post is a software update to skip using the Adafruit MCP4725 library which is itself based on the standard Arduino I2C “Wire” library.

If you are new to Arduino, see the Getting Started pages.

Parts list

  • Arduino Uno
  • MCP4725 breakout board (I have a Sparkfun board and some equivalents)
  • Breadboard and jumper wires

The Circuit

This uses the same board and circuit from part 1 or part 2.

The Code

I’ve adapted the previous Adafruit example to use the non-interrupt using I2C routines provided in Peter Fleury’s library, to install and use do the following:

  • Grab the i2cmaster.zip file from Peter Fleury’s site and extract the files somewhere useful.
  • Grab my example file from GitHub.
  • Copy the twimaster.c and i2cmaster.h files into your Arduino sketch alongside my MCP4725-DirectI2C.ino file.  You can ignore the other files from the ZIP file.
  • In twimaster.c change the #include statement for i2cmaster.h to the following:
#include "i2cmaster.h"

When i2cmaster.h is included in the main sketch file it needs to be referenced as follows (as it is a “C” file being used in a “C++” file):

extern "C" {
#include "i2cmaster.h"
}

At this point everything should build and work as expected.  The basic sequence to write data out to the MCP4725 is as follows:

#define MCP_ADDR 0x60  // I2C address of the device

// in setup()
i2c_init();
delay (1000); // Short delay to let things stabilize

// when writing
uint8_t val1 = (dacvalue & 0xf00) >> 8;
uint8_t val2 = (dacvalue & 0xff);
i2c_write(val1);
i2c_write(val2);
i2c_stop();

This is using the “fast write mode” of the MCP4725 (see the datasheet for details), which is different to the Adafruit library which was using the normal “Write DAC Register” with an option to write to the EEPROM.

The I2C protocol combines the address to use with an indication of read or write by using the seven most significant bits as the I2C address and bit 0 as a read or write indication.  The Arduino Wire library hides this detail, but this library assumes you will set the RW bit yourself.

Consequently, the call to i2c_start requires the bit-shifted address and bit 0 set to 1 if reading and cleared to 0 if writing.

I was also finding that device detection was a little intermittent until I added a delay (I used 1 second) after the i2c_init() function call.

A Short Note on I2C Clocks…

One other change that might be useful to make – I2C has a “normal” and “fast” mode where the bus clock runs at 100kbps or 400bps.  There is a definition at the top of twimaster.c that sets the clock.  After everything was confirmed working, I change the clock as follows:

/* I2C clock in Hz */
#define SCL_CLOCK 400000L

The Arduino TWI hardware has a “Bit Rate Generator Unit” that is used to set the clock.  The formula for working out the SCL frequency from the CPU clock and register settings (see the ATMega328 data sheet for the TWI) is:

SCL Frequency = CPU Clock Frequency / (16 + 2*TWBR * prescaler)

Rearranging we can get the equation for the value for TWBR as follows:

TWBR = ((CPU Clock Frequency / SCL Frequency) – 16) / (2 * prescaler)

which is the formula used in twimaster.c to set TWBR.  The Arduino (ATMega328) clock speed is 16MHz, or 16,000,000 so the various TWBR values possible are:

  • I2C 100kHz; TWBR = 72
  • I2C 400kHz; TWBR = 12

Anything lower than this is likely to be unstable apparently, even though the I2C specification does suggest higher speeds should be possible.

With all these changes, my demo code was giving me a steady 47Hz audio signal which means it was running at a sample rate of around 47 x 256 (as it takes an output of 256 samples per cycle) or  approx 12kHz. Naturally the audio frequency could be doubled with a lower bit resolution of sinewave (for example one of just 128 samples).

Closing Thoughts

This gets to the point of being able to repeat the DAC example sinewave with the new direct I2C library, but it remains to be seen if this is quick enough to output a stream of DAC values from a synthesis routing such as those found in Mozzi, which has a sample rate of either 16 or 32kHz.

There might be a couple more optimizations possible seeing as this is the only thing on the bus… watch this space.

But this could be a case of reporting back on a “maker fail” – there is a good chance an I2C DAC just isn’t quick enough to keep up with Mozzi.

Kevin

 

Leave a comment