Notifications
Clear all

My version of the Coffee Scale

18 Posts
2 Users
0 Reactions
586 Views
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 4 years ago
Posts: 7959
Topic starter  

It's taken waaaaay too long, I had to learn to 3D print, but first had to learn to use Autodesk Fusion (at least the first 6 lessons) and as always the mechanical parts proved to be my undoing.

I will confess the first version had 2 buttons and a rotary encoder (those are really fun to write code for). I was going to allow push-button access to re-calibration and taring (tareing?). I had a lot of trouble with the formulas, there was something I was not getting so I gave up. It turns out recalibration is very rare for this accuracy. I just added a mandatory Tare at startup because I discovered the one-and-done approach had a lot of inaccuracy. I think it had to do with temperature.

The USB connector is in the back and is connected to a wall wart.

I used it for the first time this morning and the wastage of ground coffee was waaaaaay less.

IMG 8628
/*
 * ToDo
 *
*/

#ifndef COFFEE_SCALE_H
#define COFFEE_SCALE_H
#include "CoffeeScale.h"
#endif

String lcdLine1;  // max length 16
String lcdLine2;  // max length 16

void setup() {
  unsigned long oldMillis = 0;

  Serial.begin(115200);  // Remember to set Serial monitor to 115200 as well
  while (!Serial && (millis() < 5000));  // Wait up to 5 secs for the Serial device to be ready

  setupLCD();

  if (0 == lcdLine1.reserve(16)) {
    displayLCD(0, 0, "Reserve error 1");
    while (1);
  }
  if (0 == lcdLine2.reserve(16)) {
    displayLCD(0, 0, "Reserve error 2");
    while (1);
  }

  setupScale();

  lcdDisplay.clear();
  displayLCD_Centered(0, "Place Bowl");
  displayLCD_Centered(1, "On Scale");
  oldMillis = millis();
  while ((millis() - oldMillis) < TwoSecs);  // wait 2 secs

  lcdDisplay.clear();
  displayLCD_Centered(0, "TARE");
  displayLCD_Centered(1, "WAIT");
  coffeeScale.tare(10);
  oldMillis = millis();
  while ((millis() - oldMillis) < TwoSecs);  // wait 2 secs

  lcdDisplay.clear();
  displayLCD_Centered(0, "Done");
  displayLCD_Centered(1, "Proceed");
}

void loop() {
  const float convertGmsToOzs = 0.035273962;
  float itemWeightGm      = 0.0;
  float itemWeightOz      = 0.0;
  float itemWeightGmRounded = 0.0;
  float itemWeightGmRoundedSaved = 0.0;

  itemWeightGm = coffeeScale.get_units(scaleSamples);
  itemWeightOz = itemWeightGm * convertGmsToOzs;
  itemWeightGmRounded = itemWeightGm + 0.5;

  // Only display if value has changed to try and eliminate flicker
  if (itemWeightGmRoundedSaved != itemWeightGmRounded) {
    lcdDisplay.clear();
    lcdLine1 = " ";
    lcdLine2 = " ";
    lcdLine1 = String((itemWeightGm + 0.05), 1) + " Gm";
    lcdLine2 = String((itemWeightOz) + 0.005, 2) + " Oz";
    displayLCD_Centered(0, lcdLine1);
    displayLCD_Centered(1, lcdLine2);
    itemWeightGmRoundedSaved = itemWeightGmRounded;
  }
}

 

#ifndef COFFEE_SCALE_H
#define COFFEE_SCALE_H
#include "CoffeeScale.h"
#endif
/*
 * ToDo
 * 
*/
#include <Arduino.h>
#include <Wire.h>
#include <HX711.h>
#include <EEPROM.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcdDisplay(0x27, 16, 2);

HX711 coffeeScale;

// Forward declares (probably a better name but I can't be bothered to find out
void (*resetFunc)(void) = 0;
void displayLCD_Centered(int Row, String textToPrint);

long myOffset = 0;
float myScale = 0.0;

const uint8_t scaleSamples = 19;

const uint8_t dataPin = 4;
const uint8_t clockPin = 5;
const bool FastProcessor = true;

const uint32_t msecsToSecs = 1000;
const uint32_t TwoSecs = 2 * msecsToSecs;

