Arduino scale & display for food dispenser & weight calculation

Hello,

I have been looking at using a scale amplifier (similar to the HX-711) with an ESP32 & Teensy 3.2 (similar to most Arduinos) to weigh a food carrying dispenser before and after dispensing to find the weight dispensed by the container. I have connected a button to trigger the dispensing of the components where in essence it will turn a DC motor to turn an auger & dispense but that is not the main topic of this thread.

My issue is with the calculation of the weight before the button is pressed. I am using the ESP32 as the Master which is measuring the weight from the amplifier and sending it to the Teensy 3.2 slave in accordance to the I2C protocol as found here:

ESP32 code:

/**
   A simple sketch to test and demo the dispenser's current features
*/

#include "Dispenser.h"
#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>

// Const
const int dispensingTimout = 5000; //in milliseconds
int prevWeight = 0;

// Wifi constants
const char* wifiSsid = "Crunchy";
const char* wifiPassword = "muffin4me";
const int httpConnectionTimeout = 500;
const char* cartServiceURL = "http://10.0.0.88:8080/Cart";
const char* pulseServiceURL = "http://10.0.0.88:8080/Pulse";

static char weightstr[15];
Dispenser dp;

void setup()
{
  Wire.begin();               
  Serial.begin(115200);
  // Initialise dispenser

  // Connect to WiFi
  WiFi.begin(wifiSsid, wifiPassword);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("Connecting to WiFi...");
  }

  Serial.println("Connected to WiFi...");
  Serial.println(WiFi.localIP());

  dp.begin();
}

void loop()
{
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient pulseClient;
    pulseClient.setConnectTimeout(httpConnectionTimeout);
    pulseClient.begin(pulseServiceURL);
    int pulseResponseCode = pulseClient.GET();
    pulseClient.end();
       
        
        // Keep filling container until timout is reached
        unsigned long startTime = millis();
        //while (millis() - startTime < dispensingTimout) {
          float weight = dp.getWeight();
          dtostrf(weight, 7, 2,weightstr);
          Serial.println(weightstr);
          Wire.beginTransmission(8); // transmit to device #8
          Wire.write(weightstr);
          Wire.endTransmission();    // stop transmitting


          String xml1 = "<CartItem xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/Dispenser.WebService.Model\">\r\n";
          String xml2a = "    <CardId>";
          String xml2b = "</CardId>\r\n";
          String xml3a = "    <MachineId>";
          String xml3b = "</MachineId>\r\n";
          String xml4a = "    <Weight>";
          String xml4b = "</Weight>\r\n";
          String xml5 = "</CartItem>";

          String payload = xml1 + xml2a + xml2b + xml3a + xml3b + xml4a + xml4b + xml5;
          //String payload = xml1 + xml2a + cardID + xml2b + xml3a + machineID + xml3b + xml4a + weightString + xml4b + xml5;
          Serial.println(payload);

          HTTPClient webclient;
          webclient.setConnectTimeout(httpConnectionTimeout);
          webclient.begin(cartServiceURL);
          webclient.addHeader("Content-Type", "application/xml");
          webclient.addHeader("Accept", "application/xml");
          int httpResponseCode = webclient.POST(payload);

          if (httpResponseCode == 200) {
            Serial.println("Sending data: success");
          } else {
            Serial.println("Sending data: failed");
          }
          webclient.end();
       }
}

Teensy 3.2 Code:

/**
   A simple sketch to test and demo the dispenser's current features
*/

#include "DispenserData.h"
#include "DispenserUI.h"
#include "Dispenser.h"
#include <Wire.h>

// Const
const int dispensingTimout = 5000; //in milliseconds
float weight; 
float prevWeight = 0;

Dispenser dp;

void setup()
{
  dp.begin();
  // Show product screen at startup
  dp.clearScreen();
  dp.showProductScreen();
  Serial.begin(115200);
}

