Debugging Occasional Incorrect SCD30 Results

Share on:

Currently, I am working with a Sensirion SCD30 CO2 and RH/T Sensor Module for both work and Senior Design. The module includes an IR led detection chamber for measuring CO2 as well as a SHT** relative humidity and temperature sensor for calibration of the CO2. This improves integration and is a pretty good deal if one needs both in their system.

Sensirion Library

Sensirion is nice enough to have invested engineering time into creating open source libraries for their sensors located on Github. After quickly porting the sensirion_hw_i2c_implementation.c library functions to my microcontroller and soldering to the carrier board I designed, I had the sensor running with accurate results. However, although the results were accurate every once in a while there would be erroneous result.

Segger RTT Log of SCD30 results showing single erroneous result

Bad Result

As seen below each of the values in the erroneous result are all the same, and once debugged in Eclipse Embedded CDT (go open source!) it can be seen that all the results are 0xFF which corresponds to MAX_VALUE. However, what is most interesting is that the checksum bytes are correct for 0xFFFF chunks.

At this point it appears that the SCD30 is intentionally sending 0xFF results. Time to dive in a little deeper....

Stepped through Eclipse code showing erroreous data

Timing Analysis

The demo I am currently running is directly from the Git repo above. It configures the SCD30 to operate with an internal measurement interval of two seconds and then samples every two seconds to fetch the result.

 1	scd30_set_measurement_interval(interval_in_seconds);
 2	sensirion_sleep_usec(20000u);
 3	scd30_start_periodic_measurement(0);
 4	sensirion_sleep_usec(interval_in_seconds * 1000000u);
 6	while (1) {
 7		/* Measure co2, temperature and relative humidity and store into
 8		 * variables.
 9		 */
10		err = scd30_read_measurement(&co2_ppm, &temperature,
11				&relative_humidity);
12		if (err != STATUS_OK) {
13			SEGGER_RTT_printf(0, "error reading measurement\n");
15		} else {
16			SEGGER_RTT_printf(0, "measured co2 concentration: %6f ppm\r\n"
17					"\t measured temperature: %6f degreeCelsius\r\n"
18					"\t measured humidity: %6f %%RH\r\n", co2_ppm, temperature,
19					relative_humidity);
20		}
22		sensirion_sleep_usec(interval_in_seconds * 1000000u);
23	}
25	scd30_stop_periodic_measurement();

This makes me wonder. Could it be a race condition, where a sample is occurring while I try and fetch? Time to check the I2C bus with the Saleae logic analyzer.

I2C Waveform

Checking the general timing, it can be seen below that the microcontroller is indeed waiting two seconds before performing another query from the module. Interestingly enough the ~80mA current spikes can be seen on the 5V rail when the module internally performs an acquisition.

Looking further at the timing of these spikes, each of the I2C queries occur at a different delta from the end of the internal acquisition. The erroneous sample occurs at Δt1, Δt2 and Δt3. Where Δt1 > Δt2 > Δt3. So to me it seems unlikely that Δt2 would be the erroneous sample.

Full I2C view of valid, erroneous and then valid sample

I2C Up Close

Below is a zoomed in waveform of each of the above I2C queries in order.

SCD30: I2C waveform valid 1 SCD30: I2C waveform erroneous SCD30: I2C waveform valid 2

From the above waveforms, the general I2C read operation can be observed. Notably I2C clock stretching can be seen operating correctly in the 4th frame of each image. I see no reason for the erroneous 0xFF results due to bad I2C connections or power supply issues.


From the above, I am led to believe that the sensor will return a sea of 0xFF if a sample is somehow stale or other random issue internal to the module. Luckily, coding around this will be quite easy as the results are clearly out of expected range.


Turns out that the data sheet specifically states to:

Make sure that the measurement is completed by reading the data ready status bit before read out.

Yet Sensirion's own demo does not follow this note. Is it time for a pull request? (Edit: turns out it was, #44)