← Advanced Instrumentation

PHYS 351 · Advanced Instrumentation

Lecture 08 · ADC, SPI & remote access

PHYS 351 — LECTURE 08 NOTES
The SPI Protocol, the MCP3008 ADC, and Remote Access to the Raspberry Pi
Covers Lab 8

© Ran Yang, Ph.D.
yangran.org/teaching/phys351/

1Where Lab 8 Sits

This is the last lab before the group project, and it closes a circle that opened in Lab 1.

In Lab 1 you built analog circuits and read them with a DMM and an oscilloscope — instruments someone else built. This week you build the instrument. The MCP3008 turns a voltage into a number, which is precisely what the front end of every digital multimeter, oscilloscope and data logger does. Once you have an ADC and a Pi, you have a general-purpose measuring instrument that you programmed yourself.

Two other threads close here as well. SPI is the second synchronous serial protocol, completing the comparison with the I²C of Lab 6. And remote access means the Pi no longer needs a monitor and keyboard — which is how it will be used in your project, and how essentially all real instrumentation is operated.

The one idea to carry out of this lecture

An ADC replaces a continuous voltage with a number drawn from a finite set, sampled at particular instants. Both of those are approximations, and both have consequences you can calculate: quantisation sets how finely you can resolve a value, and sampling rate sets what frequencies you can faithfully represent. Neither can be fixed later in software.

Lab 8 needs an internet connection

Unlike every previous lab in this course, Task 1 asks you to connect the Pi to the internet, update the operating system, and download a file. Plan for it, and expect the update step to take a while. If network access is unavailable, ask the instructor before improvising — some of the later tasks depend on it.

Lab 8 taskWhat you doConcept & section
1Network, lab directory, OS update, enable SPI, download the provided moduleBringing SPI up (§2.4)
2Wire the MCP3008; read a channel as a voltage; demonstrateSPI wiring; counts to volts (§5, §6)
3Build a 0–3.3 V source; compare your readings against a DMM; error handlingCalibration and accuracy (§6.3)
4Output raw counts as well as volts for 20+ points; plot volts against counts; analyse linearityThe transfer function (§6)
5Sine wave into both the ADC and the scope; plot voltage against time; find the maximum usable frequencySampling, Nyquist, aliasing (§7, §8)
6–7SSH from your laptop; VNC desktop; run your programs remotelyRemote access (§9)

2The SPI Bus

SPI — Serial Peripheral Interface — is the other synchronous serial protocol. Like I²C it sends a clock alongside the data; unlike I²C it uses separate wires for each direction and selects devices with a dedicated pin rather than an address.

2.1  Four wires

SignalNameDirectionPi pin
SCLKSerial clockcontroller → deviceBCM 11, physical 23
MOSIController Out, Sensor Incontroller → deviceBCM 10, physical 19
MISOController In, Sensor Outdevice → controllerBCM 9, physical 21
CE0Chip enable / chip selectcontroller → deviceBCM 8, physical 24

A second chip-select line, CE1 on BCM 7 (physical 26), allows a second device on the same bus. Note that SCLK, MOSI and MISO are shared by every SPI device; only the chip-select line is unique to each.

2.2  How a transfer works

SPI is a shift register spanning two chips, and that single picture explains all of its behaviour. The controller pulls the chosen device's chip-select line low to begin, then issues clock pulses. On each pulse one bit leaves the controller on MOSI and one bit arrives on MISO, simultaneously.

SPI is full duplex — and that matters here

Data moves in both directions on every clock pulse. There is no separate request and reply phase: to receive a byte you must send a byte, even if the byte you send is meaningless padding.

This is why reading the MCP3008 takes the form of sending three bytes and receiving three bytes in the same transaction. The first bytes you send carry the start bit and the channel selection; the bytes coming back during the later clocks carry the conversion result. Seeing the exchange as one simultaneous swap rather than a question followed by an answer is what makes the MCP3008's protocol make sense.

Because the chip-select line identifies the device, SPI needs no addresses, no acknowledge bits and no START or STOP conditions. It is a simpler protocol than I²C, and it runs considerably faster.

2.3  Clock polarity and phase

