Use HX711 library inside custom class

Hi team

I am working on a project that weights out coffee beans. It will use a vibratory feeder to flow coffee into a drum sitting on scales.

I am currently programming the ESP8266 that will manage the scales and feeder. I am trying to put all of my scale interest into a custom library/Class (using PlatformIO).

The issue I am having is getting a reading from the HX711 library. Below is the code I have been playing with (cut down). I have tried initializing the HX711 library inside the Class (but I wasn't sure how to do that), and passing the initialized HX7111 as a pointer. But all I can get from scale.get_units(5); is inf. If I paste the same code into the setup() and loop() functions it works perfectly.

Where am I doing wrong? I am a rookie so thank you greatly for your help.
Library GitHub - bogde/HX711: An Arduino library to interface the Avia Semiconductor HX711 24-Bit Analog-to-Digital Converter (ADC) for Weight Scales.

main.cpp

#include <Arduino.h>
#include "HX711.h"
#include <Scales.h>

const int LOADCELL_DOUT_PIN = D5;
const int LOADCELL_SCK_PIN = D6;

HX711 hx711;
Scales scale;

void setup()
{
  hx711.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  scale.setup(hx711);
}

void loop() {
  scale.run();
}

Scales.h

#ifndef Scales_h
#define Scales_h

#include "Arduino.h"
#include "HX711.h"

class Scales
{
  public:
    Scales();
    bool setup(HX711& scale);
    void run();
    float weight;
  private:
    int _calibrationFactor;
    HX711* _scale;
};
#endif

Scales.cpp

#include "Arduino.h"
#include "Scales.h"
#include <HX711.h>

Scales::Scales() {

}

bool Scales::setup(HX711& scale) {
  _scale = &scale;
  _calibrationFactor = 53887;

  if (_scale->wait_ready_timeout(1000)) {
    _scale->set_scale(_calibrationFactor);
    delay(3000);
    _scale->tare();
    Serial.println("Scale setup");
    return true;
  } else {
    Serial.println("HX711 not found.");
    return false;
  }
}

void Scales::run() {
    weight = _scale->get_units(5);
    Serial.print("Weight: ");
    Serial.print(weight, 3);
}

Also how would I initalize the HX711 library inside the custom class?

Scales::Scales() {
  HX711 _scale;
}

// OR ----- 
Scales::Scales() {
  HX711 scale;
  _scale = scale:
}

Thank you so much for your help. I am so gratful for the generocity of the community.

You declare hx711 inside of the setup() function, so it goes out of scope (is destroyed) when setup ends. You are then left with a pointer to where hx711 used to be.

[edit] Read this wrong, please ignore.

@jfjlaros thank you so much for your time. I thought that HX711 hx711; on line 5 of the main.cpp was the declaration? I call begin() in the setup function which assigns the pins?

Yes, my mistake.

I am not sure how "pass by reference" works in Arduino, but just to be sure, you can try to pass the HX711 object into the constructor.
I am afraid that when passing the reference to a function, it uses a shadow address, which won't be valid after the the function executes. Therefore, when using the address inside the "loop" function, the address is no longer valid.
So, just to be sure that your HX711 address pointer remains valid through the entire life-cycle of your Scale instance, pass the reference to its constructor, instead of setup function, and save it there.

Here is an example from one of my projects;

Main.ino :

// ================= LCD ================
//  https://github.com/duinoWitchery/hd44780

#include <Wire.h>
#include <hd44780.h> // main hd44780 header -
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header

// declare lcd object: auto locate & auto config expander chip
hd44780_I2Cexp lcd;

// My Lcd Wrapper Class
#include "MyLcdPrint.h"
// Create object with reference to lcd
MyLcdPrint LcdManager(&lcd);

MyLcdPrint.h ;


#ifndef MYLCDPRINT_H
#define MYLCDPRINT_H

#include <Arduino.h>

#include <Wire.h>
#include <hd44780.h> // main hd44780 header -
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header


class MyLcdPrint {
  public:

    // constructor that takes reference to lcd into temporary pointer
    MyLcdPrint(hd44780_I2Cexp *lcd);

    uint8_t printMenuPage(uint8_t page);
    void    printInt(const uint16_t NUM, const uint8_t COL, const uint8_t ROW);
    void    printColor(const uint16_t COLOR, const uint8_t COL, const uint8_t ROW);

  private:
    // private pointer to lcd
    hd44780_I2Cexp *_lcd;

    void printMenuStr(const char *str);
};

#endif

MyLcdPrint.cpp :

#include "Arduino.h"
#include "MyLcdPrint.h"
#include "PrintData.h"
#include "WireBot_Constants.h"

#include <Wire.h>
#include <hd44780.h> // main hd44780 header -
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header

// ============================================
//                Constructor
// ============================================
// Constructor uses temporary pointer to init private pointer
MyLcdPrint::MyLcdPrint(hd44780_I2Cexp *lcd): _lcd(lcd) {}

// ============================================
//                 printMenuPage()
// ============================================
// Thanks to Nick Gammon http://www.gammon.com.au/progmem

uint8_t MyLcdPrint::printMenuPage(uint8_t page) {
  _lcd->clear();
  _lcd->noCursor();
  _lcd->noBlink();
  for (uint8_t i = 0; i < LCD_ROWS; i++) {
    _lcd->setCursor(0, i);
    printMenuStr((const char *)pgm_read_word(&pMenuPages[(page - 1)][i]));
  } // End for
  return page;
} // End printMenuPage()

// ============================================
//               printMenuStr()
// ============================================
// Thanks to Nick Gammon http://www.gammon.com.au/progmem

void MyLcdPrint::printMenuStr(const char *str) {
  char c;
  if (!str) return; // ERROR TODO <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  while ((c = pgm_read_byte(str++)))
    _lcd->print(c);
} // End printMenuStr()

// ============================================
//               printInt()
// ============================================

void MyLcdPrint::printInt(const uint16_t NUM, const uint8_t COL, const uint8_t ROW) {
  _lcd->setCursor(COL, ROW);
  _lcd->cursor();
  _lcd->blink();
  _lcd->print(NUM);
  if (!NUM) _lcd->setCursor(COL, ROW);
} // End printInt()

// ============================================
//               printColor()
// ============================================

void MyLcdPrint::printColor(const uint16_t COLOR, const uint8_t COL, const uint8_t ROW) {
  _lcd->setCursor(COL, ROW);
  printMenuStr((const char *)pgm_read_word(&pColors[COLOR]));
} // End printColor()
1 Like

@Hutkikz legend thank you so much. I refactored my code using your approach and it worked. @robert_c thank you for taking the time to help also.

Seems there must be a difference between references and pointers. Well beyond my knowledge but once I changed it to a reference as demonstrated in @Hutkikz's code it worked. I think I need to spend some more time learning the difference (or even just try understand them haha). For anyone interested I found this post while doing research. c++ - How to pass Serial object by reference to my class in Arduino? - Stack Overflow

For anyone finding this here is my code.

Main.cpp

#include <Arduino.h>
#include <HX711.h>
#include <Scales.h>

const int LOADCELL_DOUT_PIN = D5;
const int LOADCELL_SCK_PIN = D6;

HX711 hx711;
Scales scale(&hx711);

void setup()
{
  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
}

void loop() {
  scale.run();
}

Scales.h

#ifndef Scales_h
#define Scales_h

#include "Arduino.h"
#include "HX711.h"

class Scales
{
  public:
    Scales(HX711 *scale);
    bool begin(byte dataPin, byte clkPin);
    void run();
    float weight;
  private:
    int _calibrationFactor;
    HX711* _scale;
};

#endif

Scales.cpp

#include <Arduino.h>
#include <HX711.h>
#include <Scales.h>

Scales::Scales(HX711* scale): _scale(scale) {}


bool Scales::begin(byte dataPin, byte clkPin) {
  EEPROM.begin(4);
  EEPROM.get(0, _calibrationFactor);

  _scale->begin(dataPin, clkPin);

  if (_scale->wait_ready_timeout(1000)) {
    _scale->set_scale(_calibrationFactor);
    delay(3000);
    _scale->tare();
    Serial.println("Scale setup");
    return true;
  } else {
    Serial.println("HX711 not found.");
    return false;
  }
}


void Scales::run() {
    weight = _scale->get_units(5);
    Serial.print("Weight: ");
    Serial.println(weight, 3);
}

Thank you so much for your help everyone. It is so greatly appreciated.

Is the above loop() different from that of IDE's sketch loop()? If yes, please can you change it to some other meaningful name to avoid confusion?

Sure thing. I have updated to run()

That's simply untrue.

Is it Main.cpp or Main.ino?

See:

Probably, I just said I am not sure.
But can you explain why?
Why, holding a reference with a pointer inside a function works, but outside of it (more specifically, inside of the class instance), doesn't?

Here are 3 different ways to pass a Reference to an object into a class object and store it in the object as either a pointer or a Reference. They all work:

class HelloClass {
  public:
    HelloClass(Print &p) : printRef(p) {}

    void sayHello() {
      printRef.println("Hello World");
    }

  private:
    Print &printRef;
};

HelloClass helloObj(Serial);

void setup() {
  Serial.begin(115200);
  delay(1000);

  helloObj.sayHello();
}

void loop() {
}

---------------------------------------

class HelloClass {
  public:
    HelloClass(Print &p) : printPtr(&p) {}

    void sayHello() {
      printPtr->println("Hello World");
    }

  private:
    Print *printPtr;
};

HelloClass helloObj(Serial);

void setup() {
  Serial.begin(115200);
  delay(1000);

  helloObj.sayHello();
}

void loop() {
}

---------------------------------------

class HelloClass {
  public:
    HelloClass() {}
    void begin(Print &p) {
      printPtr = &p;
    }
    void sayHello() {
      printPtr->println("Hello World");
    }

  private:
    Print *printPtr;
};

HelloClass helloObj;

void setup() {
  Serial.begin(115200);
  delay(1000);

  helloObj.begin(Serial);
  helloObj.sayHello();
}

void loop() {
}
1 Like

You can test this as follows:

void test1(int& a) {
  Serial.println((size_t)&a);
}

void test2(int* a) {
  Serial.println((size_t)a);
}

void setup() {
  Serial.begin(9600);

  int a{};
  test1(a);
  test2(&a);
}

Both functions print the same address.

Thank you for explaining that @gfvalvo I am still learning how that works and been seeing different ways as I was researching. That was great to see them laid out. Is there a reason you use one method over another? I have read there is preferences but no-one has explained why you use a reference over a pointer for instance. I have created all of my custom libraries to use the first method you shared and they are all working now.

Also do you always need to initialises an instance and pass it in? Or in the case of my Scale example where it's the only place it's used could I have initialized it inside the custom class?

One advantage of using a pointer is that it can be changed later during program execution. Once a reference is bound to an object, it stays that way for it's entire lifetime. Your application will determine if that's important.

I'd probably make the entire HX711 instance private:

#include "HX711.h"
class Scales {
  public:
    Scales(uint8_t d, uint8_t c) : data(d), clock(c)  {}
    void begin() {
      Privatescale.begin(data, clock);
    }

  private:
    HX711 Privatescale;
    uint8_t data;
    uint8_t clock;
};

const uint8_t dataPin = 5;
const uint8_t clockPin = 6;
Scales scale(dataPin, clockPin);

void setup() {
  scale.begin();
}

void loop() {
}
1 Like

@gfvalvo Thank you so much for taking the time to explain that. Your time is precious and I am grateful for you taking the time to help me learn.

Ok cool, I think my projects are pretty straight forward for now so I don't imagine I would see them changing. I am slowly starting to understand how these work.

In the class above you never "initialized" the HX711 instance? Like I would do say at the start of my file with HX711 scale; before I use scale.begin(...) in my setup(). Is this what happens in the above line anyway? That looks so much cleaner.

Curretly initialize HX711 then pass it into my Scales constructor like so

HX711 hx711;
Scales scale(hx711);

Thank you for your time. Greatly appreciated.

[EDIT] Yes, it really is that simple. I just tried it out. Thank you so much @gfvalvo

 private:
    HX711 Privatescale;

Because the HX711 class has a default constructor (see HX711.h / HX711.cpp), the private HX711 instance will be constructed before the Scales class instance. So, all you need to do is call scale.begin() in setup().

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.