A Raspberry Pi Colorimeter With E-Paper Display

by Dr H in Circuits > Sensors

4371 Views, 21 Favorites, 0 Comments

A Raspberry Pi Colorimeter With E-Paper Display

IMG_20181231_164853-01.jpeg
IMG_20181231_164509-01.jpeg
AS7262 spectra.gif
Spectrum green food colorant E141.png
IMG_20181231_164604-01.jpeg
AS7262/Inky pHAT Photometer

I had been starting to work on this idea in 2018, being an extension of a previous project, a colorimeter. My intension was to use a e-paper display, so the colorimeter could be used as a stand-alone solution without the requirements for an external monitor, e.g. for class room or field applications.

I had some time to play on the project over Christmas vacations 2018/2019, but, while even a draft of the instructable had already been written, a few things I intended to do were still missing. Then I had to concentrate again on the job, had to finish my projects there and started in a new position in April. So I had not much time for silly projects for a while, and finally the project below became one of several ideas and concepts hibernating in my small "Bastelecke" ("tinker corner"?), being untouched since January 2019.

If it would not be for the "Finish it already" contest, this instructable might be still unpublished for years.

So as Pentecost 2020 is nearing now, I decided to make a just few changes to the draft instructable's text and layout, and publish it.

And maybe I will find the time to build a housing for the device and perform these enzyme kinetics measurements I wanted to present someday. Or you will do that before me.

Happy Tinkering

H

----------------------------------------------------------------------------------------
In this instructable I would like to describe a small, inexpensive and mobile six channel photometer composed of a Raspberry Pi Zero with an Inky pHAT e-ink display, an AS7262 six color sensor breakout, a cuvette holder and some push buttons, LEDs and cables.

To assemble the device does not require much specialized skills or tools above the soldering of header strips. The device might be of interest for educational, hobby or citizen science applications and could be a nice STEM project.

In the configuration described here, instructions and measurement results are displayed on the e-ink display and on an optional computer display. The results of the measurement are also stored in CSV-files on the SD card of the RasPi, allowing a subsequent data analysis.

Instead of the Inky pHAT you could use other displays as well. But the e-ink display has a number of benefits, including very low power consumption and very good legibility even in bright daylight, allowing to build devices for in-field applications that can run for hours being powered by a power pack or batteries.

I am using the AS7262 six channel color sensor. This sensor measures the intensity of light at relatively narrow ranges (~40 nm) throughout the visible spectrum, covering violet (450 nm), blue (500 nm), green (550 nm), yellow (570 nm), orange (600 nm) and red (650 nm). This allows much more precise measurements compared to RGB-sensors as the TCS34725. A minor limitation is that a few areas of the visible spectrum, e.g. cyan, are not well covered. But as most dyes will have a wide absorption spectrum, this issue should not be too relevant for most applications.

The program is written in Python3 and uses the Adafruit Blinka and AS7262 libraries as well as the Pimoroni Inky pHAT and the GPIOzero libraries. It therefore should be easy to modify and optimize the script for your special application.

As several parts and concepts already have been described in previous instructables, I like to refer to these for some details or layout options.

Supplies

Please see "Materials" step, as the original draft of this instructable had been written a while ago.

Theory and Background

Visible Spectrum.jpg
RGB bromothymol blue and cresol purple.jpg
phenol red in medium.jpg
Phenol red structure.gif
Bromothymol blue - spectra.gif

A photometer basically measures the amount of light that reaches the sensor. If a beam of light passes through a colored solution or a color filter, a certain fraction of the light gets absorbed or scattered, resulting in a loss of signal compared to a blank reference. Dyes do absorb light of a given wavelength, whereas particles that make a solution turbid will scatter the light out of the light path, both resulting in a loss of signal. But while the absorption of light by a dye is wavelength-dependent, scatter usually is not, or at least to a much lesser degree.

By using a multi-channel photometer you can measure the color composition of a solution, filter or surface. In analytical chemistry color reactions often are used to detect specific substances, e.g. metal ions, or the acidity/pH of a solution. In some cases the detection reactions will result in colored substances, while many indicator dyes are changing their color from one to another, e.g.l depending on the pH. In the first case the absolute color intensity must be measured, in the second the measurement of the ratio of intensities of two or more colors will allow a very precise pH determination.

