My loadcell is causing problems! The HX711 library made three different photons solid white and unusable!


#1

Hi all.

I’ve been using Particle photons and spark cores for a while. Recently I’ve had one of the most annoying problems I have ever come across.

One of my apps uses a library for the HX711 IC, which is a adc converter for a simple load cell. Sparkfun sells breakout boards of these. They also provide a simple library. With some simple changes like adding #include “application.h” in the library, I was able to get it working perfectly for about oh… months! I’ve never had trouble with this library.

Only two days ago I started working on my software again and came across a solid white LED on the photon everytime I’d upload or reset. In fact resetting did nothing but go straight back to a white LED. I couldn’t get past DFU mode in the reset-mode button toggle, but I could get it into safe mode and flash an unrelated program and get it back to breathing cyan. With the Photon acting so weird I figured it was a hardware problem on my PCB… but I’ve recently narrowed it down to this library!

I’ve literally gone through three Photons (1 unopened) and two of my PCBs trying to get my load cell working again.

If I write everything I’ve tried I’d go on forever, but I attached the library files and a simple sketch program that DID work. You will see if you try to upload it that it compiles file, no errors or warnings, and will flash but right after resetting will go immediately into a solid white led. Nor is it recognized by the USB serial port to my PC.

If you take out the declaration of the HX711 scale() object (and comment out the scale. functions) in the test program it works of course. The HX711 scale(DOUT, CLK); line seems to be the direct cause of the white light on all the time.

Let me say also that connecting the HX711 hardware doesn’t change anything. This code messed up even the brand new opened photon as soon as I registered it. With or without the breakout board attached.

What gives? If anyone could check out the program that be awesome. Try uploading it to a photon. I am totally at a loss here. Did Particle change something in a very recent update??

THANKS!

HX711.cpp

#include "HX711.h"

void bitWrite(uint8_t &x, unsigned int n, bool b) {
  if (n <= 7 && n >= 0) {
      if (b) {
          x |= (1u << n);
      } else {
          x &= ~(1u << n);
      }
  }
}

HX711::HX711(byte dout, byte pd_sck, byte gain) {
	PD_SCK 	= pd_sck;
	DOUT 	= dout;

	pinMode(PD_SCK, OUTPUT);
	pinMode(DOUT, INPUT);

	set_gain(gain);
}

HX711::~HX711() {

}

bool HX711::is_ready() {
	return digitalRead(DOUT) == LOW;
}

void HX711::set_gain(byte gain) {
	switch (gain) {
		case 128:		// channel A, gain factor 128
			GAIN = 1;
			break;
		case 64:		// channel A, gain factor 64
			GAIN = 3;
			break;
		case 32:		// channel B, gain factor 32
			GAIN = 2;
			break;
	}

	digitalWrite(PD_SCK, LOW);
	read();
}

byte HX711::get_gain() {
  return GAIN;
}

long HX711::read() {
	// wait for the chip to become ready
	while (!is_ready()) Spark.process();

	byte data[3];

	// pulse the clock pin 24 times to read the data
	for (byte j = 3; j--;) {
		for (char i = 8; i--;) {
			digitalWrite(PD_SCK, HIGH);
			bitWrite(data[j], i, digitalRead(DOUT));
			digitalWrite(PD_SCK, LOW);
		}
	}

	// set the channel and the gain factor for the next reading using the clock pin
	for (int i = 0; i < GAIN; i++) {
		digitalWrite(PD_SCK, HIGH);
		digitalWrite(PD_SCK, LOW);
	}

	data[2] ^= 0x80;

	return ((uint32_t) data[2] << 16) | ((uint32_t) data[1] << 8) | (uint32_t) data[0];
}

long HX711::read_average(byte times) {
	long sum = 0;
	for (byte i = 0; i < times; i++) {
		sum += read();
	}
	return sum / times;
}

double HX711::get_value(byte times) {
	return read_average(times) - OFFSET;
}

float HX711::get_units(byte times) {
	return get_value(times) / SCALE;
}

void HX711::tare(byte times) {
	double sum = read_average(times);
	set_offset(sum);
}

void HX711::set_scale(float scale) {
	SCALE = scale;
}

void HX711::set_offset(long offset) {
	OFFSET = offset;
}