void loop()
{
      // Continuosly check for rfid cards
      if (dp.isCardPresent()) {
        Serial.print("CardID: ");
        Serial.println(dp.getCardID());
        // If one is detected, toggle LED
        // and display sale screen
        dp.buzz();
        dp.clearScreen();
        dp.showSaleScreen(0);
        dp.ledOn();

        
        // Keep filling container until timout is reached
         unsigned long startTime = millis();
         while (millis() - startTime < dispensingTimout) {
            Wire.begin(8);                // join i2c bus with address #8
            Wire.onReceive(receiveEvent);
             
          // Toggle motor depending on whether
          // button is pressed
          if (dp.isButtonPressed()) {
            startTime = millis();
            Serial.println("Dispensing.");
            dp.motorOn(255);
//            Wire.onReceive(receiveEvent);
           } else {
            dp.motorOff();
          }
          delay(5000);
       }

        // After timout, "stop dispensing",
        // and return to product screen
        dp.motorOff();
        dp.ledOff();
        dp.clearScreen();
        dp.showProductScreen();
      }
      delay(2500);
}
void receiveEvent(int howmany) {
  
  String weightstring = "";
  while(Wire.available()) // loop through all but the last
  {
    char w = Wire.read(); // receive byte as a character
    weightstring = weightstring + w;
    //Serial.print(weightstring);         // print the character
  }
   weight = weightstring.toFloat(); // respond with message of 6 bytes // as expected by master
   dp.showSaleScreen(weight);
   prevWeight = weight; 
  
}

The idea is to take the weight measured before the button is pressed and store it in a float type variable. Then after the button dispensing timeout is done, the weight is measured yet again and subtracted from the initial weight before the button is pressed to give the weight dispensed. The issue comes in retrieving the weight value on the Teensy 3.2 before the while loop. I am not sure how to initialize the Wire.begin( 8 ) / Wire.onReceive(receiveEvent) to execute the code appropriately and have it return to the beginning. Any insight on how to perform this would be very welcome. I have also attached my classes with the slave and master code below for reference. Thank you!

firmware_slave.zip (7.44 KB)

Firmware_master.zip (5.86 KB)

Your Tensy code looks strange to me. Check the curly brackets below Setup(). There is one right bracket too much.

If you are doing this for selling items , then the scale has to be approved by the relevant weights and measures organisation .

Railroader:
Your Tensy code looks strange to me. Check the curly brackets below Setup(). There is one right bracket too much.

It seems that I added it by mistake when pasting the code, Thanks! I edited the above

iqasi096:
It seems that I added it by mistake when pasting the code, Thanks! I edited the above

Any change?

Railroader:
Any change?

Unfortunately, this error was not existent in my arduino code. It was an error when submitting the thread :frowning:

hammy:
If you are doing this for selling items , then the scale has to be approved by the relevant weights and measures organisation .

Yes, I am aware! This is just for a prototype which I plan to upgrade with a more industrialized approach down the line. I just require some guidance for the I2C protocol since I think the way I am currently calling it is probably not the proper way. As far as I know, the wire library should be initialized and run in the setup(), I just require that it continuously calls the Wire.onReceive() function multiple times without impeding the loop's ability to return to the beginning and be able to retrieve the computations discussed here:

iqasi096:
The idea is to take the weight measured before the button is pressed and store it in a float type variable. Then after the button dispensing timeout is done, the weight is measured yet again and subtracted from the initial weight before the button is pressed to give the weight dispensed.

Okey, I've got that.
We need to focus on the sending ESP32. You write that the predisposing calc goes wrong. What about the after disposal measurement? That ought to be bad too. Right?

Did You calibrate the weight system?

Use Serial Monitor to verify the calcs int the ESP32. If thats goes wrong no protocol can correct for it.

Railroader:
Okey, I've got that.
We need to focus on the sending ESP32. You write that the predisposing calc goes wrong. What about the after disposal measurement? That ought to be bad too. Right?

Did You calibrate the weight system?

Yes I have accurately configured the weight system and I am able to read the weight on the ESP32 and write the weight into the float variable weight on my Teensy 3.2 here:

void receiveEvent(int howmany) {
 
  String weightstring = "";
  while(Wire.available()) // loop through all but the last
  {
    char w = Wire.read(); // receive byte as a character
    weightstring = weightstring + w;
    //Serial.print(weightstring);         // print the character
  }
   weight = weightstring.toFloat(); // respond with message of 6 bytes // as expected by master
   dp.showSaleScreen(weight);
   prevWeight = weight;
 
}

How am I able to use that variable appropriately into the Teensy 3.2 slave loop??

You have the variable "weight" calculated. Why not use that?

Railroader:
You have the variable "weight" calculated. Why not use that?

I have used it and was able to display the values on my slave. The problem comes when I have to take the weight difference between the weight before the button is pressed and the weight after the button is pressed.

The component connections are as follows:
ESP32 Master:

  • Load cell amplifier

Teensy 3.2 Slave:
-4.2 inch Waveshare e-paper display
-LED embedded antivandal push button
-MFRC522 RFID module
-Piezo buzzer

How would I be able to tell my ESP32 to store the weight calculated before the button is pressed and subtract the final weight (after the button is no longer pressed) from it when it is not connected to the pushbutton?? Should I be doing the storing of the weight values in the Teensy 3.2 slave instead?? What would be the most feasible approach??

Just before weight is calculated You add a line like thisoldWeight = weight;
Declare oldWeight the same way and in the same place as weight.

Railroader:
Just before weight is calculated You add a line like this

oldWeight = weight;

Declare oldWeight the same way and in the same place as weight.

Are you saying I do this on the Slave or Master?? In the case you mentioned, the oldweight would only take the last calculated weight value which is calculated about every 5ms, which is good when constantly updating the screen. The way my device is calculating things though is by subtracting the final weight from the initial one.

Example:

I have an initial weight of 40g which is taken, stored and is constantly being updated as im dispensing[Just before button is pressed]:

40
39
38
37
.
.
.
32

I require 40 g to be indicated as my zero point and as i go down to 32 the decrease in weight is incremented my scale. So 39 is 1 g, 38 is 2g etc until it gets to 32 showing a final weight of 8g. After this the 32g would become the initial weight and becoming the new zero point

The way I have it now is that I'm getting a live value of the scale as show here :

40
39
38
37
.
.
.
32

What can I do to manipulate the weight variable I have now to achieve this with I2C??

I have implemented an initial and final weigh function here:

Teensy 3.2 code:

/**
   A simple sketch to test and demo the dispenser's current features
*/

#include "DispenserData.h"
#include "DispenserUI.h"
#include "Dispenser.h"
#include <Wire.h>

// Const
const int dispensingTimout = 10000; //in milliseconds
float weight; 
float in_weight;
float fin_weight;
float prevWeight = 0;

Dispenser dp;

void setup()
{
  // Initialise dispenser
  dp.begin();
  // Show product screen at startup
  dp.clearScreen();
  dp.showProductScreen();
  Serial.begin(115200);
}

void loop()
{
      // Continuosly check for rfid cards
      if (dp.isCardPresent()) {
        Serial.print("CardID: ");
        Serial.println(dp.getCardID());
        // If one is detected, toggle LED, buzzer and display sale screen
        dp.buzz();
        dp.clearScreen();
        dp.showSaleScreen(0);
        dp.ledOn();
        
        // Keep filling container until timout is reached
         unsigned long startTime = millis();
         while (millis() - startTime < dispensingTimout) {   
          Wire.begin(8);                // join i2c bus with address #8
          Wire.onReceive(initialweight);
          // Toggle motor depending on whether
          // button is pressed
          if (dp.isButtonPressed()) {
            startTime = millis();
            Serial.println("Dispensing.");
            dp.motorOn(255);
           } else {
            dp.motorOff();
          }
          delay(500);
          Wire.onReceive(finalweight);
       }

        // Debug echo
//        Serial.println("Exiting dispensing mode.");
//        Serial.print("CardID: ");
//        Serial.println(cardID);
//        Serial.print("machineID: ");
//        Serial.println(machineID);
//        Serial.print("weightGrams: ");
//        Serial.println(weightGrams);

        // After timout, "stop dispensing",
        // and return to product screen
        dp.motorOff();
        //dp.showSaleScreen(weight);
        dp.ledOff();
//        Wire.onReceive(receiveEvent);
        //dp.resetWeight(); //reset
        dp.clearScreen();
        dp.showProductScreen();
    }
      delay(2500);
//      weight = 0;
//      prevWeight = 0;
}
void initialweight(int howmany) {
  
  String weightstring = "";
  while(Wire.available()) // loop through all but the last
  {
    char w = Wire.read(); // receive byte as a character
    weightstring = weightstring + w;
    //Serial.print(weightstring);         // print the character
  }
   in_weight = weightstring.toFloat(); // respond with message of 6 bytes // as expected by master
   //Serial.println(weight);
   //if (weight < prevWeight) {
      prevWeight = weight; 
   // }
//    Serial.println(weight);
    Serial.println("Initial weight is:");
    Serial.println(in_weight);
}

