Skip to content

Add option in DHT driver to do the measure without blocking interrupts #6946

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

EddieParis
Copy link
Contributor

This is done because ESP8266 has a PWM relying on interrupts.
Doing the DHT measure with interruption blocking, sometimes makes
glitches on the PWM.

fixes #6945

@EddieParis EddieParis force-pushed the master branch 2 times, most recently from 4fdf251 to 2021e34 Compare February 21, 2021 09:52
This is done because ESP8266 has a PWM relying on interrupts.
Doing the DHT measure with interruption blocking, sometimes makes
glitches on the PWM.
@EddieParis
Copy link
Contributor Author

To reproduce issue, assuming dht is on gpio 0 and led on gpio14

import dht
from machine import Pin, PWM
p=PWM(Pin(14))
p.freq(150)
p.duty(100)
d=dht.DHT22(Pin(0))
while True:
d.measure()

You should see glitches on the leds

change to d=dht.DHT22(Pin(0), False), no more glitches

@@ -37,7 +37,9 @@
#define mp_hal_pin_od_high_dht mp_hal_pin_od_high
#endif

STATIC mp_obj_t dht_readinto(mp_obj_t pin_in, mp_obj_t buf_in) {
STATIC mp_obj_t dht_readinto(mp_obj_t pin_in, mp_obj_t buf_in, mp_obj_t irq_lock) {
mp_uint_t irq_state=0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not follow the code style in the file. Put spaces around '='. Also there seems to be no need for the 0 initialization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately you have to :-( That is why I added it.

+1: ../../drivers/dht/dht.c: In function 'dht_readinto':
./xtirq.h:48:5: error: 'irq_state' may be used uninitialized in this function [-Werror=maybe-uninitialized]
asm volatile ("wsr %0, ps; rsync" : : "a" (ps));
^
../../drivers/dht/dht.c:41:15: note: 'irq_state' was declared here
mp_uint_t irq_state;
^
cc1: all warnings being treated as errors

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, either I'm missing something or compiler is wrong about that but well, in any way, we want to make it happy.

>>> while not measure_ok:
>>> try:
>>> d.measure()
>>> measure_ok = True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A minor comment, I think the more common way of handling it would be to get rid of this variable and use brake like so:

while True:
   try:
     d.measure()
     break
   except [...]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes ! good point.

@@ -54,7 +56,9 @@ STATIC mp_obj_t dht_readinto(mp_obj_t pin_in, mp_obj_t buf_in) {
mp_hal_pin_od_low(pin);
mp_hal_delay_ms(18);

mp_uint_t irq_state = mp_hal_quiet_timing_enter();
if (mp_obj_is_true(irq_lock)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mp_obj_is_true() is a little involved function so it would be worth calling it only once instead if tree times. You should consider creating a bool that will "cache" this computation once, something like:
bool irq_lock_true = mp_obj_is_true(irq_lock);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, I considered the call to mp_obj_is_true() as almost free. I can do that if you prefer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check its implementation here. For most cases it is, indeed, but if someone passes some object there, it does require quite some checks.
Ultimately it's not my decision, I'm just a random guy doing a review, but I do think its worth saving those few CPU cycles.


def measure(self):
buf = self.buf
dht_readinto(self.pin, buf)
dht_readinto(self.pin, buf, self.irq_block)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider putting a retry loop here in case self.irq_block is false to free the users from doing that. I would avoid the infinite loop, though.. so maybe some fixed amount of retries should be introduced?

Copy link
Contributor Author

@EddieParis EddieParis Feb 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to let the user decide to manage that as he prefers. In my case, I have other things to do so I can't lock main loop for too long, so I retry later. DHT is already really slow, not worth adding more delays.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems valid argument, indeed. I was just afraid that anybody has to implement this loop while using this argument anyways.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a DHT sensor you have to implement a try:except: retry.. loop anyway because they are not very stable.

@kadamski
Copy link
Contributor

As a general comment, the same issue exist with onewire module (as DHT protocol is very similiar to onewire bus) so if you are already putting a warning to the documentation, maybe it would make sense to mention onewire case as well?

@EddieParis
Copy link
Contributor Author

EddieParis commented Feb 24, 2021

Yeah, why not. But in the onewire, bits are read one by one and the protocol is waaaaaay faster (locking is shorter), so I'm not sure it can produce visible effects.

@kadamski
Copy link
Contributor

kadamski commented Feb 25, 2021

Yeah, why not. But in the onewire, bits are read one by one and the protocol is waaaaaay faster (locking is shorter), so I'm not sure it can produce visible effects.

True. But if this (locking for ~70us) is not a problem then maybe for DHD it would also be ok to do a lock/unlock in every iteration of the loop (which takes 80-120us) and only for a machine_time_pulse_us(). This way we still can get a wrong result if entering into the critical region is delayed much more than 50us (the time of a low pulse before each high pulse we are measuring) but we might get a little better results than without any locking while the overall latency would be still be much smaller.

DHT will send 26-28us pulse for "0" and 70us for "1" but dht.c is considering a "1" everything that is >48us so we already have a safety margin of around 22us in case our machine_time_pulse_us() starts late (when the 50us low singal is already over).

To sum up, my understanding is this:

  1. If we do not do any locking:
    • we might get an interrupt before machine_time_pulse_us() starts counting "high" time - this may give us shorter measurement so we might end up interpreting "1" as "0"
    • we might also get an interrupt during machine_time_pulse_us() counting "high" time - this may give us longer measurement so we may interpret "0" as "1"
  2. If we do machine_time_pulse_us inside of a lock that is released and reackquired each iteration:
    • we might get an interrupt before machine_time_pulse_us starts so we can also interpret "1" as "0"
    • but the interrupt during machine_time_pulse_us() will be delayed so we can't interpret "0" as "1"
    • the downside is that we can introduce up to around 100us of latency but that is still nothing compared to the ~4ms of latency we have with full locking.

What do you think ?

@EddieParis
Copy link
Contributor Author

Theoretically you are right. But I think this is over complicated for a very little benefit. My software is running for almost a year with this patch and 3 channel PWM in parallel, I observed very few checksum errors, and no weird measures that could have gone through checksum verification.
Any third party opinion ?

@projectgus
Copy link
Contributor

This is an automated heads-up that we've just merged a Pull Request
that removes the STATIC macro from MicroPython's C API.

See #13763

A search suggests this PR might apply the STATIC macro to some C code. If it
does, then next time you rebase the PR (or merge from master) then you should
please replace all the STATIC keywords with static.

Although this is an automated message, feel free to @-reply to me directly if
you have any questions about this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ESP8266: PWM glitches when doing DHT measure
5 participants