analog2pi

IN PROGRESS: I'm re-doing this from my old site to be Zero-specific, and to use a real sound device kernel module. Don't rely on anything below: it's still being edited. That being said, the story and circuit are valid.

TWO CHANNEL ANALOG INPUT FOR UNDER A DOLLAR

THE STORY

You've heard it a million times: "The Arduino has analog inputs, why doesn't the Pi?". So, awesome hacker that you are, you decide to do something about it. But it wouldn't be any fun if there weren't some serious constraints. How about it has to be low cost, have low parts count, consist entirely of easy to acquire parts, and be simple to build even for novices? Something like PiFM, where the entire hardware interface is a piece of wire? (OK, a piece of wire is an over-simplification. It works, but you're supposed to add a filter consisting of something like 3 inductors and 2 capacitors.) 

So you begin thinking: there is a well-known trick used with microcontrollers to measure analog values using a digital input. Here is how it works: you connect a capacitor from a GPIO to ground, and a resistor across the capacitor. You set the GPIO to output a one: this charges the capacitor. Then set the GPIO to an input, and measure the time for the input to go from a one to a zero. This works because a digital input can be used as a fairly consistent analog comparator, with a fixed threshold of approximately half the power supply voltage (for most CMOS devices). Typically this technique is used to measure the resistor or the capacitor. But you realize you can measure voltage if the the resistor is disconnected from the ground end of the capacitor and is connected to a voltage source instead. The voltage will, of course, have to be less than the input threshold voltage. But it CAN be below ground!

In microcontrollers it's easy to measure small units of time, but not so with the Pi. So you wonder: how can you sample a GPIO pin at regular intervals? Anything like a shift register will suffice, regularly sampling the input and combining the samples into a byte or word. And guess what, the Pi has two peripherals like this: PCM and SPI, which are both little more than shift registers.

However, there's a small problem: these peripherals can't change a GPIO from input to output to charge the capacitor. But maybe there's a way to use another output to do this job? It could charge the capacitor through a diode when it is high, and would have no affect when it is low. Then the sampling input could remain an input. How about if this reset signal was generated by the SPI output? It would just need to be fed the right data. And you have to feed the SPI output data for the input to work anyway!

Now it's time to review the design to see if things could be further improved. How about eliminating the capacitor? Since digital inputs have some parasitic capacitance associated with them, and the diode adds even more capacitance, maybe that will be enough. So you run some numbers to see if it will work: you pick a 1 megohm resistor (doesn't all test equipment have 1 megohm input resistance?) and take an educated guess that the input capacitance will be about 10 picofarads.

Using your old friend the RC time constant, and assuming a grounded input, you figure out that the capacitance will discharge to the Pi's threshold voltage in around 10 microseconds. This means the sample rate is around 100 kilohertz, which implies you will be able to measure anything lower than half that frequency. It looks like you will be able to measure audio-range signals: not too shabby!  But you decide you can do a little better: since the capacitance doesn't have to be charged up much past the threshold voltage, you can use two diodes in series. This has the effect of cutting the capacitance they contribute in half, decreasing the sample time.

After thinking about it for a while you realize you can use the PCM peripheral to add a second channel! Just use a second set of diodes from DOUT to DIN. You have to synchronize the PCM and SPI clocks, but you've done this before. And what's more, these peripherals can be serviced by DMA controllers. No processor intervention is needed during sampling. The only effect there might be is a small reduction in memory bandwidth.

Wow, you just designed yourself a two-channel audio band DSO (Digital Sampling Oscilloscope) using just 6 inexpensive parts!

THE CIRCUIT

Here it is in all it's glory:

THE SOFTWARE

Of course this circuit won't do anything without some awesome software. So you crank out the code in a jiffy. (Okay, it actually took several weekends of work. You're not as good a hacker as you thought.) As it turns out your original estimate of the input capacitance wasn't far off: it's actually close to 20 picofarads. And the two inputs have different capacitances and/or thresholds. But after changing the code to account for this, your sampling rate is still just over 50 kilohertz. This means you can easily capture 20 kilohertz signals: that's the whole audio band!

Now that it's working, you need to write graphical display software. And then re-write it to be a sound device so you can use the xoscope program for output...

THE REALITY

That's the end of my little story. It's actually close to how things really happened. There were a few iterations of the circuit, and I originally started by using the PCM peripheral instead of SPI.

