Introduction

Freshwater Version: Suitable for freshwater environments (such as rivers, lakes, and ponds).

Seawater Version: Designed specifically for seawater or high-salinity waters (such as oceans and marine aquaculture farms), it utilizes specialized calibration and algorithms to compensate for the effects of salinity on dissolved oxygen measurement.

This is a seawater dissolved oxygen sensor that uses fluorescence to accurately measure dissolved oxygen concentration and saturation in water. Compared to traditional electrochemical sensors, this sensor calculates dissolved oxygen content by detecting the decay time of a fluorescent substance. This completely avoids issues such as electrode polarization and electrolyte depletion, significantly improving long-term stability and reliability while reducing maintenance and operating costs.

The dissolved oxygen sensor is equipped with an RS485 communication interface and supports the standard ModBus-RTU protocol, making it easily integrated into various monitoring systems or data platforms. It has a measurement range of 0-20 mg/L (dissolved oxygen concentration) and 0-200% (saturation). A built-in temperature sensor and automatic temperature compensation ensure real-time response to ambient temperature changes, ensuring accurate and reliable data output under varying conditions.

The dissolved oxygen sensor is rigorously calibrated before shipment and is ready for immediate use, requiring no recalibration. It also supports zero-oxygen and full-scale calibration via register settings, allowing users to quickly optimize measurement accuracy after extended use. The probe is made of 316 stainless steel, with an overall waterproof rating of IP68. It is corrosion-resistant and salt-alkali-resistant, and is suitable for marine aquaculture, ocean monitoring, and monitoring of estuary salt and fresh water confluence areas.

Fluorescence measurement:
Fluorescence measurement is based on the principle of fluorescence quenching. Blue light strikes a fluorescent substance, exciting it and causing it to emit red light. Because oxygen molecules can absorb this energy (a quenching effect), the duration and intensity of the excited red light are inversely proportional to the oxygen concentration. By measuring the phase difference between the excited red light and reference light and comparing it with an internal calibration value, the oxygen concentration can be calculated.

Dissolved Oxygen Saturation Concentration Temperature Comparison Table

Temperature (°C) Dissolved Oxygen (mg/L) Temperature (°C) Dissolved Oxygen (mg/L)
0 14.64 18 9.46
1 14.22 19 9.27
2 13.82 20 9.08
3 13.44 21 8.90
4 13.09 22 8.73
5 12.74 23 8.57
6 12.42 24 8.41
7 12.11 25 8.25
8 11.81 26 8.11
9 11.53 27 7.96
10 11.26 28 7.82
11 11.01 29 7.69
12 10.77 30 7.56
13 10.53 31 7.43
14 10.30 32 7.30
15 10.08 33 7.18
16 9.86 34 7.07
17 9.66 35 6.95

Features

  • Stainless steel probe, IP68 waterproof rating.
  • Fluorescence measurement principle, no oxygen consumption, no electrolyte required, reducing maintenance.
  • Built-in temperature sensor and automatic temperature compensation ensure measurement accuracy.
  • RS485 communication interface, supporting ModBus-RTU protocol.
  • Simple calibration function, supports zero and full-scale calibration.
  • Using special calibration and algorithm to compensate for salinity, it can be used in seawater or high-salinity water for a long time.

Applications

  • Mariculture
  • Ocean monitoring
  • Estuarine freshwater confluence monitoring

Specifications:

  • Power Supply: DC10-30V
  • Power Consumption: 0.2W
  • Communication Interface: RS485 (Standard ModBus-RTU Protocol)
  • Measurement Principle: Fluorescence
  • Measuring Range: 0-20mg/L, 0-200% Saturation
  • Measurement Error: ±3% FS; ±0.5°C (25°C)
  • Resolution: 0.01mg/L; 0.1%; 0.1°C
  • Response Time: ≤60s
  • Operating Temperature: 0-40°C
  • Fluorescent Film Lifespan: 1 year under normal use
  • Waterproof Rating: IP68
  • Pressure Resistance: 0.6MPa
  • Cable Length: 5m
  • Casing Material: Corrosion-Resistant Plastic

