Raspberry Pi has only digital inputs. In this article we read analog data through Analog to Digital Converter (ADC) over Serial Peripheral Interface (SPI).
In my project I just needed to read data from 2 proximity sensors. I'm using Raspberry Pi 2 (pin mappings) and MCP3008 (datasheet).
- Connect Pi
SPI MOSI
to MCP3008Din
, and PiSPI MISO
to MCP3008Dout
. Yes, inputs of both devices are connected together, and so are outputs. - Use either Pi's
SPI CS0
orSPI CS1
pin, you can pick which one to use in the code. This allows you to connect two SPI devices. - You can connect up to 10 analog wires to the MCP3008 ADC, and Raspberry Pi supports two ADCs thanks to two SPI chip select lines. This means that by using two MCP3008s, your Raspberry Pi can read analog data from 20 lines!
To connect to the ADC, you need to obtain an instance of SpiDevice
from Widows.Devices.Spi
namespace. To access this namespace, you need to first add a reference to "Windows IoT Extensions for the UWP"
Alright, let's instantiate the SpiDevice
. This code is adapted from the Potentiometer Sensor Sample.
This is boilerplate code where I would pay attention mostly to SpiConnectionSettings
. The parameter is the Chip Select line you used earlier in the circuit, and ClockFrequency
should not exceed the maximum frequency specified by the device's datasheet. Mode0
seems to work for MCP3008.
SpiDevice ADC;
// . . .
var settings = new SpiConnectionSettings(0) // Chip Select line 0
{
ClockFrequency = 500 * 1000, // Don't exceed 3.6 MHz
Mode = SpiMode.Mode0,
};
string spiAqs = SpiDevice.GetDeviceSelector("SPI0"); /* Find the selector string for the SPI bus controller */
var devicesInfo = await DeviceInformation.FindAllAsync(spiAqs); /* Find the SPI bus controller device with our selector string */
ADC = await SpiDevice.FromIdAsync(devicesInfo[0].Id, settings); /* Create an SpiDevice with our bus controller and SPI settings */
Now here's the real challenging code, and we'll get into detail. Use TransferFullDuplex
to read data from the SPI device. You pass in two byte arrays as parameters; one that tells the device what to do, and another, empty array where the device will put the data.
byte[] request = new byte[3] { 0x01, 0x80, 0 };
byte[] response = new byte[3];
// . . .
ADC.TransferFullDuplex(request, response);
First, we need to establish the size of the arrays. Let's start with the response buffer:
The ADC response is 10 bits long. We can fit it into 2 bytes. We also add one byte padding. Hence, the response is 3 bytes long.
In SPI communication, for every byte we want to receive, we send one byte. Therefore, the request bytes are also 3 bytes long.
Now, what do we send? The ADC datasheet calls for a 4 bit pattern 1000
to read from the first channel, and 1001
to read from the second channel. A few pages further, the datasheet also says that the message needs to be preceeded with a 1
bit. So, to read values from the first channel, we send 11000
.
This is where various code snippets I found online get confusing: You can convert 11000
to hexadecimal 0x18
. and 110000
to 0x30
. Both approaches will work, but the code becomes not maintainable.
To address this issue, we just need to split the data into bytes in a different way: Let's make it such that the first byte contains the initial 1
, and the second byte contains the channel selecting pattern. Hence, we will send 0000 0001 1000 xxxx xxxx xxxx
(x
is don't care), which translates to { 0x01, 0x80, 0 }
. Any change of the pattern will be reflected exclusively in the third hexadecimal digit (now 8
).
Finally, we must convert the raw response of type byte[]
to a more useful type, such as int
. What happens in the ConvertToInt
method, then?
int result = 0;
result = data[1] & 0x03;
result <<= 8;
result += data[2];
return result;
The datasheet of the ADC says that the device outputs 10-bit chunks of data. This code extracts these ten bytes from the input data array.
Let's illustrate this code by plugging in values into variables. Suppose that byte[]
data looks like this: AAAAaaaa BBBBbbbb CCCCcccc
, and the ten bytes with actual data are bbCCCCcccc
.
-
data[1] & 0x03
becomesBBBBbbbb & 00000011
, which is000000bb
-
result <<= 8
transforms this into000000bb 00000000
. -
result += data[2];
produces000000bb CCCCcccc
, which is the ten bits that we were after!
These were the biggest obstacles that I had when working with the SPI code. There is some non trivial math in this code, so feel free to or reach out to me on Twitter @HiAmadeus. To see how this code is used, watch Smart Mirror episode 7 - Distance Sensor, ADC, SPI - code.
Happy hacking!
Comments