Ticker

6/recent/ticker-posts

Hydrogen Sulfide sensing with Arduino Due

It’s almost two years since I was forced to relocate my office. The good thing is that now I work on in quiet location and I have to drive only 15 minutes from my home. Unfortunately, there’s a bad thing, too. During the hot summer days, a nasty smell originating from a garbage dump located some 3km away lingers in the air. It seems that the rotten egg smell is typical to hydrogen sulfide (H2S). I’m not the only one bothered by the smell; there were a lot of complaints this summer. The local environment authorities have performed some measurements and found that none of the values are above the legal limits. Still, there’s the smelly issue…

As hydrogen sulfide is bad for health, even in concentrations as small as ten parts per million, I wanted to build my monitoring station, to know exactly what’s in the air I breathe. Easier said than done!

The biggest issue was finding a reliable H2S sensor.

There’s the cheap MQ-136 H2S sensor, which goes below 30$. The problem is, that sensor has an analog output. As such, I have to read it with an ADC, and it’s extremely difficult to calibrate without access to specialized lab equipment.

The MQ-136 sensor is also sensitive to other gases, such as carbon monoxide, sulfur dioxide or methane. So, do I have a high level of hydrogen sulfide, or a high level of carbon monoxide, or what? It’s quite difficult to get an accurate reading from this sensor.

Second, there are dedicated H2S sensors, calibrated, reliable, but they cost a fortune. I’m not going to pay 2000$ for such a sensor, considering that it will go into a small project.

And then, recently, I stumbled upon the website of Spec Sensors, a company specialized manufacturing sensors for gas sensing application. What caught my attention is their line of digital gas sensors for IoT. Besides the hydrogen sulfide sensor I was looking for, one can find dedicated sensors for carbon monoxide, ozone, sulfur dioxide, nitrogen dioxide, breath alcohol, and other respiratory irritants. And the best thing is that, with a price tag of 75$, these sensors are quite cheap.

As a side note, I ordered mine from digikey.com, as I got zero transport cost. All I had to do is pay the VAT to the local customs office.

The DGS-H2S 968-036 digital hydrogen sulfide sensor comprises the sensor itself, the digital breakout board and one USB-UART adapter (not pictured here). I haven’t used the UAB-UART adapter at all; it could have been omitted from the package at all to save a few bucks.

The sensor itself doesn’t say much. From its datasheet I find that it’s an amperometric gas sensor, that is an electrochemical sensor which generates a current proportional to the volumetric fraction of the gas. More information can be found in the technical documents. The bottom line is, this sensor needs a complicated analog front-end, something difficult to design and build by the average maker. And here comes into play the digital breakout.

The analog front end is implemented using one LMP91000, a programmable AFE potentiostat designed for use in use in micro-power electrochemical sensing applications. This little thing supports gas sensitivities over a range of 0.5 nA/ppm and up to 9500 nA/ppm. Communication with the microcontroller is done via I2C, plus one analog line for the sensor readings.

The microcontroller used is a PIC24F16KM202, featuring a 12-bit A/D converter with threshold detect, which takes care of all the computations and sends data to the host application via UART interface (3.3V logic levels).

There’s also one IC that looks to me like a Si7021-A20 I2C temperature and humidity sensor. On the top side, there’s an IC marked “SPH3” which I haven’t been able to identify.

Anyway, that’s not even important; all one has to know is the communication protocol.

Performance and characteristics

This little hydrogen sulfide sensor has some pretty impressive specifications.

  • The measurement range is from 0 to 10ppm, with a resolution of 10 ppb (parts per billion).
  • Measurement accuracy is 15% of the reading
  • Measurement repeatability is below ±3% of reading
  • Very important, it has a low cross-sensitivity to other gases:
    • 10ppm chlorine will be registered as -2.2ppm H2S
    • 10ppm NO2 will be registered as -2.0ppm H2S
    • 20ppm SO2 will be registered as 1.7ppm H2S
    • 50ppm NO will be registered as 1.2ppm H2S
    • 400ppm CO will be registered as 1.1ppm H2S
    • Other gases such as ozone (5ppm), methane (500ppm), ammonia (100ppm) will alter the H2S readings by less than 1ppm.
  • The sensor is designed to be powered from 3.3V, with a range of 2.6 to 3.3V power supply voltage
  • Power consumption is 1mW for 1 minute triggered samples and 12mW for continuous operation.
  • Its environmental characteristics allow it to operate outside
    • Temperature Range is 20 to 40 C (-30 to 55 C intermittent)
    • Humidity Range is 15 to 95% (0 to 100% non – condensing intermittent)
    • Sensor lifespan is over 5 years.

Arduino code for the H2S sensor