struct eepromData {
  long  Offset = 0;
  float Scale  = 0;
};

void setupEEPROM(){
  int eeAddress = 0;
  eepromData savedData;

  EEPROM.get(eeAddress, savedData);

  myOffset = savedData.Offset;
  myScale  = savedData.Scale;
}

void setupScale() {
  coffeeScale.begin(dataPin, clockPin, FastProcessor);
  coffeeScale.power_up();

  // Wait up to 3 secs, retry every 1/2 sec
  if (!coffeeScale.wait_ready_timeout((uint32_t)3000, (uint32_t)500)) {
    lcdDisplay.clear();                           // timed out, NOT ready
    displayLCD_Centered(0, "Check Scale");
    displayLCD_Centered(1, "Press Button 1x");    //RCA Add a Buzzer?
    resetFunc();
  }

  setupEEPROM();

  coffeeScale.set_offset(myOffset);
  coffeeScale.set_scale(myScale);

  coffeeScale.set_medavg_mode();
}

void setupLCD() {
  lcdDisplay.init();
  lcdDisplay.backlight();
  lcdDisplay.clear();
  displayLCD_Centered(0, "Coffee Scale Rdy");  // Beep
  delay(TwoSecs);
}

void displayLCD(int Col, int Row, String textToPrint) {
  lcdDisplay.setCursor(Col, Row);
  lcdDisplay.print(textToPrint);
}

void displayLCD_Centered(int Row, String textToPrint) {
  const uint8_t Width = 17;
  uint8_t centeredCol = 0;
  uint8_t len = 0;

  len = textToPrint.length();
  centeredCol = (((Width - len) / 2) - 1);
  displayLCD(centeredCol, Row, textToPrint);
}

 

 

 

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.


   
Quote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 6 months ago
Posts: 252
 

@zander

Do you have CoffeeScale.h available?

The one who has the most fun, wins!


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 4 years ago
Posts: 7959
Topic starter  

@tfmccarthy it's the 2nd file.

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 4 years ago
Posts: 7959
Topic starter  

@tfmccarthy That version of the code still doesn't work right. To 'fix' it, in setup after setupScale(); remove all the lines to the end of setup.

The concept here is a dedicated task concept. The key variables are computed for the empty bowl on the scale and stored in EEPROM. There is NO tare function.

The Reason is the big button I chose for a tare function is very flaky. I destroyed one when soldering AFTER I had hot glued it in place. The second button sometimes 'clicks' sometimes does not. I found an example of doing a scale with the tare already done BUT of course I keep trying to make it work.

WHY, because these devices are very random, I think it's temperature fluctuations but I am not sure.

There are some other functions of unknown purpose that may affect the randomness, but I need to move on.

I am back into astronomy again, and in fact I have an organizing meeting in an hour plus I am spending more time on the big forum these days.

I figure if I keep busy enough, my body will forget to quit.

You may be asking why I don't use a different button,  and the answer is I am STUBBORN, plus it's HUGE so will leave a big hole. Time will tell what I end up doing.

I also have a first cut at a sleeping version, but it fails badly and the doc'n is sparse plus I am too busy now.

Oh yes, there is another sketch needed to calculate and save the variables to EEPROM. Or you can hard code them. There was no way in hades I was going to hard code them when I have an EEPROM on the NANO. (clone old bootloader CHEAP)

BTW, the PICO-2 is on it's way!!!

Here is screen shot of what to remove

Screenshot 2024 08 21 at 07.53.13

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.


   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 6 months ago
Posts: 252
 

@zander 

Ah! What threw me was the header guard in both files

#ifndef COFFEE_SCALE_H
#define COFFEE_SCALE_H
#include "CoffeeScale.h"
#endif

 Made me think the second file was a cpp file.

The header guard in CoffeeScale.h won't work as is. You need to move the #endif to the end of the file

EDIT: ... and drop the include "CoffeeScale.h"

 

The one who has the most fun, wins!


   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 6 months ago
Posts: 252
 

@zander

Posted by: @zander

I figure if I keep busy enough, my body will forget to quit.

Newton's First Law

Posted by: @zander

You may be asking why I don't use a different button,  and the answer is I am STUBBORN,

I'd say, "determined." Devices should work correctly and do our bidding. Asimov's second  law.

