This article appeared in Make: Vol. 89. Subscribe for more maker projects and articles!

Achieving the ultimate cup of coffee involves the detailed preparation of ingredients and a concise choreography with your espresso machine. Part of this ritual is controlling the brew ratio, precisely matching the volume of beans to the amount of heated water. Exact measurements will help to make the entire process consistent, ensuring tasty success with each brew.

The Clue Coffee Scale is designed to weigh both ground coffee beans and the espresso machine’s extraction output to achieve repeatable results — and it mounts right on your machine’s drip tray. The scale uses a load cell sensor that employs a strain gauge, a sensitive resistance bridge array that is attached to the load cell’s bendable metal beam. The slight changes in the bridge’s resistance when the beam is deflected are measured by a special analog-to-digital converter (ADC). The NAU7802 24-bit ADC breakout board connects to the load cell, provides an excitation current, and measures the bridge’s differential voltage, which is then used to calculate the weight of the mass placed on the load cell.

The CircuitPython software running on the Clue interfaces with the ADC board to automatically update the display’s animated linear scale as well as showing numerical results in both grams and ounces. To measure the contents of a container, the scale can be zeroed with a push of a button to subtract the container’s tare weight. And to top it off, the flexibility of using CircuitPython makes it easy to customize the display and add features to the scale.

This guide will show how to build, calibrate, and code the scale. Your perfect cup awaits!

NOTE: A load cell is a precision device particularly when paired with a sensitive high-resolution ADC such as the NAU7802. However, because of component variations and software accuracy limitations, the scale is not recommended for safety, health, scientific, or commercial weight measurement applications — just amazing coffee.

What will the next generation of Make: look like? We’re inviting you to shape the future by investing in Make:. By becoming an investor, you help decide what’s next. The future of Make: is in your hands. Learn More.

Project Steps

1. Attach plates to load cell

This is one specific implementation of the scale for use on an espresso machine. Your build will vary depending on the target machine.

To minimize the size and weight of the scale that will be attached to espresso machine’s drip tray, only two aluminum brackets, the load cell, and a few fasteners are used (Figure A).

The fixed arm of the load cell (the end with the wires) is already tapped with M5 screw threads, while the weighing arm that can flex is tapped with M4 threads (the end with the yellow arrow sticker and load rating). Attach the large cross-plate to the underside of the load cell on the weighing arm, using an M4 screw (Figure B). Attach the 3-hole coupling plate with an M5 screw to the underside of the load cell at the fixed end (Figures C and D), being careful not to pinch any of the wires.

2. Mount scale to drip tray

Use M3 screws, spacers, and nuts to attach the coupling plate to your drip tray grid as shown in Figures E, F, and G. Spacers can be M3 washers or nuts as long as they’re large enough to span the drip tray grid wires. The spacers are necessary to give the load cell enough clearance to flex under load as shown in Figure H.

3. Prep wires

If you like, you can add short lengths of heat-shrink tubing to collect the four load cell wires into a cable (Figure I). Add four small wire ferrules to the ends of the wires to make it easier to connect them to the screw terminals on the ADC board (Figure J).

TIP: These wires are so thin that the standard method of crimping the ferrules isn’t recommended. Instead, solder the wire to the inside of the ferrules at the tip.

4. Connect the Clue

Attach the clear acrylic case to the Clue board, but leave off one screw in the upper right corner. Use a longer M2.5 screw and nut to connect the ADC board to the backside of the Clue (Figures K and L).

Connect the Clue and ADC with a Stemma QT cable (Figure M).

5. Mount Clue to drip tray

Mount the 2020 corner brace to the front of the drip tray grid using an M3 screw, nut, and washer (Figures N and O).

Attach the Clue to the corner brace using the remaining M3 nylon screw and nut from the acrylic case kit (Figures P and Q).

6. Connect battery pack

Connect the AAA battery pack’s wire connector to the Clue’s 3V–6V battery jack (Figure R). Use an adhesive square to attach the battery pack lid to the drip tray’s side (Figure S). Be certain to attach it so that the pack can be easily opened to replace batteries.