Here comes the fun part: using the sensor with an Arduino board. I used one Arduino Due, as that board works with 3.3V logic levels. The on;y thing to keep in mind is that the Due is a 32-bit board, and as such the int variable is 32 bit (ranges from -2,147,483,648 to 2,147,483,647). On 8-bit boards some readings will have to be recorded as floats.

Considering the sensor connected to UART1, UART0 being used by the serial monitor, the code I propose performs an EEPROM dump, showing all the settings of the sensor, and then triggers a measurement every 10 seconds. The results are sent to the PC using the Serial Monitor in Arduino IDE.

NOTE: The code below is for sensor firmware 14FEB17.
For newer firmware versions see this blog post.


// SPEC H2S Sensor demo code
// This code works with sensor firmware 14FEB17
// Serial number of my sensor
// #define mysensor_serial_no 012017030207
// #define SERIAL1_RX_BUFFER_SIZE 256
// #define SERIAL1_TX_BUFFER_SIZE 256
// Sensor values  
// The format of the output is: SN[XXXXXXXXXXXX], PPB [0 : 999999], TEMP [-99:99],  
// RH[0:99], RawSensor[ADCCount], TempDigital, RHDigital, Day[0:99], Hour [0:23]  
// Note that on Arduino Due integer variable (int)  stores a 32-bit (4-byte) value.   
// This yields a range of -2,147,483,648 to 2,147,483,647   
// (minimum value of -2^31 and a maximum value of (2^31) - 1).   
// On 8 bit boards some readings have to be recorded as floats
String SensorSerialNo; 
int H2S;
int Temperature;
int RH;
int RawSensor;
int TempDigital;
int RHDigital;
int Days;
int Hours;
int Minutes;
int Seconds;

#define command_delay 500
#define start_delay 2500

String dataString = "";
String responseString = "";
boolean dataStringComplete = 0;
char inChar;

void setup() {  
    Serial.begin(9600);  
    Serial.println("H2S sensor demo code!");  
    Serial1.begin(9600);  
    // Normally, data is returned within one second  
    Serial1.setTimeout(1500);  
    // reserve 80 bytes for the dataString  
    dataString.reserve(80);  
    responseString.reserve(150);    
    // Wait for sensor   
    delay(500);  
    flush_serial1();    
    // EEPROM dump  
    SPEC_dump_EEPROM();  
    Serial.println(" ");  
    Serial.println("STARTING MEASUREMENTS");  
    Serial.println(" ");
}  

void loop() {  
    // Do a readout every 10 seconds  
    SPEC_Data_read();  
    SPEC_parse_data();  
    SPEC_print_data();  
    delay(10000);
}

/* ******************************************************************************** 
* This function triggers one measurement and receives the data from the sensor 
**********************************************************************************/
void SPEC_Data_read() {  
    // First, we do some initialization  
    // dataStringComplete is set as "false", as we don't have any valid data received  
    dataStringComplete = 0;  
    // Clear the data string  
    dataString = "";  
    // Now we trigger a measurement  
    Serial1.print(" ");  
    // We wait for the sensor to respond  
    dataString = Serial1.readStringUntil('\n');  
    //Serial.println(dataString);
}

/* ******************************************************************************** 
* This function takes the received string and upodates sensor data 
**********************************************************************************/
void SPEC_parse_data() {  
    // Parses the received dataString  
    // Data string is comma separated  
    // The format of the output is: SN[XXXXXXXXXXXX], PPB [0 : 999999], TEMP [-99:99],  
    // RH[0:99], RawSensor[ADCCount], TempDigital, RHDigital, Day[0:99], Hour [0:23],  
    // Minute[0:59], Second[0 : 59]\r\n  
    // Take a look also at  
    // https://stackoverflow.com/questions/11068450/arduino-c-language-parsing-string-with-delimiter-input-through-serial-interfa  
    // We look first for the SN  
    int idx1 = dataString.indexOf(',');  
    SensorSerialNo = dataString.substring(0, idx1);  
    int idx2 = dataString.indexOf(',', idx1 + 1);  
    // Hint: after comma there's a space - it should be ignored  
    String S_gas = dataString.substring(idx1 + 2, idx2

);  
    H2S = S_gas.toInt();  
    int idx3 = dataString.indexOf(',', idx2 + 1);  
    String S_temp = dataString.substring(idx2 + 2, idx3);  
    Temperature = S_temp.toInt();  
    int idx4 = dataString.indexOf(',', idx3 + 1);  
    String S_humi = dataString.substring(idx3 + 2, idx4);  
    RH = S_humi.toInt();  
    int idx5 = dataString.indexOf(',', idx4 + 1);  
    String S_raw_gas = dataString.substring(idx4 + 2, idx5);  
    RawSensor = S_raw_gas.toInt();  
    int idx6 = dataString.indexOf(',', idx5 + 1);  
    String S_Tdigital = dataString.substring(idx5 + 2, idx6);  
    TempDigital = S_Tdigital.toInt();  
    int idx7 = dataString.indexOf(',', idx6 + 1);  
    String S_RHdigital = dataString.substring(idx6 + 2, idx7);  
    RHDigital = S_RHdigital.toInt();  
    int idx8 = dataString.indexOf(',', idx7 + 1);  
    String S_Days = dataString.substring(idx7 + 2, idx8);  
    Days = S_Days.toInt();  
    int idx9 = dataString.indexOf(',', idx8 + 1);  
    String S_Hours = dataString.substring(idx8 + 2, idx9);  
    Hours = S_Hours.toInt();  
    int idx10 = dataString.indexOf(',', idx9 + 1);  
    String S_Minutes = dataString.substring(idx9 + 2, idx10);  
    Minutes = S_Minutes.toInt();  
    int idx11 = dataString.indexOf('\r');  
    String S_Seconds = dataString.substring(idx10 + 2, idx11);  
    Seconds = S_Seconds.toInt();
}

