Trouble Using a Winsen ZE25-O3 Sensor and UART

Hi all,

I am helping out a colleague with a project using Winsen ZE25-O3 Ozone sensor (data sheet: https://www.winsen-sensor.com/d/files/air-quality/ze25-o3-ozone-module--manual1_1.pdf), and I keep running into some trouble with the UART communication. I would appreciate any help that you can give, and if anyone has any experience with Winsen sensors, I would appreciate your wisdom, as well.

For the details, there is no library currently available for this sensor (although if I am able to get this working, I will try to add it as a library), but there were some examples for Arduino (like this one: https://forum.arduino.cc/index.php?topic=630111.0). I tried piecing information from that and the datasheet together to write my code. I had initially written this as a library, but after running into several issues with not receiving the expected return value back from the sensor, I simplified things to the following code:

SYSTEM_MODE(MANUAL); // this project is using a Particle Argon, but temporarily WiFi is unreliable. 

// number of bytes for UART communication
#define buffer_length 9

// commands from the datasheet. 
const byte active_upload_off[] = {0xFF, 0x01, 0x78, 0x41, 0x00, 0x00, 0x00, 0x00, 0x46};
const byte active_upload_on[] = {0xFF, 0x01, 0x78, 0x40, 0x00, 0x00, 0x00, 0x00, 0x47};
const byte qa_read_concentration[] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};

void setup() {
    // for communication with computer
    Serial.begin(9600);
    
    // opening UART Serial for the Winzen ZE25-O3
    Serial1.begin(9600, SERIAL_8N1);
    
    // setting the sensor in active upload
    Serial1.write(active_upload_on, buffer_length);
}

void loop() {
    // initializing char buffer to receive information from the sensor
    char raw_buff[buffer_length];
    
    // Waiting for a response from the Serial
    while(!Serial.read()){

    }
    
    // reading the data
    Serial1.readBytes(raw_buff, buffer_length);
    
    // printing raw
    Serial.print("Character Packet: ");
    
    for(int ii = 0; ii < buffer_length; ii++){
        Serial.print(int(raw_buff[ii]), HEX);
        Serial.print(" ");
    }
    Serial.println("\n");
}

With this particular piece of firmware, I get the following packet as a response {0x8B, 0x40, 0x03, 0x00, 0x80, 0x6C, 0x00, 0x20, 0x00}. This does not match up to the expected response based on the data sheet. The data sheet lists two “return command for reading gas concentration” {0xFF, 0x86, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x30} and {0xFF, 0x2A, 0x04, 0x00, 0x00, 0x25, 0x27, 0x10, 0x75}. In the first one bytes 2 and 3 correspond to the ozone concentration, and in the second one bytes 4 and 5 correspond to the ozone concentration. I had assumed that the first one corresponds to question and answer mode, and the other corresponds to active upload mode. I am not 100% sure about that; the datasheet was a bit sparse. If anyone else has other suggestions of how to interpret those, I am all ears! The response I am getting back does not match either of them though.

I checked a few other things. There seems to be no difference in response to active upload or question and answer mode. The data sheet says that it is in active upload mode by default, so I have tried including and removing “Serial1.write(active_upload_on, buffer_length);”, and that does not seem to make a difference either. In the data sheet, it says that it is a has a baud rate of 9600, and the packets are 8 bytes with 1 stop bit and Null for stop bits. As far as I understand from the Argon documentation, that would mean that in Serial1.begin, I should have 9600 first and SERIAL_8N1 second. I have tried a few other things like SERIAL_9N1 and using a buffer length of 8. Any of those permutation gives a slightly different packet response, but not the expected one. This may just be a decoding issue, and I am not the most familiar with UART. I would appreciate any help or advice. Thank you!!

The first thing to note

This won’t do what you expect since Serial.read() will return (int)-1 when there is no data to read, so that loop will will virtually never spin.

You should also catch the return value of Serial1.readBytes() to check whether you actually got all the data you expected.

It appears the lead-in for each packet is 0xFF and when the sensor is actually defaulting to active upload you may be seeing some residual data in the RX buffer you don’t want to be read into your buffer.
Try syncing on the 0xFF byte before reading into your buffer.

Are you sure? I guess this shoule be 1 stop bit, no parity (which is also the default when not stating SERIAL_8N1).

Thank you for the advice on Serial.read()! I will edit that. Also, my bad on the type-o there. The data sheet says 8 bytes with 1 stop byte (not bit, sorry) and null for check bits (not stop bits, again sorry). I thought I read over the entire thing before hitting post, but I missed a few things.

Do you mind if I just confirm that I understand what you are saying with regard to the residual data on the RX buffer? If I am understanding you correctly, there is some currently unknown number of bytes being received through the RX pin of the Argon. Of that set of bytes, 9 of them, starting with 0xFF will be the packet of information. I am not feeling super confident about syncing off the 0xFF byte, so do you mind checking over what I have in mind? An approach that would seem reasonable to me is to make my buffer length arbitrarily long (say 100 bytes), and try to read into that. Then once I have the longer buffer, I can scan it for 0xFF to indicate the start of a packet. Then the subsequent 9 bytes would be my packet. In code, this might look something like:

SYSTEM_MODE(MANUAL); 

#define buffer_length 100
#define packet_length 9

const byte active_upload_off[] = {0xFF, 0x01, 0x78, 0x41, 0x00, 0x00, 0x00, 0x00, 0x46};
const byte active_upload_on[] = {0xFF, 0x01, 0x78, 0x40, 0x00, 0x00, 0x00, 0x00, 0x47};
const byte qa_read_concentration[] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};

void setup() {
    Serial.begin(9600);
    
    // opening UART Serial for the Winzen ZE25-O3
    Serial1.begin(9600, SERIAL_8N1);
    
    // setting the sensor in active upload
    Serial1.write(active_upload_on, buffer_length);
}

void loop() {
    // initializing 
    char raw_buff[buffer_length];
    char packet_buff[packet_length];
    
    // wait if nothing is coming through on RX
    while(Serial1.read() == -1){
        
    }
    
    // reading the data
    Serial1.readBytes(raw_buff, buffer_length);

    // finding the packet in the raw buff
    for(int ii = 0; ii < buffer_length; ii ++){
        if(int(raw_buff[ii]) == 255){
            packet_buff = raw_buff[ii: (ii + packet_length  - 1)];
            break;
        }
    }
    
    // printing raw
    Serial.print("Character Packet: ");
    
    for(int ii = 0; ii < packet_length; ii++){
        Serial.print(int(packet_buff [ii]), HEX);
        Serial.print(" ");
    }
    Serial.println("\n");
}

I am unsure if this will cause a problem with SERIAL_8N1 in Serial1.begin(). Is that what you are suggesting, or is there a better way of approaching this problem? Thank you so much!

I’m not saying that this is caused by the Argon, but when your sensor just starts publishing data as soon it’s powered on, then you may see some packets already arriving in the RX buffer before you are ready to deal with them. If the cadence of the packets is high enough this may even mean that you may have filled the RX buffer beyond its capacity resulting in partly overwritten packets in there.
Hence, once you are ready to deal with the fresh coming data you should flush out the entire RX buffer and then start with a known to be good set of packets therein.

How so?

Since this is the default, you can leave it out entirely.

See here

I’ve written proprietary firmware with the ZE02 oxygen sensor working for an altitude detection use case.

At a high level: serial read in the main loop (int inByte = Serial1.read()). Watch for FF and 86 and then you know the high byte and low bytes are next. Save them, convert to decimal. It’s that easy.

Give me a message with some information on your project so we can check it’s not in conflict with ours and can talk more.