void HX711::power_down() {
	digitalWrite(PD_SCK, LOW);
	digitalWrite(PD_SCK, HIGH);
}

void HX711::power_up() {
	digitalWrite(PD_SCK, LOW);
}

HX711.h

#ifndef HX711_h
#define HX711_h

#ifdef ARDUINO
    #if ARDUINO >= 100
	    #include "Arduino.h"
    #else
	    #include "WProgram.h"
    #endif
#else
  #include "application.h"
  //#include "spark_wiring.h"
  //#include "spark_wiring_usbserial.h"
#endif

class HX711
{
	private:
		byte PD_SCK;	// Power Down and Serial Clock Input Pin
		byte DOUT;		// Serial Data Output Pin
		byte GAIN;		// amplification factor
		long OFFSET;	// used for tare weight
		float SCALE;	// used to return weight in grams, kg, ounces, whatever

	public:
		// define clock and data pin, channel, and gain factor
		// channel selection is made by passing the appropriate gain: 128 or 64 for channel A, 32 for channel B
		// gain: 128 or 64 for channel A; channel B works with 32 gain factor only
		HX711(byte dout, byte pd_sck, byte gain = 128);

		virtual ~HX711();

		// check if HX711 is ready
		// from the datasheet: When output data is not ready for retrieval, digital output pin DOUT is high. Serial clock
		// input PD_SCK should be low. When DOUT goes to low, it indicates data is ready for retrieval.
		bool is_ready();

		// set the gain factor; takes effect only after a call to read()
		// channel A can be set for a 128 or 64 gain; channel B has a fixed 32 gain
		// depending on the parameter, the channel is also set to either A or B
		void set_gain(byte gain = 128);

    byte get_gain();

		// waits for the chip to be ready and returns a reading
		long read();

		// returns an average reading; times = how many times to read
		long read_average(byte times = 10);

		// returns (read_average() - OFFSET), that is the current value without the tare weight; times = how many readings to do
		double get_value(byte times = 1);

		// returns get_value() divided by SCALE, that is the raw value divided by a value obtained via calibration
		// times = how many readings to do
		float get_units(byte times = 1);

		// set the OFFSET value for tare weight; times = how many times to read the tare value
		void tare(byte times = 10);

		// set the SCALE value; this value is used to convert the raw data to "human readable" data (measure units)
		void set_scale(float scale = 1.f);

		// set OFFSET, the value that's subtracted from the actual reading (tare weight)
		void set_offset(long offset = 0);

		// puts the chip into power down mode
		void power_down();

		// wakes up the chip after power down mode
		void power_up();
};

#endif /* HX711_h */

TEST PROGRAM

/*Example using the SparkFun HX711 breakout board with a scale
 By: Nathan Seidle
 SparkFun Electronics
 Date: November 19th, 2014
 License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
 
 This is the calibration sketch. Use it to determine the calibration_factor that the main example uses. It also
 outputs the zero_factor useful for projects that have a permanent mass on the scale in between power cycles.
 
 Setup your scale and start the sketch WITHOUT a weight on the scale
 Once readings are displayed place the weight on the scale
 Press +/- or a/z to adjust the calibration_factor until the output readings match the known weight
 Use this calibration_factor on the example sketch
 
 This example assumes pounds (lbs). If you prefer kilograms, change the Serial.print(" lbs"); line to kg. The
 calibration factor will be significantly different but it will be linearly related to lbs (1 lbs = 0.453592 kg).
 
 Your calibration factor may be very positive or very negative. It all depends on the setup of your scale system
 and the direction the sensors deflect from zero state
 This example code uses bogde's excellent library: https://github.com/bogde/HX711
 bogde's library is released under a GNU GENERAL PUBLIC LICENSE
 Arduino pin 2 -> HX711 CLK
 3 -> DOUT
 5V -> VCC
 GND -> GND
 
 Most any pin on the Arduino Uno will be compatible with DOUT/CLK.
 
 The HX711 board can be powered from 2.7V to 5V so the Arduino 5V power should be fine.
 
*/

#include "HX711.h"

#define DOUT  D6
#define CLK  D7

HX711 scale(DOUT, CLK);

int calibration_factor = 878; //-7050 worked for my 440lb max scale setup

