Context & Problem Description
I was attempting to use this library to implement Triple Modular Redundancy (TMR) by running three concurrent DHT11 sensors on a single Pico's pio0.
Only the first initialized DHT sensor operates correctly. The second and third sensors consistently report DHT_RESULT_TIMEOUT, even though the sensors themselves are confirmed to be working.
My diagnosis indicates that the issue is rooted in the handling of the PIO program offset during repeated calls to dht_init.
Environment
- Sensor Model: DHT11 (Tested with all three sensors working individually).
- Platform: Raspberry Pi Pico (RP2040).
- PIO Block: pio0 (All three state machines claimed successfully).
- Data Pins: GP13, GP14, GP15.
Steps to Reproduce
Initialize three independent dht_t structs.
Call dht_init for each struct.
In a loop, call dht_start_measurement for all three, followed by dht_finish_measurement_blocking.
static const dht_model_t DHT_MODEL = DHT11;
static const uint DATA_PIN_1 = 13;
static const uint DATA_PIN_2 = 14;
static const uint DATA_PIN_3 = 15; //init three sensors.
int main() {
stdio_init_all();
puts("\nDHT test");
dht_t dht[3];
dht_init(&dht[0], DHT_MODEL, pio0, DATA_PIN_1, true /* pull_up */);
dht_init(&dht[1], DHT_MODEL, pio0, DATA_PIN_2, true /* pull_up */);
dht_init(&dht[2], DHT_MODEL, pio0, DATA_PIN_3, true /* pull_up */);
while (true) {
float humidity[3];
float temperature_c[3];
for (int i = 0; i < 3; i++) {
dht_start_measurement(&dht[i]);
}
for (int i = 0; i < 3; i++) {
uint32_t start = time_us_32();
dht_result_t result = dht_finish_measurement_blocking(&dht[i], &humidity[i], &temperature_c[i]);
uint32_t elapsed_us = time_us_32() - start;
printf("Sensor %d: ", i + 1);
if (result == DHT_RESULT_OK) {
printf("%.1f C (%.1f F), %.1f%% humidity (took %lu us)\n",
temperature_c[i],
celsius_to_fahrenheit(temperature_c[i]),
humidity[i],
elapsed_us);
} else if (result == DHT_RESULT_TIMEOUT) {
printf("not responding (took %lu us). Please check wiring.\n", elapsed_us);
} else {
printf("Bad checksum (took %lu us)\n", elapsed_us);
}
}
sleep_ms(2000);
}
return 0;
}
As output this gives the following:
Sensor 1: 22.5 C (72.5 F), 64.0% humidity (took 21792 us)
Sensor 2: not responding (took 2 us). Please check wiring.
Sensor 3: not responding (took 2 us). Please check wiring.
Sensor 1: 22.8 C (73.0 F), 64.0% humidity (took 21760 us)
Sensor 2: not responding (took 1 us). Please check wiring.
Sensor 3: not responding (took 1 us). Please check wiring.
Technical Diagnosis
As we can see in the program above, "not responding" is printed only when we receive a DHT_RESULT_TIMEOUT
as result, and inside the function dht_finish_measurement_blocking() dht.c:175 we can see that this is only returned if dma_channel_is_busy(dht->dma_chan).
Okay so are the instances using the same DMA channel? They shouldnt be because the pio should be claiming an unused dma channel, and there are 12 channels (rp2040 datasheet, ch 2.5)
To figure out what was happening I added debug prints of all interesting states inside the initialization function:
void dht_init(dht_t *dht, dht_model_t model, PIO pio, uint8_t data_pin, bool pull_up) {
assert(pio == pio0 || pio == pio1);
memset(dht, 0, sizeof(dht_t));
dht->model = model;
dht->pio = pio;
dht->pio_program_offset = pio_add_program(pio, &dht_program);
printf("[DHT INIT] Added DHT PIO program at offset: %u\n", dht->pio_program_offset);
dht->sm = pio_claim_unused_sm(pio, true);
printf("[DHT INIT] Claimed state machine: %u\n", dht->sm);
dht->dma_chan = dma_claim_unused_channel(true);
printf("[DHT INIT] Claimed DMA channel: %u\n", dht->dma_chan);
dht->data_pin = data_pin;
printf("[DHT INIT] Using data pin: %u (pull-up: %s)\n", data_pin, pull_up ? "enabled" : "disabled");
pio_gpio_init(pio, data_pin);
gpio_set_pulls(data_pin, pull_up, false);
printf("[DHT INIT] Pull configuration set (up=%d, down=0).\n", pull_up);
}
DHT test
[DHT INIT] Added DHT PIO program at offset: 15
[DHT INIT] Claimed state machine: 0
[DHT INIT] Claimed DMA channel: 0
[DHT INIT] Using data pin: 13 (pull-up: enabled)
[DHT INIT] Pull configuration set (up=1, down=0).
[DHT INIT] Added DHT PIO program at offset: 247
[DHT INIT] Claimed state machine: 1
[DHT INIT] Claimed DMA channel: 1
[DHT INIT] Using data pin: 14 (pull-up: enabled)
[DHT INIT] Pull configuration set (up=1, down=0).
[DHT INIT] Added DHT PIO program at offset: 247
[DHT INIT] Claimed state machine: 2
[DHT INIT] Claimed DMA channel: 2
[DHT INIT] Using data pin: 15 (pull-up: enabled)
[DHT INIT] Pull configuration set (up=1, down=0).
As we can see, we have a first offset of 15, and then ... 247? But there is only 32 instructions max?
This seems to be because we are storing the pio program offset as a uint8_t
and it is retrourning PICO_ERROR_INSUFFICIENT_RESOURCES which is equal to -9.
Solution
I simply separated the call to pio_add_program() and separated it out into its own function. This does add
some additional required knowledge for the average use case and is also not backwards compatible. So not sure if it is the best possible solution.
I pushed my fix to a branch in a fork here:
https://github.com/VitriolOlja/pico_dht/tree/tripmodred
Context & Problem Description
I was attempting to use this library to implement Triple Modular Redundancy (TMR) by running three concurrent DHT11 sensors on a single Pico's pio0.
Only the first initialized DHT sensor operates correctly. The second and third sensors consistently report DHT_RESULT_TIMEOUT, even though the sensors themselves are confirmed to be working.
My diagnosis indicates that the issue is rooted in the handling of the PIO program offset during repeated calls to dht_init.
Environment
Steps to Reproduce
Initialize three independent dht_t structs.
Call dht_init for each struct.
In a loop, call dht_start_measurement for all three, followed by dht_finish_measurement_blocking.
As output this gives the following:
Technical Diagnosis
As we can see in the program above, "not responding" is printed only when we receive a DHT_RESULT_TIMEOUT
as result, and inside the function dht_finish_measurement_blocking() dht.c:175 we can see that this is only returned if dma_channel_is_busy(dht->dma_chan).
Okay so are the instances using the same DMA channel? They shouldnt be because the pio should be claiming an unused dma channel, and there are 12 channels (rp2040 datasheet, ch 2.5)
To figure out what was happening I added debug prints of all interesting states inside the initialization function:
As we can see, we have a first offset of 15, and then ... 247? But there is only 32 instructions max?
This seems to be because we are storing the pio program offset as a uint8_t
and it is retrourning PICO_ERROR_INSUFFICIENT_RESOURCES which is equal to -9.
Solution
I simply separated the call to pio_add_program() and separated it out into its own function. This does add
some additional required knowledge for the average use case and is also not backwards compatible. So not sure if it is the best possible solution.
I pushed my fix to a branch in a fork here:
https://github.com/VitriolOlja/pico_dht/tree/tripmodred