Two settings define exactly when bits are driven and sampled: clock polarity (whether the idle clock is low or high) and clock phase (whether data is sampled on the first or second clock edge). Together they give four modes, conventionally numbered 0 to 3.

Both ends must agree, and the MCP3008 works in mode 0, which is also the default. You rarely need to touch this — but if an SPI device returns data that looks like your value shifted by one bit, a mode mismatch is the classic cause and is worth knowing about.

2.4  Bringing SPI up

# Enable SPI: Interface Options -> SPI -> <Yes>, then reboot
sudo raspi-config

# After rebooting, confirm the device nodes exist -- one per chip select
ls /dev/spidev*
# expect: /dev/spidev0.0  /dev/spidev0.1

There is no SPI equivalent of i2cdetect. Because there are no addresses and no acknowledge bit, an SPI device cannot announce itself — a missing or mis-wired chip simply returns zeros or meaningless data instead of an error. This is a real disadvantage when debugging, and §10.2 gives the alternative approach.

3SPI Compared with I²C

You have now used both. Comparing them is natural report material, and the comparison is genuinely instructive because neither is simply better.

PropertyI²C (Lab 6, Lab 7)SPI (Lab 8)
Wires2 total, regardless of device count3 shared + 1 chip select per device
Device selection7-bit address sent on the busA dedicated hardware pin per device
SpeedTypically 100 kHz to 400 kHzTypically several MHz — far faster
DirectionHalf duplex — one way at a timeFull duplex — both directions at once
ElectricalOpen drain with pull-ups; active lowPush-pull; no pull-ups needed
Error detectionACK/NAK on every byteNone — no acknowledgement of any kind
ScalingAdd devices with no extra pins, until addresses collideEvery extra device costs a GPIO pin
Discoveryi2cdetect finds everything on the busNo discovery mechanism at all
The trade in one sentence

I²C spends speed to save pins; SPI spends pins to buy speed. A temperature sensor read twice a second has no use for megahertz, so I²C suits it. An ADC digitising a waveform needs every bit of throughput it can get, so SPI suits it. Both choices are correct for their device, which is why the Raspberry Pi provides both.

4What an ADC Does

An analog-to-digital converter performs two distinct approximations, and keeping them separate is essential because they have different consequences.

  1. Sampling — discretising time. The continuous signal is examined only at particular instants, and everything in between is unrecorded. This governs which frequencies you can represent (§7–8).
  2. Quantisation — discretising amplitude. The voltage at each instant is mapped to the nearest of a finite number of levels. This governs how precisely you can resolve a value (§4.1).

4.1  Quantisation and the LSB

An N-bit converter has 2N levels spanning its reference voltage. The size of one step — the least significant bit — is

VLSB = Vref2N (1)
Worked example — the MCP3008's resolution

The MCP3008 is a 10-bit converter. With Vref = 3.3 V:

VLSB = 3.3 V210 = 3.3 V1024 = 3.22 mV

So the finest voltage difference the converter can distinguish is about 3.2 mV, and no amount of software can recover anything finer. Because the converter rounds to the nearest level, the quantisation error of any single reading is up to ±½ LSB, or about ±1.6 mV.

This is the number to quote when Task 3 asks you to compare your readings with the DMM: agreement to better than a couple of millivolts is not possible, so do not treat a 2 mV discrepancy as an error to chase.

1024 levels, but the counts run 0 to 1023

A 10-bit converter has 210 = 1024 distinct levels, numbered 0 through 1023. There is no count of 1024 — the same off-by-one as an array of length 1024 whose last index is 1023.

It matters for your conversion formula. Dividing by 1024 and dividing by 1023 give slightly different volts-per-count, and the choice determines whether full scale maps to Vref or to one LSB below it. Pick one convention, state it in your report, and use it consistently — the difference is about 3 mV at the top of the range, which is exactly the size of the effect you are trying to measure in Task 3.

4.2  Successive approximation

The MCP3008 is a successive-approximation converter, and the algorithm is worth knowing because it explains both the speed and the pin count.

