Real time measurement of kHz signals (i.e. DHT22 devices) and interrupts not viable with Argon

Particle Argon running OS 1.4.2, with AM2302 (Device “A”) connected to D4 pin, and DHT22 (Device “B”) connected to D5 pin. An oscilloscope was used to verify proper signal levels and timing from DHT devices to Argon. Library to support DHT devices was imported from eliteio/PietteTech_DHT. The example program “DHT_2sensor” was compiled within VS code with Particle workbench.

Results: While a 90% valid result may be acceptable (Device “A”), it was concerning that less than 50% valid results for the 2nd (Device “B”). This was from a sample size of 130 continuous samples.

More analysis was accomplished measuring the timing in and around the interrupt routine using an Argon OS 1.4.2. Using pinSetFast(D7) upon entry to the isrCallback and several pinResetFast(D7) placed at the potential exit points of the ISR and an oscilloscope, the measured time from falling edge to beginning of the ISR was between 8us to 44us. The time within the ISR was approx. 12us.

Additionally, acquiring both devices simultaneously produced valid results less than 50% of the time. However, acquiring in a sequential fashion (i.e. start device B after device A has returned), produced valid results better than 96% of the time.

Conclusion: Argon, and maybe all Mesh Particle devices, can’t be used to measure kHz speed signals (8 - 14 kHz, “1” pulse width and “0” pulse width respectively).due to the high latency of entering the interrupt service routine.

I think the best option is to use the I2S peripheral in the nRF52 for decoding DHT22 and similar signals. Even though I2S is a sound protocol, the main thing is that it can store the digital states of the signal over time in buffers. It can also generate arbitrary digital waveforms in the same way, which would be useful for generating 1-Wire signals. The main difference is that the hardware handles all of the data acquisition so there is no worry about interrupt latency. You’d only get an interrupt after the buffer receive or send is complete, where it’s no longer timing critical. This is much better than per-bit interrupts.

One issue in particular with timing-sensitive interrupts is that the mesh radio in Gen 3 devices runs at a high interrupt level, which tends to affect other interrupts. But it can’t be lowered, or the radio would stop running reliably.

I2S would eliminate this issue and I’m pretty sure it’s possible, but it’s non-trivial to implement. I played around with it a bit and I’m pretty sure it’s possible, but I never got it to work.

2 Likes

@rickkas7: I understand the concept, but can’t wrap my head around the clock source as all the timers are utilized (unless I disable mesh or some other connectivity which defies purpose of using Argon/Boron).

Also, I presume I’d need two timers, one to generate a sampling clock (as DHT does not provide clock) and one to time-out the sampling period (~2.5 ms).

Any ideas how and what resources I could use?

The way the I2S peripheral for I2S is that it captures a certain number of bits per channel (16 or 24) for left, then captures the same number of bits on the right channel. This corresponds to a single sample of audio, stereo, usually at a rate of 16000, 32000, or 44100 samples per second. Other sampling rates are possible.

However, the other way of looking at this is that 16 bits per sample at 16000 Hz, stereo, it’s capturing 512,000 digital 1-bit samples per second and saving them in a 64,000 buffer in RAM. (Well, since the devices don’t have that much RAM you probably can’t buffer a whole second, but you get the idea.) 512,000 samples per second is a 1.95 us period per sample.

What I would do is generate the start condition (18 ms low, then 20-40 us high) manually. Switch the pin to INPUT, then capture the data using the I2S peripheral. At worst, this is 50 us + 70 us for a 1 bit, so 130 us per bit = 5 bytes x 8 bits per byte = 40 bits = 5200 us.

With the I2S configured as above:

  • Low period (50 us) = 25.6 samples of 0

  • 0 bit (26-28 us) = 13.3 samples of 1

  • 1 bit (70 us) = 34.9 samples of 1

And since you only have to capture 5.2 ms of data, you only need 2666 samples or 333 bytes of data, so it will easily buffer in RAM without double buffering. I’d capture a little extra to be sure, say 350 bytes.

Once you have the 350 bytes of sample data, you then just count the pulse widths to figure out which bits are 0 and which are 1, then save this as 5 data bytes and calculate the checksum. Since all of the bits have already been read, the is no time sensitivity to this, but the calculation is not that difficult.

You don’t need any timer peripherals and don’t need any interrupts.

1 Like

There’s been a problem with DHT22 libraries on Gen 3 devices. Interrupt latency causes it to misinterpret bits for interrupt-based libraries (like PietteTech_DHT) and bit-banging isn’t much better. The narrowest pulse is 26-28 us.

