crjeder / hx711_spi

This is a library for the hx711 chip. It uses SPI instead of bit banging for more reliability.
MIT License
14 stars 4 forks source link

Introduce a feature to invert signal on SDO #16

Closed hyx0329 closed 1 year ago

hyx0329 commented 1 year ago

Is your feature request related to a problem? Please describe.

I was trying to utilize the crate on ESP32-C3. The block!(hx711.read()) will always block. After digging around, I found the SDO of ESP32-C3 is always idle at HIGH and I haven't find a way to change the behavior. So I decided to use a MOSFET to hold the SDO at LOW when no data is transfered. However, the CS on ESP32-C3 is disabled after SDO pulled itself HIGH, the value I got is not correct.

using CS to hold SDO at LOW

Later I tried to invert the polarity of SDO with the MOSFET and change the code, it looks better now.

Invert SDO

Describe the solution you'd like

Introduce a new feature invert-sdo, and enable it will invert the signal on SDO, allowing external inverter. For example

#[cfg(not(feature = "invert-sdo"))]
let value = 0x00;
#[cfg(feature = "invert-sdo")]
let value = 0xFF;

Describe alternatives you've considered

Maybe there's a way to fix it on ESP32-C3's side.

Additional context

MOSFET model 1N60C, an additional 1kΩ resistor is used when inverting the SDO. SPI speed is 1MHz.

Edit: wrong resistor value

crjeder commented 1 year ago

I am not an electronic expert. Could the SPI mode (clock polarity and clock phase) play a role here? Additionally the CS line is not used, even not defined in my Raspberry PI example. This could be a problem, too. I'll have a look.

crjeder commented 1 year ago

I haven't found any documentation yet, but it seems that the default state of the SPI lines is influenced by the mode. Do you mind to try your code with SPI mode 1 instead?

crjeder commented 1 year ago

Idle high for the SCLK pin would be desireable because it would auto power-down the hx711 (see datasheet) By looking at your first graph, I recognized something else: The SDI signal is high for the first 9 (fake) clock cycles which indicates that ther is no data waiting to be retrived. In this case the 'read' funktion should not emit any clock signal but instead keep the SDO line low for 8 cycles and then return with a 'WouldBlock'. When the function is called again (though the block! macro) it will see the SDI line go low and then start to write the clock cycle to SDO. Unfortunately I don't own a logic analyzer wich makes debugging a bit hard for me.

hyx0329 commented 1 year ago

Could the SPI mode (clock polarity and clock phase) play a role here?

Nope, I've tried all 4 modes and they only affect the CLK's polarity and not the SDO's idle state.

Additionally the CS line is not used, even not defined in my Raspberry PI example.

The additional CS is used to control an external mosfet so I can pull the SDO low(in the first attempt). It's not used when I just invert the polarity.

I recognized something else: The SDI signal is high for the first 9 (fake) clock cycles which indicates that ther is no data waiting to be retrived. In this case the 'read' funktion should not emit any clock signal but instead keep the SDO line low for 8 cycles and then return with a 'WouldBlock'.

When I wasn't using the mosfet, the code did block, infinitely, because DOUT(DT) is always HIGH and there's nothing to be read. Bitbanging doesn't have this issue. The SDI signal for the first 9 clock cycles might be caused by the first pulse I pointed out(I mean the chip started to output a value).

datasheet-table

time-measurement

In the meantime, I think I recognize a new bug. The transaction should be started only when the lowest bit of the value read is zero. So the code below should be change a bit.

https://github.com/crjeder/hx711_spi/blob/e244b58a454ea2761f3e58390676222d9e5365d3/src/lib.rs#L68-L74

maybe to something like this

if txrx[0] & 0b01 == 0b01
 // as soon as the lowest bit is low data is ready 
 { 
     // sleep for 1 millisecond which is 1/100 of the conversion period to grab the data while it's hot 
     self.delay.delay_ms(1); // not sure if that's ok with nb 
     return Err(nb::Error::WouldBlock); 
 } 

I'm uploading the data I captured for anyone interested. Should work with DSView.

DSLogic-captures.zip

Edit: for pulseview users, try srzips in the following zip

exported-captures.zip

hyx0329 commented 1 year ago

Idle high for the SCLK pin would be desireable because it would auto power-down the hx711 (see datasheet)

In this case, the fake clock should be sent immediately after the initial probing. That means everything should be done in one atomic SPI transaction, and the value is conditionally decoded using the last bytes. Otherwise after the probing, the chip is quickly powered off(because of idle HIGH), and it may not ready for a read at the time fake clock generated.

crjeder commented 1 year ago

Thank you for helping me to debug this!

Could the SPI mode (clock polarity and clock phase) play a role here?

Nope, I've tried all 4 modes and they only affect the CLK's polarity and not the SDO's idle state.

That's what I would expect but it was the case for one STM32 user.

When I wasn't using the mosfet, the code did block, infinitely, because DOUT(DT) is always HIGH and there's nothing to be read. Bitbanging doesn't have this issue. The SDI signal for the first 9 clock cycles might be caused by the first pulse I pointed out(I mean the chip started to output a value).

The hx711 chip shuld ignore the SCK signal when it pulls the DOUT high but who knows.

if txrx[0] & 0b01 == 0b00
 // as soon as the lowest bit is low data is ready 
 { 
      return Err(nb::Error::WouldBlock); 
 } 

You are right. This should detect the falling edge in the DOUT much more reliable! Note: it should be == 0b010 and I would delete the unnecessary delay which I think may cause problems on platforms other then Raspebrry PI. Unfortunately this change will break the API (not requiring the delay parameter anymore)

hyx0329 commented 1 year ago

Note: it should be == 0b0~1~0

well, SPI bus seems to be active LOW so I think it should be 1

PS. occasionally, I'll get the result in the following picture, I know it's busy because the scale has no load on it.

Screenshot 3

crjeder commented 1 year ago

My bad. You are right! It should be

if txrx[0] & 0b01 == 0b01 {
     // as long as the lowest bit is high there is no data waiting 
     return Err(nb::Error::WouldBlock); 
} 
crjeder commented 1 year ago

Concerning your PR: maybe I'd have to invert the bit pattern for the clock. I am looking into this, too.

crjeder commented 1 year ago

I was testing on my Raspberry Pi with rppal as hal implementation and I get an extra signal on the hx711 clk line every second even without my code running. This blocks the chip for ~9 ms. grafik But that's not the same what you experienced, right? Edit to add: On a freshly booted machine I don't see those extra spikes.

hyx0329 commented 1 year ago

I get an extra signal on the hx711 clk line every second even without my code running

That's not the same what I experienced. But I think the spikes play a very similar role in disturbing the communication, like those in my setup.

crjeder commented 1 year ago

That's a typical sequence using Raspberry PI and rppal: grafik You can see the probes to see if hx711 is ready ant then it starts the read out. Looks good to me.

hyx0329 commented 1 year ago

That's a typical sequence using Raspberry PI and rppal:

That looks good!

crjeder commented 1 year ago

After doing a loop-back test using spidev-test I am confident that the spikes are caused by the cheap hx711 board. They seem to originate in the SCK line and due to crosstalk there are shorter spikes in the DOUT. During a data transfer they are not causing any problems (in my case) and when the hx711 is idle it will be busy for ~90 us, which is no big issue.

Maybe the spike you are seeing is caused by the hardware? It looks like it happens shortly after SPI clock is turned off. That would explain why this problem doesn't exsits with bit-banging.

hyx0329 commented 1 year ago

I have several setups:

  1. bit-banging. There's no external mosfet, and CS is not used, everything works smoothly, except some minor delay issue.
  2. SPI, scenario 1. When wired like the first setup, the DOUT of hx711 is always high, and I cannot read anything because I'm blocked by the code waiting the chip ready. Because the ESP32-C3's SPI is idle at high(from my experiments), I suspect that is the cause of unexpected hx711 behavior. It leads to the next setup.
  3. SPI, scenario 2. I decided to use external mosfet to pull the SDO low at idle. CS signal is utilized in this setup. I can get values, but they are almost 2 times of the values from bit-banging. Then I found a spike on SCK(SDO), and I found CS signal didn't change quick enough. I thought there might be an issue with the configuration or something.
  4. SPI, scenario 2.1. I wrote some SPI registers with low level access to make CS respond faster, so the mosfet can pull SCK(SDO) low in time, without affecting hx711's behavior. In this setup, the values I got seem correct, but the logic analyzer capture didn't change at all. CS signal still arrived later than I expected.
  5. SPI, inverted SDO. In this setup, I only use an external mosfet(with a resistor) as signal inverter so SDO can be idle low(with everything inverted). CS is not used. Everything worked fine then. After that I came here to request a feature to invert the SDO.

Even if the spikes(when mosfet is used) are caused by hardware, my CS's response on ESP32-C3 is still slow, thus I cannot use CS to keep SCK(SDO) idle low, which will likely to cause problems. (PS. SPI peripherals read value on clock transitions so my spikes(when mosfet is used) will not affect standard SPI communication.)

Key points:

The spikes are not that relevant to my issue.

hyx0329 commented 1 year ago

I think I have to apologize for not making everything clear enough. (´;ω ;`)

crjeder commented 1 year ago

You explaind very well, and I understand what you want. I just needed to understand what my code is doing and why it worked for me. This crate is my attemt to learn two things at once: Rust and embedded / bare metal programming. I just kept the Raspi as dev platform because I did not want to throw a third unknown into the mix.

crjeder commented 1 year ago

Hey! I've imlemented the feature invert-sdo in the 0.5.0 branch. Could you have a look and maybe give it a try, please? Thanks!

hyx0329 commented 1 year ago

Two small bug:

the second one should be GAIN64 https://github.com/crjeder/hx711_spi/blob/ab323db5d6db2a70006326de3614b0485855db81/src/lib.rs#L25-L28

1 missed here, should be 0b01010101 https://github.com/crjeder/hx711_spi/blob/ab323db5d6db2a70006326de3614b0485855db81/src/lib.rs#L34-L35

The rest look good. Good job!

crjeder commented 1 year ago

Done!

crjeder commented 1 year ago

I was looking for a reason of the behavior and found a clue: the "normal" SPI is a Motorola developed protocol but there is a TI variant wich sends an extra "high" signal between frames. Together with a frame length of 16 bit this would explain the extra signal and the value being of by a factor of 2 (skipping the 17th bit). The best reference I found: ARM

hyx0329 commented 1 year ago

there is a TI variant wich sends an extra "high" signal between frames

And that can explain why CS is not deserted after all data sent. In this case, some hosts may need the extra "high" signal to work with some devices implementing TI's variant of SPI.

I grabbed one random datasheet from the internet, found the example below.

image