That’s it, the scale is built!

7. Get the project code

First be certain that you’re running the latest version of Adafruit CircuitPython for the Clue board. Update it from the Downloads section of circuitpython.org if necessary.

Connect your Clue to your computer via a known-good data + power cable. The CIRCUITPY drive will appear in your Finder or File Explorer (depending on your operating system).

The Clue Coffee Scale project will require a specific set of CircuitPython libraries, a bitmap image, fonts, a calibration method, and the main code.py file. They’re all in the bundle folder of the code repository. Download the bundle and place those files in the root directory of the USB-attached Clue board.

The Clue board’s root directory should now contain the following folders and files (Figure T):

  • clue_scale_bkg.bmp — background bitmap image
  • clue_scale_calibrator.py — load cell calibration program code
  • code.py — main project program code
  • fonts folder containing: helvB24.bdf Helvetica font file
  • lib folder containing these required libraries:
    • adafruit_apds9960
    • adafruit_bitmap_font
    • adafruit_bmp280
    • adafruit_clue
    • adafruit_display_shapes
    • adafruit_display_text
    • adafruit_lis3mdl
    • adafruit_lsm6ds
    • adafruit_register
    • adafruit_sht31d
    • cedargrove_nau7802
    • neopixel
    • simpleio

NOTE: In the future, the project’s lib folder may need to be updated to maintain compatibility with new releases of CircuitPython. You can either copy newer library file versions the Adafruit CircuitPython Library Bundle or use the Adafruit circup command-line utility to update the libraries.

8. Code walkthrough

The main CircuitPython code module, code.py, prepares and operates the Clue Coffee Scale. Before using the scale, you’ll need to update code.py with a calibration ratio that’s unique to the load cell you attached to the NAU7802 breakout. First let’s take a walk through the code and look at how each section works.

8a. Import and set defaults

This section imports all the needed modules and libraries, including the NAU7802 sensor driver. After importing, the first visible task of this section is to turn the Clue board’s NeoPixel LED yellow to indicate that the Coffee Scale is initializing. During operation, the indicator LED will glow green when operating normally or red when zeroing the scale.

Next, the scale defaults are specified. These are constants with names that are capitalized to help identify them as constants. You can change these values to alter the operation of the scale:

MAX_GR is the full-scale value of the scale in grams. The tick mark values adjacent to the graduated scale graphic are automatically derived from MAX_GR which can be any positive integer value. Since the graduated scale graphic is divided into tenths of full-scale, with five numbered tick marks, it’s best to choose an integer MAX_GR value that will display nicely such as 1000, 500, 250, 100, 50, 25, 10, or 5.

DEFAULT_GAIN is the gain setting of the NAU7802 internal ADC’s incoming signal pre-amplifier. Normally, this is set to the highest value of 128.

SAMPLE_AVG specifies the number of measurements that are averaged when the load cell is measured. The measurement value will be more stable with a high SAMPLE_AVG value. The display will update more slowly with a higher value; it’s a trade-off between stability and speed.

8b. Load cell calibration ratio

CALIB_RATIO is the factor that is used to convert the NAU7802’s raw measurement value into grams. This ratio will need to be updated after running the calibrator method for your particular load cell in Step 9 below. You should only have to measure and record this calibration ratio once.

8c. Instantiate the sensor and display

The NAU7802 board is connected to the Clue’s I2C bus and lives at address 42 (hexadecimal 2A). The NAU7802 24-bit ADC chip is capable of supporting two load cell channels, but only the first channel is available on the Adafruit Stemma breakout board; active_channels is therefore set to 1.

Next, the Clue’s integrated display is instantiated along with the primary displayio graphics group layer, scale_group, which will contain the background bitmap image and other display layers.

Font objects for displaying measurement values and the tick mark labels are defined next.

8d. Display the background bitmap image

The display background file that includes the graduated scale (Figure U) is read from the Clue root directory and is appended as the first item in the primary displayio group layer. All other graphics objects will be put on layers over this background.

8e. Define and display text and graphics

