DHT22 on Pico

RP2040 based microcontroller boards running MicroPython.
Target audience: MicroPython users with an RP2040 boards.
This does not include conventional Linux-based Raspberry Pi boards.
danjperron
Posts: 51
Joined: Thu Dec 27, 2018 11:38 pm
Location: Québec, Canada

DHT22 on Pico

Post by danjperron » Sun Feb 14, 2021 3:03 pm

This is my version using PIO function to read the DHT22.

Still try to figure out how the StateMachine works. I can't find good documentation. Just parcel of it all spread out.

The way I made the code is limit the time to wait for the pulse lenght, 'nop() [31]' = clock * 32, if the pulse is to long then just put '1' in the FIFO. This way the return checksum won't be valid.

the sm.get() is nice but I wasn't able to find a function to see if any data is available. This prevent my first idea to create a lock up.

This is my DHT22 class https://github.com/danjperron/PicoDHT22

Daniel

User avatar
bouffski
Posts: 6
Joined: Tue Feb 23, 2021 7:54 pm

Re: DHT22 on Pico

Post by bouffski » Fri Feb 26, 2021 11:58 pm

Thanx! It all worked like a charm. Just... How to set the clock?
Thanx too for the format-of-the-numbers, was looking for it a long time...
Hope to see more ? :idea:

User avatar
bouffski
Posts: 6
Joined: Tue Feb 23, 2021 7:54 pm

Re: DHT22 on Pico

Post by bouffski » Sun Feb 28, 2021 5:06 pm

To answer my own question about setting the right time:
https://www.raspberrypi.org/forums/view ... 5#p1810708
Also a brilliant solution, All credits for this champ! (DWiskow)

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: DHT22 on Pico

Post by Roberthh » Sun Feb 28, 2021 8:31 pm

Looking at the data sheet, I made another version of a PIO script, which takes less care about pulse times, except for the 0/1 distinction of the data. I miss some kind of signalling from the state machine. I did not try IRQ yet. That may be an option.
During my tests I had some strange effects on jmp(not_x, ...) and jmp(not_y, ...) instructions. I'll test that separately.

Code: Select all

# the clock is set to 200 kHz or 5µs per tick

    @rp2.asm_pio(
        in_shiftdir=rp2.PIO.SHIFT_LEFT,
        set_init=rp2.PIO.OUT_HIGH,
        autopull=False,
        autopush=True,
        push_thresh=8
    )
    def DHT_pio():
        pull()                       # get the number of clock cycles
        mov(x, osr)                  # for the start pulse
        set(pindirs, 1)              # set to output
        set(pins, 0)
        # create the start pulses
        label("start_pulse")  
        jmp(x_dec, "start_pulse")
        set(pins, 1)        [7]      # and wait six ticks
        set(pindirs, 0)              # set back to input mode
        nop()               [2]      # wait two ticks
        jmp(pin, "error")            # if not 0, not DHT response
        wait(1, pin, 0)              # wait for the first 1

        label("wait_0")
        wait(0, pin, 0)              # wait for the first or next 0

        label("wait_1")
        wait(1, pin, 0)              # wait for the bit pulse
        nop()               [7]      # wait another 40 µs
        in_(pins, 1)                 # get one bit
        jmp(pin, "wait_0")           # if bit == 1, wait for 0
        jmp("wait_1")                # Otherwise wait for the next bit

        label("error")
        mov(isr, invert(y))
        push(noblock)

danjperron
Posts: 51
Joined: Thu Dec 27, 2018 11:38 pm
Location: Québec, Canada

Re: DHT22 on Pico

Post by danjperron » Sun Feb 28, 2021 11:10 pm

Looking at the data sheet, I made another version of a PIO script, which takes less care about pulse times, except for the 0/1
I didn't want to go on that road because if the sensor failed it is stuck!!!! This is a big problem.

This is why if time out I put 1 . Normally it is good enough to create a checksum error !

The other method is to check the fifo empty like I did with the period counter but that involve mem32 function to check fifo empty register.

About your start pulse. The DHT11 needs at least 20ms compare to the DHT22 .

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: DHT22 on Pico

Post by Roberthh » Mon Mar 01, 2021 7:34 am

Yes, the StateMachine class would need some additional methods for status and aborting. That would allow a more general error handling. Until then, it could be easily implemented using viper code, which makes direct memory access easy and fast.

In the pico-sdk, the following functions are available for peeking the FIFO states and forcing a reset:

static inline void pio_sm_restart(PIO pio, uint sm)

static inline bool pio_sm_is_rx_fifo_full(PIO pio, uint sm)
static inline bool pio_sm_is_rx_fifo_empty(PIO pio, uint sm)
static inline uint pio_sm_get_rx_fifo_level(PIO pio, uint sm)
static inline bool pio_sm_is_tx_fifo_full(PIO pio, uint sm)
static inline bool pio_sm_is_tx_fifo_empty(PIO pio, uint sm)
static inline uint pio_sm_get_tx_fifo_level(PIO pio, uint sm)

So maybe at least sm.active(1) could also force a reset.

About DHT11: Like in your code, just the initial value to be put into the state machine has to be changed. I did not show that part of the code.

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: DHT22 on Pico

Post by Roberthh » Mon Mar 01, 2021 9:20 am