Internally the ADC performs a binary search. It guesses that the input is in the upper half of the range and compares; the result gives it the most significant bit. It then halves the remaining interval and compares again for the next bit, and so on. Ten comparisons yield ten bits — one bit per step, which is why conversion time scales with resolution exactly as it did in the MCP9808's resolution register (Lecture 06 §11). It is the same trade, appearing in a different device.

5The MCP3008

5.1  What it offers

  • 10-bit resolution, successive approximation.
  • Eight input channels, CH0 through CH7, multiplexed — one converter shared between eight inputs, selected per conversion.
  • Single-ended or differential input modes. Single-ended measures each channel against ground and is what Lab 8 uses.
  • SPI interface, as above.

5.2  Wiring

MCP3008 pinConnect toNote
VDD3.3 VThe chip's supply.
VREF3.3 VThis sets the full-scale range. Tying it to the same 3.3 V rail gives a 0–3.3 V input range, which is what Task 3 assumes.
AGND and DGNDgroundAnalog and digital grounds. Both must be connected.
CLKBCM 11 (physical 23)SCLK
DINBCM 10 (physical 19)MOSI — data into the chip
DOUTBCM 9 (physical 21)MISO — data out of the chip
CS/SHDNBCM 8 (physical 24)CE0, chip select
CH0–CH7your signalMust stay between 0 V and VREF
Never exceed the input range

Input voltages must remain between 0 V and VREF. A negative voltage or one above 3.3 V can damage the chip, and will certainly give nonsense.

This matters in Task 5. A function generator will happily produce a sine wave that swings symmetrically about zero — and the negative half is outside the ADC's range. You need to offset the signal so it sits entirely within 0–3.3 V, using the generator's DC offset control or a bias network. Check the signal on the oscilloscope before connecting it to the ADC, and set the generator's amplitude and offset with the scope, not from the front-panel numbers alone.

Fit a decoupling capacitor of about 0.1 µF close to VDD, for the same reason as the AD633 in Lab 2. An ADC measures small voltage differences, so supply noise translates directly into noisy readings — and on a breadboard next to a switching Pi there is plenty of it.

6Counts, Volts and Calibration

6.1  The transfer function

The ADC returns a raw count; converting it to volts inverts Equation (1):

V = count1023 × Vref     or     V = count1024 × Vref (2)

depending on the convention you adopt in §4.1. Task 4 asks you to output both the raw count and the voltage — which is good practice generally, because the raw value is what the hardware actually produced and the voltage is your interpretation of it.

6.2  What the plot should show

Task 4 asks for at least 20 points and a plot of voltage against count, with an analysis of linearity and accuracy. Equation (2) is a straight line through the origin, so the plot is a test of the converter itself:

  • The slope should be Vref divided by full scale — about 3.2 mV per count. A different slope means your actual VREF is not what you assumed. Measure it with the DMM; the Pi's 3.3 V rail is not exactly 3.300 V.
  • The intercept should be zero. A non-zero offset points to a ground problem or an input offset.
  • Plot the residuals. As in Lab 2, a straight-line plot hides everything; the residuals reveal whether departures are random scatter at the ±½ LSB level, or a systematic curve indicating real nonlinearity.

6.3  Comparing with the DMM — Task 3

Set expectations before you measure

Task 3 asks you to verify your Python output against a DMM. Before starting, work out what agreement is achievable:

  • Quantisation contributes ±½ LSB ≈ ±1.6 mV, unavoidably.
  • The DMM has its own accuracy specification — look it up rather than treating the meter as perfect.
  • VREF is whatever the 3.3 V rail actually is, so measure it and use the measured value.
  • Noise on the supply and the input adds scatter, which averaging will reduce.

Then judge your agreement against that budget. “The ADC and DMM agreed to within 4 mV, consistent with the ±1.6 mV quantisation limit plus the meter's stated accuracy” is a professional statement. “The values were close” is not.

For the adjustable 0–3.3 V source, a potentiometer across the 3.3 V rail is the obvious choice — and it is worth taking readings near both ends, since that is where offset and full-scale errors show up most clearly.

Task 3 also asks for proper error handling: the ADC can return values outside what you expect, the SPI device may not open, and a channel may be floating. Decide what your program should do in each case rather than letting it raise an unhandled exception.