While absorption defines the fraction of light that gets "catched" by the dye, we are actually measuring the amount of light detected by the sensor. The amount of light absorbed depends on the absorbance of the dye at a given wavelength, which is a molecular constant, the concentration of the dye, and the path length. The Beer-Lambert law allows to calculate the concentration of a dye, or particles, in a solution.

It is a non-linear function: -log(I/Io) = e*c*d.

I and Io are the measured and the reference (blank) intensities. Given the molar extinction coefficients e and the path length d are constant, the concentration c can be calculated from I/Io. Therefore a logarithmic serial dilution of a dye (e.g. 1:2, 1:4, 1:8, …) shall give a linear I/Io curve, which usually is true in the range of I/Io between 20 and 80%. Have a look on the experimental part a later step.

More on the theory behind spectrophotometry and its applications can be found at the Wikipedia articles on this subject, and one of my previous instructables.

For chemical analyses, in most cases a set of defined standard solutions of the component will be used to calibrate a photometric device. This allows to perform quantification by direct comparison and improves precision, as photometric curves usually follow a sigmoid shape, i.e. are flat at very low and high concentrations and steep in the middle.

Standard photometric cuvettes have a defined thickness/path length of 10 mm, allowing very reproducible measurements. For most measurements in the visible spectrum, cheap plastic single use cuvettes should be sufficient.

For demonstration and evaluation purposes I am using color filter strips, as these allow to perform measurements very easily and reproducibly. I would recommend to use such as reference materials and for introduction lectures. Aquarium shops are a good source for reagents for wet experiments, as they are offering a wide spectrum of indicator kits.

Materials

Raspberry-Pi-Zero-1-211x148.jpg
14347-04.jpg
IMG_20181201_153238.jpg
IMG_20171206_095110.jpg
IMG_20181201_153217.jpg
IMG_20181201_154118.jpg
WIN_20171119_13_59_37_Pro.jpg
AS7262 - parts for cuvette frame.png
WIN_20171119_13_27_58_Pro.jpg
  • Raspberry Pi Zero.
    Or any other Raspberry version, with Raspian (Jessie or later) installed.
  • AS7262 breakout.
    I here used the SparkFun version. The breakout from Adafruit should work as well, but will require changes in the layout of the cuvette holder.
  • Cuvette and breakout holder.
    I here used a version printed in polyamide I described in an earlier instructable. There you may also find a simpler layout made of Forex plates. Attached you find the layout file.
  • Three 20 mm M3 nylon screws and two 40 mm M3 brass screws, M3 nuts.
  • Two push buttons, two naturally white LEDs (5 mm, 3 V, narrow emission angle, e.g. Nichia NSPW500DS), a breadboard and some jumper cables.
  • An USB power pack or power adapter to power the RasPi.
  • Standard plastic photometric cuvettes, available e.g. via Amazon.
  • Disposable 1 ml or 2 ml pipettes.
  • Food colorants, inks, pH indicator dyes, detection reagents, … as required.


Estimation of costs:

Pi Zero W, incl. SD card: about €25

AS7262 breakout: about €25

Cuvette/Sensor holder: about €20 (incl. shipping)

Inky pHAT: €23 Euro

LEDs, buttons, cables, screws: about 10 Euro

Pico Hacker Shim: about €3
Headers: about €5

Estimated total cost: about €120

Assembly

IMG_20181231_165148-01.jpeg
IMG_20181231_164556-01.jpeg
IMG_20190105_161704~2.jpg
WIN_20171119_13_28_23_Pro.jpg

Build the cuvette and breakout holder. I had my version printed in polyamide by a service provider for 16 Euro plus shipping. Alternatively, you could build a cheaper one using Forex, balsa or plywood, as described in a previous instructable.

Solder a six-pin header (or cables) to the back (!) of AS7262 breakout. Cut the heads of the Nylon screws and glue them to the LED side of the housing. Fix the breakout to the cuvette holder using M3 brass screws, nuts and plastic washers and check that the hole on the sensor chip is placed correctly in the light path. Attach the 5 mm LED and fix its position with backplate and nuts, looking for its alignment to the light path.

