A01NYUB Waterproof Ultrasonic Sensor - Micropython?

Hi Core guys,
Im hoping to be able to connect one of these A01NYUB sensors to a Pico W but the UART comms and seemingly required Python driver makes me wonder if its possible.

https://core-electronics.com.au/a01nyub-waterproof-ultrasonic-sensor.html

Any thoughts or can you confirm?

Basically I am looking for a weatherproof ultrasonic sensor to put into my water tanks. I currently am using a HC-SR04 on my bench but want to move to something a bit more appropriate when I install into the tank.

Thanks
Jon

1 Like

Hi Jon,

I’ve had a read through the product wiki on that product, and while I usually hate serial sensors with a passion (they usually need some convoluted setup sequence and decoding) this one is surprisingly easy, and shouldn’t be too hard to implement in MicroPython.

First off, have a read of the UART micropython wiki page:
https://docs.micropython.org/en/latest/library/machine.UART.html

Then take a look at the info on the wiki page, primarily:

  • Drive the RX line on the sensor high or low on a GPIO pin depending on whether you want the sensor to filter and average the readings (a bit slower than raw) or just send everything as fast as possible
  • Set up your UART, and use select.poll() to wait for new data in the RX buffer of your pico
  • Read 4 bytes (header, data high, data low, checksum) and process each accordingly
  • You should be able to use << to shift the high bits left and add them to the low bits as numbers (though python is a bit weirder than C for dealing with bytes and integers) more here

Give this a go, and if you can’t get it working, I’ll have a go at setting one of these up on the bench.
-James

2 Likes

Thanks James,
At least I now know its doable, even if my knowledge falls short!

I have taken the plunge and added this to an order. I selected the A02YYUW rather than the A01NYUB because of the shorter blind distance and smaller mounting holes required in the water tank. At least the UART requirement is the same.

I have taken a look at the micropython docs and your response and whilst I can just about follow the logic, Im not sure I’ll be able to make it work… I’ll wait for the sensor to arrive and have a go, if I get stuck I’ll reach out for some assistance.

Thanks
Jon

2 Likes

Guys, this has me beat!

Ive studied to the best of my abuility the micropython docs and the DFRobot info on the UART Ultrasonics and also the Adafruit code that I found buried in the coreelectronics product descriptions.

From all that reading and experimenting, the only thing I can get to work reliably is the Arduino code running on an old Uno that I have. The Raspberry Pi code seems to randomly kill the Wifi and I had no luck reconguring to use the second UART (on the Pi).

This code for the Pico W is a mash of what Ive pulled out of the micrpython docs and the Python Adafruit code. Only problem is it doesnt work and Ive run out of knowledge.

I have got the TX (Pin 4 - Green wire) on the sensor connected to RX on the Pico (Pin7 / GP5 / UART1 TX) and RX (Pin 3 - Blue wire) on the sensor connected to TX on the Pico (Pin6 / GP4 / UART1 TX).

#Micropython code for RaspberryPi Pico W
import time
import machine

#print uart info
uart = machine.UART(1)
print(uart)

def read_me007ys(c, timeout = 1.0):
    ts = time.ticks_ms()
    buf = bytearray(3)
    idx = 0
    time.sleep(0.25)

    while True:
        # Option 1, we time out while waiting to get valid data
        if time.ticks_diff(time.ticks_ms(), ts) > timeout:
#            raise RuntimeError("Timed out waiting for data")
            print("Timeout Error")

#        c = ser.read(1)[0]
        c = uart.read(1)[0]
        print(c)
        if idx == 0 and c == 0xFF:
            buf[0] = c
            idx = idx + 1
        elif 0 < idx < 3:
            buf[idx] = c
            idx = idx + 1
        else:
            chksum = sum(buf) & 0xFF
            if chksum == c:
                return (buf[1] << 8) + buf[2]
            idx = 0
    return None

while True:
    dist = read_me007ys(uart)
    print("Distance = %d mm" % dist)

Would anyone care to take a look and tell me where Im going wrong?

Thanks
Jon

1 Like

Guys / James,
Just giving this a little bump in the hope that someone can assist making the Pico talk nicely to the UART and decode the distance from the incoming info.

Thx
Jon

1 Like

Hi Jon,

I wrote up a bit of code for this, give it a go:

# Import all of the modules that we need
from machine import Pin, UART
from utime import sleep_ms


# Create an LED object that we can turn on and off
led = machine.Pin("LED",machine.Pin.OUT)
avg_pin = machine.Pin(2, machine.Pin.OUT, value=1) # tell sensor to average data in hardware by driving RX pin on sensor high


# Initialise UART
uart = UART(0, 9600)
uart.init(bits=8, parity=None, stop=1) # Defaults from DFrobot wiki page: https://wiki.dfrobot.com/A01NYUB%20Waterproof%20Ultrasonic%20Sensor%20SKU:%20SEN0313


def read_distance():
    uart.read() # clear buffer (could contain many measurements)
    while (uart.any() == 0): # wait for the sensor to send a packet
        pass # pass and check again if you didn't get one
    data_buff = uart.read(4) # read the 4 bytes it sent
    checksum = (data_buff[0] + data_buff[1] + data_buff[2]) & 0xFF # checksum seems to be header + data H + data L, masked to a single byte
    if (checksum == data_buff[3]):
        measurement = ((data_buff[1] << 8) + data_buff[2]) # shift data H left 8 bits, and add with data low
        if measurement == 250:
            return(-1) # return -1 if "250" (invalid measurement) is recieved, consider adding 0 too, as it's usually not accurate
        else:
            return(measurement)
    else:
        return(-2) # if checksum fails (normally because minimum range has been exceeded, return -1)