Practice 6

(a) The ADC returns a count of 512 with Vref = 3.30 V. What voltage is that, under each convention in Equation (2)? How much do the answers differ?

(b) Your DMM reads 1.652 V and the ADC reports 1.649 V. Is that agreement consistent with the quantisation limit?

(c) You measure the Pi's rail at 3.28 V but your code assumes 3.30 V. What percentage error does that introduce, and is it constant across the range?

(d) What count corresponds to 1.000 V, and what voltage does the nearest whole count actually represent?

7Sampling a Waveform

Task 5 moves from static voltages to a sine wave, and introduces the second approximation of §4: time.

7.1  What sets your sampling rate

Three things limit how fast you can sample, and it is worth knowing which one binds:

  1. The ADC's own conversion time — fast, hundreds of thousands of samples per second.
  2. The SPI clock — each conversion needs three bytes exchanged, still fast at megahertz clock rates.
  3. Your Python loop — and this is almost certainly the binding constraint, by a wide margin.

Every sample costs a Python function call, an SPI transaction and a list append, all under a non-real-time operating system. This is the Lecture 03 analysis again, now setting the bandwidth of an instrument rather than the brightness of an LED.

Measure your sample rate — do not assume it

Time a loop of a few thousand conversions with time.perf_counter() and divide. That number is your true sampling rate, and everything in §8 depends on it. Report it.

Also look at the spread of the intervals, not just the mean. Sampling jitter smears a waveform in time and is the reason a digitised sine wave can look ragged even when every individual voltage reading is accurate. If your reconstructed waveform looks noisier than the scope trace of the same signal, timing jitter — not the ADC — is usually the culprit.

8Aliasing and the Frequency Limit

Task 5 asks you to determine the maximum frequency your setup can handle and to analyse the limitation. The governing result is one of the most important in all of measurement.

The Nyquist–Shannon sampling theorem

To represent a signal faithfully, you must sample at more than twice its highest frequency:

fsample > 2fsignal     equivalently     fmax = fsample2 (3)

Half the sampling rate is the Nyquist frequency. Above it, information is not merely degraded — it is irrecoverably destroyed.

8.1  What happens above the limit

Sample a signal faster than the Nyquist frequency and it does not vanish or turn into noise. It aliases: it reappears as a completely different, lower frequency that was never present in the input, and the false signal looks entirely legitimate in the data.

Worked example — a fictitious signal

Suppose your loop achieves fsample = 1000 samples per second, so the Nyquist frequency is 500 Hz.

  • A 100 Hz input is sampled ten times per cycle. Reconstructed faithfully.
  • A 400 Hz input gives only 2.5 samples per cycle — above Nyquist, but only just. The shape is poor, though the frequency is still recoverable.
  • A 900 Hz input aliases to |900 − 1000| = 100 Hz. Your plot shows a clean 100 Hz sine wave that does not exist.
  • A 1000 Hz input aliases to 0 Hz — a constant. The signal appears to have stopped entirely.

The third case is the dangerous one, because nothing about the data looks wrong. This is why the oscilloscope is in Task 5: the scope samples far faster and shows the truth, so comparing your reconstruction against it is how you catch an alias. Demonstrating one deliberately — sweeping the generator up until your plot shows a frequency falling as the input rises — makes a memorable figure and proves you understand the effect.

8.2  Answering Task 5 properly

“Maximum frequency” has more than one defensible answer, so define yours:

  • The absolute limit is the Nyquist frequency, fsample/2, above which aliasing occurs.
  • The practical limit for a recognisable waveform shape is lower — perhaps fsample/10 or so, since two or three points per cycle recover the frequency but not the shape. Decide what you mean by “recognisable”.
  • Sampling jitter degrades things further, and increasingly so as frequency rises, because a fixed timing error is a larger fraction of a shorter period.

Measure the amplitude your ADC reconstructs as you raise the input frequency at constant generator amplitude, and plot it. You should see it hold steady and then fall away — the practical bandwidth of the instrument you built. Compare it with fsample/2 from Equation (3), and explain the gap.