Solder the headers to the Pico Hacker shim, as described in another instructable. There are two versions, that have different space requirements. Take care to use sufficient but not too much solder and not to contaminate the long pins with solder.

Get your RasPi up and running. Install the Adafruit Blinka and AS7262 CircuitPython libraries and the Pimoroni Inky pHAT and the GPIOzero libraries, as described on the corresponding websites. Copy the scripts attached to this instructable to the RasPi.

Combine RasPi, the header holder and the Inky pHAT and check that everything is working well.

Place the LED and push buttons on the breadboard and connect them with the header shim on the Pi using male jumper cables. The LED is connected to GPIO 17, the push buttons to GPIO 23 and 24.

Connect the Vin, GND, SCL and SDA port of the AS7262 breakout with the 3V (!), GND, SCL and SDA ports of the Pi. Take care, as 5V may damage the SparkFun AS7262 breakout. Preferentially connect them via the breadboard, as you may need a second 3V port to power the LED at the cuvette holder. Connect it and keep in mind that the longer foot is Vin. Check all connections.

Turn on the RasPi, check that Inky pHAT and AS7262 are working.

Open the provided program in Python 3 and run it.

Using the Device

IMG_20181231_164841-01.jpeg
WIN_20171119_13_25_00_Pro.jpg
IMG_20190105_161745.jpg

  1. Check all connections, power the RasPi. Start the script.
  2. As requested, place a reference cuvette into the cuvette holder, press button B. Wait for the Inky pHAT to confirm.
  3. Place a cuvette with the sample to measure into the cuvette holder, press the M button. Wait for the results to be displayed on the Inky pHAT. Perform further measurements.
  4. In case, reset the blanks values with a reference cuvette by pressing button B.
  5. To end the script, press both buttons at the same time.
  6. Read and analyze the values from the CSV-file(s). They are named according to the timestamp at the time of creation. Values contain the timestamp at measurement, the [v/b/g/y/o/r]-values, absolute values for blanks and % transmittance for measured values, and an indicator (“B”/”M”) for blanks or measurements.
  7. To optimize measurement conditions, you may change the gain and integration time parameters.

Example: Color Filters

IMG_20190103_200406~2.jpg
IMG_20190103_230924.jpg

Above you find an image of a set of color filters I measured using the device, and a bar graph with the corresponding results. The results are fitting quite well to the expectations.

The color filters are about 8 mm wide strips cut from a set of Rosco filters I had bought as a sample pack. The filters were placed into the cuvette.

Example: Three Food Colorants and a Dilution Curve

IMG_20190105_205034~2.jpg
Spectra of three food colorants.png
IMG_20190105_174705~2.jpg
Dilution Green lin-log.png
Dilution Green lin-lin.png

For class room experiments you may use food colorants or inks. These are easy to get in a wide variety if colors, cheap and non-toxic.

Here I used three food colorants I bought at the supermarket and measured dilutions of them.

  • "Green" is cooper chlorophyllin, E141, a modification of the green color of plants.
  • "Blue" is indigo carmine, E132. It is also a pH and redox indicator.
  • "Red" is a plant extract containing anthocyanins, E162x.
    Their color is also pH dependent, red in acid, violet-blue in basic solutions. It is the type of color found in red cabbage and red wine.

I also prepared a dilution curve of the green colorant.

Here at first I identified the dilution where about 95% of light gets absorbed, in this case a 1:5. Then, using this as starting point, subsequent 1:3 dilutions (2+1 volumes) were prepared and analysed. As you can see, the resulting curves are sigmoid, meaning S-shaped, and nearly linear for I/Io values between 10 and 85%. As we have a logarithmic dilution, this can be expected from the Beer-Lambert law. Values outside of this range are much more sensitive to noise or measurement errors.

The results were astonishingly good for such a simple device. They also shows you a mayor limitation of colorimetric assays, as here just dilutions between 15- and 450-fold are in the linear range, allowing a precise quantification. Fluorometric or luminometric assay have a much wider linear range and are therefore preferred in many clinical analysis applications.

The Script

Here your find the script. It is written in Python 3 and requires a number of external libraries.