while True:
    print(str(read_distance()) + " mm") # convert to string (int can't concatenate) and append mm


This works on the bench for me, though you’ll have to do some work on your side to get WiFi working at the same time. Could be worth setting up serial receive as an interrupt that interrupts the loop used to send/serve data.

Let me know if you need anything explained, your understanding of everything is more important to me than having it “just work”
-James

3 Likes

James, you are a wizard! I can follow the logic but there are gaps in my knowledge that I’ll expand on below.

I finally got my code kinda working, but its not fancy and seems VERY susceptible to timing issues and seems slow to react. For completeness, my rubbish code is below and I wont take it any further.

I have tried your code and it is MUCH more snappy in its operation and I like the error checking and trapping. I do have questions though, which I’ll continue with below my rubbish code!

import time
import machine

#print uart info
uart = machine.UART(1, 9600)
print(uart)
data = 1

def read_UART_Ultrasonic(data, timeout = 1.0):
    ts = time.ticks_ms()
    buf = bytearray(3)
    idx = 0
    time.sleep(0.1)

    while True:
        # Option 1, we time out while waiting to get valid data
        if time.ticks_diff(time.ticks_ms(), ts) > timeout:
            RuntimeError
        c = uart.read(1)[0]
        if idx == 0 and c == 0xFF:
            buf[0] = c
            idx = idx + 1
        elif 0 < idx < 3:
            buf[idx] = c
            idx = idx + 1
        else:
            chksum = sum(buf) & 0xFF
            if chksum == c:
                return (buf[1] << 8) + buf[2]
            idx = 0
    return None

while True:
    dist = read_UART_Ultrasonic(data)
    print("Distance = %d mm" % dist)

Questions about your code to further my understanding:
Q1. I can see from the DFRobot datasheet that the RX pin can be driven high to provide a more steady / consistant measurement. This appears to be done on Pin 2 when avg_pin gets set to as 1, so Im wondering what is the purpose of the led line above the avg_pin line? Is it somewhow required to set the RX high?

led = machine.Pin("LED",machine.Pin.OUT)

Q2:
Using UART0 plays havoc with using the REPL in Thonny via USB (at least whilst coding & de-bugging). Using UART1 on Pins 6 & 7 resolves this issue, hence my question above re driving Pin 2 (UART0 RX) High. I think I can just change Pin 2 to 7 on the avg_pin line and then change the address of uart. So I would have the following to use UART1:

avg_pin = machine.Pin(7, machine.Pin.OUT, value=1) # tell sensor to average data in hardware by driving UART1 RX pin on sensor high
...
# Initialise UART1
uart = UART(1, 9600)

Q3: When cheksum is being defined, the array items in [0], [1], [2] are added togther, what does “& 0xFF” do? I know from the datasheet that 0xFF is the header, so does the checksum end up being some numeric value (the product of adding the 3 array numbers) and appending 0xFF to the end, eg 12340xFF ?

checksum = (data_buff[0] + data_buff[1] + data_buff[2]) & 0xFF

Q4: I understand what the below is doing, what I dont know is where “250”, (-1) and (-2) come from. Are these in a datasheet somewhere that Ive missed, or are (-1) and (-2) micropython error traps of some kind?

        if measurement == 250:
            return(-1) # return -1 if "250" (invalid measurement) is recieved, consider adding 0 too, as it's usually not accurate
        else:
            return(measurement)
    else:
        return(-2) # if checksum fails (normally because minimum range has been exceeded, return -1)

Q5: Finally, your commentry says consider adding 0 too, as its usually not accurate. What would this look like and where would it get added?

Once again, many thanks and sorry for all the questions! Being able to see the working code and then reach out to further my own knowledge is just amazing.

Jon

2 Likes

Hi Jon,

I’m a different James but I should be able to help with this one :slight_smile:

This is likely just a hangover from the default “blink” sketch :wink:, I have a bad habit of starting my projects there too.

Good catch! Switching to UART1 is the right move, as is moving to another non-UART GPIO for driving the averaging pin high. I would steer clear of the RX pin as MicroPython obfuscates pin multiplexing, so it may behave strangely.

In python this is a bitwise AND. This means that for each binary bit of the first number, the matching bit of the second number is compared, and if both are 1, the result is 1. The effect of this is that you can “mask” the larger-than-8-bit number from your addition back down to 8 bits. A clunky way to think about this is that this number will now “wrap around” to a low number again if you kept adding to it and masking.

Ultrasonic sensors like these often have a constant that they spit out when the object is too close (someone who knows more about them can tell me why :slight_smile: ) I’d say if you got rid of the ifs, find that the sensor will spit out 250 in that condition.

As for -1 and -2, these likely arise from a habit carried over from a typed language like C, where the function is expected to return a particular type (int in the case of read_distance()) and so sending back a string is not possible Only works if you never expect to get negatives I imagine.

Hope this sorts you out!
-James

3 Likes

Hi, what is the pinout on the raspberry pi for this example?