diff --git a/Examples/Thermometr/CMakeLists.txt b/Examples/Thermometr/CMakeLists.txt index 235290f..e274f88 100644 --- a/Examples/Thermometr/CMakeLists.txt +++ b/Examples/Thermometr/CMakeLists.txt @@ -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}) diff --git a/Examples/Thermometr/src/dht22.c b/Examples/Thermometr/src/dht22.c new file mode 100644 index 0000000..f65df62 --- /dev/null +++ b/Examples/Thermometr/src/dht22.c @@ -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; +} diff --git a/Examples/Thermometr/src/dht22.h b/Examples/Thermometr/src/dht22.h new file mode 100644 index 0000000..1fc3a09 --- /dev/null +++ b/Examples/Thermometr/src/dht22.h @@ -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 diff --git a/Examples/Thermometr/src/dht22.pio b/Examples/Thermometr/src/dht22.pio new file mode 100644 index 0000000..73b1662 --- /dev/null +++ b/Examples/Thermometr/src/dht22.pio @@ -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 diff --git a/Examples/Thermometr/src/main.c b/Examples/Thermometr/src/main.c index f244307..aa42fc4 100644 --- a/Examples/Thermometr/src/main.c +++ b/Examples/Thermometr/src/main.c @@ -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 // Подключаем swprintf для форматирования строки FPS. +#include "pico/stdlib.h" +#include "display/display.h" +#include "display/render/context.h" +#include "Font/font_data.h" +#include "dht22.h" +#include +#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); + } +}