It also glosses over many little problems with the circuit. For example: Although the input resistance is 1 Megohm, current is not drawn continuously, so it could introduce noise into the measured circuit. This also means the signal should be DC coupled. The resolution is only about 6 bits: that's not great, but still usable to get the general idea of signal shape and timing. Since there is no low-pass filter on the inputs, aliasing will be a problem. The sample values must be corrected in software since the circuit is non-linear and both inputs measure differently. Since each circuit/Pi combination will be different, it may be necessary to determine correction coefficients for each. As designed the input range is 1V P-P, so you'll need to attenuate anything greater than this with external circuitry. And it's probably sensitive to temperature. Actually, it seems there are quite a few problems...

On the other hand, I've been surprised at how well this actually works. Every time I think it's measuring incorrectly and double-check it with a 'real' oscilloscope it's spot on. Turns out the PI sound output (Future project: use the Pi as a signal generator!) is pretty crappy...

USING IT

To use this project, make sure SPI is disabled. You can do this in the advanced section of the raspi-config program. Compile and load (as root) the kernel module. There are some notes regarding this at the beginning of the source. My modules page has compiling instructions if you're not familiar with module compilation. You also need to compile and run the piScope display program. Compile instructions are at the beginning it's source code. All files are in this project's files section. Of course you need to build the interface circuit: all of the parts are directly soldered to the Zero. Note that the two sections are identical, just rotated on installation. I love it when something like this happens! It looks like this before soldering:

Once everything is running, the scope program will continuously display the captured signal. Press the space bar to stop sampling, press it again to sample. The arrow keys move things around, and the Q key quits.

The Pi headphone output provides a convenient test signal source: the speaker-test program can be used to generate test signals. Or you can install siggen, a spiffy console mode signal generator program. It needs to be run in combination with the aoss utility (part of alsa-oss) in order to work. Remember that the Pi's maximum output frequency is about 10 kilohertz: it does funny things above this frequency.

CALIBRATION

If you use my code as-is, you'll probably have some distortion and incorrect amplitudes. Especially if you are using a Pi 2, since it's input capacitances are very different. If you want an accurate display, you'll need to calibrate it.

To do this, you'll need a source of several calibration voltages. The easiest and cheapest way is a series string of five 1K resisors, with a 10K resistor at the end of the string. You can solder it together or use a proto board. The ends of this string are connected to a 1.5V battery: the battery positive to the 10K resistor, negative to the 1K resistor. Measure and record the voltages between each pair of resistors, with the meter ground lead connected to the battery negative terminal.  A cheap DVM is quite adequate. The voltages should be at roughly .1, .2, .3, .4, and .5 volts. For each measured value, subtract the value from .5 and multiply the result by 300. These are the positive values. For the negative values, add .5 to the measured value and multiply the result by 300.  Write these values on labels at each corresponding resistor junction. 

Now connect a Pi ground pin to the battery negative lead, and both input leads to one of the resistor junctions. Make sure the kernel module is loaded, and compile and run the analog2screen program. It will display the capture values for SPI and PCM. Write down the value for each: use the average value displayed. Also write down the positive junction label value. Move the inputs to each resistor junction and repeat the measurement.

Now reverse connections: the inputs go to the battery negative, and the pi ground pin is connected to a resistor junction. Do the measurements like above, moving the Pi ground to each resistor junction in turn. You are, however, measuring negative voltages: use the negative label values. The final measurement is with the inputs connected to a Pi ground pin: this gives measurements for a value of 150. You should now have 11 measurements recorded for SPI and 11 for PCM. I know this isn't a very good description, so I've listed my values here to help you figure it out. These are for SPI on a PI 2:

OUT   SPI

-13   64.8

20    54.2

52    45.4

85    38.1

117   31.8

150   26.6

183   22.4

215   18.5

248   15.1

281   12.0

313   9.3

Next you'll have to use these values to generate coefficients for a linearizing equation. Since the Pi has Mathematica already installed, we will use that. Enter your SPI values into a text file named spi.wm. The file contents should look like this:

data={{64.8,-13},{54.2,20},{45.4,52},{38.1,85},{31.8,117},{26.6,150},{22.4,183},{18.5,215},{15.1,248},{12.0,281},{9.3,313}}

Fit[data,{1,x,x^2,x^3},x]

Then run the following command:

wolfram < spi.wm

The output of the command lists the coefficient values. Edit the spilin function in the piScope.c file and replace the existing values with yours.

Repeat the process for PCM, but instead use pcm.wm and edit pcmlin. After piScope.c is compiled the new values will take effect. Test it to make sure.

EXTRAS

There is a utility program, analog2au, that can get a continuous block of data (you must specify the size) and save it as an .au sound file. Instructions are in the source code. This file can be played back or analyzed in a sound editor such as Audacity. This combo kind of makes up for the limited functionality of the piScope program.

This site has been tested to display correctly using Epiphany on the Raspberry Pi