This section defines the graphic elements that will be placed in front of the background graphic. First, the zeroing button graphic is appended to the primary displayio group.

The subsection that starts with for i in range… is the code that steps through the graduated scale’s tick marks, creating a value label that’s calculated using the MAX_GR constant and appending each label to the displayio group. Measurement values and units labels are added next.

Lastly, the indicator_group with its floating indicator bubble is created and added to the primary displayio group. The indicator_group layer is now the front-most graphics layer of the display (Figure V).

The indicator bubble is a yellow circle that travels up and down the graduated scale, pointing to the measured value. The center of the circle will normally be transparent, but will appear yellow or red depending on the scale’s current status; yellow when initializing, red when zeroing.

8f. Helpers

Two helper functions work with the NAU7802 driver class to zero the scale and to read the load cell’s current raw value:

The zero_channel( ) helper sets up and zeros the NAU7802’s internal amplifiers and ADC (analog to digital converter) to prepare it to receive signals. This function is used when the scale is first powered up, and when it’s manually zeroed by pressing the Clue board’s A button.

The load cell’s raw measurement value is obtained by the read( ) helper. This helper accepts an integer parameter that specifies the number of samples to be averaged each time the helper is called, defaulting to one sample (no averaging). The helper returns the averaged raw measurement value. Averaging the raw value can help reduce the jitter of the displayed value.

8g. Activate sensor and prepare to loop

This is where the NAU7802 ADC is enabled and calibrated for use. Before calibrating and zeroing, the internal sensor amplifier’s gain is set to the default value. Once completed, the Clue will chirp some welcoming notes.

8h. The primary code loop

The primary loop is the main operational process of the scale. The loop indicates the scale’s status on the Clue board’s NeoPixel; green when the scale is operating, red when it’s busy zeroing.

After setting the NeoPixel color, the load cell raw value is measured and converted to grams and ounces. The converted values are formatted and placed into the corresponding on-screen display labels and are also printed in the REPL.

The grams measurement value is used to position the on-screen indicator bubble along the graduated scale graphic using the map_range( ) function. Also, if the grams measurement value falls outside of the minimum or maximum range, the bubble is “parked” at the extreme position and its interior color is changed from transparent to red.

Finally, the Clue board’s A button is watched to see if it was pressed. When pressed, the Clue plays a sound and will begin zeroing the scale. During the zeroing process, the NeoPixel, the zeroing button graphic, and the center of the bubble are all set to a red color. After zeroing has completed, the code waits until the button is released before playing a completion sound, setting the bubble and zeroing button graphic interiors to transparent, and returning the Clue Coffee Scale to normal operation.

# The Primary Code Loop

# Read sensor, move bubble, display values

while True:

    clue.pixel[0] = clue.GREEN  # Set status

indicator to green (ready)

    # Read the raw scale value and scale for

grams and ounces

    value = read(SAMPLE_AVG)

    mass_grams = round(value * CALIB_RATIO, 1)

    mass_ounces = round(mass_grams * 0.03527, 2)

    grams_value.text = f”{mass_grams:5.1f}”

    ounces_value.text = f”{mass_ounces:5.2f}”

    print(f” {grams_value.text} grams 

{ounces_value.text} ounces”)

    # Reposition the indicator bubble based

