DIY spectrometer (AS7341 based)

The past week I’ve been playing around with a colour sensor in an attempt to make something that hopefully resembles a spectrometer. The intention is to make something that can give relatively reliable info on CCT, Duv and possibly light quality (CRI/TM-30 data). The idea is to document any progress here. You guys might have questions, so ask away. And I’m sure I’ll need some answers as well. Hardware/software I know fairly well, but I’m new to the whole calculating light quality/properties and there are people on this forum who have such an insane amount of knowledge… hoping some of that rubs of and we all learn something along the way.

So first some background. After seeing all those Opple Light Master measurements popping up, I thought of getting one as well. But alas all sold out and expected return date was May. Wait till May and still nothing. A quick inquiry resulted in an updated date of August. Not the answer I as hoping for, but by now it is known what sensor they use (a Reddit user did a quick tear down). Turns out it uses the AMS AS7262 - Consumer Grade Smart 6-Channel VIS Sensor (AS7262 Spectral Sensing Engine | ams). Checking the AMS website reveals that one is out of stock and deprecated. But fortunately they have a new an improved sensor; an 11-channel sensor. 11 channels must be better than 6 right?! And thus this topic was born :smiley:

On to the hardware; the whole thing is based on the AMS AS7341. This is an 11-channel spectral color sensor (AS7341 optical sensor / spectral sensor | ams). First I needed to actually get the sensor. Not wanting to start from scratch, I started looking at dev boards. There’s an official dev kit, but that costs an arm and a leg. There are some boards on AE, but the big electronic components distributors also sell some. Ended up going for the Adafruit AS7143 (Adafruit AS7341 10-Channel Light / Color Sensor Breakout [STEMMA QT / Qwiic] : ID 4698 : $15.95 : Adafruit Industries, Unique & fun DIY electronics and kits). It seemed decent, has modern USB-C, is documented and uses a decent LED apparently (not needed for flashlight testing though). Being in the EU, Mouser was the best way to buy, provided some extra stuff was ordered as well to get free shipping from the US. Much faster than AE too!

Now on to the sensor; it communicates via I2C. There are many ways of doing that, but as it turns out, Adafruit also makes a USB to I2C board based on the MCP2221 (Adafruit MCP2221A Breakout - General Purpose USB to GPIO ADC I2C [Stemma QT / Qwiic] : ID 4471 : $6.50 : Adafruit Industries, Unique & fun DIY electronics and kits). Added to cart, will see if and how it works. Also added some ready made wires, no soldering required. Plug and play so to say (if you consider writing software play).

On a sidenote, the devkit seems to be using an FTDI FT2232 IC (dual USB UART/FIFO IC). Having worked with FTDI chips before, I recognized the name and it so happens to be that I have a breakout board with that ic. 10 years old, but should still work. The software of the official dev kit is downloadable, so I’m going to give this a go as well at some point (e.g. for verification purposes).

All hardware used/mentioned:

Software is written in C and runs on a normal computer (main reason is easy development). I’m the MCP2221 DLL for I2C communication with the sensor. This USB to I2C ‘converter’ seems to be relatively unused, so I ended up writing a class for it from scratch. Mostly easy going, but during testing I had some bugs that prevented getting proper readings (I2C is big endian, x86 is little endian and anything bigger than 8-bit bytes got its bytes swapped).