After I gander at this, I may have a few ideas. But I'm mainly interested in what gets your blood pounding.

BTW, I'm immensely impressed you learned to 3D print. I'm astonished!

 

The one who has the most fun, wins!


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 4 years ago
Posts: 7959
Topic starter  

@tfmccarthy It's my first time using the guard thing, normally I wouldn't do anything as it isn't really needed in this case. RIGHT?

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.


   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 6 months ago
Posts: 252
 

@zander

Posted by: @zander

It's my first time using the guard thing, normally I wouldn't do anything as it isn't really needed in this case. RIGHT?

In this case, you're correct, it's not necessary.

In general, it's a healthy habit to get into as it saves headaches down the road and is trivial to add. The pattern is: all your header files (*.h) should include a header guard

#ifndef COFFEE_SCALE_H
#define COFFEE_SCALE_H

// actal header code here .../

#endif  // COFFEE_SCALE_H

I add the comment on the #endif to help keep track of them (technically, I think there is a complaint about it, but it's common)

The idea is that a header guard allows you to include a header in multiple files, wherever you choose without causing multiple definition errors, et al.

Almost every compiler now has a compiler pragma that performs this:

#pragma once

This has the same effect as the #if - #endif pair and the compiler will do the work. (It's always a good idea to have the compiler on your side, working for you,)

I use a "belt-and-suspenders" approach

#pragma once
#ifndef COFFEE_SCALE_H
#define COFFEE_SCALE_H

// .... add  header  declartions and definitions ...

#endif

Thus, if the compiler fails me, I handle it. And it costs nothing.

The one who has the most fun, wins!


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 4 years ago
Posts: 7959
Topic starter  

@tfmccarthy I like belt and suspenders, I will modify as per your instructions. I have done a tweak to the code to once again get the tare done each time without any buttons. When I post that I will also add the guards.

Thanks for educating this old dog.

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 4 years ago
Posts: 7959
Topic starter  

@tfmccarthy The compiler did NOT complain even at level ALL

#endif // COFFEE_SCALE_H

I add the comment on the #endif to help keep track of them (technically, I think there is a complaint about it, but it's common)

 

 

 

 

 

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.


   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 6 months ago
Posts: 252
 

@zander 

Posted by: @zander

The compiler did NOT complain even at level ALL

By "technically" I mean according to the C++ language specification. The #endif should not have anything after it. Nothing, zero, nada. Well, maybe spaces but nothing else.

However, it is so common that practically every compiler doesn't implement that part of the language specification with a warning or error.

The one who has the most fun, wins!


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 4 years ago
Posts: 7959
Topic starter  

Updated code with include guards done right, and the startup now working as intended after going down a rabbit hole.

Main ino

/*
 * ToDo
 *
*/
#include "CoffeeScale.h"

String lcdLine1;  // max length 16
String lcdLine2;  // max length 16

void setup() {
  unsigned long oldMillis = 0;

  Serial.begin(115200);  // Remember to set Serial monitor to 115200 as well
  while (!Serial && (millis() < 5000));  // Wait up to 5 secs for the Serial device to be ready

  setupLCD();

  if (0 == lcdLine1.reserve(16)) {
    displayLCD(0, 0, "Reserve error 1");
    while (1);
  }
  if (0 == lcdLine2.reserve(16)) {
    displayLCD(0, 0, "Reserve error 2");
    while (1);
  }

  setupScale();

  lcdDisplay.clear();
  displayLCD_Centered(0, "Place Empty Bowl");
  displayLCD_Centered(1, "On Scale");
  oldMillis = millis();
  while ((millis() - oldMillis) < ThreeSecs);  // wait 3 secs

  coffeeScale.tare(10);

}