I created a new library, DHT22Gen3_RK, that takes an entirely different approach. It uses the nRF52 I2S peripheral not to capture sound data, but as a DMA-based digital signal sampler. It can grab a full DHT22 sample into a 360 byte buffer at a 512 kHz sampling rate. It doesn’t require any interrupts, no timers are required, and it’s completely non-blocking. It doesn’t have to disable interrupts or thread swapping.

I ran a test overnight and captured 14,703 samples every 2.5 seconds with no checksum errors. The library will retry on error, but didn’t have to at all.

It can sample multiple sensors (sequentially, not at the same time), one per GPIO pin. There one annoying caveat: You need to allocate two spare GPIO pins. The I2S peripheral requires that you expose SCK and LRCK on physical GPIO pins or it doesn’t work. These signals are of no use for capturing DHT22 samples, but they still need to be allocated. Most people have a few GPIO left, so hopefully that’s OK.

The API is callback/lambda based, as it takes about 24 milliseconds to query a DHT22. The callback allows the loop to flow freely during this time, but the callback is dispatched from the loop context so you can still do things like Particle.publish from the callback. The callback is particularly helpful if a retry is required because of a bad checksum. Since the DHT22 can only be queried every 2 seconds, retries take a long time.

Repository and instructions: https://github.com/rickkas7/DHT22Gen3_RK

3 Likes

Thanks for doing this. I’ll start playing with it.

New version of DHT22Gen3_RK:

0.0.2 (2019-12-17)

  • Support for DHT11. Actually it worked before, but it wasn’t tested and there wasn’t an example for it.
2 Likes

Is there any way to use this DHT22Gen3_RK library to run multiple sensors or poll a sensor multiple times in a single loop? I want to use it for two sensors. I am not able to collect a second sample at all. The errors reported indicate that the checksum fails when trying to gather a second data point. Calling dht.loop() again to try to clear the buffer doesn’t help.

I am using the publish example, and tried to duplicate the data collection block in the loop like this,


void loop() {
dht.loop();

if (millis() - lastCheck >= CHECK_INTERVAL) {
	lastCheck = millis();

	dht.getSample(A3, [](DHTSample sample) {
		if (sample.isSuccess()) {
			char buf[128];
			snprintf(buf, sizeof(buf), "{\"temp\":%.1f,\"hum\":%1.f}", sample.getTempC(), sample.getHumidity());
			if (Particle.connected()) {
				Particle.publish("temperatureTest", buf, PRIVATE);
				Log.info("published: %s", buf);
			}
			else {
				Log.info("not published: %s", buf);
			}
		}
		else {
			Log.info("sample is not valid sampleResult=%d", (int) sample.getSampleResult());
		}
	});
	
delay(3000);

dht.loop();

	dht.getSample(A3, [](DHTSample sample) {
		if (sample.isSuccess()) {
			char buf2[128];
			snprintf(buf2, sizeof(buf2), "{\"temp\":%.1f,\"hum\":%1.f}", sample.getTempC(), sample.getHumidity());
			if (Particle.connected()) {
				Particle.publish("temperatureTest2", buf2, PRIVATE);
				Log.info("published: %s", buf2);
			}
			else {
				Log.info("not published: %s", buf2);
			}
		}
		else {
			Log.info("sample is not valid sampleResult=%d", (int) sample.getSampleResult());
		}
	});
	
}

}

I’m trying to use the Argon for an automated greenhouse project, and this issue is a big one. After discovering it, wish I had stuck with the Photon, but it’s a bit late now since we manufactured a PCB to match the Argon footprint. Any help would be appreciated.

That code won’t work because getting the second sample doesn’t wait for the first sample. It should look something like this:

#include "DHT22Gen3_RK.h"

SerialLogHandler logHandler;

SYSTEM_THREAD(ENABLED);
// SYSTEM_MODE(MANUAL);

// How often to check the temperature in humidity in milliseconds
const unsigned long CHECK_INTERVAL = 5000;
unsigned long lastCheck = 0;

// The two parameters are any available GPIO pins. They will be used as output but the signals aren't
// particularly important for DHT11 and DHT22 sensors. They do need to be valid pins, however.
DHT22Gen3 dht(A4, A5);

void sampleCallback(DHTSample sample);

void setup() {
	dht.setup();
}