Board Overview

Num Label Description
Brown line VCC Power input positive pole, DC10-30V power supply
Black line GND Power ground wire
Yellow line 485-A RS485 data line A
Blue line 485-B RS485 data line B

Dimensional Drawing

Communication Protocol

1. Basic communication parameters

Interface Encoding Data bits Parity bits Stop bits Error checking Baud rate
RS485 8-bit binary 8 None 1 CRC 2400bit/s, 4800bit/s, 9600bit/s, 19200bit/s, 38400bit/s, 57600bit/s, 115200bit/s configurable, default 4800bit/s

2. Data frame format definition

Using Modbus-RTU communication protocol, the format is as follows:

Initial structure ≥4 bytes of time

Address code = 1 byte

Function code = 1 byte

Data area = N bytes

Error check = 16-bit CRC code

End structure ≥4 bytes of time

Address code: The address of the sensor, which is unique in the communication network (factory default 0x01).

Function code: The function indication of the command sent by the host. This sensor reads the register function code 0x03/0x04 and writes the register function code 0x06/0x10

Data area: The data area is the specific communication data. Note that the high byte of 16-bit data is in front!

CRC code: A two-byte check code.

Register address

Register Address Content Supported Function Code Definition
0000H, 0001H Dissolved Oxygen Saturation 0x03/0x04 1 = 100%; floating point number, big endian
0002H, 0003H Dissolved Oxygen Concentration 0x03/0x04 mg/L; floating point number, big endian
0004H, 0005H Temperature 0x03/0x04 °C; floating point number, big endian
1010H Calibration 0x06/0x10 Write 0x0001 to calibrate the zero point, write 0x0002 to calibrate the 100% saturation point
1020H Salinity 0x03/0x04/0x06/0x10 %; Seawater version defaults to 30%
1022H Atmospheric pressure 0x03/0x04/0x06/0x10 kPa; Default is 101.33, 100 times the actual value
07D0H Device address 0x03/0x04/0x06/0x10 1 to 254 (factory default 1)
07D1H Device baud rate 0x03/0x04/0x06/0x10 0 represents 2400, 1 represents 4800, 2 represents 9600, 3 represents 19200, 4 represents 38400, 5 represents 57600, and 6 represents 115200

3. Communication Protocol Example and Explanation

3.1. Reading Dissolved Oxygen Saturation (%), Dissolved Oxygen Concentration (mg/L), and Temperature

Inquiry Frame:

Address Code Function Code Register Starting Address Register Length Check Code Low Bit Check Code High Bit
0x01 0x03 0x00 0x00 0x00 0x06 0xC5 0xC8

Response Frame:

Address Code Function Code Return Valid Bytes Dissolved oxygen concentration, saturation, and temperature values Value Check Code Low Bit Check Code High Bit
0x01 0x03 0x0C 0x3F 0x6A 0xEB 0x52 0x40 0xE2 0x48 0xB0 0x41 0xE5 0x85 0xC5 0xA7 0x49

Floating point number big-endian 3F 6A EB 52 is 0.917653, dissolved oxygen saturation is 91.8%
Floating point number big-endian 40 E2 48 B0 is 7.071373, dissolved oxygen concentration is 7.07 mg/L
Floating point number big-endian 41 E5 85 C5 is 28.690317, temperature is 28.7°C

3.2. Write atmospheric pressure 101.35 kPa

Query frame:

Address code Function code Register address Modified value Checksum low bit Check Code High Bit
0x01 0x06 0x10 0x22 0x27 0x97 0x77 0x5E

Response Frame:

Address Code Function Code Register Address Modified Value Check Code Low Bit Check Code High Bit
0x01 0x06 0x10 0x22 0x27 0x97 0x77 0x5E

3.3. Zero Calibration

Inquiry Frame: After the sensor's value stabilizes in oxygen-free water, write to register 1010H. 0x0001