As for code handling the sensor, I ripped some code from Adafruit’s Arduino library and started fixing compile bugs and rewrite where needed (convert from using arduino libs to using the MCP2221 API. This allowed a quick start, but only for getting basic readings from the sensor. Next step was taking the application notes from AMS and get some calibrated values. AMS provides 2 ways:

  • Photometric Results after XYZ Calibration
  • Reconstructed Spectrum plus CIE1931 mapping plus Photometric Results

For the time being I’m using the 1st method since it uses less data. It uses a ‘special calibration matrix’ that sounds good enough for my purposes:

I took those values and integrated them into the code. With these ‘calibrated’ channel readings we can go to CIE1931 XYZ coordinates and from that to CCT and Duv. Sounds simple and there’s quite some info on the net, but not everything is immediately clear on how to actually apply the various calculations. To verify the calculations I took the example readings AMS uses and it matches (there are slight difference, but that’s probably due to rounding errors). By now the output seems to produce sense making values for known CCTs of some flashlights used for testing. One major issue I’m still having is that the distance from sensor / brightness easily oversaturates the sensor and the values get really off. I’ll have to do some testing with automatic gain and see if that makes readings more consistent.

To be continued…. Until then here’s a photo from a few days ago I posted in the Opple thread:

Some light reading for those interested:
AS7341 datasheet
Evaluation Software for Color + Spectral Measurement
from ADC to LUX and CCT
Calibration methods

So first question to anyone who has more experience with these kind of calculations. What’s the correct equation for McCamy’s approximation (approximate CCT using CIE 1931 xy values)? Wikipedia and AMS application notes seem to disagree.

n = (x - 0.325) / (y - 0.154)
CCT = 449 * n^3 + 3525 * n^2 6823.3 * n + 5520.33

n = (x - 0.3320) / (0.1858 - y)
CCT = 437 * n^3 + 3601 * n^2 + 6861 * n + 5517

Some example output (took a few tries to get the saturation correct)

B35AM 3500K
X = 29.233201, Y = 27.391400, Z = 16.250845
CCT = 3476K, Duv = –0.005747

LH351D 5700K
X = 5.552438, Y = 6.011020, Z = 5.591114
CCT = 5873K, Duv = 0.008654

Very interesting project.

Test an incandescent lamp or the sun, those being two common sources with known duv.

Right know I’d put no value into the results, except a quick check to see if things make sense. Currently suffering from a slight information overload. It’s fascinating stuff, but a steep learning curve at the same time. Now reading application note AN000651 “AS7341 Auto Gain & Optimization”. And I need to look into normalising the readings. Results jump too much all over the place, depending on where I hold the lightsource. Need to get some consistency in there. This is far from finished.

Btw, I anyone is interested in the code I can set up a github repo in the near future.

And I just realised I have no incandescent lamps, it’s all LED lamps…

How about the light in your refrigerator?

You probably need a diffuser over the sensor, all spectrophotometers have one.


This may pair well with Gcharts DIY Lux meter.

Yes, it’s on the things-to-buy list.

AMS also distinguishes between emissive and reflection for their eval kits:



Without diffuser I’m basically using an emissive lightsource while the sensor is in reflection mode. Recommended is the Kimoto 100 PBU (125 microns, 66% transmission, 89.5% haze, 35.5 degree half-angle).

But off course, thanks! Silly me, the whole house, including the freezer has led. But the fridge is old and still has incandescent.

The official dev kit looks to be a no go. I hooked it up to the FTDI break out board as follows:

SK - Connect to SCL
DO/DI - Wired together and connected to SDA (I2C pull ups are on the sensor board)

The AS7341_EvalSw_ALS application detects the board (error msg without board), but fails with some power on issue (tried both channels). Too bad, but worth a try.

Todo… need to switch to the reconstructed spectrum method to get CRI guesstimates;

And some needed data:
Coordinates in both CIE 1931 xy and CIE 1976 u’v’ for both the black body locus and daylight locus, listed by 10K and 1K CCT increments
CRI or Ra TCS spectrum data in 1 nm, 2 nm and 5 nm increments

A minor but interesting detail concerning the eval software. After going through the manual, the select device popup should only happen when there are multiple cables attached. Not the case for me, so I figured maybe it doesn’t recognize it at all. It uses a standard FTDI cable apparently, so I looked up the vendor/product ID, generated a serial and programmed those into the FTDI board. The eval software no longer sees multiple cables and goes directly to the error message.

“Error during setting power on.” means “Error occurred. Set the PON bit in the Enable register (0x80).” Seems I2C communication is not working. I’m guessing this is the 1st thing that’s send out. In my software this is the 2nd thing it does, after checking the AS7341 chip ID.

Another quick update on getting the evaluation kit software up and running; no success. I’ve tried everything I could think of, looked at all the schematics and tried various ways of hooking it up. Just keep hitting that power on error message. To verify that the board and sensor actually work, I even quickly hacked one of the FTDI examples to see if communication with the sensor was possible (e.g. request chip ID). That worked fine. Currently at a loss why this it not working with the eval software. Would have been nice if it worked, but alas. Moving on for the time being.

Btw, any thoughts about this type of diffuser (another one) Doesn’t have any specs to speak of, but probably better than nothing.

Status update: I’ve added auto gain (sensor internal auto gain) and it seems work as it should. The sensor no longer saturates. But (there’s always a but), it seems that the CCT results are way off. They’re too high (e.g. 3500K is reported as 5500K) and I haven’t found a solution yet. The raw values seem to be ok, but then a count get’s calculated that serves as basis for all other calculations.
To calculate the count, an integration time is needed, which in turn depends on the gain used. My guess is that something is off there and I haven’t found the solution yet. I’ve gone over the datasheet many times and there’s nothing obvious there. Count calculation is the same for manual and auto gain, yet the result is way off when using auto gain. I’ll have to look at that later, but for now the garden/house needs some care. So I won’t be working on this for a few days.

For those interested, I’ve uploaded the code to GitHub Note that this is a work in progress, probably has bugs, does not yet produce reliable results and is NOT an end user grade application.

I’m keen to give it a go with Python :innocent:

From the Opple thread:
There’s an introduction to the Adafruit AS7341 and some examples using CircuitPython here:

AMS have a user guide for an AS7341 evaluation board that mentions calibration and CIE1931 XYZ Target Transformation. You may need to determine appropriate correction factors to apply.

The raw values for each frequency bin could be stored in some spectral power distribution object using the colour project for Python and then the CRI can be calculated:

Get the boards I mentioned above. It’s relatively cheap to get going :wink:

I’ve looked at the Adafruit code. The problem is that they don’t go beyond basic functionality (e.g. reading raw values and print them to screen). They’ve added all registers, but don’t use them. Such is the case with auto gain.

Also when not using own readings, all calculations yield the same results as the AMS application notes. I’ve used their example readings as input and the result matches. So it’s unlikely that part of the code is the problem. Disabling auto gain provides believable values, hence my belief something is wrong with how I handle readings done with auto gain.

Did a quick and dirty fix. When taking a reading, it 1st takes are reading with auto gain enabled to determine the best gain factor. Then disable it and take the real readings. Much better results. Needs some optimizing, but later (e.g. less SMUX channel switching). Using channel F8 for the time being since that seems to have the highest raw values (even though F7 has the highest irradiance responsivity).

Ordered Adafruit AS7341, Adafruit QT Py and some cables. :beer:

Awesome! Would be really nice if we can develop some sort of BLF spectrometer that’s better than the Opple :sunglasses:

I’ll see if I can create some CSV files that have the calibration matrices. Then they can be shared and don’t need to be created multiple times. Don’t know about Python, but it’s not too tricky to read those in C.

XYZ and spectral calibration matrices (taken from AS7341_AD000198_3-00.xlsm aka “AS7341 - from ADC to LUX and CCT”).

Note that there are 3(!) different versions (“Based on Full, Production-Wide Device-Characteristic Dataset”, did device characteristics change over time?);

  • v1.1 (2019-02-13)
  • v3.0 (2021-04-28)
  • Initialization data that comes with