Some remarks on the script:

In the presented version the script uses calibrated, i.e. corrected, values, not the raw data coming from the sensor. To my understanding the differences are due to a color compensation process that corrects the reported values. Especially for the yellow channel, the detected range of the spectrum overlaps with the ones of the green and orange channels. The correction omits that a very strong green or orange signal will result in an artificially enhanced yellow signal. This color compensation will be helpful in most cases, but may lead to artefacts under very specific circumstances. You may also read the raw data from the breakout.

The example code

'''A script for the AS7262 color sensor using Adafruit Blinka and the Pimoroni Inky pHAT
Based on the AS726X/Circuit Python and Inky pHAT example codes by Adafruit ans Pimoroni
Version: Dec 31 2018 HF
Requires Adafuit Blinka, Circuit Python AS726X, and the Inky pHAT libraries, and GPIOzero.
'''
# libraries
# General
import time
import datetime
# Blinka
import board
import digitalio
import busio
# AS7262
from adafruit_as726x import Adafruit_AS726x
#GPIOzero
from gpiozero import Button, LED
from signal import pause
# for Inky pHAT
import sys
from PIL import ImageFont
import inkyphat
# CSV function
import csv
# definitions
b_button = Button (24)       # Momentary button at GPIO 24
m_button = Button (23)       # Momentary button at GPIO 23
led = LED(16)                #LED at GPIO 16. Do not use GPIO 4 or 17.
inkyphat.set_colour('black')  # for b/w inky phat
#inkyphat.set_colour('red')  # for red inky phat
inkyphat.set_rotation(180)  # turn display 180°
font1 = ImageFont.truetype(inkyphat.fonts.FredokaOne, 16) # Select standard font header
font2 = ImageFont.truetype(inkyphat.fonts.FredokaOne, 14) # Select standard font data
# define language "EN", "DE", ...
lang = "EN"
#Initialize
# Try digital") input
pin= digitalio.DigitalInOut(board.D4)
print ("Digital IO OK")
#Try I2C
i2c = busio.I2C(board.SCL, board. SDA)
print ("I2C ok")
sensor = Adafruit_AS726x(i2c)
print ()
#AS7262 settings
sensor.conversion_mode = sensor.MODE_2 # mode_2: contious reading of all 6 channels
sensor.gain = 1                        # 1, 3.7, 16, 64; default 64 adjust if required
sensor.integration_time = 360          # between 2.8 and 714 ms; default: 140 
''' 
# blank values (manual setting, required in basic version)
v0 = 18650
b0 = 20490
g0 = 28980
y0 = 47420
o0 = 35000
r0 = 28760
'''
CSV_name = 'Meas_AS7262-2.csv'
#functions
def get_timestamp():   #get timestamp
        ts0_EN ='{:%Y-%m-%d}'.format(datetime.datetime.now()) # timestamp - date EN
        ts0_DE ='{:%d.%m.%Y}'.format(datetime.datetime.now()) # timestamp - date DE/german
        ts1='{:%H:%M:%S}'.format(datetime.datetime.now()) # timestamp - time
        if (lang =="DE"):
            ts0 = ts0_DE
        else:
            ts0 = ts0_EN
        ts = ts0 + " " + ts1
        return ts
def init_csv(CSV_Name):
    with open (CSV_Name,'w', newline='') as csvfile:
        fieldnames = ['timestamp', 'v','b','g','y','o','r', 'type']
        csv_writer = csv.DictWriter(csvfile,fieldnames=fieldnames)
        csv_writer.writeheader()
        #csv_writer.close()