Address Code Function Code Register Address Modified Value Check Code Low Bit Check Code High Bit
0x01 0x06 0x10 0x10 0x00 0x01 0x4D 0x0F

Response Frame:

Address Code Function Code Register Address Modified Value Check Code Low Bit Check Code High Bit
0x01 0x06 0x10 0x10 0x00 0x01 0x4D 0x0F

3.4. Change the current address to 0x02

Inquiry frame:

Address code Function code Register address Modified value Check code low bit Check code high bit
0x01 0x06 0x07 0xD0 0x00 0x02 0x08 0x86

Response frame:

Address code Function code Register address Modified value Check code low bit Check code high bit
0x01 0x06 0x07 0xD0 0x00 0x02 0x08 0x86

3.5. Change the current baud rate to 9600

Inquiry frame:

Address code Function code Register address Modify value Check code low bit Check code high bit
0x01 0x06 0x07 0xD1 0x00 0x02 0x59 0x46

Response frame:

Address code Function code Register address Modify value Check code low bit Check code high bit
0x01 0x06 0x07 0xD1 0x00 0x02 0x59 0x46

Tutorial

Requirements

Connection Diagram

If the power of the RS485 device is small and the required current is less than 12V-160mA, the RS485 to UART signal conversion module does not require a 12V external power supply, making wiring more convenient.

Sample Code

#include <SoftwareSerial.h>
SoftwareSerial mySerial(2, 3);                                         //TX,RX
uint8_t Com[8] = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x06, 0xC5, 0xC8 };   
uint8_t Com1[8] = { 0x01, 0x03, 0x10, 0x22, 0x00, 0x01, 0x20, 0xC0 };  
uint8_t Com2[8] = { 0x01, 0x03, 0x10, 0x20, 0x00, 0x01, 0x81, 0x00 };  
float sal, ap;

void setup() {
  Serial.begin(9600);
  mySerial.begin(4800);
}
void loop() {
  readDO_DOS_TEM();
  Atmospheric_pressure();
  Serial.print("AP = ");
  Serial.print(ap, 2);
  Serial.print("Kpa  ");
  Salinity();
  Serial.print("Salinityt = ");
  Serial.print(sal,0);
  Serial.println("%  ");
  Serial.println("   ");
  delay(1000);
}