/* ******************************************************************************** 
* This function prints the sensor data 
**********************************************************************************/
void SPEC_print_data() {  
    Serial.println("********************************************************************");  
    Serial.print("Sensor Serial No. is ");  
    Serial.println(SensorSerialNo);  
    Serial.print("H2S level is ");  
    Serial.print(H2S);  
    Serial.println(" ppb");  
    Serial.print("Temperature is ");  
    Serial.print(Temperature, DEC);  
    Serial.println(" deg C");  
    Serial.print("Humidity is ");  
    Serial.print(RH, DEC);  
    Serial.println("% RH");  
    Serial.print("Sensor is online since: ");  
    Serial.print(Days, DEC);  
    Serial.print(" days, ");  
    Serial.print(Hours, DEC);  
    Serial.print(" hours, ");  
    Serial.print(Minutes, DEC);  
    Serial.print(" minutes, ");  
    Serial.print(Seconds, DEC);  
    Serial.println(" seconds");  
    Serial.println("Raw Sensor Data");      
    Serial.print("Raw gas level: ");  
    Serial.println(RawSensor);  
    Serial.print("Temperature digital: ");  
    Serial.println(TempDigital);  
    Serial.print("Humidity digital: ");  
    Serial.println(RHDigital);  
    Serial.println("");
}

/* ******************************************************************************** 
* EEPROM dump 
**********************************************************************************/
void SPEC_dump_EEPROM() {  
    // First we trigger a measurement  
    Serial1.print(" ");  
    // Within one second time we send the command "e"  
    delay(400);  
    Serial1.print("e");  
    dataString = Serial1.readStringUntil('\n');  
    // You can uncomment this line if you wish  
    //Serial.println(dataString);  
    for (int i = 0; i < 20; i++) {     
        responseString = Serial1.readStringUntil('\n');    
        Serial.println(responseString);  
    }   
}  

void flush_serial1() {  
    // Do we have data in the serial buffer?  
    // If so, flush it  
    if (Serial1.available() > 0) {    
        Serial.println("Flushing serial buffer...");    
        while (1) {      
            inChar = (char)Serial1.read();      
            delay(10);      
            Serial.print(inChar);      
            if (inChar == '\n') break;     
        }    
        Serial.println(" ");    
        Serial.println("Buffer flushed!");  
    }
}

Some comments regarding the code

The DGS-H2S 968-036 operating manual of the sensor (version March 2017) is a bit ambiguous regarding the UART communication protocol. On page 5 it states that a command is recognized if it’s received within 1-second from a TRIGGER event. On the next page, we find a 5-seconds interval for a command to be recognized.

I have found that the commands are recognized within an 800-milliseconds interval after the trigger event (before the sensor starts sending back the measured values). As a result, configuring the sensor with a terminal program such TeraTerm is next to impossible.

Also, the commands must NOT be terminated with \r\n or with \n, Just the command letter is enough.

Code examples for other commands will be published soon,

When starting for the first time, you will get some extreme readings. This is because the way this sensor works. In one of the technical documents, I have found a piece of information that compares this sensor with a capacitor, with bias placed across the working and reference electrodes being similar to the voltage across the plates of a capacitor. The high current observed at the sensor startup is like the charging current of a capacitor. As such, it is recommended to leave the sensor run for about one hour after power-up to get accurate readings.

[Update 23 October 2018] This is how to use the sensor with an Arduino Uno.

```

Post a Comment

0 Comments