void setup() {
  Serial.begin(9600);
  Serial.println("HX711 calibration sketch");
  Serial.println("Remove all weight from scale");
  Serial.println("After readings begin, place known weight on scale");
  Serial.println("Press + or a to increase calibration factor");
  Serial.println("Press - or z to decrease calibration factor");

  scale.set_scale();
  scale.tare();	//Reset the scale to 0

  long zero_factor = scale.read_average(); //Get a baseline reading
  Serial.print("Zero factor: "); //This can be used to remove the need to tare the scale. Useful in permanent scale projects.
  Serial.println(zero_factor);
}

void loop() {

 scale.set_scale(calibration_factor); //Adjust to this calibration factor

  Serial.print("Reading: ");
  Serial.print(scale.get_units(), 1);
  Serial.print(" lbs"); //Change this to kg and re-adjust the calibration factor if you follow SI units like a sane person
  Serial.print(" calibration_factor: ");
  Serial.print(calibration_factor);
  Serial.println();

  if(Serial.available())
  {
    char temp = Serial.read();
    if(temp == '+' || temp == 'a')
      calibration_factor += 10;
    else if(temp == '-' || temp == 'z')
      calibration_factor -= 10;
  }
}

#2

@beckerk2, it takes a lot to make a Photon “unusable”. For most cases, putting the Photon in safe mode will allow you to recover by flashing a known-good app to it like Tinker using the web IDE.

One common problem that crops up these days is due to system firmware updates. For a user app to work correctly, the matching system firmware must be installed on the Photon. This is normally done automatically by the IDE when it first detects the need for the update. Make sure you give enough time for the firmware update to take place when you flash over your app. If you flash over Tinker using the web IDE, as discussed above, it will update your system firmware as required. NOTE: this system firmware update DOES NOT occur automatically when you use Particle CLI or DEV. :slight_smile:

BTW, I don’t see any issues with your code which I why I suspected the above condition.


#3

Hi @beckerk2

There are two things I would try:

I would get rid of these macros and just use D6 and D7. Other things in the library are called DOUT and there could some kind of weird interaction. Better safe in this case.

//#define DOUT  D6
//#define CLK  D7

HX711 scale(D6, D7);

If that is not it, then I would try calling the constructor in setup() instead of globally. The order of constructors for globals is not knowable in C++ so strange interactions can happen. That would look like this:

HX711 *scale = NULL;

void setup() {
...
  scale = new HX711(D6, D7);
...
}

#4

@bko THANK YOU! that worked!

All I needed to do was declare the constructor as you did in setup. I had to replace the functions with the dereferencing arrow operator “->” such as scale->tare(); I replaced the code I needed in my main software and that seems to be up and running great.

I’m more of an electronics guy rather than a programmer, but if you don’t mind could you explain why this could mess up so easily? I’m even more confused as to why it’s worked perfectly up until now!


#5

Hi @beckerk2

Glad that worked for you!

The problem is the scale class uses things like Spark.process() which depends on the Spark object being constructed and ready to use before the scale object. But in C++ the order that all the things that need be done before the first line of your code is run is not predictable.

The HX711 constructor calls set_gain() which in turn calls read() which calls Spark.process(), but the Spark object may or or may not have even been setup in memory yet.

It is a good practice in a C++ library for Particle to have a simple constructor that just sets a few local class variables and then have a separate begin() method that really starts operating and set pins or reads data. This is the way that the Arduino wiring libraries are done, such as Serial and TCPclient.


#6

Do you have a completed example? I’m going crazy trying to get an hx711 to work with my photon. I move it to an esp8266 no problems try using your above code settings not working. Trying to use the library in particle web ide doesn’t look like its supported. Really appreciate any help.


#7

I seem to be having a problem with the HX711 as well. I keep getting this error message.

hx711test.cpp:1:19: fatal error: HX711.h: No such file or directory
 #include "HX711.h"
                   ^

compilation terminated.
make[1]: *** [../build/target/user/platform-6hx711test.o] Error 1
make: *** [user] Error 2
Flash unsuccessful.
#include "HX711.h"
#define calibration_factor -7050.0 //This value is obtained using the SparkFun_HX711_Calibration sketch
#define zero_factor 8421804 //This large value is obtained using the SparkFun_HX711_Calibration sketch

HX711 scale(D6, D7);

void setup() {
  Serial.begin(9600);
  Serial.println("HX711 power test");
scale = new HX711(D6, D7);
  scale.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale.set_offset(zero_factor); //Zero out the scale using a previously known zero_factor

}

void loop() {
  Serial.print("Reading: ");
  Serial.print(scale.get_units(), 1); //scale.get_units() returns a float
  Serial.print(" lbs"); //You can change to kg but you'll need to change the calibration_factor
  Serial.println();

  Serial.println("Powering down...");
  scale.power_down(); //Put the HX711 in sleep mode
  delay(5000);
  Serial.println("Powering up...");
  scale.power_up();
  delay(5000);
}

#8

If you are using Build and have imported the library, the include statement should be

#include "HX711ADC/HX711ADC.h"

But since I found some issues in the implementation and the original contributor seems to have vanished from the forum, I had forked my own version and added some fixes.


#9

Could you please provide a link or post your improved code @ScruffR ? This library is driving me crazy! Thanks a bunch :relaxed:


#10

I’ve not put the together as a lib, but here you can have the code

HX711.h

#ifndef HX711_h
#define HX711_h

#if defined(PARTICLE)
#  include "Particle.h"
#else
#  if ARDUINO >= 100
#    include "Arduino.h"
#  else
#    include "WProgram.h"
#  endif
#endif
#include <math.h>

class HX711
{
	private:
		byte PD_SCK;	// Power Down and Serial Clock Input Pin
		byte DOUT;		// Serial Data Output Pin
		byte GAIN;		// amplification factor
		long OFFSET = 0;	// used for tare weight
		float SCALE = 1;	// used to return weight in grams, kg, ounces, whatever

	public:
		// define clock and data pin, channel, and gain factor
		// channel selection is made by passing the appropriate gain: 128 or 64 for channel A, 32 for channel B
		// gain: 128 or 64 for channel A; channel B works with 32 gain factor only
		HX711(byte dout, byte pd_sck, byte gain = 128);

		HX711();

		virtual ~HX711();

		// Allows to set the pins and gain later than in the constructor
		void begin(byte dout, byte pd_sck, byte gain = 128);

		// check if HX711 is ready
		// from the datasheet: When output data is not ready for retrieval, digital output pin DOUT is high. Serial clock
		// input PD_SCK should be low. When DOUT goes to low, it indicates data is ready for retrieval.
		inline bool is_ready() { return !digitalRead(DOUT); };

		// set the gain factor; takes effect only after a call to read()
		// channel A can be set for a 128 or 64 gain; channel B has a fixed 32 gain
		// depending on the parameter, the channel is also set to either A or B
		void set_gain(byte gain = 128);

		// waits for the chip to be ready and returns a reading (1sec default timeout)
		long read(time_t timeout = 1000);

		// returns an average reading; times = how many times to read
		long read_average(byte times = 10);

		// returns (read_average() - OFFSET), that is the current value without the tare weight; times = how many readings to do
		double get_value(byte times = 1);

		// returns get_value() divided by SCALE, that is the raw value divided by a value obtained via calibration
		// times = how many readings to do
		float get_units(byte times = 1);

		// set the OFFSET value for tare weight; times = how many times to read the tare value
		void tare(byte times = 10);

		// set the SCALE value; this value is used to convert the raw data to "human readable" data (measure units)
		void set_scale(float scale = 1.f);

		// get the current SCALE
		float get_scale();

		// set OFFSET, the value that's subtracted from the actual reading (tare weight)
		void set_offset(long offset = 0);

		// get the current OFFSET
		long get_offset();

		// puts the chip into power down mode
		void power_down();

		// wakes up the chip after power down mode
		void power_up();

#if defined(PARTICLE) 
    // to keep the Particle cloud happy when the library blocks
    inline void yield() { Particle.process(); }; 
#endif
};

#endif /* HX711_h */

HX711.cpp

#include "HX711.h"

HX711::HX711(byte dout, byte pd_sck, byte gain) {
	begin(dout, pd_sck, gain);
}

HX711::HX711() {
}

HX711::~HX711() {
}

void HX711::begin(byte dout, byte pd_sck, byte gain) {
	PD_SCK = pd_sck;
	DOUT = dout;

	pinMode(PD_SCK, OUTPUT);
	pinMode(DOUT, INPUT);

	set_gain(gain);
}

void HX711::set_gain(byte gain) {
	switch (gain) {
		case 128:		// channel A, gain factor 128
			GAIN = 1;
			break;
		case 64:		// channel A, gain factor 64
			GAIN = 3;
			break;
		case 32:		// channel B, gain factor 32
			GAIN = 2;
			break;
	}

	digitalWrite(PD_SCK, LOW);
	//read();
}

long HX711::read(time_t timeout) {
	// wait for the chip to become ready
	for (time_t ms=millis(); !is_ready() && (millis() - ms < timeout);) {
		// Will do nothing on Arduino but 
    // prevent resets of ESP8266 (Watchdog Issue)
    // or keeps cloud housekeeping running on Particle devices
		yield();
	}
  // still not ready after timeout periode, report error Not-A-Number
  if (!is_ready()) return NAN;

	unsigned long value = 0;
	uint8_t data[3] = { 0 };
	uint8_t filler = 0x00;

	// pulse the clock pin 24 times to read the data
	data[2] = shiftIn(DOUT, PD_SCK, MSBFIRST);
	data[1] = shiftIn(DOUT, PD_SCK, MSBFIRST);
	data[0] = shiftIn(DOUT, PD_SCK, MSBFIRST);

	// set the channel and the gain factor for the next reading using the clock pin
	for (unsigned int i = 0; i < GAIN; i++) {
		digitalWrite(PD_SCK, HIGH);
		digitalWrite(PD_SCK, LOW);
	}

	// Replicate the most significant bit to pad out a 32-bit signed integer
	if (data[2] & 0x80) {
		filler = 0xFF;
	} else {
		filler = 0x00;
	}

	// Construct a 32-bit signed integer
	value = static_cast<unsigned long>(filler)  << 24
		  | static_cast<unsigned long>(data[2]) << 16
		  | static_cast<unsigned long>(data[1]) << 8
		  | static_cast<unsigned long>(data[0]) ;

	return static_cast<long>(value);
}

long HX711::read_average(byte times) {
  if (times <= 0) return NAN;
	long sum = 0;
	for (byte i = 0; i < times; i++) {
		sum += read();
		yield();
	}
	return sum / times;
}

double HX711::get_value(byte times) {
	return read_average(times) - OFFSET;
}

float HX711::get_units(byte times) {
	return get_value(times) / SCALE;
}

void HX711::tare(byte times) {
	double sum = read_average(times);
	set_offset(sum);
}

void HX711::set_scale(float scale) {
  if (scale) {
	  SCALE = scale;
  }
  else {
    SCALE = 1;
  }
}

float HX711::get_scale() {
	return SCALE;
}

void HX711::set_offset(long offset) {
	OFFSET = offset;
}

long HX711::get_offset() {
	return OFFSET;
}

void HX711::power_down() {
	digitalWrite(PD_SCK, LOW);
	digitalWrite(PD_SCK, HIGH);
}

void HX711::power_up() {
	digitalWrite(PD_SCK, LOW);
}


#11

Thanks a lot. Your code works perfectly with the examples on sparkfun.
It can be found here for any future readers:
https://learn.sparkfun.com/tutorials/load-cell-amplifier-hx711-breakout-hookup-guide


#12

Are there any specific changes that I would need to make to get this working with an Argon?

I’ve got @ScruffR’s code pulled in and followed the Sparkfun tutorial (calibration code flashed), but I’m getting 0.0 lbs, regardless of the weight on the sensor.

I’m using the 50kg load cell.


#13

I’d have to look into it with Gen3 controllers, but just as an update

This part isn’t true anymore. The library can now be imported directly
https://build.particle.io/libs/HX711ADC/0.0.3/tab/HX711ADC.cpp


#14

Ok. I was able to use the code provided previously in the thread, which compiled fine, I just can’t seem to get any values.


#15

How have you got your HX711 and the load cells wired?


#16

Sorry, haven’t had a chance to work on the project in a bit. I’ll upload a photo the next time I get a chance.