void loop() {
  const float convertGmsToOzs = 0.035273962;
  float itemWeightGm      = 0.0;
  float itemWeightOz      = 0.0;
  float itemWeightGmRounded = 0.0;
  float itemWeightGmRoundedSaved = 0.0;

  itemWeightGm = coffeeScale.get_units(scaleSamples);
  itemWeightOz = itemWeightGm * convertGmsToOzs;
  itemWeightGmRounded = itemWeightGm + 0.5;

  // Only display if value has changed to try and eliminate flicker
  if (itemWeightGmRoundedSaved != itemWeightGmRounded) {
    lcdDisplay.clear();
    lcdLine1 = " ";
    lcdLine2 = " ";
    lcdLine1 = String((itemWeightGm + 0.05), 1) + " Gm";
    lcdLine2 = String((itemWeightOz) + 0.005, 2) + " Oz";
    displayLCD_Centered(0, lcdLine1);
    displayLCD_Centered(1, lcdLine2);
    itemWeightGmRoundedSaved = itemWeightGmRounded;
  }
}

.h file

#pragma once
#ifndef COFFEE_SCALE_H
#define COFFEE_SCALE_H
/*
 * ToDo
 * 
*/
#include <Arduino.h>
#include <Wire.h>
#include <HX711.h>
#include <EEPROM.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcdDisplay(0x27, 16, 2);

HX711 coffeeScale;

// Forward declares
void (*resetFunc)(void) = 0;
void displayLCD_Centered(int Row, String textToPrint);

long myOffset = 0;
float myScale = 0.0;

const uint8_t scaleSamples = 19;

const uint8_t dataPin = 4;
const uint8_t clockPin = 5;
const bool FastProcessor = true;

const uint32_t msecsToSecs = 1000;
const uint32_t OneSec    = 1 * msecsToSecs;
const uint32_t ThreeSecs = 3 * msecsToSecs;

struct eepromData {
  long  Offset = 0;
  float Scale  = 0;
};

void setupEEPROM(){
  int eeAddress = 0;
  eepromData savedData;

  EEPROM.get(eeAddress, savedData);

  myOffset = savedData.Offset;
  myScale  = savedData.Scale;
}

void setupScale() {
  coffeeScale.begin(dataPin, clockPin, FastProcessor);
  coffeeScale.power_up();

  // Wait up to 3 secs, retry every 1/2 sec
  if (!coffeeScale.wait_ready_timeout((uint32_t)3000, (uint32_t)500)) {
    lcdDisplay.clear();                           // timed out, NOT ready
    displayLCD_Centered(0, "Check Scale");
    displayLCD_Centered(1, "Press Button 1x");
    resetFunc();
  }

  setupEEPROM();

  coffeeScale.set_offset(myOffset);
  coffeeScale.set_scale(myScale);

  coffeeScale.set_medavg_mode();
}

void setupLCD() {
  lcdDisplay.init();
  lcdDisplay.backlight();
  lcdDisplay.clear();
  displayLCD_Centered(0, "Coffee Scale Rdy");
  delay(OneSec);
}

void displayLCD(int Col, int Row, String textToPrint) {
  lcdDisplay.setCursor(Col, Row);
  lcdDisplay.print(textToPrint);
}

void displayLCD_Centered(int Row, String textToPrint) {
  const uint8_t Width = 17;
  uint8_t centeredCol = 0;
  uint8_t len = 0;

  len = textToPrint.length();
  centeredCol = (((Width - len) / 2) - 1);
  displayLCD(centeredCol, Row, textToPrint);
}

#endif  // COFFEE_SCALE_H

 

 

 

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 4 years ago
Posts: 7959
Topic starter  

The scale is working as envisioned including the initial TARE. It turns out the bowl can be placed on any time before the first measurement cycle.

NEXT is adding auto power down after 5 minutes and the button will wake it up. I have the most technical sleep information I could find and will now massage it into a form usable by the scale code. It will be tested standalone first then shoehorned into the scale sketch. I hope it won't take more than a couple of weeks but I rarely can work more than a couple hours a day and all to many days it's zero.

BTW, the PICO-2 showed up today. so I might be down some rabbit holes as well.

-30-

 

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 4 years ago
Posts: 7959
Topic starter  

I modified the code to enable a sleep forever after 5 mins, and a push button to wake it up. I also made some minor changes to timing and messaging plus removed all the empty lines just because.

If anyone wants to see the 'final' version let me know and I will post it.

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.


   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 6 months ago
Posts: 252
 

@zander

Posted by: @zander

If anyone wants to see the 'final' version let me know and I will post it.

Of course I do.  I enjoy reading code. post, please.

The one who has the most fun, wins!


   
ReplyQuote
Page 1 / 2