def write_to_csv(CSV_Name, t_stamp, values, typ):   #timestamp, vales (as tuple) and type (B or M)
    with open (CSV_Name,'a', newline='') as csvfile:
        fieldnames = ['timestamp', 'v','b','g','y','o','r', 'type']
        csv_writer = csv.DictWriter(csvfile,fieldnames=fieldnames)
        # extract value tupels
        v_c = ("{0:0.1f}".format(values[0]) )
        b_c = ("{0:0.1f}".format(values[1]) )
        g_c = ("{0:0.1f}".format(values[2]) )
        y_c = ("{0:0.1f}".format(values[3]) )
        o_c = ("{0:0.1f}".format(values[4]) )
        r_c = ("{0:0.1f}".format(values[5]) )
        
        csv_writer.writerow({'timestamp': t_stamp,'v':v_c, 'b':b_c, 'g':g_c, 'y':y_c, 'o':o_c, 'r':r_c,'type':typ})</p><p>def blank(): # read blank values
    while not sensor.data_ready:
        time.sleep(.1)
    #read calibrated values from sensor
    violet = sensor.violet
    blue = sensor.blue
    green = sensor.green 
    yellow = sensor.yellow
    orange = sensor.orange
    red = sensor.red
    b_val =(violet, blue, green, yellow, orange, red)  # blanks as tuple
        
    led.on()       #reading sensor indicator
    time.sleep (1)
    led.off()
    # get timestamp
    ts = get_timestamp()
    # print timestamp to display 
    print (ts)
    print ()
    # write to csv
    write_to_csv(CSV_name, ts, b_val, 'B')    
    return b_val
    
def measure():           #perform measurement
    while not sensor.data_ready:
        time.sleep(.1)
        
    #read calibrated values from sensor
    violet = sensor.violet
    blue = sensor.blue
    green = sensor.green 
    yellow = sensor.yellow
    orange = sensor.orange
    red = sensor.red
 
    led.on()      # sensor read indicator
    time.sleep (1)
    led.off()
    # pause()
    # reads channel-compensated values, not the raw values
    print ("calibrated values")         
    print ("V: " + str(int(violet)))
    print ("B: " + str(int(blue)))
    print ("G: " + str(int(green)))
    print ("Y: " + str(int(yellow)))
    print ("O: " + str(int(orange)))
    print ("R: " + str(int(red)))
    print (" \n")
    # calculate transluminescence in %
    t_v = 100*violet/v0
    t_b = 100*blue/b0
    t_g = 100*green/g0
    t_y = 100*yellow/y0
    t_o = 100*orange/o0
    t_r = 100*red/r0
    t_val = (t_v, t_b, t_g, t_y, t_o, t_r)                        
    # print transluminescence values to screen 
    print ("T_v: " + str(round(t_v)) + " %")
    print ("T_b: " + str(round(t_b)) + " %")
    print ("T_g: " + str(round(t_g)) + " %")
    print ("T_y: " + str(round(t_y)) + " %")
    print ("T_o: " + str(round(t_o)) + " %")
    print ("T_r: " + str(round(t_r)) + " %")
    print (" \n")
    # bargraph transluminescence values, 50x "=" equals 100%
    print ("T_V: " + int(t_v/2)*'=')
    print ("T_B: " + int(t_b/2)*'=')
    print ("T_G: " + int(t_g/2)*'=')
    print ("T_Y: " + int(t_y/2)*'=')
    print ("T_O: " + int(t_o/2)*'=')
    print ("T_R: " + int(t_r/2)*'=')
    print ("    |" + 10*'____|')
    print ("\n")
    # get timestamp
    ts = get_timestamp()
    # print timestamp to display 
    print (ts)
    print ()
    # write to csv
    write_to_csv(CSV_name, ts, t_val, 'M')                         
    # print values to Inky pHAT
    # set tabs (simplifies optimization of layout)
    t1 = 5   # tab 1, frist column 
    t2 = 30  # tab 2, second column
    t3 = 100  # tab 3, 3rd column
    t4 = 160 # tab 4, not used here
    
    # write timestamp
    inkyphat.clear()
    inkyphat.text((t1, 0), ts, inkyphat.BLACK, font2) # write timestamp
    #inkyphat.text((t3, 0), ts1, inkyphat.BLACK, font2) # write timestamp time
    inkyphat.line((t1, 16, 207,16), 1,3)               # draw a horizontal line
    inkyphat.line((t3-15,16, t3-15,111), 1,3)          # draw a vertical line
    # write transluminescence values
    inkyphat.text((t1, 18), "V: ", inkyphat.BLACK, font2)
    inkyphat.text((t2, 18), ("{0:0.1f}".format(t_v) + "%"), inkyphat.BLACK, font2)
    inkyphat.text((t1, 32), "B: ", inkyphat.BLACK, font2)
    inkyphat.text((t2, 32), ("{0:0.1f}".format(t_b) + "%"), inkyphat.BLACK, font2)
    inkyphat.text((t1, 46), "G: ", inkyphat.BLACK, font2)
    inkyphat.text((t2, 46), ("{0:0.1f}".format(t_g) + "%"), inkyphat.BLACK, font2)
    inkyphat.text((t1, 60), "Y: ", inkyphat.BLACK, font2)
    inkyphat.text((t2, 60), ("{0:0.1f}".format(t_y) + "%"), inkyphat.BLACK, font2)
    inkyphat.text((t1, 74), "O: ", inkyphat.BLACK, font2)
    inkyphat.text((t2, 74), ("{0:0.1f}".format(t_o) + "%"), inkyphat.BLACK, font2)
    inkyphat.text((t1, 88), "R: ", inkyphat.BLACK, font2)
    inkyphat.text((t2, 88), ("{0:0.1f}".format(t_r) + "%"), inkyphat.BLACK, font2)
    # draw bargraph    
    inkyphat.rectangle((t3, 21,(t3 + 100), 21+9),fill=inkyphat.WHITE, outline=inkyphat.BLACK) 
    inkyphat.rectangle((t3, 35,(t3 + 100), 35+9),fill=inkyphat.WHITE, outline=inkyphat.BLACK)
    inkyphat.rectangle((t3, 49,(t3 + 100), 49+9),fill=inkyphat.WHITE, outline=inkyphat.BLACK)
    inkyphat.rectangle((t3, 63,(t3 + 100), 63+9),fill=inkyphat.WHITE, outline=inkyphat.BLACK)
    inkyphat.rectangle((t3, 77,(t3 + 100), 77+9),fill=inkyphat.WHITE, outline=inkyphat.BLACK)
    inkyphat.rectangle((t3, 91,(t3 + 100), 91+9),fill=inkyphat.WHITE, outline=inkyphat.BLACK)
    inkyphat.rectangle((t3, 21,(t3 + int(t_v)), 21+9),fill=inkyphat.BLACK, outline=inkyphat.BLACK) 
    inkyphat.rectangle((t3, 35,(t3 + int(t_b)), 35+9),fill=inkyphat.BLACK, outline=inkyphat.BLACK)
    inkyphat.rectangle((t3, 49,(t3 + int(t_g)), 49+9),fill=inkyphat.BLACK, outline=inkyphat.BLACK)
    inkyphat.rectangle((t3, 63,(t3 + int(t_y)), 63+9),fill=inkyphat.BLACK, outline=inkyphat.BLACK)
    inkyphat.rectangle((t3, 77,(t3 + int(t_o)), 77+9),fill=inkyphat.BLACK, outline=inkyphat.BLACK)
    inkyphat.rectangle((t3, 91,(t3 + int(t_r)), 91+9),fill=inkyphat.BLACK, outline=inkyphat.BLACK)
    # write to Inky pHAT
    inkyphat.show()