Until the statemachine module is eventually expanded, I quickly made four tiny functions to access the FIFO status and ot restart the state machine. The argument is the integer number of the state machine, not the sm object.

Code: Select all

    PIO0_BASE = const(0x50200000)
    PIO1_BASE = const(0x50300000)
    PIO_CTRL = const(0)
    PIO_FSTAT = const(1)
    PIO_FLEVEL = const(3)
    
    @micropython.viper
    def sm_restart(sm: int):
        if sm < 4:   # PIO 0
            pio = ptr32(uint(PIO0_BASE))
        else:  # PIO1
            pio = ptr32(uint(PIO1_BASE))
        sm %= 4
        pio[PIO_CTRL] = 1 << (sm + 4)

    @micropython.viper
    def sm_rx_fifo_level(sm: int) -> int:
        if sm < 4:   # PIO 0
            pio = ptr32(uint(PIO0_BASE))
        else:  # PIO1
            pio = ptr32(uint(PIO1_BASE))
        sm %= 4
        return (pio[PIO_FLEVEL] >> (8 * sm + 4)) & 0x0f

    @micropython.viper
    def sm_tx_fifo_level(sm: int) -> int:
        if sm < 4:   # PIO 0
            pio = ptr32(uint(PIO0_BASE))
        else:  # PIO1
            pio = ptr32(uint(PIO1_BASE))
        sm %= 4
        return (pio[PIO_FLEVEL] >> (8 * sm)) & 0x0f

    @micropython.viper
    def sm_fifo_status(sm: int) -> int:
        if sm < 4:   # PIO 0
            pio = ptr32(uint(PIO0_BASE))
        else:  # PIO1
            pio = ptr32(uint(PIO1_BASE))
        return pio[PIO_FSTAT]
Using that, the DHTpio code can be further simplified. The python code can check for a timeout, and if that happens, the current sampling is aborted and the state machine is restarted.

Code: Select all

# the clock is set to 200 kHz or 5µs per tick

    @rp2.asm_pio(
        in_shiftdir=rp2.PIO.SHIFT_LEFT,
        set_init=rp2.PIO.OUT_HIGH,
        autopull=False,
        autopush=True,
        push_thresh=8
    )
    def DHT_pio():
        pull()                       # get the number of clock cycles
        mov(x, osr)                  # for the start pulse
        set(pindirs, 1)              # set to output
        set(pins, 0)

        label("start_pulse")
        jmp(x_dec, "start_pulse")    # test for more bits

        set(pins, 1)        [7]      # and wait six ticks

        set(pindirs, 0)              # set back to input mode
        wait(1, pin, 0)

        label("wait_0")
        wait(0, pin, 0)

        label("wait_1")
        wait(1, pin, 0)
        nop()               [7]     # wait another 40 µs
        in_(pins, 1)                # get one bit
        jmp(pin, "wait_0")          # if one, wait for 0
        jmp("wait_1")               # Otherwise wait for the next bit
The python code can check for a timeout, and if that happens, the current sampling is aborted and the state machine is restarted. Like below:

Code: Select all

    pin_base = Pin(10, Pin.OUT, value=1)
    sm_freq = 200_000
    sm_tick = 1_000_000 // sm_freq

    sm = rp2.StateMachine(0, DHT_pio, freq=sm_freq,
                          set_base=pin_base, in_base=pin_base, 
                          out_base=pin_base, jmp_pin=pin_base)

    recv = array.array("H", bytearray(10))

    sm.put(1000 // sm_tick)  # number of wait cycles for the start pulse
    sm.active(1)
    index = 0
    for i in range(1000):
        if sm_rx_fifo_level(0) > 0:
            recv[index] = sm.get()
            index += 1
            if index == 5: 
                break
        else:
            time.sleep_ms(1)
    else:
        print("Acquistion error")
        sm_restart(0)
    sm.active(0)
    print([hex(i) for i in recv])

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: DHT22 on Pico

Post by Roberthh » Mon Mar 01, 2021 1:33 pm

I made a PR to integrate three of these functions into the StateMachine module. https://github.com/micropython/micropython/pull/6973

danjperron
Posts: 51
Joined: Thu Dec 27, 2018 11:38 pm
Location: Québec, Canada

Re: DHT22 on Pico

Post by danjperron » Mon Mar 01, 2021 2:34 pm

My understanding about StateMachine on PIO is that we have 32 addresses available to put code there and it will run without using the cpu time.

Is putting code outside the StateMachine and adding functions to be controlled by the cpu better? Maybe simpler but we will sacrifice cpu time against a standalone process.

I'm agree that we should have fifo status function.

I.M.O. We should use interrupts and minimize the intervention of the cpu as possible. One function to start the conversion and one callback function to get the data when it is ready. This will let the cpu doing other processes in the mean time.

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: DHT22 on Pico

Post by Roberthh » Mon Mar 01, 2021 4:41 pm

It's not about doing stuff in cpu that the PIO can do better. My aim was to allow clean-up actions to be enforced by the cpu such that the PIO code can be shorter and simpler. The restart() function is not completely what I hoped it would do. It seems not to reset the PC of the state machine.
I did not try interrupts yet. That is probably the good way for the PIO to signal a state to the cpu. Only the opposite seems missing.
Another method of possibly resolving error states is using sm.exec() to run additional statements out-of-band, like a missing push(). I will give it a try.

Post Reply