A final point worth a sentence: in a real instrument an anti-aliasing filter — a low-pass filter before the ADC, exactly like the RC network in Lab 1 Task 3 — removes everything above the Nyquist frequency before sampling, so it cannot alias. Your setup has no such filter, which is precisely why you can demonstrate the effect.

Practice 8

(a) Your loop achieves 2000 samples/s. What is the Nyquist frequency? How many samples per cycle for a 250 Hz input?

(b) With that rate, what apparent frequency does a 1900 Hz input produce?

(c) You see a 50 Hz signal in your data but the scope shows 1950 Hz at the input. Explain, and give your sampling rate.

(d) A sine wave sampled at exactly twice its frequency can reconstruct to zero amplitude. Sketch why, and say what this implies about the strictness of the inequality in Equation (3).

9Remote Access: SSH and VNC

Tasks 6 and 7 cut the monitor, keyboard and mouse loose. This is how the Pi will be used in your project and how nearly all deployed instrumentation is operated.

MethodWhat you getWhen to use it
SSH
Secure Shell
A terminal on the Pi, in a window on your laptopRunning scripts, editing files, everything command-line. Light, fast, works over a poor connection.
VNC
Virtual Network Computing
The Pi's entire graphical desktop, mirrored on your laptopAnything needing graphics — viewing a Matplotlib window, using the desktop tools. Much heavier on the network.
# On the Pi: find its address on the network
hostname -I
ip addr

# Enable SSH and VNC: Interface Options -> SSH, and -> VNC
sudo raspi-config

# From your laptop's terminal (macOS, Linux, or Windows PowerShell)
ssh pi@<the address you found>

# Copy a file back from the Pi to the machine you are sitting at
scp pi@<address>:/home/pi/lab8/data.csv .
Why a plot needs more than SSH

SSH carries text. If you run a script over SSH that calls plt.show(), there is no display for the window to appear on and it will fail or hang.

Three ways round it, and knowing which to choose is the real lesson:

  1. Save instead of showing. Write the figure to a PNG and copy it back with scp. The best option for automated data collection, and what a deployed instrument does.
  2. Use VNC, which gives a real desktop for the window to open on. This is what Task 7 asks for when it says to run the temperature program remotely.
  3. X11 forwarding (ssh -X), which tunnels individual windows back over the SSH connection.
Practical notes
  • Your laptop and the Pi must be on the same network. Campus networks often isolate wireless clients from one another, which will block the connection regardless of correct settings — if SSH times out, suspect this before suspecting your configuration.
  • The Pi's address may change between sessions if assigned by DHCP. Note it each time, or use the hostname.
  • The first SSH connection asks you to accept the host key. That is expected.
  • Task 6 asks you to demonstrate remote operation of your voltage measurement program — so plan a screenshot showing your laptop's terminal running the Pi's program, with the ADC reading changing as you adjust the potentiometer.
  • A Pi on a network with SSH enabled and a default password is genuinely insecure. Change the password — it is good practice and takes ten seconds.

10Common Mistakes and Bench Safety

10.1  Things that destroy hardware

  • Input voltage outside 0–VREF. Especially a function generator swinging negative. Check on the scope first (§5.2).
  • 5 V to the MCP3008 or its inputs while the Pi's SPI pins are connected.
  • VDD and ground swapped on the chip. Note that the MCP3008 is a 16-pin DIP — count pins carefully from the notch.
  • Rewiring with power applied.

10.2  Debugging SPI without a detect command

Because SPI has no acknowledgement and no discovery, a wrong connection returns plausible-looking numbers rather than an error. Work through it in this order:

  1. Does /dev/spidev0.0 exist? If not, SPI is not enabled — go back to raspi-config and reboot.
  2. Ground the input channel. You should read very close to 0. If not, the problem is wiring or configuration, not your conversion arithmetic.
  3. Tie the input to 3.3 V. You should read very close to full scale. These two tests bracket the whole transfer function in ten seconds.
  4. All zeros, always? Suspect MISO, chip select, or power.
  5. Wildly fluctuating readings? The input is probably floating. An unconnected ADC channel is a high-impedance node that picks up everything nearby — this is normal, not a fault.
  6. Reading exactly half, or double, what you expect? Suspect a bit-shift error in assembling the 10-bit result, or an SPI mode mismatch (§2.3).