#main part:
ts_csv = get_timestamp()
CSV_name = ("AS7262-"+ ts_csv +".csv")
init_csv(CSV_name)
                            
# Ouput to display
print ("Hello, this is a script for the AS7262 color sensor")
print ("using Adafruit Blinka and the Pimoroni Inky pHAT")
print ()
print ("Press button 'B' to perform a blank measurement")
print ("Press button 'M' to perform a measurement")
print ()
print ("Please start with a blank measurement ")
print ()
print ("Please now place the reference (blank) cuvette into photometer,")
print ("then press (white) B-button")
# b_button.wait_for_press()
# output on Inky pHAT
inkyphat.clear()
inkyphat.text((5, 5), "AS7262/Inky pHAT", inkyphat.BLACK, font1)
inkyphat.text((5, 20), "6 channel photometer", inkyphat.BLACK, font1)
inkyphat.text((5, 40), "First perform a blanks", inkyphat.BLACK, font1)
inkyphat.text((5, 55), "measurement. Insert", inkyphat.BLACK, font1)
inkyphat.text((5, 70), "reference cuvette ", inkyphat.BLACK, font1)
inkyphat.text((5, 85), "and press button B", inkyphat.BLACK, font1)
inkyphat.show()
b_button.wait_for_press()
        
#read blank values at start
b_val = blank()
v0 = int(b_val [0])
b0 = int(b_val [1])
g0 = int(b_val [2])
y0 = int(b_val [3])
o0 = int(b_val [4])
r0 = int(b_val [5])
                            
