Tutorial – Arduino and PCF8591 ADC DAC IC

Learn how to use the NXP PCF 8591 8-bit A/D and D/A IC with Arduino in chapter fifty-two of my Arduino Tutorials. The first chapter is here, the complete series is detailed here.

Updated 17/06/2013


Have you ever wanted more analogue input pins on your Arduino project, but not wanted to fork out for a Mega? Or would you like to generate analogue signals? Then check out the subject of our tutorial – the NXP PCF8591 IC. It solves both these problems as it has a single DAC (digital to analogue) converter as well as four ADCs (analogue to digital converters) – all accessible via the I2C bus. If the I2C bus is new to you, please familiarise yourself with the readings here before moving forward.

The PCF8591 is available in DIP form, which makes it easy to experiment with:


You can get them from the usual retailers. Before moving on, download the data sheet. The PCF8591 can operate on both 5V and 3.3V so if you’re using an Arduino Due, Raspberry Pi or other 3.3 V development board you’re fine. Now we’ll first explain the DAC, then the ADCs.

Using the DAC (digital-to-analogue converter)

The DAC on the PCF8591 has a resolution of 8-bits – so it can generate a theoretical signal of between zero volts and the reference voltage (Vref) in 255 steps. For demonstration purposes we’ll use a Vref of 5V, and you can use a lower Vref such as 3.3V or whatever you wish the maximum value to be … however it must be less than the supply voltage. Note that when there is a load on the analogue output (a real-world situation), the maximum output voltage will drop – the data sheet (which you downloaded) shows a 10% drop for a 10kΩ load. Now for our demonstration circuit:


Note the use of 10kΩ pull-up resistors on the I2C bus, and the 10μF capacitor between 5V and GND. The I2C bus address is set by a combination of pins A0~A2, and with them all to GND the address is 0x90. The analogue output can be taken from pin 15 (and there’s a seperate analogue GND on pin 13. Also, connect pin 13 to GND, and circuit GND to Arduino GND.

To control the DAC we need to send two bytes of data. The first is the control byte, which simply activates the DAC and is 1000000 (or 0x40) and the next byte is the value between 0 and 255 (the output level). This is demonstrated in the following sketch:

Did you notice the bit shift of the bus address in the #define statement? Arduino sends 7-bit addresses but the PCF8591 wants an 8-bit, so we shift the byte over by one bit. 

The results of the sketch are shown below, we’ve connected the Vref to 5V and the oscilloscope probe and GND to the analogue output and GND respectively:


If you like curves you can generate sine waves with the sketch below. It uses a lookup table in an array which contains the necessary pre-calculated data points:

And the results:


For the following DSO image dump, we changed the Vref to 3.3V – note the change in the maxima on the sine wave:


Now you can experiment with the DAC to make sound effects, signals or control other analogue circuits.

Using the ADCs (analogue-to-digital converters)

If you’ve used the analogRead() function on your Arduino (way back in Chapter One) then you’re already familiar with an ADC. With out PCF8591 we can read a voltage between zero and the Vref and it will return a value of between zero and 255 which is directly proportional to zero and the Vref. For example, measuring 3.3V should return 168. The resolution (8-bit) of the ADC is lower than the onboard Arduino (10-bit) however the PCF8591 can do something the Arduino’s ADC cannot. But we’ll get to that in a moment.

First, to simply read the values of each ADC pin we send a control byte to tell the PCF8591 which ADC we want to read. For ADCs zero to three the control byte is 0x00, 0x01, ox02 and 0x03 respectively. Then we ask for two bytes of data back from the ADC, and store the second byte for use. Why two bytes? The PCF8591 returns the previously measured value first – then the current byte. (See Figure 8 in the data sheet). Finally, if you’re not using all the ADC pins, connect the unused ones to GND.

The following example sketch simply retrieves values from each ADC pin one at a time, then displays them in the serial monitor:

Upon running the sketch you’ll be presented with the values of each ADC in the serial monitor. Although it was a simple demonstration to show you how to individually read each ADC, it is a cumbersome method of getting more than one byte at a time from a particular ADC.

To do this, change the control byte to request auto-increment, which is done by setting bit 2 of the control byte to 1. So to start from ADC0 we use a new control byte of binary 00000100 or hexadecimal 0x04. Then request five bytes of data (once again we ignore the first byte) which will cause the PCF8591 to return all values in one chain of bytes. This process is demonstrated in the following sketch:

Previously we mentioned that the PCF8591 can do something that the Arduino’s ADC cannot, and this is offer a differential ADC. As opposed to the Arduino’s single-ended (i.e. it returns the difference between the positive signal voltage and GND, the differential ADC accepts two signals (that don’t necessarily have to be referenced to ground), and returns the difference between the two signals. This can be convenient for measuring small changes in voltages for load cells and so on.

Setting up the PCF8591 for differential ADC is a simple matter of changing the control byte. If you turn to page seven of the data sheet, then consider the different types of analogue input programming. Previously we used mode ’00’ for four inputs, however you can select the others which are clearly illustrated, for example:


So to set the control byte for two differential inputs, use binary 00110000 or 0x30. Then it’s a simple matter of requesting the bytes of data and working with them. As you can see there’s also combination single/differential and a complex three-differential input. However we’ll leave them for the time being.


Hopefully you found this of interest, whether adding a DAC to your experiments or learning a bit more about ADCs. We’ll have some more analogue to digital articles coming up soon, so stay tuned. And if you enjoy my tutorials, or want to introduce someone else to the interesting world of Arduino – check out my new book “Arduino Workshop” from No Starch Press.


In the meanwhile have fun and keep checking into tronixstuff.com. Why not follow things on twitterGoogle+, subscribe  for email updates or RSS using the links on the right-hand column? And join our friendly Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other –  and we can all learn something.

Posted in ADC, arduino, beginnner, dac, differential, education, electronics, I2C, lesson, NXP, PCF8591, tronixstuff, tutorial14 Comments

Tutorial: Arduino and the SPI bus part II

This is chapter thirty-six of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – A seemingly endless series of articles on the Arduino universe. The first chapter is here, the complete series is detailed here

[Updated 10/01/2013]

This is the second of several chapters in which we are investigating the SPI data bus, and how we can control devices using it with our Arduino systems. If you have not done so already, please read part one of the SPI articles. Again we will learn the necessary theory, and then apply it by controlling a variety of devices. As always things will be kept as simple as possible.

First on our list today is the use of multiple SPI devices on the single bus. We briefly touched on this in part one, by showing how multiple devices are wired, for example:

Notice how the slave devices share the clock, MOSI and MISO lines – however they both have their own chip select line back to the master device. At this point a limitation of the SPI bus becomes prevalent – for each slave device we need another digital pin to control chip select for that device. If you were looking to control many devices, it would be better to consider finding I2C solutions to the problem. To implement multiple devices is very easy. Consider the example 34.1 from part one – we controlled a digital rheostat. Now we will repeat the example, but instead control four instead of one. For reference, here is the pinout diagram:

Doing so may sound complex, but it is not. We connect the SCK, MOSI and  MISO pins together, then to Arduino pins D13, D11, D12 respectively. Each CS pin is wired to a separate Arduino digital pin. In our example rheostats 1 to 4 connect to D10 through to D7 respectively. To show the resistance is changing on each rheostat, there is an LED between pin 5 and GND and a 470 ohm resistor between 5V and pin 6. Next, here is the sketch:

Although the example sketch may be longer than necessary, it is quite simple. We have four SPI devices each controlling one LED, so to keep things easy to track we have defined led1~led4 to match the chip select digital out pins used for each SPI device. Then see the first four lines in void setup(); these pins are set to output in order to function as required. Next – this is very important – we set the pins’ state to HIGH. You must do this to every chip select line! Otherwise more than one CS pins may be initially low in some instances and cause the first data sent from MOSI to travel along to two or more SPI devices. With LEDs this may not be an issue, but for motor controllers … well it could be.

The other point of interest is the function

We pass the value for the SPI device we want to control, and the value to send to the device. The value for l is the chip select value for the SPI device to control, and ranges from 10~7 – or as defined earlier, led1~4. The rest of the sketch is involved in controlling the LED’s brightness by varying the resistance of the rheostats. Now to see example 36.1 in action via the following video clip:

(If you are wondering what I have done to the Freetronics board in that video, it was to add a DS1307 real-time clock IC in the prototyping section).

Next on the agenda is a digital-to-analogue converter, to be referred to using the acronym DAC. What is a DAC? In simple terms, it accepts a numerical value between zero and a maximum value (digital) and outputs a voltage between the range of zero and a maximum relative to the input value (analogue). One could consider this to be the opposite of the what we use the function analogRead(); for. For our example we will use a Microchip MCP4921 (data sheet.pdf):

(Please note that this is a beginners’ tutorial and is somewhat simplified). This DAC has a 12-bit resolution. This means that it can accept a decimal number between 0 and 4095 – in binary this is 0 to 1111 1111 1111 (see why it is called 12-bit) – and the outpout voltage is divided into 4096 steps. The output voltage for this particular DAC can fall between 0 and just under the supply voltage (5V). So for each increase of 1 in the decimal input value, the DAC will output around 1.221 millivolts.

It is also possible to reduce the size of the voltage output steps by using a lower reference voltage. Then the DAC will consider the reference voltage to be the maximum output with a value of 4095. So (for example) if the reference voltage was 2.5V, each increase of 1 in the decimal input value, the DAC will output around 0.6105 millivolts. The minimum reference voltage possible is 0.8V, which offers a step of 200 microvolts (uV).

The output of a DAC can be used for many things, such as a function generator or the playback of audio recorded in a digital form. For now we will examine how to use the hardware, and monitoring output on an oscilloscope. First we need the pinouts:

By now these sorts of diagrams shouldn’t present any problems. In this example, we keep pin 5 permanently set to GND; pin 6 is where you feed in the reference voltage – we will set this to +5V; AVss is GND; and Vouta is the output signal pin – where the magic comes from 🙂 The next thing to investigate is the MCP4921’s write command register:

Bits 0 to 11 are the 12 bits of the output value; bit 15 is an output selector (unused on the MPC4921); bit 14 controls the input buffer; bit 13 controls an inbuilt output amplifier; and bit 12 can shutdown the DAC. Unlike previous devices, the input data is spread across two bytes (or a word of data). Therefore a small amount of work needs to be done to format the data ready for the DAC. Let’s explain this through looking at the sketch for example 36.2 that follows. The purpose of the sketch is to go through all possible DAC values, from 0 to 4095, then back to 0 and so on.

First. note the variable outputvalue – it is a word, a 16-bit unsigned variable. This is perfect as we will be sending a word of data to the DAC. We put the increasing/decreasing value for a into outputValue. However as we can only send bytes of data at a time down the SPI bus, we will use the function highbyte() to separate the high side of the word (bits 15~8) into a byte variable called data.

We then use the bitwise AND and OR operators to set the parameter bits 15~12. Then this byte is sent to the SPI bus. Finally, the function lowbyte() is used to send the low side of the word (bits 7~0) into data and thence down the SPI bus as well.

Now for our demonstration sketch:

And a quick look at the DAC in action via an oscilloscope:

By now we have covered in detail how to send data to a device on the SPI bus. But how do we receive data from a device?

Doing so is quite simple, but some information is required about the particular device. For the rest of this chapter, we will use the Maxim DS3234 “extremely accurate” real-time clock. Please download the data sheet (.pdf) now, as it will be referred to many times.

The DS3234 is not available in through-hole packaging, so we will be using one that comes pre-soldered onto a very convenient breakout board:

It only takes a few moments to solder in some header pins for breadboard use. The battery type is CR1220 (12 x 2.0mm, 3V); if you don’t have a battery you will need to short out the battery holder with some wire otherwise the IC will not work. Readers have reported that the IC doesn’t keep time if the USB and external power are both applied to the Arduino at the same time.

A device will have one or more registers where information is read from and written to. Look at page twelve of the DS3234 data sheet, there are twenty-three registers, each containing eight bits (one byte) of data. Please take note that each register has a read and write address. An example – to retrieve the contents of the register at location 08h (alarm minutes) and place it into the byte data we need to do the following:

Don’t forget to take note of  the function SPI.setBitOrder(MSBFIRST); in your sketch, as this also determines the bit order of the data coming from the device. To write data to a specific address is also quite simple, for example:

Up to this point, we have not concerned ourselves with what is called the SPI data mode. The mode determines how the SPI device interprets the ‘pulses’ of data going in and out of the device. For a well-defined explanation, please read this article. With some devices (and in our forthcoming example) the data mode needs to be defined. So we use:

to set the data mode, within void(setup);. To determine a device’s data mode, as always – consult the data sheet. With our DS3234 example, the mode is mentioned on page 1 under Features List.

Finally, let’s delve a little deeper into SPI via the DS3234. The interesting people at Sparkfun have already written a good demonstration sketch for the DS3234, so let’s have a look at that and deconstruct it a little to see what is going on. You can download the sketch below from here, then change the file extension from .c to .pde.

Don’t let the use of custom functions and loops put you off, they are there to save time. Looking in the function SetTimeDate();, you can see that the data is written to the registers 80h through to 86h (skipping 83h – day of week) in the way as described earlier (set CS low, send out address to write to, send out data, set CS high). You will also notice some bitwise arithmetic going on as well. This is done to convert data between binary-coded decimal and decimal numbers.

Why? Go back to page twelve of the DS3234 data sheet and look at (e.g.) register 00h/80h – seconds. The bits 7~4 are used to represent the ‘tens’ column of the value, and bits 3~0 represent the ‘ones’ column of the value. So some bit shifting is necessary to isolate the digit for each column in order to convert the data to decimal. For other ways to convert between BCD and decimal, see the examples using the Maxim DS1307 in chapter seven.

Finally here is another example of reading the time data from the DS3234:

So there you have it – more about the world of the SPI bus and how to control the devices within.


In the meanwhile have fun and keep checking into tronixstuff.com. Why not follow things on twitterGoogle+, subscribe  for email updates or RSS using the links on the right-hand column? And join our friendly Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other –  and we can all learn something.

Posted in arduino, BOB-10160, dac, DS3234, education, learning electronics, lesson, MCP4162, MCP4921, microcontrollers, SPI, tutorial, Uncategorized14 Comments

Subscribe via email

Receive notifications of new posts by email.

The Arduino Book

Arduino Workshop

Für unsere deutschen Freunde

Dla naszych polskich przyjaciół ...

Australian Electronics!

Buy and support Silicon Chip - Australia's only Electronics Magazine.

Use of our content…

%d bloggers like this: