adafruit / Adafruit_CircuitPython_HCSR04

CircuitPython library for controlling HC-SR04 ultrasonic range sensors
MIT License
9 stars 22 forks source link

Does not work well without pulseio #28

Open ladyada opened 2 years ago

ladyada commented 2 years ago

from https://forums.adafruit.com/viewtopic.php?f=62&t=190920&p=924044#p924041

for itsybitsy m0 and gemma m0

Your adafruit_hcsr04.mpy does not work with CircuitPython 7.x -- To get the sensor to work, I had to install:

adafruit-circuitpython-gemma_m0-en_US-6.3.0.uf2

and then use:

adafruit-circuitpython-bundle-6.x-mpy-20211213.zip

After downgrading the Gemma to these version - the sensor now works perfect.

I tried multiple versions of 7.X - none worked, it was only when I downgraded back to 6.3.0 - that the sensor started working.

### Code/REPL

```python
import time
import board
import adafruit_hcsr04
sonar = adafruit_hcsr04.HCSR04(trigger_pin=board.RX, echo_pin=board.TX)

while True:
    try:
        print((sonar.distance,))
    except RuntimeError:
        print("Retrying!")
    time.sleep(0.1)

Behavior

(17.1204,)
(0.0,)
(0.0,)
(0.0,)
(0.0,)
(17.1204,)
(17.1204,)
(17.1204,)

Which is just random - no relation to distance of anything in front of the sensor. It's always just jumping between 0.0 and 17.xx

Description

No response

Additional information

No response

mwisslead commented 2 years ago

Looks like @dhalbert removed pulseio for the itsybitsy m0 express in commit 843598ec. I tested this with an itsybitsy m0 express and rcwl-1601 and it does work but the accuracy is terrible without pulseio.

dhalbert commented 2 years ago

We traded off pulseio against other things when we needed space. Perhaps the library should not attempt to work without pulseio.

mwisslead commented 2 years ago

I discovered that time.monotinic has limited accuracy. For the m0 it seems to be about 1000 microseconds. time.monotonic_ns seems to be about 183 microseconds.

Switching to monotonic_ns could reduce the problem but only to an accuracy of about 3 meters. Could something be done to improve the timing accuracy (possibly by modifying circuitpython?)

ladyada commented 2 years ago

not really, the chip just isnt fast enough - faster chips can bitbang read the GPIO, the samd21 cannot!

mwisslead commented 2 years ago

I looked at the circuitpython code a bit and it looks like for the samd port the monotonic_ns function is timed off the rtc running at 16384hz so the resolution should be about 61us but for some reason I can only get a difference of 183us (I get a difference of 183us 25% of the time and 0 75% of the time). I think that means you could time an event as short as ~45us if the function returned a more precise value on the samd21.

I tested how fast you can check the pin and if the python is optimized some I think you can check the pin value in a loop while incrementing a timeout counter in ~41us per loop on the samd21. Combining these, you could time the echo as short as 45us with a precision of 61us but right now it's 183us for a reason I don't understand. That's about 3cm (I was incorrect when I said 3 meters in my last comment)

Just putting this info out there to better decide if pulseio should be required. 🙊

dhalbert commented 2 years ago

Continuing to try this on the Gemma M0 constrains you a lot. It would be easier if you replaced it with a QT Py RP2040 (not SAMD21) or some other small board, if form factor is an issue.

Precise timing in Python is never going to be a strong suit: there may be garbage collection delays, there are background task delays (such as processing USB interrupts), etc. That's our motivation for writing C-level code like pulseio to do the timing-sensitive things.

Arold commented 6 months ago

I tried to use the HC-SR04 with my FT232H which doesn't have pulseio (to my knowledge). It wasn't working well with the original code. So I looked into it and changed time.monotonic() to time.perf_counter(). After that it worked better.

(As a side note, time.monotonic() is as precise as time.monotonic_ns(), it is just that the first one returns a float whereas the second one return a int. time.perf_counter() is more precise. See perf_couter() in python documentation)

I was still unsatifyed with the results. Since the timing is critical and there was an unnecessary variable declaration (pulselen = None and a _USE_PULSEIO check, I removed them and now, it looks like it is a bit more accurate particularly at small distances.

I would recomand to make 2 functions, one for pulseio and one without and select which one to use in the init. This way you would have a fully optimise functions for each case. Hopefully, I was clear enough for you to make the change if you want to. Otherwise, if you want me to do a pull request, let me know! Thanks for this wonderfull resource!