# write_to_csv('Start', b_val, 'B')        
#output to display
print ("Blanks measured")
print
print ("You may now perform your measurements")
print ()
print (" Press button 'M' for measurement")
print (" or button 'B' to reset blanks,")
print (" or both to stop the script")
print ()
print (" You may have to press buttons for up to two seconds!")
#output instructions to Inky pHAT
inkyphat.clear()
inkyphat.text((5, 5), "Starting blanks taken.", inkyphat.BLACK, font1)
inkyphat.text((5, 25), "For sample measurements", inkyphat.BLACK, font1)
inkyphat.text((5, 45), "press button M, to reset", inkyphat.BLACK, font1)
inkyphat.text((5, 65), "blanks press button B or", inkyphat.BLACK, font1)
inkyphat.text((5, 85), "both buttons to end.", inkyphat.BLACK, font1)
inkyphat.show()
# measurement loop
while True:
    
    if (m_button.is_pressed and b_button.is_pressed): # exit loop and end script
        led.on()
        time.sleep (.5)
        led.off()
        break
    
    elif m_button.is_pressed:        # evoke measurement sample
        measure ()
        print ("Measurement performed")
        print ("Press M or B or both")
        print ()
        
    elif b_button.is_pressed:        # measure and reset blank values
        blank ()
        #read blank values to reset
        b_val = blank()
        v0 = int(b_val [0])
        b0 = int(b_val [1])
        g0 = int(b_val [2])
        y0 = int(b_val [3])
        o0 = int(b_val [4])
        r0 = int(b_val [5])
        
        ts=get_timestamp()
        
        print ("Blanks have been redefined at " + ts)
        print ("Press buttons M or B or both")
        
        inkyphat.clear()
        inkyphat.text((5, 5), "Blanks redefined at", inkyphat.BLACK, font1)
        inkyphat.text((5, 25), ts, inkyphat.BLACK, font1)
        inkyphat.text((5, 70), "To measure samples,", inkyphat.BLACK, font1)
        inkyphat.text((5, 85), "press button M. ", inkyphat.BLACK, font1)
        inkyphat.show()
        
    else:
        time.sleep(2)
    
inkyphat.clear() # empty Inky pHAT display procedure,
inkyphat.show()  
print ("bye!")

Limitations and Outlook

LED spectum cool white.png
blank nat white.png

The current version is more a working prototype then a complete solution. One of the mayor limitations is that all parts are not located in a compact housing. A project for the future, I won’t have the time now.

The LEDs seem to deteriorate over time, so it is recommended to perform blank corrections from time to time and to replace the LED if required. One option to improve LED lifetime would be to place a switch in the LEDs power line or to toggle its state by an additional push button. for a more elaborate version, a secondary light sensor, as a TSL2561, may be used to control the amount of light emitted by the LED and to allow internal correction.

White LEDs come with a basic limitation. Their spectrum is not homogenous over the whole spectrum but have a strong violet/blue component and a broad peak from blue to red, with a weak signal in the cyan and red areas. As the 6-channel sensor just is measuring thin slices of the spectrum, and has a gap at cyan, this is not a critical issue in our case. Nevertheless, even with a natural white LED, the emission of blue and red is less then 30% compared to violet and yellow.

I have been experimenting with small 3.6 V Xenon lamps, as these should a more continuous emission throughout the visible spectrum. But these require much power and get very hot. They might just be an option if just powered for a short time ( -> Xenon flash) and placed in a heat dissipation frame. In addition, the brightness of the Xenon lamp was much lower then that of the LED.

For a housed version some additional status LEDs could be handy, as does an on/off-switch.

The program does have much room for optimization, any help and suggestions are welcome.

To use the device as a stand alone solution you may run the script on startup. There a several ways to do this, you may find detailed information on this elsewhere.