Pico Spectrometer
Firmware for a Raspberry Pi Pico that reads a Hamamatsu C12880MA micro-spectrometer and draws its spectrum live on a small TFT screen, with a knob for exposure. Built in 2023 as a self-contained instrument, so a spectrum could be seen without a computer attached.
This is firmware, not a web page. There is nothing to run in the browser here. The page describes what the device does and how it was put together, for the sake of keeping the work on the record alongside the rest.
What it does
The C12880MA is a fingertip-sized grating spectrometer with a 288-element detector. The Pico clocks data out of it, converts each of the 288 channels into a screen column, and redraws the whole display in a loop. The result is a live spectrum: point the slit at a light source and the shape of its output appears immediately.
Each bar is drawn in the color of its own wavelength rather than a single flat color, so the plot reads as a spectrum rather than as a bar chart. That conversion runs once at startup: every channel index is turned into a wavelength, the wavelength into a CIE XYZ triple, and that into the 16-bit color the display wants.
The pieces
| Microcontroller | Raspberry Pi Pico, built against the pico-sdk with pico_stdlib,
hardware_adc and hardware_spi. |
|---|---|
| Detector | Hamamatsu C12880MA, 288 channels. Clock, start and video lines on GPIO 2, 3 and 26. |
| Display | ILI9341 TFT, 240×320, over SPI0 on GPIO 4 to 10. One full frame is pushed per reading. |
| Exposure control | A potentiometer on ADC channel 1, GPIO 27. |
| Wavelength range | Roughly 307 to 878 nm across the 288 channels, about 2 nm per channel. |
Calibration
Hamamatsu ships each C12880MA with its own calibration, a fifth-order polynomial mapping pixel index to
wavelength. The coefficients in the firmware are for the specific unit the lab had, serial
20L00801, and are hard-coded as SERIAL_20L00801. A different sensor needs its
own numbers; using these with another unit would shift every wavelength.
That polynomial is also what makes the colored plot honest. The color of a bar is derived from the same calibration that places it, so the hues line up with where the lines actually fall.
The exposure knob
Integration time is set by hand because the useful range is enormous: a bright lamp and a dim reflectance reading are orders of magnitude apart. Turning the knob linearly across that range would make almost the whole sweep useless, so the reading is raised to the fourth power before it becomes a time. Most of the knob's travel sits in the short exposures, where the difference matters, and the long end arrives quickly at the top.
The knob is also sampled fifty times and smoothed into a running value rather than read once, which keeps a noisy ADC reading from making the plot flicker between frames.
Known rough edges
These are marked in the source and were never resolved:
- The sensor timing is bit-banged. Clock pulses are driven in software with microsecond sleeps, and the integration time carries a hand-measured correction for the 48 clock pulses that follow the start pulse. Two comments in the source note that this should be moved to the Pico's PIO blocks, which exist for exactly this, and that the correction is a fudge standing in for a real measurement.
- Nothing is printed to the screen but the plot. There is no axis, no peak readout and no scale, so the display shows the shape of a spectrum without saying where anything sits. A note in the source marks the peak value as the thing to add first.
- Readings are not recorded. The device shows a spectrum; it does not store or export one. Anything to be kept had to be photographed off the screen.
Source
The firmware is eleven files of C: the sensor driver, the display driver, the color conversion and a main
loop that ties them together. It builds with the standard pico-sdk toolchain and the README covers the
dependencies. It is not published in this repository and lives in the
pico-spectrometer project on its own.
The color conversion is based on John Walker’s specrend.c, as noted in
color.h. The main loop began from a Raspberry Pi example and keeps its BSD-3-Clause header.
The C12880MA datasheet and its per-unit calibration sheet come from Hamamatsu.