#include #include #include #include #include #include #include #include "hardware/gpio.h" #include "hardware/spi.h" #include "pico/stdlib.h" namespace { constexpr auto pin_lcd_cs = std::uint32_t{9}; constexpr auto pin_lcd_sclk = std::uint32_t{10}; constexpr auto pin_lcd_mosi = std::uint32_t{11}; constexpr auto pin_lcd_miso = std::uint32_t{12}; constexpr auto pin_lcd_rst = std::uint32_t{13}; constexpr auto pin_lcd_dc = std::uint32_t{14}; constexpr auto pin_lcd_bl = std::uint32_t{15}; auto select() -> void { gpio_put(pin_lcd_cs, 0); } auto deselect() -> void { gpio_put(pin_lcd_cs, 1); } auto command(std::uchar_t value) -> void { gpio_put(pin_lcd_dc, 0); select(); spi_write_blocking(spi1, &value, 1); deselect(); } auto data(std::uchar_t const* values, std::size_t count) -> void { gpio_put(pin_lcd_dc, 1); select(); spi_write_blocking(spi1, values, count); deselect(); } auto data8(std::uchar_t value) -> void { data(&value, 1); } auto set_window(std::int32_t x0, std::int32_t y0, std::int32_t x1, std::int32_t y1) -> void { x0 = std::clamp(x0, std::int32_t{0}, vamp_pico::display_width - std::int32_t{1}); y0 = std::clamp(y0, std::int32_t{0}, vamp_pico::display_height - std::int32_t{1}); x1 = std::clamp(x1, std::int32_t{0}, vamp_pico::display_width - std::int32_t{1}); y1 = std::clamp(y1, std::int32_t{0}, vamp_pico::display_height - std::int32_t{1}); command(0x2a); auto column = std::array{ static_cast((x0 >> 8) & 0xff), static_cast(x0 & 0xff), static_cast((x1 >> 8) & 0xff), static_cast(x1 & 0xff), }; data(column.data(), column.size()); command(0x2b); auto row = std::array{ static_cast((y0 >> 8) & 0xff), static_cast(y0 & 0xff), static_cast((y1 >> 8) & 0xff), static_cast(y1 & 0xff), }; data(row.data(), row.size()); command(0x2c); } auto same_color(vamp_style::color left, vamp_style::color right) -> std::bool_t { return left.r == right.r && left.g == right.g && left.b == right.b && left.a == right.a; } template auto same_text(std::array const& cached, std::char_t const* text) -> std::bool_t { auto const* value = text == nullptr ? "" : text; return std::strncmp(cached.data(), value, Count) == 0; } template auto copy_text(std::array& cached, std::char_t const* text) -> void { auto const* value = text == nullptr ? "" : text; auto count = std::min(Count - 1U, std::strlen(value)); std::memcpy(cached.data(), value, count); cached[count] = '\0'; } auto blend_channel(std::uint8_t foreground, std::uint8_t background, std::uint8_t alpha) -> std::uint8_t { auto fg = static_cast(foreground) * alpha; auto bg = static_cast(background) * (15U - alpha); return static_cast((fg + bg + 7U) / 15U); } auto blend(vamp_style::color foreground, vamp_style::color background, std::uint8_t alpha) -> vamp_style::color { if (alpha >= 15U) { return foreground; } return { .r = blend_channel(foreground.r, background.r, alpha), .g = blend_channel(foreground.g, background.g, alpha), .b = blend_channel(foreground.b, background.b, alpha), .a = 255, }; } auto draw_centered_text(vamp_pico::display& display, std::int32_t y, std::char_t const* text, std::uint16_t color, std::int32_t preferred_scale, std::int32_t max_width) -> void { if (text == nullptr || text[0] == '\0') { return; } auto scale = preferred_scale; while (scale > 1 && vamp_style::ui_text_width(text, scale) > max_width) { --scale; } auto width = vamp_style::ui_text_width(text, scale); auto x = std::max(12, (vamp_pico::display_width - width) / 2); display.draw_text(x, y, text, color, scale); } } auto vamp_pico::display::init() -> void { spi_init(spi1, 62'500'000); gpio_set_function(pin_lcd_sclk, GPIO_FUNC_SPI); gpio_set_function(pin_lcd_mosi, GPIO_FUNC_SPI); gpio_set_function(pin_lcd_miso, GPIO_FUNC_SPI); gpio_init(pin_lcd_cs); gpio_init(pin_lcd_dc); gpio_init(pin_lcd_rst); gpio_init(pin_lcd_bl); gpio_set_dir(pin_lcd_cs, GPIO_OUT); gpio_set_dir(pin_lcd_dc, GPIO_OUT); gpio_set_dir(pin_lcd_rst, GPIO_OUT); gpio_set_dir(pin_lcd_bl, GPIO_OUT); deselect(); gpio_put(pin_lcd_bl, 1); gpio_put(pin_lcd_rst, 0); sleep_ms(50); gpio_put(pin_lcd_rst, 1); sleep_ms(120); command(0x01); sleep_ms(150); command(0x11); sleep_ms(120); command(0x3a); data8(0x55); command(0x36); data8(0x60); command(0x21); command(0x13); command(0x29); sleep_ms(20); fill(vamp_pico::color_black); remote_valid_ = false; } auto vamp_pico::display::fill(std::uint16_t color) -> void { fill_rect(0, 0, vamp_pico::display_width, vamp_pico::display_height, color); } auto vamp_pico::display::fill_rect(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height, std::uint16_t color) -> void { if (width <= 0 || height <= 0) { return; } auto x0 = std::clamp(x, std::int32_t{0}, vamp_pico::display_width - std::int32_t{1}); auto y0 = std::clamp(y, std::int32_t{0}, vamp_pico::display_height - std::int32_t{1}); auto x1 = std::clamp(x + width - std::int32_t{1}, std::int32_t{0}, vamp_pico::display_width - std::int32_t{1}); auto y1 = std::clamp(y + height - std::int32_t{1}, std::int32_t{0}, vamp_pico::display_height - std::int32_t{1}); if (x1 < x0 || y1 < y0) { return; } set_window(x0, y0, x1, y1); auto hi = static_cast((color >> 8) & 0xff); auto lo = static_cast(color & 0xff); auto row = std::array{}; for (auto i = std::size_t{}; i < row.size(); i += 2U) { row[i] = hi; row[i + 1U] = lo; } gpio_put(pin_lcd_dc, 1); select(); for (auto yy = y0; yy <= y1; ++yy) { spi_write_blocking(spi1, row.data(), static_cast(x1 - x0 + 1) * 2U); } deselect(); } auto vamp_pico::display::draw_text(std::int32_t x, std::int32_t y, std::char_t const* text, std::uint16_t color, std::int32_t scale) -> void { if (text == nullptr || scale <= 0) { return; } auto foreground = vamp_style::color{ .r = static_cast(((color >> 11U) & 0x1fU) * 255U / 31U), .g = static_cast(((color >> 5U) & 0x3fU) * 255U / 63U), .b = static_cast((color & 0x1fU) * 255U / 31U), .a = 255, }; auto cursor = x; auto previous = std::char_t{}; for (auto i = std::size_t{}; i < std::strlen(text); ++i) { if (previous != std::char_t{}) { cursor += vamp_style::ui_font_pair_adjust(previous, text[i]) * scale; } auto glyph = vamp_style::ui_font_glyph(text[i]); for (auto row = 0; row < vamp_style::ui_font_height; ++row) { auto col = std::int32_t{}; while (col < static_cast(glyph.width)) { auto row_offset = static_cast(row * vamp_style::ui_font_max_width); while (col < static_cast(glyph.width) && glyph.alpha[row_offset + static_cast(col)] == 0U) { ++col; } auto start = col; auto alpha = start < static_cast(glyph.width) ? glyph.alpha[row_offset + static_cast(start)] : std::uint8_t{}; while (col < static_cast(glyph.width) && glyph.alpha[row_offset + static_cast(col)] == alpha) { ++col; } if (col > start) { auto run_color = vamp_style::rgb565(blend(foreground, vamp_style::background, alpha)); fill_rect(cursor + start * scale, y + row * scale, (col - start) * scale, scale, run_color); } } } cursor += static_cast(glyph.advance) * scale; if (cursor >= vamp_pico::display_width) { return; } previous = text[i]; } } auto vamp_pico::display::show_boot(std::char_t const* line1, std::char_t const* line2, std::uint16_t accent) -> void { remote_valid_ = false; fill(vamp_style::rgb565(vamp_style::background)); fill_rect(0, 0, vamp_pico::display_width, 28, accent); draw_text(14, 48, "VAMP", vamp_style::rgb565(vamp_style::ink), 2); if (line1 != nullptr) { draw_text(92, 54, line1, vamp_style::rgb565(vamp_style::ink), 1); } if (line2 != nullptr) { draw_text(14, 96, line2, vamp_style::rgb565(vamp_style::muted), 1); } } auto vamp_pico::display::show_remote(vamp_style::color status, std::char_t const* title, std::char_t const* part, std::uint32_t page, std::uint32_t pages, std::bool_t active) -> void { auto const* title_text = title == nullptr ? "" : title; auto const* part_text = part == nullptr ? "" : part; auto status_changed = !remote_valid_ || !same_color(last_status_, status); auto content_changed = !remote_valid_ || last_active_ != active || !same_text(last_title_, title_text) || !same_text(last_part_, part_text) || last_page_ != page || last_pages_ != pages; if (!remote_valid_) { fill(vamp_style::rgb565(vamp_style::background)); } if (status_changed) { fill_rect(0, 0, vamp_pico::display_width, 24, vamp_style::rgb565(status)); } if (content_changed) { fill_rect(0, 24, vamp_pico::display_width, vamp_pico::display_height - 24, vamp_style::rgb565(vamp_style::background)); if (active && title_text[0] != '\0') { draw_centered_text(*this, 66, title_text, vamp_style::rgb565(vamp_style::ink), 1, vamp_pico::display_width - 24); draw_centered_text(*this, 118, part_text, vamp_style::rgb565(vamp_style::muted), 1, vamp_pico::display_width - 24); if (pages != 0U) { auto page_text = std::array{}; auto written = std::snprintf(page_text.data(), page_text.size(), "PAGE %lu/%lu", static_cast(page), static_cast(pages)); if (written > 0) { draw_centered_text(*this, 168, page_text.data(), vamp_style::rgb565(vamp_style::muted), 1, vamp_pico::display_width - 24); } } } } last_status_ = status; last_active_ = active; copy_text(last_title_, title_text); copy_text(last_part_, part_text); last_page_ = page; last_pages_ = pages; remote_valid_ = true; }