void readDO_DOS_TEM(void) {
  uint32_t val = 0, val1 = 0, val2 = 0;
  uint8_t Data[18] = { 0 };
  uint8_t ch = 0;
  bool flag = 1;
  long timeStart = millis();
  long timeStart1 = 0;
  while (flag) {

    if ((millis() - timeStart1) > 100) {
      while (mySerial.available() > 0) {
        mySerial.read();
      }
      mySerial.write(Com, 8);
      timeStart1 = millis();
    }

    if ((millis() - timeStart) > 1000) {
      Serial.println("Time out");
      return -1;
    }

    if (readN(&ch, 1) == 1) {
      if (ch == 0x01) {
        Data[0] = ch;
        if (readN(&ch, 1) == 1) {
          if (ch == 0x03) {
            Data[1] = ch;
            if (readN(&ch, 1) == 1) {
              if (ch == 0x0C) {
                Data[2] = ch;
                if (readN(&Data[3], 14) == 14) {
                  if (CRC16_2(Data, 15) == (Data[15] * 256 + Data[16])) {
                    val = Data[3];
                    val = (val << 8) | Data[4];
                    val = (val << 8) | Data[5];
                    val = (val << 8) | Data[6];
                    float *dos = (float *)&val;
                    float Dos = *dos * 100.00;
                    Serial.print("DO Sat = ");
                    Serial.print(Dos, 1);
                    Serial.print("%  ");
                    val1 = Data[7];
                    val1 = (val1 << 8) | Data[8];
                    val1 = (val1 << 8) | Data[9];
                    val1 = (val1 << 8) | Data[10];
                    float *Do = (float *)&val1;
                    Serial.print("DO = ");
                    Serial.print(*Do, 2);
                    Serial.print(" mg/L  ");
                    val2 = Data[11];
                    val2 = (val2 << 8) | Data[12];
                    val2 = (val2 << 8) | Data[13];
                    val2 = (val2 << 8) | Data[14];
                    float *tem = (float *)&val2;
                    Serial.print("TEM = ");
                    Serial.print(*tem, 1);
                    Serial.println("°C");
                    flag = 0;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

void Atmospheric_pressure(void) {
  uint8_t Data[12] = { 0 };
  uint8_t ch = 0;
  bool flag = 1;
  long timeStart = millis();
  long timeStart1 = 0;
  while (flag) {

    if ((millis() - timeStart1) > 100) {
      while (mySerial.available() > 0) {
        mySerial.read();
      }
      mySerial.write(Com1, 8);
      timeStart1 = millis();
    }

    if ((millis() - timeStart) > 1000) {
      Serial.println("Time out2");
      return -1;
    }

    if (readN(&ch, 1) == 1) {
      if (ch == 0x01) {
        Data[0] = ch;
        if (readN(&ch, 1) == 1) {
          if (ch == 0x03) {
            Data[1] = ch;
            if (readN(&ch, 1) == 1) {
              if (ch == 0x02) {
                Data[2] = ch;
                if (readN(&Data[3], 4) == 4) {
                  if (CRC16_2(Data, 5) == (Data[5] * 256 + Data[6])) {
                    ap = (Data[3] * 256 + Data[4]) / 100.0;
                    flag = 0;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

void Salinity(void) {
  uint8_t Data[12] = { 0 };
  uint8_t ch = 0;
  bool flag = 1;
  long timeStart = millis();
  long timeStart1 = 0;
  while (flag) {

    if ((millis() - timeStart1) > 100) {
      while (mySerial.available() > 0) {
        mySerial.read();
      }
      mySerial.write(Com2, 8);
      timeStart1 = millis();
    }

    if ((millis() - timeStart) > 1000) {
      Serial.println("Time out3");
      return -1;
    }

    if (readN(&ch, 1) == 1) {
      if (ch == 0x01) {
        Data[0] = ch;
        if (readN(&ch, 1) == 1) {
          if (ch == 0x03) {
            Data[1] = ch;
            if (readN(&ch, 1) == 1) {
              if (ch == 0x02) {
                Data[2] = ch;
                if (readN(&Data[3], 4) == 4) {
                  if (CRC16_2(Data, 5) == (Data[5] * 256 + Data[6])) {
                    sal = Data[3] * 256 + Data[4];
                    flag = 0;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

uint8_t readN(uint8_t *buf, size_t len) {
  size_t offset = 0, left = len;
  int16_t Tineout = 500;
  uint8_t *buffer = buf;
  long curr = millis();
  while (left) {
    if (mySerial.available()) {
      buffer[offset] = mySerial.read();
      offset++;
      left--;
    }
    if (millis() - curr > Tineout) {
      break;
    }
  }
  return offset;
}

unsigned int CRC16_2(unsigned char *buf, int len) {
  unsigned int crc = 0xFFFF;
  for (int pos = 0; pos < len; pos++) {
    crc ^= (unsigned int)buf[pos];
    for (int i = 8; i != 0; i--) {
      if ((crc & 0x0001) != 0) {
        crc >>= 1;
        crc ^= 0xA001;
      } else {
        crc >>= 1;
      }
    }
  }

  crc = ((crc & 0x00ff) << 8) | ((crc & 0xff00) >> 8);
  return crc;
}

Expected Results

Calibration Instructions

1. Calibration Preparation

Zero Oxygen Standard Solution Preparation: In a windless, sun-proof location, take enough pure water (distilled or deionized) to cover the stainless steel parts of the device. Add 5g of anhydrous sodium sulfite to every 95g of water and stir thoroughly to dissolve to create a 5% sodium sulfite solution. Let stand for 1 hour to ensure that the oxygen in the solution is completely consumed. If possible, add a trace amount of cobalt chloride as a catalyst.

100% Oxygen Environment Preparation: In a windless, sun-proof location, take enough pure water (distilled or deionized) to cover the stainless steel parts of the device. Using an air pump, continuously introduce air into the water for 1 hour. Then, let it stand for 30 minutes to create air-saturated water. Alternatively, in a windless, sun-proof location, take a small amount of water in a sealed container and shake vigorously for 30 seconds to create water-saturated air. During calibration, keep the device's fluorescent film approximately 1cm above the water surface to ensure it is moist but free of water droplets.

2. Start Calibration

Place the sensor in a 100% oxygen environment. After the dissolved oxygen value stabilizes, send the 100% saturation calibration command to complete the 100% oxygen saturation calibration.
Then place the sensor in zero oxygen standard solution, gently stir to accelerate the response, and let it sit. After the dissolved oxygen value stabilizes, send the zero oxygen calibration command to complete the zero oxygen calibration.
Note: The sensor should be allowed to sit for a sufficient period of time in each calibration environment to ensure that the sensor temperature is consistent with the ambient temperature.

Installation

The sensor should be installed below the liquid surface. Avoid bumping or scratching the fluorescent membrane during installation and use. Keep the fluorescent membrane away from sediment. Remove the rubber protective cover before use.

Precautions and Maintenance

◆ If the device exhibits obvious malfunctions, do not open it and attempt to repair it yourself. Contact us immediately!
◆ Avoid bumping or scratching the fluorescent membrane on the front of the device. Any damage will reduce measurement accuracy or even render it unusable. ◆ Avoid using organic solvents and avoid using organic solvents to clean the fluorescent cap.
◆ When installing the device, avoid excessive tension or stress on the cables.
◆ Device Cleaning:
The exterior surface of the device can be cleaned with tap water and a damp soft cloth. Stubborn dirt can be removed by adding some household detergent to the tap water.
For the exterior surface of the fluorescent cap, rinse any dirt from the sensor's optical window with clean water. If wiping is necessary, wipe gently with a soft cloth and avoid scratching vigorously to prevent damage to the fluorescent film, which can lead to inaccurate measurements or inability to measure. If dust or moisture enters the fluorescent cap, remove the cap and rinse the interior surface and the device's optical glass window with tap water. If there is grease or oil, rinse with tap water mixed with household detergent. Rinse off the detergent and wipe all cleaned surfaces dry with a lint-free soft cloth. Place in a dry place to allow the moisture to evaporate completely. Cleaning every 30 days is recommended.
◆ Before measuring, remove the black rubber protective cover.
◆ During long-term storage, add water to the sponge in the protective case and squeeze out any excess water to keep the phosphor film moist. Keep the temperature above 0°C to prevent freezing and damage. For dry storage, the lowest temperature can be -10°C. Before use, soak the device in water for 48 hours to restore the phosphor film's response.
◆ It is recommended to replace the phosphor film annually.
◆ Calibrate the device before each measurement. For long-term use, calibrate every three months. The calibration frequency should be adjusted based on application conditions (such as the degree of dirt and chemical deposits).

FAQ

  1. The computer has multiple COM ports, and the selected port is incorrect.
  2. The device address is wrong, or there are devices with duplicate addresses (all factory defaults are 1).
  3. Baud rate, check mode, data bit, stop bit error.
  4. Host polling interval and waiting response time are too short, both need to be set above 200ms.
  5. 485 bus is disconnected, or A and B lines are connected in reverse.
  6. Too many devices or too long wiring, power supply should be provided nearby, 485 enhancer should be added, and 120Ω terminal resistance should be added.
  7. USB to 485 driver is not installed or damaged.
  8. Equipment is damaged.

Note: To ensure the accuracy of the equipment, please clean the lower surface of the measuring area of ​​the equipment regularly to keep it clean and free of dust or other foreign matter.

For any questions, advice or cool ideas to share, please visit the DFRobot Forum.

More Documents

Buy Link