Restore DHT22 reading and on-screen telemetry in Thermometr

This commit is contained in:
Stanislav N Mikhailov
2026-04-05 15:41:54 +03:00
parent f4b1b26f4a
commit 29c8a5e610
5 changed files with 222 additions and 121 deletions
+6
View File
@@ -59,10 +59,16 @@ target_compile_definitions(display_engine PUBLIC
add_executable(${PROJECT_NAME}
src/main.c
src/dht22.c
)
pico_generate_pio_header(${PROJECT_NAME}
${CMAKE_CURRENT_LIST_DIR}/src/dht22.pio
)
target_link_libraries(${PROJECT_NAME}
display_engine
hardware_pio
)
pico_add_extra_outputs(${PROJECT_NAME})
+90
View File
@@ -0,0 +1,90 @@
#include "dht22.h"
#include "dht22.pio.h"
#include "hardware/gpio.h"
#include "hardware/clocks.h"
#include "pico/stdlib.h"
#define DHT22_START_LOW_US 1200u
#define DHT22_PREPARE_US 30u
#define DHT22_WORD_TIMEOUT_US 2000u
static bool wait_for_rx_word(PIO pio, uint sm, uint32_t* out, uint32_t timeout_us) {
uint64_t deadline = time_us_64() + timeout_us;
while (pio_sm_is_rx_fifo_empty(pio, sm)) {
if (time_us_64() > deadline) {
return false;
}
}
*out = pio_sm_get(pio, sm);
return true;
}
void dht22_init(dht22_t* dev, PIO pio, uint sm, uint pin) {
dev->pio = pio;
dev->sm = sm;
dev->pin = pin;
dev->offset = pio_add_program(pio, &dht22_program);
pio_gpio_init(pio, pin);
pio_sm_config c = dht22_program_get_default_config(dev->offset);
sm_config_set_in_pins(&c, pin);
sm_config_set_clkdiv(&c, (float)clock_get_hz(clk_sys) / 1000000.0f);
sm_config_set_in_shift(&c, false, true, 8);
pio_sm_init(pio, sm, dev->offset, &c);
pio_sm_set_enabled(pio, sm, false);
gpio_init(pin);
gpio_pull_up(pin);
gpio_set_dir(pin, GPIO_IN);
}
dht22_status_t dht22_read(dht22_t* dev, int16_t* temperature_x10, uint16_t* humidity_x10) {
if (!gpio_get(dev->pin)) {
return DHT22_BUS_STUCK;
}
gpio_set_dir(dev->pin, GPIO_OUT);
gpio_put(dev->pin, 0);
sleep_us(DHT22_START_LOW_US);
gpio_set_dir(dev->pin, GPIO_IN);
gpio_pull_up(dev->pin);
sleep_us(DHT22_PREPARE_US);
pio_sm_set_enabled(dev->pio, dev->sm, false);
pio_sm_clear_fifos(dev->pio, dev->sm);
pio_sm_restart(dev->pio, dev->sm);
pio_sm_set_enabled(dev->pio, dev->sm, true);
uint8_t data[5] = {0};
for (uint i = 0; i < 5; ++i) {
uint32_t word = 0;
if (!wait_for_rx_word(dev->pio, dev->sm, &word, DHT22_WORD_TIMEOUT_US)) {
pio_sm_set_enabled(dev->pio, dev->sm, false);
return DHT22_TIMEOUT;
}
data[i] = (uint8_t)word;
}
pio_sm_set_enabled(dev->pio, dev->sm, false);
uint8_t checksum = (uint8_t)(data[0] + data[1] + data[2] + data[3]);
if (checksum != data[4]) {
return DHT22_CHECKSUM_ERROR;
}
uint16_t raw_h = (uint16_t)((data[0] << 8) | data[1]);
uint16_t raw_t = (uint16_t)((data[2] << 8) | data[3]);
*humidity_x10 = raw_h;
if (raw_t & 0x8000u) {
*temperature_x10 = -(int16_t)(raw_t & 0x7fffu);
} else {
*temperature_x10 = (int16_t)raw_t;
}
return DHT22_OK;
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef DHT22_H
#define DHT22_H
#include "pico/types.h"
#include "hardware/pio.h"
typedef enum {
DHT22_OK = 0,
DHT22_TIMEOUT,
DHT22_CHECKSUM_ERROR,
DHT22_BUS_STUCK
} dht22_status_t;
typedef struct {
PIO pio;
uint sm;
uint pin;
uint offset;
} dht22_t;
void dht22_init(dht22_t* dev, PIO pio, uint sm, uint pin);
dht22_status_t dht22_read(dht22_t* dev, int16_t* temperature_x10, uint16_t* humidity_x10);
#endif
+19
View File
@@ -0,0 +1,19 @@
.program dht22
; Capture 40 bits from DHT22 by sampling each high pulse around 35us after rising edge.
.wrap_target
wait 0 pin 0
wait 1 pin 0
set y, 4
byte_loop:
set x, 7
bit_loop:
wait 0 pin 0
wait 1 pin 0
nop [31]
nop [2]
in pins, 1
jmp x-- bit_loop
jmp y-- byte_loop
.wrap
+83 -121
View File
@@ -1,135 +1,97 @@
#include "pico/stdlib.h" // Подключаем базовые функции SDK Pico (время, таймеры, sleep, assert).
#include "display/display.h" // Подключаем API дисплейного движка (init + begin/end paint).
#include "display/render/context.h" // Подключаем контекст рисования для примитивов.
#include "display/render/grid.h" // Подключаем функцию рисования сетки.
#include "display/render/sine_wave.h" // Подключаем функцию рисования синусоиды.
#include "Font/font_data.h" // Подключаем шрифты и draw_string.
#include <wchar.h> // Подключаем swprintf для форматирования строки FPS.
#include "pico/stdlib.h"
#include "display/display.h"
#include "display/render/context.h"
#include "Font/font_data.h"
#include "dht22.h"
#include <wchar.h>
#define WIDTH 320
#define HEIGHT 240
#define DHT22_PIN 2
#define DHT22_POLL_INTERVAL_US 2000000ULL
#define WIDTH 320 // Ширина экрана в пикселях.
#define HEIGHT 240 // Высота экрана в пикселях.
#define TEXT_H 22 // Высота глифа шрифта для расчета нижней границы текста.
static inline void wait_for_irq(void)
{
__asm volatile ("wfi");
}
static inline void wait_for_irq(void) // Вспомогательная функция для энергосберегающего ожидания прерываний.
{
__asm volatile ("wfi"); // Инструкция ARM Wait For Interrupt: CPU спит до любого IRQ.
}
static const wchar_t* dht22_status_text(dht22_status_t status)
{
switch (status)
{
case DHT22_OK: return L"DHT: OK";
case DHT22_TIMEOUT: return L"DHT: timeout";
case DHT22_CHECKSUM_ERROR: return L"DHT: crc error";
case DHT22_BUS_STUCK: return L"DHT: bus stuck";
default: return L"DHT: unknown";
}
}
static uint16_t text_width_px(const wchar_t* str) // Считаем фактическую ширину строки в пикселях через таблицу ширин глифов.
{
uint16_t width = 0; // Накапливаем итоговую ширину строки.
while (*str) // Пока не дошли до нулевого терминатора строки.
{
width = (uint16_t)(width + get_char_width(*str)); // Добавляем ширину текущего символа.
str++; // Переходим к следующему символу.
}
return width; // Возвращаем итоговую ширину строки.
}
int main()
{
stdio_init_all();
static void update_axis_reflect(int* pos, int* dir, int min_pos, int max_pos) // Обновляем одну координату с зеркальным отражением от границ.
{
if (max_pos <= min_pos) // Если объект больше доступного диапазона или диапазон вырожден.
{
*pos = min_pos; // Фиксируем позицию в единственной доступной точке.
*dir = 0; // Останавливаем движение по этой оси.
return; // Завершаем обработку оси.
}
*pos += *dir; // Двигаем объект на 1 пиксель по текущему направлению.
if (*pos < min_pos) // Проверяем удар о минимальную границу (верх/лево).
{
*pos = min_pos + (min_pos - *pos); // Зеркально отражаем координату внутрь диапазона.
*dir = -*dir; // Инвертируем направление движения.
}
else if (*pos > max_pos) // Проверяем удар о максимальную границу (низ/право).
{
*pos = max_pos - (*pos - max_pos); // Зеркально отражаем координату внутрь диапазона.
*dir = -*dir; // Инвертируем направление движения.
}
}
int main()
{
stdio_init_all(); // Инициализируем стандартный ввод/вывод (UART/USB stdio при необходимости).
display_config_t cfg = { // Формируем конфигурацию дисплейного движка.
.width = WIDTH, // Передаем ширину кадра.
.height = HEIGHT, // Передаем высоту кадра.
.buffer_count = 1, // Используем один буфер кадра (SAFE режим блокирует доступ при DMA).
.mode = DISPLAY_MODE_SAFE, // Выбираем безопасный режим работы буфера.
display_config_t cfg = {
.width = WIDTH,
.height = HEIGHT,
.buffer_count = 1,
.mode = DISPLAY_MODE_SAFE,
};
display_init(&cfg); // Инициализируем дисплей и внутренний контекст по заданной конфигурации.
float phase = 0.0f; // Фаза синусоиды для анимации волны.
const wchar_t* text1 = L"Проверка кириллицы"; // Текст первой строки.
const wchar_t* text2 = L"Latin character check"; // Текст второй строки.
const wchar_t* text3 = L"1234567890!@#$%^&*()"; // Текст третьей строки.
const int text1_w = text_width_px(text1); // Фактическая ширина первой строки в пикселях.
const int text2_w = text_width_px(text2); // Фактическая ширина второй строки в пикселях.
const int text3_w = text_width_px(text3); // Фактическая ширина третьей строки в пикселях.
int text1_x = 50; // Начальная X-координата первой строки.
int text1_y = 90; // Начальная Y-координата первой строки.
int text2_x = 45; // Начальная X-координата второй строки.
int text2_y = 110; // Начальная Y-координата второй строки.
int text3_x = 35; // Начальная X-координата третьей строки.
int text3_y = 130; // Начальная Y-координата третьей строки.
int text1_dx = 1; // Горизонтальное направление первой строки: +1 вправо, -1 влево.
int text1_dy = 1; // Вертикальное направление первой строки: +1 вниз, -1 вверх.
int text2_dx = -1; // Горизонтальное направление второй строки.
int text2_dy = 1; // Вертикальное направление второй строки.
int text3_dx = -1; // Горизонтальное направление третьей строки.
int text3_dy = -1; // Вертикальное направление третьей строки.
uint32_t fps_value = 0; // Текущее вычисленное значение FPS.
uint32_t fps_frame_count = 0; // Количество завершенных кадров в текущем окне измерения.
uint64_t fps_window_start_us = time_us_64(); // Время старта текущего окна измерения в микросекундах.
wchar_t fps_text[16] = L"FPS: 0"; // Буфер строки FPS для вывода в угол экрана.
display_init(&cfg);
render_ctx_t rc; // Контекст рендера, который будет привязываться к текущему буферу кадра.
render_ctx_t rc;
const wchar_t* title = L"Thermometr";
const wchar_t* subtitle = L"DHT22 data";
wchar_t temp_text[24] = L"T: --.- C";
wchar_t hum_text[24] = L"H: --.- %";
const wchar_t* dht_status = L"DHT: init";
bool dht_has_valid_data = false;
int16_t dht_temp_x10 = 0;
uint16_t dht_hum_x10 = 0;
uint64_t next_dht_poll_us = time_us_64();
while (1) // Бесконечный основной цикл приложения.
{
uint16_t* buf = display_begin_paint_try(); // Пытаемся начать новый кадр и получить буфер рисования.
if (buf == NULL) // Если кадр ещё выводится или paint уже начат, пропускаем этот тик.
dht22_t dht;
dht22_init(&dht, pio0, 0, DHT22_PIN);
while (1)
{
uint64_t now_us = time_us_64();
if (now_us >= next_dht_poll_us)
{
wait_for_irq(); // Спим до прерывания, чтобы не крутить пустой busy-loop.
dht22_status_t status = dht22_read(&dht, &dht_temp_x10, &dht_hum_x10);
dht_status = dht22_status_text(status);
if (status == DHT22_OK)
{
dht_has_valid_data = true;
}
next_dht_poll_us = now_us + DHT22_POLL_INTERVAL_US;
}
if (dht_has_valid_data)
{
uint16_t t_abs_x10 = (dht_temp_x10 < 0) ? (uint16_t)(-dht_temp_x10) : (uint16_t)dht_temp_x10;
wchar_t sign = (dht_temp_x10 < 0) ? L'-' : L'+';
(void)swprintf(temp_text, sizeof(temp_text) / sizeof(temp_text[0]), L"T: %lc%u.%u C", sign, (unsigned)(t_abs_x10 / 10u), (unsigned)(t_abs_x10 % 10u));
(void)swprintf(hum_text, sizeof(hum_text) / sizeof(hum_text[0]), L"H: %u.%u %%", (unsigned)(dht_hum_x10 / 10u), (unsigned)(dht_hum_x10 % 10u));
}
uint16_t* buf = display_begin_paint_try();
if (buf == NULL)
{
wait_for_irq();
continue;
}
render_begin(&rc, buf, WIDTH, HEIGHT); // Привязываем контекст рендера к буферу и размерам экрана.
render_clear(&rc, RGB16(9,19,9)); // Очищаем кадр темно-зеленым фоном.
render_grid(&rc, 10, 10, 20, RGB16(40,40,40)); // Рисуем фон-сетку с шагом и цветом.
render_sine_wave(&rc, WIDTH*3, 100, 2.0f, 0, HEIGHT / 2, phase, RGB16(0,255,0)); // Рисуем анимированную синусоиду.
draw_string(&rc, (uint16_t)text1_x, (uint16_t)text1_y, text1, RGB565(255, 255, 255)); // Рисуем первую строку по текущим X/Y.
draw_string(&rc, (uint16_t)text2_x, (uint16_t)text2_y, text2, RGB565(0, 0, 255)); // Рисуем вторую строку по текущим X/Y.
draw_string(&rc, (uint16_t)text3_x, (uint16_t)text3_y, text3, RGB565(255, 0, 0)); // Рисуем третью строку по текущим X/Y.
draw_string(&rc, 0, 0, fps_text, RGB565(255, 255, 0)); // Выводим текущее значение FPS в левый верхний угол.
render_begin(&rc, buf, WIDTH, HEIGHT);
render_clear(&rc, RGB16(8, 18, 8));
draw_string(&rc, 16, 16, title, RGB565(255, 255, 255));
draw_string(&rc, 16, 40, subtitle, RGB565(255, 220, 120));
draw_string(&rc, 16, 72, temp_text, RGB565(255, 255, 255));
draw_string(&rc, 16, 96, hum_text, RGB565(255, 255, 255));
draw_string(&rc, 16, 120, dht_status, RGB565(255, 200, 0));
update_axis_reflect(&text1_x, &text1_dx, 0, WIDTH - text1_w); // Двигаем первую строку по X с отражением от левой/правой грани.
update_axis_reflect(&text1_y, &text1_dy, 0, HEIGHT - TEXT_H); // Двигаем первую строку по Y с отражением от верхней/нижней грани.
update_axis_reflect(&text2_x, &text2_dx, 0, WIDTH - text2_w); // Двигаем вторую строку по X с отражением от левой/правой грани.
update_axis_reflect(&text2_y, &text2_dy, 0, HEIGHT - TEXT_H); // Двигаем вторую строку по Y с отражением от верхней/нижней грани.
update_axis_reflect(&text3_x, &text3_dx, 0, WIDTH - text3_w); // Двигаем третью строку по X с отражением от левой/правой грани.
update_axis_reflect(&text3_y, &text3_dy, 0, HEIGHT - TEXT_H); // Двигаем третью строку по Y с отражением от верхней/нижней грани.
phase += 0.16f; // Продвигаем фазу синусоиды для плавной анимации.
if (phase > 6.2831853f) // Если фаза превысила 2*pi.
{
phase -= 6.2831853f; // Возвращаем фазу в базовый диапазон без скачка формы.
}
bool submitted = display_end_paint(); // Завершаем paint-секцию и отправляем кадр.
hard_assert(submitted); // В этом сценарии отправка обязана проходить успешно.
fps_frame_count++; // Учитываем успешно отправленный кадр в статистике FPS.
uint64_t now_us = time_us_64(); // Фиксируем текущее время для проверки окна измерения.
uint64_t elapsed_us = now_us - fps_window_start_us; // Сколько прошло с начала окна измерения.
if (elapsed_us >= 1000000ULL) // Пересчитываем FPS примерно раз в секунду.
{
fps_value = (uint32_t)((fps_frame_count * 1000000ULL) / elapsed_us); // Средний FPS за окно.
(void)swprintf(fps_text, sizeof(fps_text) / sizeof(fps_text[0]), L"FPS: %lu", (unsigned long)fps_value); // Обновляем строку для отрисовки.
fps_frame_count = 0; // Начинаем новое окно измерения.
fps_window_start_us = now_us; // Сдвигаем старт окна на текущее время.
}
}
}
bool submitted = display_end_paint();
hard_assert(submitted);
}
}