void finalweight(int howmany) {
  
  String weightstring = "";
  while(Wire.available()) // loop through all but the last
  {
    char w = Wire.read(); // receive byte as a character
    weightstring = weightstring + w;
    //Serial.print(weightstring);         // print the character
  }
   weight = weightstring.toFloat(); // respond with message of 6 bytes // as expected by master
   fin_weight = in_weight - weight;
   //Serial.println(weight);
   if (fin_weight > prevWeight) {
      dp.showSaleScreen(fin_weight);
      prevWeight = fin_weight; 
   }
//    Serial.println(weight);
  else{
        Serial.println("Final weight is:");
        Serial.println(fin_weight);
   }
}

My serial monitor as I am dispensing a 1.7 g dispenser looks like this:

Initial weight is:

1.67

Initial weight is:

1.66

Initial weight is:

1.69

Initial weight is:

1.63

Dispensing.

Initial weight is:

1.68

Dispensing.

Dispensing.

Initial weight is:

1.69

Dispensing.

Initial weight is:

1.73

Initial weight is:

1.67

Initial weight is:

1.66

Initial weight is:

1.73

Initial weight is:

1.50

Initial weight is:

1.46

Initial weight is:

1.39

Initial weight is:

1.43

Initial weight is:

1.51

Initial weight is:

1.52

As I dispense the ingredients and the weight decreases, my initial weight decreases with it which is not tolerable as it affects the final weight reading found here:

Final weight is:
Final weight is:

0.63

Final weight is:

0.54

Final weight is:

0.55

Final weight is:

0.64

Final weight is:

0.62

after removing 0.8 g worth of ingredients from the scale which is off by 0.2g. How do I prevent my initial weight value to stop updating once the button is pressed?

You have 2 controllers, the ESP and the Tensy. You just have to decide which controller is doing what.
I would like to se some kind of logical drawing, information flow chart, showing who is doing what. Buttons and while loops I leave to You for the time being.
I don't know the overall architexture of Your build.

Railroader:
You have 2 controllers, the ESP and the Tensy. You just have to decide which controller is doing what.
I would like to se some kind of logical drawing, information flow chart, showing who is doing what. Buttons and while loops I leave to You for the time being.
I don't know the overall architexture of Your build.

I have designed a flow chart shown in the bottom of the post. My complete source code can be found in the zip files that I have attached to the first post of this thread, I have however attached it to this post again for convenience.

Thank you!

firmware_slave.zip (7.44 KB)

Firmware_master.zip (5.86 KB)

Read the first topics telling "How to use forum" etc. and "How to attache code". Many helpers don't take on the job of downloading and filling their computers with members code several times per day. Some helpers using tablets or phones just can't read code this way. You cut of a good part the life line.

Thanks for the flow chart. The Tensy reads the triggering button and handles the motor.
I suggest a serie of events below.
Could the Tensy ask for weight when the button is pressed, receive "previousWeight, run the motor and stop it, and then ask for the weight again, calculate the difference and display it.
What do You think?

