Boron - Clock Frequency Error/Variations

I have been having issues with communicating with a HX711 over the past few months, which I have attempted to resolve in another thread. HERE I have opened a new thread, as I believe this is an SPI communication issue. Please see the following investigation.

Case #1: Historically there were no issues using the HX711 with an Arduino Uno. In this scenario, I have connected an Arduino & a HX711. In the image below, I have measured both the DOUT pin (in YELLOW), and the CLK pin (in GREEN) using an oscilloscope. NOTE: Immediately after DOUT goes LOW, the Arduino pulses the CLK pin 25 times, at a frequency of 2.5kHz. This returns perfect consistent results from the HX711.

Case #2: When this experiment is conducted with the exact same code (and library) on a Boron I get the following result.


NOTE: Similarly, after DOUT goes LOW, the Boron pulses the CLK pin 25 times. But does this with a considerably different frequency of 881Hz. Although this communication was 500ms slower, nevertheless, it also returns perfect consistent results from the HX711.

The BUG/PROBLEM lies in 20% of communications from the Boron to the HX711. See the below image:


NOTE: In this case, pulse #6 pauses on HIGH for a considerable amount of time. Unfortunately this time is greater than the 60us which puts the HX711 into sleep mode, and therefore, no more data is transferred. This results in a large amount of bogus data. This issue can happen on any one of the 25 pulses used to communicated with the HX711. As stated, this happens during 20% of attempts. (80% are good clean pulses). The clock sometimes (but rarely) pauses on LOW.

The code I am using is very straight forward:

#include "HX711.h"


#define calibration_factor -7050.0 //This value is obtained using the SparkFun_HX711_Calibration sketch

#define DOUT  3
#define CLK  2

HX711 scale;
long time1 = 0;
long time2 = 0;
long time31 = 0;
void setup() {
  Serial.begin(9600);
  Serial.println("HX711 scale demo");

  scale.begin(DOUT, CLK);
  scale.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0

  Serial.println("Readings:");

}

void loop() {

time1 = millis();
Serial.print("R= ");

Serial.print(scale.read()); //scale.get_units() returns a float

Serial.println();
}

I am unsure how I can address the current issue. I would appreciate any advice available.

In the meantime, I have done some follow on testing. No libraries included this time.
See below code:

#define DOUT  3
#define CLK  2

void setup() {
pinMode(CLK, OUTPUT);
}

void loop() {
    delay(100);
    for(int i = 0; i < 150; ++i) {
    digitalWrite(CLK, HIGH);
    //delayMicroseconds(1);
    digitalWrite(CLK, LOW);
    //delayMicroseconds(1);
    }
    
}

The objective of this code is to test the reliability of the CLK pin, by pulsing 150 times and measure the result. I am measuring the output via an oscilloscope. Please see the below results:

The green shows 150 pulses, but there is a gap in the middle. This pause is intermittent, and presents its self in ~32% of the samples. This is enough to cause serious problems when communicating with external components such as the HX711.

WHen operating with SYSTEM_THREAD(ENABLED);

#include "Particle.h"

#define DOUT  3
#define CLK  2


SYSTEM_THREAD(ENABLED);

void threadFunction(void);

Thread thread("testThread", threadFunction);

volatile int counter = 0;
unsigned long lastReport = 0;




void setup() {
	Serial.begin(9600);
	pinMode(CLK, OUTPUT);
}

void loop() {

}


void threadFunction(void) {
	while(true) {
		delay(100);
        for(int i = 0; i < 150; ++i) {
            digitalWrite(CLK, HIGH);
            //delayMicroseconds(1);
            digitalWrite(CLK, LOW);
            //delayMicroseconds(1);
        }
	}
	// You must not return from the thread function
}


It does not allow all 150 pulses without interruption, but the pause does not seem to be present.

Has anybody got any ideas what may be causing the Boron to pause for this time? Is it processing something else? Is there a way to get the boron to completely dedicate itself to this task?

Other information:
This is a new Boron, and the problem is present with other Borons currently in operation in the field.
OS - v1.5.0 - Also presents issue on v1.1.0
Currently set up with breadboard.

I believe your library uses the soft-SPI implementation with shiftIn and shiftOut. Unfortunately, that API is very susceptible to RTOS task switching and interrupts landing in the middle of the shift operation. If you are working with a timing sensitive peripheral the hardware SPI interface would be preferred. You might also be able to disable interrupts around the shift operations but that carries an element of risk if done incorrectly depending on how the library is implemented and how long you are disabling interrupts for (not servicing critical interrupts in time can impact system stability).

Hi Joel, it is great to hear from you. Yes exactly, the shiftIn is called within the library. Weight measurements from the HX711 peripheral is a critical part of our process, and therefore we are not able to work with the error we see at the moment.
From what I understand, I have two options to get this working the way I need:

  1. Disable interrupts around the switching functions. I am a mechanical engineer, so a lot of functions are outside of my knowledge base. Would you be able to point me in the direction of how I may go about this? Is there some example code I could get ideas from? Maybe there are topics on the community forum which cover this, but my terminology prohibits me from finding the correct solution…
  2. Use hardware SPI. This I do not know a lot about, and implementing something like this may take a lot of research. Do you know of a similar application which I may be able to get ideas from?
    I am very thankful for your help. This problem has caused my team a lot of stress over the past few weeks, and it’s great to hear that there may be a solution. Should I find a solution, I will be sure to post details on the forum for any future desperate semi-programmers.

Kind regards, Micheál.

https://docs.particle.io/reference/device-os/firmware/boron/#atomic_block-

You may be able to use that to wrap around the shiftIn/shiftOut calls. Wrap the smallest amount of code that needs to be uninterrupted. There is a lot of inherent risk to disabling interrupts so you can test if it is safe to do for your use-case, I don’t know enough about the library you are working with to provide much guidance.

https://docs.particle.io/reference/device-os/firmware/boron/#spi

Particle provides a hardware SPI library but it is only supported on a few pin combinations so might require reworking your hardware depending on how you have it laid out. It’s not hard to use but you need to explicitly set the clock-rate, etc (the shiftIn API just clocks in/out as fast as it can in software which is relatively slow).

Hi Joel,
I have just tested suggestion #1, I implemented the forllowing code:

#define DOUT  3
#define CLK  2


volatile int counter = 0;
unsigned long lastReport = 0;




void setup() {
	Serial.begin(9600);
	pinMode(CLK, OUTPUT);
}

void loop() {
    while(true) {
    delay(100);
    ATOMIC_BLOCK() {
        for(int i = 0; i < 150; ++i) {
            digitalWrite(CLK, HIGH);
            //delayMicroseconds(1);
            digitalWrite(CLK, LOW);
            //delayMicroseconds(1);
            }
        }
    }

}

But I still seem to have the same problem with random pauses in the middle of the ATOMIC_BLOCK(). Before I move onto option #2, have I done anything wrong here?
Please see result (clock pulses in YELLOW):

Hmm… I would have expected that to work but apparently I was wrong. Digging down a little bit it appears the ATOMIC_BLOCK explicitly doesn’t mask out the highest priority interrupts on the Gen3 platform.

// We are blocking any interrupts with priorities >= 2, without
// affecting SoftDevice interrupts which run with priorities 0 and 1.

The SoftDevice interrupts are extremely sensitive to timing issues and tend to crash the system if not serviced quickly enough. There might be a way to mask all of them but perhaps not accessible from your application and probably a good reason the built-in mechanisms don’t allow it…

If you are on compatible pins or can switch around your IO I’d definitely recommend looking into the SPI interface. It will be far more stable without mucking around with the interrupt logic. You will want to use the multi-byte SPI.transfer function but if you can work with it should clear up all your issues around interrupts interfering with the shift timing.

1 Like

Thanks Joel, I am going to raise a separate topic looking for help to write a program using SPI.transfer, unfortunately my knowledge here is limited so will need some support from the community.

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