The A02YYUW Ultrasonic Sensor

A02YYUW is a waterproof ultrasonic sensor module with 4.5m effective ranging distance. It can be used with 3.3 - 5V microcontrollers like Arduino, ESP8266, Raspberry.

The sensor sends data permanently via Serial interface (with TTL voltage). One advantage in comparison with the cheaper ultrasonic sensor is, that the A02YYUW will work autonomously and non blocking. You just have to read the Serial data.

This page not only shows how to read this kind of ultrasonic sensor, but how to read fix size messages in general.

A02YYUW Ultransonic Protocol P1 Basics

The A02YYUW protocol is an unencrypted protocol. Each line begins with a start flag 0xFF, followed by two bytes of playload and one byte as checksum. In total 4 bytes are transmitted in each message. There is no dedicated end marker.

This is an example of a value of message for 1697mm:

FF 07 A1 A7

Member Functions of the A02YYUW class

I decided to implement the A02YYUW sensor as class with following public member functions:

.getDistance() returns the last valid result of distance measurement. The unit is millimeter and restricted to a maximum of 4500mm.

.update() call this method in loop(). It will read available data on the Serial interface and update the distance value accordingly.

Additionally you can define a callback function in your sketch. This callback function will be called, when the transmission of new Data was completed.
.setOnNewData(callbackOnNewData) will inform the class to call "callbackOnNewData() whenever the data was updated completely. Obviously you must define the function callbackOnNewData() in your usersketch. The A02YYUW sends very often new data (100ms ?), so be prepared that a callback function will be called multiple times per second.

Parsing of Data - Some words to the Strategy

Like in most of my Serial interface examples I'm following the tutorial "Serial Input Basics" as it gives a nice start for a reliable communication.

As explained each message starts with 0xFF in byte[0]. Unfortunately you can't use 0xFF as the only start indicator, because 0xFF could also be used in the payload or the checksum. Therefore we will need some special measures:

The update function reads incoming bytes. if the incoming byte is 0xFF the byte will be stored and the index (ndx) will be incremented . Consecutive data will be added to the recveive buffer (receiveChars).

If we have collected 4 bytes parseLine() will be called.

parseLine() calls validateChecksum to verify if the received checksum is correct.

If the checksum is correct the data will be parsed.

If the data is correct  (only measurements up to 4500mm are accepted) and a callback function was defined the callback will be called.

In any case the current buffer will be deleted and the index (ndx) resetted to 0.

The microcontroll is ready to receive a new message from the ultrasonic sensor.

The Example Sketch

/*
   A02YYUW Ultasonic Sensor
   https://wiki.dfrobot.com/_A02YYUW_Waterproof_Ultrasonic_Sensor_SKU_SEN0311

   Serial Receive Example for a fix length telegram with start flag

   Valid telegram
   FF 07 A1 A7
   FF            start flag / start marker
      07 A1      data HI nibble, data LO nibble, result is distance in mm
            A7   check sum

   based on a discussion in: https://forum.arduino.cc/t/sen0311-uber-serielle-softwareschnittstelle-einlesen/951260

   by noiasca
   2022-01-26
*/

class A02YYUW {
  protected:
    static const byte numChars = 4;    // receiver buffer size
    char receivedChars[numChars];      // an array to store the received data
    byte ndx = 0;                      // length of received message
    boolean newData = false;           // flag to indicate when new data is complete
    Stream &stream;                    // a reference to the serial interface
    void (*cbOnNewData)();             // gets called after we received a full message
    // payload data                    
    int distance = 0;                  // distance in mm

    void delBuffer()
    {
      memcpy(receivedChars, "", sizeof(receivedChars));
      ndx = 0;
    }

    void parseData()                   // parse data and if according to specification store to internal variable
    {
      int result = (receivedChars[1] << 8) + receivedChars[2];
      if (result <= 4500)
        distance = result;
    }

    bool validateChecksum()            // validate if checksum of telegram is correct
    {
      int sum = (receivedChars[0] + receivedChars[1] + receivedChars[2]) & 0xFF;
      if (sum == (receivedChars[3] & 0xFF))
        return true;
      else
      {
        //Serial.println(F("E49: invalid checksum"));
        return false;
      }
    }

    void parseLine()                   // process a telegram
    {
      //Serial.print(F("I56: This just in ... ")); Serial.println(receivedChars);         // output all
      if (validateChecksum())
      {
        parseData();
        if (cbOnNewData) cbOnNewData();
      }
    }

  public:
    A02YYUW (Stream &stream) : stream(stream)
    {
      delBuffer();
    }

    uint16_t getDistance()             // return the last valid result
    {
      return distance;
    }

    void setOnNewData(void (*cbOnNewData)())     // set a callback function. Gets called when new data was received
    {                                            
      (*this).cbOnNewData = cbOnNewData;         
    }                                            
                                                 
    void update()                                // run this member function in loop()
    { // start flag, fix length
      constexpr char startMarker = 0xFF;
      if (stream.available() > 0) {
        char rc = stream.read();
        if (ndx == 0)
        {
          if (rc == startMarker)       // we only accept the startMarker on ndx=0
          {
            delBuffer();
            receivedChars[ndx] = rc;
            ndx++;
          }
        }
        else                           // for all other ndx positions we accept any value (including the startMarker
        {
          receivedChars[ndx] = rc;
          ndx++;
          if (ndx >= numChars) {
            ndx = 0;
            parseLine();
          }
        }
      }
    }
};

#include 
SoftwareSerial mySerial(2, 3);          // RX, TX

// create the sensor object and hand over the Serial interface to use:
A02YYUW ultrasonic(mySerial);           // on an UNO you will need SoftSerial to connect to the ultrasonic sensor
//A02YYUW ultrasonic(Serial);           // use Hardware Serial (for example for testing with the PC)
                                        
void output()                           // simple output of data
{
  Serial.print(ultrasonic.getDistance()); 
  Serial.println("mm");
}

void timerOutput()                     // a timer to output data to serial
{
  static uint32_t previousMillis = 0;  // time management
  const uint16_t interval = 3000;      // interval to display data
  if (millis() - previousMillis >= interval)
  {
    previousMillis = millis();
    output();
  }
}

void setup() {
  Serial.begin(57600);
  mySerial.begin(9600);
  //ultrasonic.setOnNewData(output);  // register a callback function which gets called when a new telegram was received
}

void loop() {
  ultrasonic.update();                // you must call the .update() method in your loop()
  timerOutput();
}

How to Test

In the code you will see some (deactivated) debug messages. You can activate them if you encounter a problem with the communication with your sensor.

Disclaimer

This example sketch has prototype status. Don't expect a perfect example - but in my opinion it should work very reliable (at least as good as the example code provided  by DFRobot).

Links

(*) Disclosure: Some of the links above are affiliate links, meaning, at no additional cost to you I will earn a (little) comission if you click through and make a purchase. I only recommend products I own myself and I'm convinced they are useful for other makers.

History

First upload: 2022-01-26 | Version: 2022-04-02