The grounded-input and full-scale tests are the SPI equivalent of reading the MCP9808's manufacturer ID: a known input proving the chain before you trust an unknown one.

10.3  Things that waste your afternoon

  • Confusing MOSI and MISO. They are not symmetric, and nothing warns you.
  • Forgetting VREF, or leaving it floating — readings become meaningless and drift.
  • Assuming exactly 3.300 V. Measure the rail (§6.2).
  • Mixing the 1023 and 1024 conventions between two parts of the same program (§4.1).
  • A function generator set to a symmetric sine with no DC offset.
  • Concluding the ADC is broken when you are actually seeing an alias (§8.1).
  • Plotting inside the sampling loop, destroying the sample rate.
  • Expecting plt.show() to work over plain SSH (§9).

10.4  Before you leave

Copy scripts, CSV files, plots and remote-access screenshots to your own machine and check they open. Remove your directory from the shared Pi, and shut down cleanly. If you changed the Pi's password or network settings, tell the instructor or a TA.

11Self-Check

  1. Name the four SPI signals and say which are shared between devices and which is not.
  2. What does full duplex mean, and why does it mean you must send bytes in order to receive them?
  3. Give three differences between SPI and I²C, and state which protocol suits a slow temperature sensor and which suits a fast ADC, with reasons.
  4. Define quantisation and sampling. Which sets amplitude resolution and which sets frequency range?
  5. Compute the LSB size for a 10-bit ADC with Vref = 3.3 V, and state the quantisation error of a single reading.
  6. Why do the counts run 0 to 1023 rather than 1 to 1024, and where does it matter?
  7. Explain successive approximation in two or three sentences, and say why conversion time grows with resolution.
  8. State the Nyquist criterion. If your loop achieves 800 samples/s, what is the highest faithfully representable frequency?
  9. At that rate, what apparent frequency does a 750 Hz input produce? Why is this more dangerous than the signal simply disappearing?
  10. What is an anti-aliasing filter, where does it go, and which earlier lab circuit is it?
  11. When would you use SSH and when VNC? Why does a Matplotlib window need more than plain SSH?
Lab takeaway

An ADC turns a voltage into a number, and both axes of that conversion are approximations you can quantify. Quantisation sets the finest voltage difference you can see; the sampling rate — here set by your Python loop, not by the hardware — sets the highest frequency you can believe. Sample too slowly and the signal does not disappear, it lies to you. Knowing both limits, and measuring rather than assuming them, is what separates an instrument from a circuit that produces numbers.

12Practical Engineering Connections

  1. ADCs are everywhere. Every digital multimeter, oscilloscope, data logger and sound card begins with the conversion you built this week.
  2. Resolution in practice. Audio uses 16–24 bits, scientific instruments 16–24, control systems often 10–12. Each extra bit halves the LSB and costs time, money and noise performance.
  3. SPI in the field. SD cards, display controllers, radio modules, flash memory and most high-rate sensors use SPI, precisely because of the speed advantage in §3.
  4. Aliasing as a real hazard. Wagon wheels turning backwards in films, moiré patterns in photographs, and misdiagnosed vibration in machinery are all the effect of §8.1 — the last of these has caused real engineering failures.
  5. Anti-aliasing filters. Mandatory ahead of any serious ADC, and the direct descendant of the RC low-pass you analysed in Lab 1.
  6. Oversampling. Sampling far faster than Nyquist and averaging buys resolution beyond the nominal bit count — the same 1/√n statistics you tested in Lab 7.
  7. Headless operation. Weather stations, telescope controllers, laboratory data loggers and industrial monitors all run without a screen, accessed over the network exactly as in Tasks 6 and 7.
  8. Toward the project. You now have sensing, actuation, timing, serial communication, digitisation and remote access. That is the full toolkit for an instrument — and the group project is where you combine them into something of your own design.

PHYS 351 · Lecture 08 Notes · © Ran Yang, Ph.D. · yangran.org/teaching/phys351/

← All course materials