on grams value

    min_gr = (MAX_GR // 5) * –1  # Minimum 

display value

    bubble.y = int(map_range(mass_grams, 

min_gr, MAX_GR, 240, 0)) – 10

    if mass_grams > MAX_GR or mass_grams < 

min_gr:

        bubble.fill = clue.RED

    else:

        bubble.fill = None

    # Check to see if zeroing button is pressed

    if clue.button_a:

        # Zero the sensor

        clue.pixel[0] = clue.RED  # Set 

status indicator to red (stopped)

        bubble.fill = clue.RED  # Set bubble

center to red (stopped)

        zero_button_circle.fill = clue.RED  # 

Set to red (stopped)

        clue.play_tone(1660, 0.3)  # Play

“button pressed” tone

        zero_channel()

        while clue.button_a:

            # Wait until button is released

            time.sleep(0.1)

        clue.play_tone(1440, 0.5)  # Play

“reset completed” tone

        zero_button_circle.fill = None  # Set

to transparent (ready)

        bubble.fill = None  # Set bubble

center to transparent (ready)

9. Calibrate the Load Cell

Before putting the scale to use at your coffee-making station, use an accurate known weight to record the unique resistance characteristics of your particular load cell sensor (Figure W). This calibration should only need to be performed once unless the load cell is replaced.

The calibration process reads and averages a handful of raw measurement samples, then prints the averaged value in the serial output window. The printed raw measurement value that corresponds to a reference weight will be used in the CircuitPython code to calculate the grams and ounces displayed on the screen.

Remember to mount the load cell so that the fixed end is securely fastened and the weighing end is free to flex with no restrictions.

9a. To begin the calibration process, remove all weights from the load cell.

9b. Edit code.py to activate the calibrator method. Using Mu or your favorite text editor (PyCharm is shown here), open the code.py file stored in the root directory of the Clue board and also open a serial output window. Remove the left-most comment hashmark and the following space character from line 10 of the file (Figure X).

9c. After making the change to line 10, save the file. The calibration method will automatically run when the code.py file is saved in the Clue board’s root directory. A notice that the calibrator is ready will print in the serial output window. See learn.adafruit.com/welcome-to-circuitpython for info on connecting to the serial console.

9d. Place a reference weight on the measurement end of the load cell. Once a few RAW VALUE measurements are taken, choose one that looks typically like a median value. In Figure Y, the 100g reference weight is producing a raw measurement value of approximately 215300.

If you need to re-zero the load cell to get a fresh measurement, remove any weights and then press and hold the Clue’s A button until you hear a confirming beep. Release the button and the calibrator will start the zeroing process. You’ll hear a confirming tone when zeroing is complete.

9e. Edit code.py again to update the CALIB_RATIO constant. This ratio is the reference weight in grams divided by the raw value you noted during calibration. In Figure Z, it’s 100 / 215300. Don’t save the code.py file just yet. There’s one more step to go.

9f. Place a comment hashmark and a space at the beginning of line 10 to “comment out” the line so that the calibrator method won’t be imported the next time code.py executes (Figure Aa).

9g. Save the file and close the editor. The code.py program will automatically begin. The Clue Coffee Scale is now ready to go!

TIP: Take a look through the code listing for the calibrator method, clue_scale_calibrator.py. It’s a good example of a fundamental approach to initiate and read the raw values of a load cell connected to the NAU7802 24-bit ADC Stemma board.

Conclusion

Hasta Barista, Baby

Goodbye inexactitude and hello precision! Let’s walk through using your new coffee scale.

Features and Operation

The Clue Coffee Scale continuously displays the load cell weight measurement in both grams and ounces. The animated five-division graduated scale in the center of the display is calibrated in grams and shows the measured weight relative to the default maximum value of 100 grams.

An indicator bubble travels along the graduated scale, pointing to the currently measured value in grams. The interior of the bubble will turn red if the value falls outside the graduated scale range.

The Clue board’s A button is used to zero or tare the scale. Press the button until a beep is heard, then release. The interior of the bubble and the Clue’s NeoPixel will turn red while zeroing the scale. A second beep is heard when the scale completes the zeroing process.

Your First Perfect Shot

You’re now instrumented to take your espresso preparation to the next level. A great starting recipe is to pull a 30g shot in 30 seconds using a 20g dose of ground coffee beans — this is known as a 1:1.5 brew ratio recipe.

  • Zero your scale (tare) with an empty container
  • Weigh again with 20g ground coffee in it
  • Zero your scale again (tare) with the cup
  • Pull the shot
  • Weigh the cup again with brewed coffee in it.

You can then fine-tune your grind to increase or decrease the time to pull that same yield in order to accentuate different aspects of the coffee. The Clue Coffee Scale will help you dial in your espresso to perfection.

This article appeared in Make: Volume 89. All photos by John Park and Jan Goolsbey