Railroader:
Read the first topics telling "How to use forum" etc. and "How to attache code". Many helpers don't take on the job of downloading and filling their computers with members code several times per day. Some helpers using tablets or phones just can't read code this way. You cut of a good part the life line.

Thanks for the flow chart. The Tensy reads the triggering button and handles the motor.
I suggest a serie of events below.
Could the Tensy ask for weight when the button is pressed, receive "previousWeight, run the motor and stop it, and then ask for the weight again, calculate the difference and display it.
What do You think?

Yes, I have tried to do that and it works well now. I just cant seem to stop the display from updating and returning to the showProductScreen() once I initialize the I2C communication as found here:

/**
   A simple sketch to test and demo the dispenser's current features
*/

#include "DispenserData.h"
#include "DispenserUI.h"
#include "Dispenser.h"
#include <Wire.h>

// Const
const int dispensingTimout = 10000; //in milliseconds
float weight;
float in_weight;
float fin_weight;
float prevWeight = 0;

Dispenser dp;

void setup()
{
  // Initialise dispenser
  dp.begin();
  // Show product screen at startup
  dp.clearScreen();
  dp.showProductScreen();
  Serial.begin(115200);
}

void loop()
{
      // Continuosly check for rfid cards
      if (dp.isCardPresent()) {
        Serial.print("CardID: ");
        Serial.println(dp.getCardID());
        // If one is detected, toggle LED, buzzer and display sale screen
        dp.buzz();
        dp.clearScreen();
        dp.showSaleScreen(0);
        Wire.begin(8);                // join i2c bus with address #8
        Wire.onReceive(initialweight);
        delay(2000);
        dp.ledOn();
       
        // Keep filling container until timout is reached
         unsigned long startTime = millis();
         while (millis() - startTime < dispensingTimout) {   
          Wire.onReceive(finalweight);
          // Toggle motor depending on whether
          // button is pressed
          if (dp.isButtonPressed()) {
            startTime = millis();
            Serial.println("Dispensing.");
            dp.motorOn(255);
           } else {
            dp.motorOff();
          }
          delay(500);
       }



        // After timout, "stop dispensing",
        // and return to product screen
        dp.motorOff();
        dp.ledOff();
        Wire.onReceive(finalweight);
        dp.clearScreen();
        dp.showProductScreen();
    }
      delay(2500);

}
void initialweight(int howmany) {
 
  String weightstring = "";
  while(Wire.available()) // loop through all but the last
  {
    char w = Wire.read(); // receive byte as a character
    weightstring = weightstring + w;
    //Serial.print(weightstring);         // print the character
  }
   in_weight = weightstring.toFloat(); // respond with message of 6 bytes // as expected by master
   //Serial.println(weight);
   //if (weight < prevWeight) {
      prevWeight = weight;
   // }
//    Serial.println(weight);
    Serial.println("Initial weight is:");
    Serial.println(in_weight);
}

void finalweight(int howmany) {
 
  String weightstring = "";
  while(Wire.available()) // loop through all but the last
  {
    char w = Wire.read(); // receive byte as a character
    weightstring = weightstring + w;

  }
   weight = weightstring.toFloat(); // respond with message of 6 bytes // as expected by master
   fin_weight = in_weight - weight;

   if (fin_weight > prevWeight) {
      dp.showSaleScreen(fin_weight);
      prevWeight = fin_weight;
   }
  else{
        Serial.println("Final weight is:");
        Serial.println(fin_weight);
   }
}

My wire.onRecieve(finalweight) function keeps repeating even when the process is over and it goes back to the if statement in the beginning of void loop()

I'm not sure if I look at the right If-statement. Is it this one:

void loop()
{
      // Continuosly check for rfid cards
      if (dp.isCardPresent()) {
        Serial.print("CardID: ");

I also don't know how You operate things. There is both a card and a button involved. If the card is present and the button is pressed for a time longer than 2000 + 500 milliseconds then the process repeats every 2.5 second.
Tell the way You operate Your stuff, card, button... The sequence, the timing. If I guess and suggest more logic changes it might not help.