/* * dht11_reader.c * * Standalone DHT11 reader using libgpiod (C). * Uses open-drain output for the host start signal and a tight * user-space polling loop to sample the line at ~1us intervals. * * Usage: * ./dht11_reader [chip] [line] * * Defaults: * chip = gpiochip0 * line = 19 (RK3562 GPIO0_C3_d on the 20-pin header pin 2) */ #include #include #include #include #include #include #include #include #include #define DEFAULT_CHIP "gpiochip0" #define DEFAULT_LINE 19 /* DHT11 timing (microseconds) */ #define START_LOW_US 20000 /* 18-30 ms */ #define RELEASE_MIN_US 20 /* host release must be >= 20us */ #define RESPONSE_TIMEOUT_US 200 #define BIT_TIMEOUT_US 120 #define BIT_THRESHOLD_US 40 /* high > 40us -> 1 */ #define MAX_SAMPLES 8192 struct sample { uint64_t ts_us; int val; }; static inline uint64_t now_us(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL; } static inline void busy_wait_us(uint64_t us) { uint64_t deadline = now_us() + us; while (now_us() < deadline) { __asm__ volatile ("nop"); } } static void usage(const char *prog) { fprintf(stderr, "Usage: %s [chip] [line]\n", prog); fprintf(stderr, " chip: gpio chip name (default: %s)\n", DEFAULT_CHIP); fprintf(stderr, " line: gpio line offset (default: %d, RK3562 GPIO0_C3_d / header pin 2)\n", DEFAULT_LINE); } static void rt_setup(void) { struct sched_param param = { .sched_priority = 50 }; if (sched_setscheduler(0, SCHED_FIFO, ¶m) < 0) { /* non-fatal */ } } int main(int argc, char *argv[]) { const char *chip_name = DEFAULT_CHIP; unsigned int line_offset = DEFAULT_LINE; if (argc >= 2) chip_name = argv[1]; if (argc >= 3) line_offset = (unsigned int)atoi(argv[2]); if (argc > 3) { usage(argv[0]); return 2; } rt_setup(); struct gpiod_chip *chip = gpiod_chip_open_by_name(chip_name); if (!chip) { fprintf(stderr, "ERR: failed to open %s: %s\n", chip_name, strerror(errno)); return 1; } struct gpiod_line *line = gpiod_chip_get_line(chip, line_offset); if (!line) { fprintf(stderr, "ERR: failed to get line %u: %s\n", line_offset, strerror(errno)); gpiod_chip_close(chip); return 1; } gpiod_line_release(line); /* Open-drain output: 1 = Hi-Z (pull-up), 0 = drive low */ if (gpiod_line_request_output_flags(line, "dht11_reader", GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN, 1) < 0) { fprintf(stderr, "ERR: failed to request open-drain output: %s\n", strerror(errno)); gpiod_chip_close(chip); return 1; } /* Stabilize high */ gpiod_line_set_value(line, 1); busy_wait_us(2000000); /* Start signal: low for 20ms */ gpiod_line_set_value(line, 0); busy_wait_us(START_LOW_US); /* Release and immediately begin sampling */ gpiod_line_set_value(line, 1); uint64_t release_ts = now_us(); struct sample samples[MAX_SAMPLES]; int n_samples = 0; uint64_t deadline = release_ts + RESPONSE_TIMEOUT_US + 40 * (80 + 80) + 1000; while (n_samples < MAX_SAMPLES && now_us() < deadline) { int v = gpiod_line_get_value(line); if (v < 0) continue; samples[n_samples].ts_us = now_us(); samples[n_samples].val = v; n_samples++; } gpiod_line_release(line); gpiod_chip_close(chip); fprintf(stderr, "DEBUG: captured %d samples over %llu us\n", n_samples, (unsigned long long)(samples[n_samples - 1].ts_us - samples[0].ts_us)); /* Find response: first sustained low (>=40us) then high */ int resp_low_start = -1, resp_low_end = -1, resp_high_end = -1; for (int i = 0; i < n_samples - 1; i++) { if (resp_low_start < 0 && samples[i].val == 0) { resp_low_start = i; } if (resp_low_start >= 0 && resp_low_end < 0 && samples[i].val == 1) { resp_low_end = i; } if (resp_low_end >= 0 && resp_high_end < 0 && samples[i].val == 0) { resp_high_end = i; break; } } if (resp_low_start < 0 || resp_low_end < 0 || resp_high_end < 0) { fprintf(stderr, "ERR: could not find response pulse\n"); return 1; } uint64_t resp_low_dur = samples[resp_low_end].ts_us - samples[resp_low_start].ts_us; uint64_t resp_high_dur = samples[resp_high_end].ts_us - samples[resp_low_end].ts_us; fprintf(stderr, "DEBUG: response low=%llu us high=%llu us\n", (unsigned long long)resp_low_dur, (unsigned long long)resp_high_dur); /* Decode 40 bits: each bit starts after a falling edge; high duration decides value */ uint8_t data[5] = {0}; int bit_idx = 0; int i = resp_high_end; while (bit_idx < 40 && i < n_samples - 1) { /* find rising edge (start of bit high pulse) */ while (i < n_samples - 1 && !(samples[i].val == 0 && samples[i + 1].val == 1)) i++; if (i >= n_samples - 1) break; uint64_t rise_ts = samples[i + 1].ts_us; /* find next falling edge (end of bit high pulse) */ i++; while (i < n_samples - 1 && !(samples[i].val == 1 && samples[i + 1].val == 0)) i++; if (i >= n_samples - 1) { /* last bit: measure until end of capture */ uint64_t high_dur = samples[n_samples - 1].ts_us - rise_ts; int bit = (high_dur > BIT_THRESHOLD_US) ? 1 : 0; data[bit_idx / 8] = (data[bit_idx / 8] << 1) | bit; fprintf(stderr, "DEBUG: bit %02d high=%llu us -> %d (tail)\n", bit_idx, (unsigned long long)high_dur, bit); bit_idx++; break; } uint64_t fall_ts = samples[i + 1].ts_us; uint64_t high_dur = fall_ts - rise_ts; int bit = (high_dur > BIT_THRESHOLD_US) ? 1 : 0; data[bit_idx / 8] = (data[bit_idx / 8] << 1) | bit; fprintf(stderr, "DEBUG: bit %02d high=%llu us -> %d\n", bit_idx, (unsigned long long)high_dur, bit); bit_idx++; } if (bit_idx != 40) { fprintf(stderr, "ERR: only decoded %d bits\n", bit_idx); return 1; } uint8_t checksum = data[0] + data[1] + data[2] + data[3]; int valid = (checksum == data[4]); int humidity = data[0]; int temperature = data[2]; printf("{"); printf("\"valid\":%s,", valid ? "true" : "false"); printf("\"humidity\":%d,", humidity); printf("\"temperature\":%d,", temperature); printf("\"raw\":[%u,%u,%u,%u,%u],", data[0], data[1], data[2], data[3], data[4]); printf("\"checksum\":%u", checksum); printf("}\n"); return valid ? 0 : 1; }