33
DHT11 Temperature Sensor Library for the Raspberry Pico: Python Prototype
The Raspberry Pico, or shorthand Pico, is a new microcontroller from the Raspberry Pi foundation. It provides a dual core ARM processor, 2MB of flash memory, and 26 GPIO pins. You can program the Pico with either a C/C++ SDK or MicroPython. I became fascinated by this device and started to develop a library for reading the DHT11 temperature sensor. However, to get the exact timing requirements of the proprietary, 1-wire requirement right, we need to use the PIO subsystem.
The previous article explained the Pio system and the language. In this article, I will design and implement a PIO prototype program for connecting a DHT11 sensor. The prototype is concerned with sending the required startup signal to the sensor, and then record its response, electrical pulses in the range of 26-70 us, which is the encoded sensor data. Development happens with MicroPython: The interactive REPL environment, and the nature of a scripting language, provides a flexible environment.
This article originally appeared at my blog.
The DHT11 protocol works over a 1-wire connection. This line is active high. To enable the DHT 11 sensor
The sensor will then transmit a total amount of 40bit data by sending differently typed impulses over the wire:
The 40bits data are divided into 2x8 Bit for the temperature, 2x 8bit for the humidity, and 8bit as the checksum.
Let’s consider the essentials of PIO programs:
These constraints shape the program approach in the following manner:
After some experimentation, I came up with a design that activates the DHT11 sensor with pure MicroPython commands, and then starts a state machine that ticks slow enough to detect the 26-28us pulses in the DHT signal.
We will implement the program step-by-step, solving one constraint at a time.
The general setup for a MicroPython program with an embedded PIO program is as follows. This example shows how to blink a single LED.
from rp2 import PIO, StateMachine, asm_pio
from machine import Pin
import time
@asm_pio(set_init=PIO.OUT_LOW)
def pio_program():
set(pins, 1) [31]
nop() [31]
set(pins, 0) [31]
state_machine = StateMachine(0, pio_program, freq = 2000, set_base=Pin(25))
state_machine.active(1)
In this program, following steps happen:
import
statements define the required objects of the program@asm_pio
identifies the very next method as a PIO program. The decorator accepts all the arguments with which to customize the state machine, like initial direction of pins, the shift direction of the ISR/OSR, and auto or threshold values for the push and pull methodsYou can set a PIO program frequency to anything between 2Khz and 133Mhz. Using an online tool, we can see how frequency translates to seconds:
2000 Hz = 500us
, 5000 Hz = 200us
, and 10000 Hz = 100us
. However, the frequency defines how fast the sum of all instructions inside the program runs. Therefore, we first calculate the cycle of all instructions as cycle = target_frequency * (number of statements + wait cycles)
, and the state machines frequency as state_machine_frequency = cycle * * (number of statements + wait cycles)
For the starter program, we need to operate around 26us. If we aim for a total of 20 instructions, this translates to
2000Hz / 20 = 100Hz = 10ms = 10000us
- too fast sending the 40us signal. And the other way around 25000 / 20 = 25000 Hz = 0.04ms = 40us
- this would force use to wait for 500 instructions.Therefore, the starter PIO program will only send the initial impulse, and all other calculations will be run by the other state machine.
The reader state machine has 16 instructions, and 1 instruction will be stalled for 31 cycles. Therefore, the cycle of the state machine needs to be
1us * (16+31)= 21277Hz
, and the total frequency is 21277Hz * (16 + 31) = 1000019Hz
.The frequency is configured as follows:
dht_reader = StateMachine(1,dht11_reader, freq = 1000019)
As mentioned before, the DHT11 startup sequence is covered by a MicroPython sections, and then the PIO program will be launched.
The startup sequence consist of setting the pin to output mode with a pull-up, then send an initial high voltage for 250ms, then a low impulse for 40ms.
dht_pin = Pin(15, Pin.OUT, Pin.PULL_DOWN)
dht_pin.value(1)
utime.sleep_ms(250)
dht_pin.value(0)
utime.sleep_ms(20)
Then, we change the pin to input with a pull up, and start the state machine. The pin will be used both as the input pin (because we will be reading a value from it) and as a jump pin (because we will use its state for branching statements).
dht_pin = Pin(15, Pin.IN, Pin.PULL_UP)
dht_reader = StateMachine(1,dht11_reader, freq = 1000019, in_base=dht_pin, jmp_pin=dht_pin)
The final requirement is to implement the core logic for detecting the DHT11 pulses and convert them into data. After several attempts, I took an approach inspired by this forum. In essence, the PIO program performs these steps:
read_bit
loop, which will again wait for the pin to go low0b1
, otherwise its 0b0
high_detected
and low_detected
code block to write 3-bit data to the ISRWhenever 24bits are written, the results will be auto-pushed to the state machine.
The complete PIO program is as follows.
@asm_pio(autopush=True, push_thresh=24)
def dht11_reader():
# ínitial LOW-HIGH confirmation pulse
wait(0,pin,0)
wait(1,pin,0)
# Main loop
# General idea: if pin changes from high to low within 32 cycles, its logic 1, otherwise logic 0
label("read_bit")
wait(1,pin,0) [31]
jmp(pin, "high_detected")
jmp("low_detected")
# Write 0b111 = (7), return to pattern loop
label("high_detected")
set(y,0b111)
in_(y,3)
wait(0,pin,0)
jmp("read_bit")
# Write 0b100 =(4), return to pattern loop
label("low_detected")
set(y,0b100)
in_(y,3)
wait(0,pin,0)
jmp("read_bit")
The main program starts an infinite loop. After waiting for 2.5ms for powering up the DHT11 sensor, the program sends the start signal to the sensor, starts the state machine, and reads 5 times 24bits of data. The data will be stored as strings inside an array. Once completed, the captured data will be printed as binary number to the scree, and then converted to humidity and temperature values.
print("Starting DHT11 Program ...")
i = 0
while True:
sleep(2.0)
i +=1
print("Cycle {} - Running state machine ...".format(i))
dht_pin = Pin(15, Pin.OUT, Pin.PULL_DOWN)
dht_pin.value(1)
utime.sleep_ms(250)
dht_pin.value(0)
utime.sleep_ms(30)
dht_pin = Pin(15, Pin.IN, Pin.PULL_UP)
dht_reader = StateMachine(1,dht11_reader, freq = 1000019, in_base=dht_pin, jmp_pin=dht_pin)
dht_reader.active(1)
data = []
for _ in range(5):
data.append(bin(dht_reader.get()))
dht_reader.active(0)
sleep(0.5)
for d in data:
print(d)
Measurements from my room, taken 03.07.2021, 10:27, yields the following data:
Starting DHT11 Program ...
Cycle 1 - Running state machine ...
0b100111111100100100111111
0b111111100111111111111100
0b111111100100100111100100
0b111111111100100111100100
0b100100100100111100100111
Temperature: 24.2 Degree Celcius
Humidity: 48.1 %
Temperature: 24.3 Degree Celcius
Humidity: 48.2 %
This article detailed the implementation of a PIO program with MicroPython to read the DHT11 temperature sensor. We learned about the PIOs essential features - frequency selection, flexible pin mappings, working with 32bit registers - and the constraints of the DHT11 - read/write operations between 80ms and 24us, switch the single pins role from output to input, write chunks of 8bit of data. From this, we crafted a program step by step. At the end, the program worked, but development time and effort were extensive. The original goal of using PIO was having precise timing controls, yet I found this only achieved at a high price in terms of time spent. In the end, I also discovered a pure MicroPython library for DHT11 that works flawlessly, and given the recent support of the Arduino framework, C libraries for this sensor could become compatible with the Pico. For these reasons, I will not continue the development of a Pico C-SDK DHT11 library.
33