We teach you how this handy device uses the SPI protocol to turn four digital I/O lines on your microcontroller into eight analogue lines, and much more.
The advent of Arduino and Raspberry Pi has changed the demographics of makers significantly. Once the preserve of those interested in electronics, with complex languages and processes involved and significant electronics knowledge required, building your own programmable projects is now easier than ever. This has meant that it appeals to many people who do not know much about electronics at all, and have come to the field from very different interests and skillsets.
This means that things that are obvious to someone with vast electronics experience may not be obvious to those attempting microcontroller projects from other backgrounds. Importantly, the reciprocal is also true. Because of this, it may seem curious to an electronics hobbyist that we are describing an integrated circuit such as an analogue to digital converter. However, people approaching microcontrollers from other backgrounds may not be aware that there are ways to alter the capabilities of the Input/Output (I/O) options on your chosen microcontroller by using external ICs.
So, it is that we introduce the MCP3008. This device is an Analogue to Digital Converter (ADC) with a 10-bit resolution, communicating with the Serial Peripheral Interface (SPI) protocol. It can be supplied from 2.7V to 5.5V DC, and when active, draws a maximum of half a milliampere. Sampling rate varies with supply voltage, from 75 thousand samples per second (ksps), to 200 ksps. Its eight inputs can be configured to work in single-ended mode, which means that they are independent, or differential mode, where the difference between two inputs is measured, rather than the levels of the inputs directly.
The whole device depends on a clock signal from its master, part of the SPI protocol, which means the IC can be software controlled.
The MCP3008 also has a reference voltage input so that the input range can be manipulated. It is commonly available in retail markets in a 16-pin DIL Package, with other packages largely confined to trade supply.
As always when we discuss specific devices, having a copy of the datasheet is essential. We used the one from Microchip Technology Inc.
The MCP3008 requires a minimum of external components connected in order to work. In fact, the inputs can cope with voltages up to the reference voltage, which itself can be as high as the supply voltage. The impedance of the source should be as low as possible. We’ll describe why later. Because of this, the voltage source to be measured is generally directly connected to the inputs - no resistors or filter capacitors are needed. Capacitors should be avoided anyway, because the sample is held on a 20pF internal capacitor that could easily be influenced by external capacitance.
The only connections needed besides the inputs are the four SPI lines, and two connections each to ground and supply voltage. Why two? The analogue and digital sections of the IC have their own grounds, and the voltage reference pin is usually tied to the supply voltage in most uses. With the reference voltage tied to a 5V supply, the 10-bit resolution is applied across 0-5V. a reading of, say 2.5V, would return a result half-way up the scale. However, if your inputs were, say, a maximum of 1V, you could set the reference voltage to 1V so that the full 10 bit resolution is applied from 0-1V, giving greater accuracy. In this case, if Vref was left at 5V, the 1V peak signal would only use a fifth of the available resolution.
WHAT IS SPI?
Although simple in concept, the details of SPI are quite involved. Involved is distinct from ‘complex’, and SPI should still be within reach of most coders: It just won’t be a fast task. A full set of instructions would be a project-length article, and some great tutorials and other resources on SPI exist online. We will perhaps look at this in the future. For now, here is a basic overview of SPI so you can decide if the MCP3008 is for you.
Many serial protocols are asynchronous, meaning that the receiving end is controlled by pulse levels and widths rather than any timing clock pulse. Although this works in many cases, the system relies on a lot of assumptions and predictions. As such, part of each byte is a timing bit, which has to be processed to ensure every bit following is received correctly. Both sender and receiver need a clock built-in to function.
SPI gets around this by using a separate clock signal, not one that is part of the data. This makes SPI quite versatile, as slave devices will connect to any SPI master, within some normalities. SPI slaves do what they need to do on either the rising edge (going from low to high) or falling edge (going from high to low) of the clock signal. They are not running in their own little world, spitting out data in the vague hope the receiver understands it, like separately-clocked serial protocols.
SPI uses two data lines. The first is labelled either MOSI for Master Out Slave In or DOUT, for Data Out. This line sends information from the master to the slave device, like the bits that tell our MCP3008 whether it should read its inputs in single-ended or differential mode. The second data line is labelled as MISO for Master In Slave Out, or DIN, for Data In, and is the way the slave gets is data back to the master. This one is the one that really carries the stuff we want, like readings from the analogue pins in the case of the MCP3008, but the two lines are just as important as each other
The last of the four SPI lines is the Slave Select line. While this is often labelled ‘SS’ on many SPI diagrams, the MCP3008 uses the terminology ‘Chip Select/Shutdown’, abbreviated as CS/SHDN. Normally, this pin is kept high. When it goes low, the device begins looking for the clock signal and doing its thing. If you want multiple devices, you can connect more than one using this feature. You can parallel-connect all the clock lines, all the Data Out lines, all the Data Ins, and use as many SS lines as you have slave devices (limited by your number of available I/O pins on your microcontroller, of course).
Alternatively, you can make the data flow through each device. The data from the master flows in the first device, and out its data out line to the next device. The last data out line in the string returns to the master. This is similar to the way the WS2812 light control protocol works, and means some very long data strings but only four I/O pins on the master for any number of slaves. The MCP3008’s datasheet does not make immediately clear whether it can function this way, but SPI certainly can. We recommend sticking to the one-device-per-SS-line method, and even if you need twenty-four analogue inputs, that’s still only six I/O pins.
USING THE MCP3008’S ANALOGUE INPUTS
The resistance of the input has an effect on the way the MCP3008 samples. The sample voltage is held on an internal capacitor, and so the higher the impedance of the input, the longer the charge time of this capacitor. Therefore, clock frequency needs to be set considering this factor. The graph in the datasheet suggests that for a 5V supply voltage with the reference pin also at 5V, an input resistance of 1000Ω results in a maximum clock frequency of just over 3MHz. Because the MC3008 deals with one bit of data every clock cycle, a frequency of 3,000,000 clocks per second is still a lot faster than the 9600 often used with serial. However, it is a factor that must be considered. Particularly because, at 10,000Ω, the rate drops to around 800,000Hz clock.
There are two options to deal with this. One is to program the clock rate to be much lower than maximum, which is fine for most makers. Even at half the maximum, it’s still an advantage over the 9600 baud of serial connections like RS232. This is the option we are taking. The other option is to use a buffer circuit to reduce the impedance. While we aren’t investigating this option just now, the datasheet elaborates on it, and information on buffering as a general concept is easy to find. In fact, Bob Harper’s Classroom article in Issue 14 shows you just what you need to know. Buffering allows you to run the device as fast as your microcontroller will.
However, if the clock pulse is too long, the internal sampling capacitor will begin to discharge, affecting the accuracy of the reading. As such, setting the rate far too low will compromise the effectiveness of your application. At maximum operating temperature of 85°C, the capacitor begins to lose charge after 1.2ms. Any clock pulse must be fast enough that the 10-bit conversion takes place within this time, rendering an effective lower clock limit of 10,000Hz.
We are not going to buffer, we’re going to use a lower clock rate. We are going to build a multi-zone temperature sensor to demonstrate the MCP3008. While we have done temperature sensors before, they are a simple (and commonly needed) way of demonstrating how the device works, and they can be manipulated easily with the aid of hairdryers, ice, freezing spray, or even sun and shade. In doing so, our input impedance will come from a voltage divider formed by a 1kΩ NTC thermistor, and a 1kΩ trimpot. Well within manageable limits.
MAKING IT GO
Because SPI usage differs from device to device, we’re describing in general terms what your code needs to do. Note that we’re driving from general I/O lines here. Some microcontrollers have dedicated SPI hardware, but there are additional complications to using it. If you know how, you’re not likely reading this bit!
At the heart of the MCP3008 is the clock input. Your code needs to give this a string of pulses at the rate described above. Next in line, the SS line for the device you want to talk with is pulled low (all SS lines start high). As soon as this goes low, the device watches the DIN pin for a high. When this is received, it is considered a start bit. Next comes the bit to determine whether the inputs are single-ended or differential. Following this, three bits select the channel to be read. They are to be provided in reverse order, with D2 first, ending with D0. After the fourth rising edge of the clock pulse, the device will start sampling the chosen input, and finish doing so on the falling edge of the fifth clock pulse.
There is a very helpful table that summarises and visualises this in the datasheet. Look for the ‘Configure Bits’ or ‘Configuration’ table.
This is a one-clock gap between the D0 bit, and the beginning of the next channel read. For the duration of this gap between D0 and the start of data return, during which the sample and hold takes place, the DIN line is ignored. On the next falling edge clock pulse, a single null bit, low, is sent, followed by the ten-bit data stream containing the analogue value, with the Most Significant Bit (MSB) coming first. After these ten bits, your code should drive the SS line high, ending the conversation. If it does not, the device will actually begin transmitting the data in reverse, with the Least Significant Bit (LSB) first. This suits some applications and is not an accident. Note that on the MCP3008, the SS pin is called the CS/SHDN pin, for Chip Select/Shutdown. After this, however, if the SS line (labeled CS/SHDN on the MCP3008) line is still low, the device gives a continuous string of zeros. The SS line must go high, then low again, before the next channel is read.
All of that is a bit much to read and follow, so we’ve adapted a diagram from the datasheets which shows this in visual form. When you’re writing or adapting code, this should help you work out what to make the I/O pins do, when. The challenge is that SPI works with individual bits of data, not neat packets of eight bits grouped in one byte that traditional serial protocols have worked with.
USING THE DATA
The data output from the MCP3008 has a resolution of 10 bits. The full-scale voltage is divided by 1024 (210), and the number presented will be between 0 and 1023, inclusive. So, if the full-scale voltage is 5V, then 5000 mV (it will be easier to work in millivolts) divided by 1024 gives us 4.88mV per increment, rounded to two decimal places. Because the nominal 5V from an Arduino is often not exactly 5V, you’ll have to measure the voltage before writing the maths in any code. It is tempting to simply measure the voltage with one of the pins and use this number in your code to divide by 1024 and gain a result for the value of each increment, but remember, VREF is tied to the supply, and so the ADC is not measuring against a theoretical 5V, but against the supply rail itself. In other words, if the supply voltage was 4.8V, then a pin of the MCP3008 (or the microcontroller’s own ADC pin) connected to the supply will read 1023 for 4.8V, instead of the 5V we would like 1023 to belong to. If you were really thoughtful with your code, we’re sure there are ways this could still work for some applications.
We are only going to use one MCP3008 for this build, as it is a concept demonstrator and code tester only. As such, it’s pretty simple, and can be built on a solderless breadboard, or protoboard. Putting it together is straightforward, with identical blocks of NTC/trimpot voltage dividers with their junctions connected to the inputs of the device. The trimpots allow a degree of adjustment to experiment with, rather than using fixed resistors. You will notice that we have not used all eight inputs. That would be hard to fit on the board in a clean, easy-to-follow (and photograph) way, so we chose to work with three inputs to show you how it works.
To assemble, follow the schematic and Fritzing, and you should have a working build in under five minutes. You’ll have to adjust the trimpots to 1K initially. Once you know which way you’re mounting them in the board, connect your multimeter to the relevant pins, and adjust so you get a reading as close to 1K as possible between the wiper and whichever outer pin you’re using.
Note: While we kept the NTC thermistors on the board for clarity, you may like to add wires to yours so you can move them away and apply different sources (or sinks) of heat to them.
|Breadboard Wire Links
|6 x Plug-to-plug Jumper Wires
|3 x 1kΩ NTC Thermistors
|3 x 1kΩ Trimpots
|1 x MCP3008 ADC IC
|1 x Arduino Uno or Compatible Generic Board
Now, you can load a code (available from our website) into your microcontroller.
We prototyped with an Arduino Uno, and the code supplied is for the same, but you can adapt it for other Arduino devices, or write one for Raspberry Pi.
Open the Serial Monitor for Arduino, set to 9600 baud, and you should see numbers appearing in the display that correspond to voltages at the inputs of the MCP3008. Applying temperature changes to one of the thermistors should produce a change in the relevant number in the serial monitor.
WHERE TO FROM HERE?
Of course, you can fiddle with the concept quite a lot. While it is hard to calibrate an NTC thermistor circuit to a specific temperature, you can use a potentiometer on another input, write code to compare the two, and trigger functions of the NTC input when it is above or below the level set by the potentiometer.
This requires you to be present when the thermistor is exposed to the temperature you want a change to occur at.
Alternatively, you could be using the analogue voltage from the LM335, or a reading from the DF Robot capacitive moisture level sensor that we have used previously.
Any sensor which produces a 0-5V (or part thereof) analogue voltage out can be read with the MCP3008.
If the voltages are known to correspond to real-world units (like the LM335), then you could add the maths to the code to turn the 1024-bit number into a unit of measure other than volts, and see that in your serial monitor instead.
At this point, you’re probably looking to move past the serial monitor, and use the voltages given from the MCP3008 to actuate functions within your code. Have fun!