void loop() {
	dht.loop();

	if (millis() - lastCheck >= CHECK_INTERVAL) {
		lastCheck = millis();

		dht.getSample(A3, [](DHTSample sample1) {
    		dht.getSample(A2, [sample1](DHTSample sample2) {
                if (sample1.isSuccess() && sample2.isSuccess()) {
                    char buf[128];
                    snprintf(buf, sizeof(buf), "{\"temp1\":%.1f,\"hum1\":%1.f,\"temp2\":%.1f,\"hum2\":%1.f}", 
                        sample1.getTempC(), sample1.getHumidity(), sample2.getTempC(), sample2.getHumidity());
                    if (Particle.connected()) {
                        Particle.publish("temperatureTest", buf, PRIVATE);
                        Log.info("published: %s", buf);
                    }
                    else {
                        Log.info("not published: %s", buf);
                    }
                }
                else {
                    Log.info("sample is not valid");
                }
            });
		});
	}
}

Ok, thank you for the quick and helpful response. I will try it soon!

This looks like the best library I’ve used so far with the DHT22. Here is an example of the data collected for the past few days. After about 4000 points I don’t see any incorrect data.

Thank you, Rickkas7! None of the other libraries I have been trying are able to report reliable data from this apparently common sensor so this is a relief!

Thank you for the great Libary. I think I have tested all, but it is the only which is working reliable. Unfortunatly my programming knowledge is poor and I want to make a beehive scale with one or two DHT22 Sensors. The problem is that sometimes there is one Sensor, sometimes two or there is no Sensor connected. I have tried the following code with no success:

dht.loop();

if (millis() - lastCheck >= CHECK_INTERVAL) {

  lastCheck = millis();

  scale.power_up(); 

  dht.getSample(D3, [](DHTSample sample1) {

        dht.getSample(D4, [sample1](DHTSample sample2) {

                 if (sample1.isSuccess() && sample2.isSuccess()) {

                  val = 1;

                  Serial.println("Case: Sensor 1 and Sensor 2");

                  } else if (sample1.isSuccess()) {

                    val = 2;

                    Serial.println("Case: Sensor 1");

                  } else if (sample2.isSuccess()) {

                    val = 3;

                    Serial.println("Case: Sensor 2");

                  } else {

                    val = 4;

                    Serial.println("Case: No Sensor");

                  }

            });

    });

}

It will work when both sensors or no sensor is connected, but not when there is only one of them.
Can you give me some advice please.

Bee Happy

Dieter Metzler

I think I have found a solution. I created a seperate object for each Sensor.

#include "DHT22Gen3_RK.h"

SerialLogHandler logHandler;

SYSTEM_THREAD(ENABLED);

// SYSTEM_MODE(MANUAL);

// How often to check the temperature in humidity in milliseconds

const unsigned long CHECK_INTERVAL = 5000;

unsigned long lastCheck = 0;

// The two parameters are any available GPIO pins. They will be used as output but the signals aren't

// particularly important for DHT11 and DHT22 sensors. They do need to be valid pins, however.

DHT22Gen3 dht_D3(A4, A5);

DHT22Gen3 dht_D4(A2, A3);

void setup() {

dht_D3.setup();

dht_D4.setup();

}

void loop() {

dht_D3.loop();

dht_D4.loop();

if (millis() - lastCheck >= CHECK_INTERVAL) {

    lastCheck = millis();

    dht_D3.getSample(D3, [](DHTSample sampleD3) {

        dht_D4.getSample(D4, [sampleD3](DHTSample sampleD4) {

            if (sampleD3.isSuccess() && sampleD4.isSuccess()) {

                Serial.println("Case: Sensor D3 and Sensor D4");

                Serial.println("TemperaturD3: " + String(sampleD3.getTempC()));

                Serial.println("HumidityD3: " + String(sampleD3.getHumidity()));

                Serial.println("TemperaturD4: " + String(sampleD4.getTempC()));

                Serial.println("HumidityD4: " + String(sampleD4.getHumidity()));

                    

              } else if (sampleD3.isSuccess()) {

                Serial.println("Case: Sensor D3");

                Serial.println("TemperaturD3: " + String(sampleD3.getTempC()));

                Serial.println("HumidityD3: " + String(sampleD3.getHumidity()));

              } else if (sampleD4.isSuccess()) {

                Serial.println("Case: Sensor D4");

                Serial.println("TemperaturD4: " + String(sampleD4.getTempC()));

                Serial.println("HumidityD4: " + String(sampleD4.getHumidity()));

              } else {

                Serial.println("Case: No Sensor");

              }

    

        });     

    });

}

}