commit b0255f2c6229cac876139226a8e2c5ca1845b508 Author: Mike Bell Date: Sat Aug 10 13:04:03 2024 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a007fea --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..445ff92 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.12) + +set(PICO_PLATFORM rp2350) + +include(pico_sdk_import.cmake) +include(pimoroni_pico_import.cmake) + +# Gooey boilerplate +project(pico_dvhstx_examples C CXX ASM) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialize the SDK +pico_sdk_init() + +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror") + +include(pico_dvhstx.cmake) + +add_subdirectory(examples/dvhstx) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bd9db94 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Michael Bell and Pimoroni Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..03bcba0 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# DVI for HSTX + +This repository is home to the Pimoroni PicoGraphics compatible DVI driver for RP2 chips with HSTX (e.g. RP2350). + +[![Build Status](https://img.shields.io/github/actions/workflow/status/MichaelBell/dvhstx/micropython.yml?branch=main&label=MicroPython)](https://github.com/MichaelBell/dvhstx/actions/workflows/micropython.yml) +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/MichaelBell/dvhstx)](https://github.com/MichaelBell/dvhstx/releases/latest/) + +- [Introduction](#introduction) +- [Download MicroPython](#download-micropython) +- [Documentation](#documentation) +- [C/C++ Resources](#cc-resources) +- [C/C++ Community Projects](#cc-community-projects) + +## Introduction + +DV HSTX will enable you to create big, bold audio visual projects using MicroPython and an HDMI display of your choice. + +## Download MicroPython + +TODO + +* [DV HSTX MicroPython Releases](https://github.com/MichaelBell/dvhstx/releases) + +## Documentation + +TODO + +## C/C++ Resources + +* :link: [C++ Boilerplate](https://github.com/MichaelBell/dvhstx-boilerplate/) + +## C/C++ Community Projects + +TODO diff --git a/drivers/dvhstx/CMakeLists.txt b/drivers/dvhstx/CMakeLists.txt new file mode 100644 index 0000000..a9fa387 --- /dev/null +++ b/drivers/dvhstx/CMakeLists.txt @@ -0,0 +1 @@ +include(dvhstx.cmake) \ No newline at end of file diff --git a/drivers/dvhstx/dvhstx.cmake b/drivers/dvhstx/dvhstx.cmake new file mode 100644 index 0000000..ac055d7 --- /dev/null +++ b/drivers/dvhstx/dvhstx.cmake @@ -0,0 +1,16 @@ +set(DRIVER_NAME dvhstx) + +# main DV display driver +add_library(${DRIVER_NAME} INTERFACE) + +target_sources(${DRIVER_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/dvi.cpp + ${CMAKE_CURRENT_LIST_DIR}/${DRIVER_NAME}.cpp + + ${CMAKE_CURRENT_LIST_DIR}/intel_one_mono_2bpp.c + ) + +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +# Pull in pico libraries that we need +target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib) diff --git a/drivers/dvhstx/dvhstx.cpp b/drivers/dvhstx/dvhstx.cpp new file mode 100644 index 0000000..fb14ab2 --- /dev/null +++ b/drivers/dvhstx/dvhstx.cpp @@ -0,0 +1,874 @@ +#include +#include + +extern "C" { +#include +} + +#include +#include "hardware/dma.h" +#include "hardware/gpio.h" +#include "hardware/irq.h" +#include "hardware/structs/bus_ctrl.h" +#include "hardware/structs/hstx_ctrl.h" +#include "hardware/structs/hstx_fifo.h" +#include "hardware/structs/sio.h" + +#include "hardware/structs/ioqspi.h" +#include "hardware/vreg.h" +#include "hardware/structs/qmi.h" +#include "hardware/pll.h" +#include "hardware/clocks.h" + +#include "dvi.hpp" +#include "dvhstx.hpp" + +using namespace pimoroni; + +#include "font.h" + +// If changing the font, note this code will not handle glyphs wider than 13 pixels +#define FONT (&intel_one_mono) + +static inline __attribute__((always_inline)) uint32_t render_char_line(int c, int y) { + if (c < 0x20 || c > 0x7e) return 0; + const lv_font_fmt_txt_glyph_dsc_t* g = &FONT->dsc->glyph_dsc[c - 0x20 + 1]; + const uint8_t *b = FONT->dsc->glyph_bitmap + g->bitmap_index; + const int ey = y - FONT_HEIGHT + FONT->base_line + g->ofs_y + g->box_h; + if (ey < 0 || ey >= g->box_h || g->box_w == 0) { + return 0; + } + else { + int bi = (g->box_w * ey); + + uint32_t bits = (b[bi >> 2] << 24) | (b[(bi >> 2) + 1] << 16) | (b[(bi >> 2) + 2] << 8) | b[(bi >> 2) + 3]; + bits >>= 6 - ((bi & 3) << 1); + bits &= 0x3ffffff & (0x3ffffff << ((13 - g->box_w) << 1)); + bits >>= g->ofs_x << 1; + + return bits; + } +} + +// ---------------------------------------------------------------------------- +// HSTX command lists + +// Lists are padded with NOPs to be >= HSTX FIFO size, to avoid DMA rapidly +// pingponging and tripping up the IRQs. + +static uint32_t vblank_line_vsync_off[] = { + HSTX_CMD_RAW_REPEAT, + SYNC_V1_H1, + HSTX_CMD_RAW_REPEAT, + SYNC_V1_H0, + HSTX_CMD_RAW_REPEAT, + SYNC_V1_H1 +}; + +static uint32_t vblank_line_vsync_on[] = { + HSTX_CMD_RAW_REPEAT, + SYNC_V0_H1, + HSTX_CMD_RAW_REPEAT, + SYNC_V0_H0, + HSTX_CMD_RAW_REPEAT, + SYNC_V0_H1 +}; + +static uint32_t vactive_line_header[] = { + HSTX_CMD_RAW_REPEAT, + SYNC_V1_H1, + HSTX_CMD_RAW_REPEAT, + SYNC_V1_H0, + HSTX_CMD_RAW_REPEAT, + SYNC_V1_H1, + HSTX_CMD_TMDS +}; + +static uint32_t vactive_text_line_header[] = { + HSTX_CMD_RAW_REPEAT, + SYNC_V1_H1, + HSTX_CMD_RAW_REPEAT, + SYNC_V1_H0, + HSTX_CMD_RAW_REPEAT, + SYNC_V1_H1, + HSTX_CMD_RAW | 6, + BLACK_PIXEL_A, + BLACK_PIXEL_B, + BLACK_PIXEL_A, + BLACK_PIXEL_B, + BLACK_PIXEL_A, + BLACK_PIXEL_B, + HSTX_CMD_TMDS +}; + +#define NUM_FRAME_LINES 2 +#define NUM_CHANS 3 + +static DVHSTX* display = nullptr; + +// ---------------------------------------------------------------------------- +// DMA logic + +void __scratch_x("display") dma_irq_handler() { + display->gfx_dma_handler(); +} + +void __scratch_x("display") DVHSTX::gfx_dma_handler() { + // ch_num indicates the channel that just finished, which is the one + // we're about to reload. + dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; + dma_hw->intr = 1u << ch_num; + if (++ch_num == NUM_CHANS) ch_num = 0; + + if (v_scanline >= timing_mode->v_front_porch && v_scanline < (timing_mode->v_front_porch + timing_mode->v_sync_width)) { + ch->read_addr = (uintptr_t)vblank_line_vsync_on; + ch->transfer_count = count_of(vblank_line_vsync_on); + } else if (v_scanline < v_inactive_total) { + ch->read_addr = (uintptr_t)vblank_line_vsync_off; + ch->transfer_count = count_of(vblank_line_vsync_off); + } else { + const int y = (v_scanline - v_inactive_total) >> v_repeat_shift; + const int new_line_num = (v_repeat_shift == 0) ? ch_num : (y & (NUM_FRAME_LINES - 1)); + const uint line_buf_total_len = ((timing_mode->h_active_pixels * line_bytes_per_pixel) >> 2) + count_of(vactive_line_header); + + ch->read_addr = (uintptr_t)&line_buffers[new_line_num * line_buf_total_len]; + ch->transfer_count = line_buf_total_len; + + // Fill line buffer + if (line_num != new_line_num) + { + line_num = new_line_num; + uint32_t* dst_ptr = &line_buffers[line_num * line_buf_total_len + count_of(vactive_line_header)]; + + if (line_bytes_per_pixel == 2) { + uint16_t* src_ptr = (uint16_t*)&frame_buffer_display[y * 2 * (timing_mode->h_active_pixels >> h_repeat_shift)]; + for (int i = 0; i < timing_mode->h_active_pixels >> 1; i += 2) { + uint32_t val = (uint32_t)(*src_ptr++) * 0x10001; + *dst_ptr++ = val; + *dst_ptr++ = val; + } + } + else if (line_bytes_per_pixel == 1) { + uint8_t* src_ptr = &frame_buffer_display[y * (timing_mode->h_active_pixels >> h_repeat_shift)]; + for (int i = 0; i < timing_mode->h_active_pixels >> 2; ++i) { + uint32_t val = (uint32_t)(*src_ptr++) * 0x01010101; + *dst_ptr++ = val; + } + } + else if (line_bytes_per_pixel == 4) { + uint8_t* src_ptr = &frame_buffer_display[y * (timing_mode->h_active_pixels >> h_repeat_shift)]; + if (h_repeat_shift == 2) { + for (int i = 0; i < timing_mode->h_active_pixels; i += 4) { + uint32_t val = display_palette[*src_ptr++]; + *dst_ptr++ = val; + *dst_ptr++ = val; + *dst_ptr++ = val; + *dst_ptr++ = val; + } + } + else { + for (int i = 0; i < timing_mode->h_active_pixels; i += 2) { + uint32_t val = display_palette[*src_ptr++]; + *dst_ptr++ = val; + *dst_ptr++ = val; + } + } + } + } + } + + if (++v_scanline == v_total_active_lines) { + v_scanline = 0; + line_num = -1; + if (flip_next) { + flip_next = false; + display->flip_now(); + } + __sev(); + } +} + +void __scratch_x("display") dma_irq_handler_text() { + display->text_dma_handler(); +} + +void __scratch_x("display") DVHSTX::text_dma_handler() { + // ch_num indicates the channel that just finished, which is the one + // we're about to reload. + dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; + dma_hw->intr = 1u << ch_num; + if (++ch_num == NUM_CHANS) ch_num = 0; + + if (v_scanline >= timing_mode->v_front_porch && v_scanline < (timing_mode->v_front_porch + timing_mode->v_sync_width)) { + ch->read_addr = (uintptr_t)vblank_line_vsync_on; + ch->transfer_count = count_of(vblank_line_vsync_on); + } else if (v_scanline < v_inactive_total) { + ch->read_addr = (uintptr_t)vblank_line_vsync_off; + ch->transfer_count = count_of(vblank_line_vsync_off); + } else { + const int y = (v_scanline - v_inactive_total); + const uint line_buf_total_len = (frame_width * line_bytes_per_pixel + 3) / 4 + count_of(vactive_text_line_header); + + ch->read_addr = (uintptr_t)&line_buffers[ch_num * line_buf_total_len]; + ch->transfer_count = line_buf_total_len; + + // Fill line buffer + int char_y = y % 24; + if (line_bytes_per_pixel == 4) { + uint32_t* dst_ptr = &line_buffers[ch_num * line_buf_total_len + count_of(vactive_text_line_header)]; + uint8_t* src_ptr = &frame_buffer_display[(y / 24) * frame_width]; + for (int i = 0; i < frame_width; ++i) { + *dst_ptr++ = render_char_line(*src_ptr++, char_y); + } + } + else { + uint8_t* dst_ptr = (uint8_t*)&line_buffers[ch_num * line_buf_total_len + count_of(vactive_text_line_header)]; + uint8_t* src_ptr = &frame_buffer_display[(y / 24) * frame_width]; + uint8_t* colour_ptr = src_ptr + frame_width * frame_height; + int i = 0; + for (; i < frame_width-1; i += 2) { + uint8_t c = (*src_ptr++ - 0x20); + uint32_t bits = (c < 95) ? font_cache[c * 24 + char_y] : 0; + uint8_t colour = *colour_ptr++; + c = (*src_ptr++ - 0x20); + uint32_t bits2 = (c < 95) ? font_cache[c * 24 + char_y] : 0; + uint8_t colour2 = *colour_ptr++; + + // This ASM works around a compiler bug where the optimizer decides + // to unroll so hard it spills to the stack. + uint32_t tmp, tmp2; + asm volatile ( + "ubfx %[tmp], %[cbits], #24, #2\n\t" + "ubfx %[tmp2], %[cbits], #22, #2\n\t" + "bfi %[tmp], %[tmp2], #8, #8\n\t" + "ubfx %[tmp2], %[cbits], #20, #2\n\t" + "bfi %[tmp], %[tmp2], #16, #8\n\t" + "ubfx %[tmp2], %[cbits], #18, #2\n\t" + "bfi %[tmp], %[tmp2], #24, #8\n\t" + "muls %[tmp], %[colour], %[tmp]\n\t" + "str %[tmp], [%[dst_ptr]]\n\t" + + "ubfx %[tmp], %[cbits], #16, #2\n\t" + "ubfx %[tmp2], %[cbits], #14, #2\n\t" + "bfi %[tmp], %[tmp2], #8, #8\n\t" + "ubfx %[tmp2], %[cbits], #12, #2\n\t" + "bfi %[tmp], %[tmp2], #16, #8\n\t" + "ubfx %[tmp2], %[cbits], #10, #2\n\t" + "bfi %[tmp], %[tmp2], #24, #8\n\t" + "muls %[tmp], %[colour], %[tmp]\n\t" + "str %[tmp], [%[dst_ptr], #4]\n\t" + + "ubfx %[tmp], %[cbits], #8, #2\n\t" + "ubfx %[tmp2], %[cbits], #6, #2\n\t" + "bfi %[tmp], %[tmp2], #8, #8\n\t" + "ubfx %[tmp2], %[cbits], #4, #2\n\t" + "bfi %[tmp], %[tmp2], #16, #8\n\t" + "ubfx %[tmp2], %[cbits], #2, #2\n\t" + "bfi %[tmp], %[tmp2], #24, #8\n\t" + "muls %[tmp], %[colour], %[tmp]\n\t" + "str %[tmp], [%[dst_ptr], #8]\n\t" + + "ubfx %[tmp], %[cbits2], #24, #2\n\t" + "ubfx %[tmp2], %[cbits2], #22, #2\n\t" + "bfi %[tmp], %[tmp2], #8, #8\n\t" + "muls %[tmp], %[colour2], %[tmp]\n\t" + "and %[tmp2], %[cbits], #3\n\t" + "muls %[tmp2], %[colour], %[tmp2]\n\t" + "bfi %[tmp2], %[tmp], #16, #16\n\t" + "str %[tmp2], [%[dst_ptr], #12]\n\t" + + "ubfx %[tmp], %[cbits2], #20, #2\n\t" + "ubfx %[tmp2], %[cbits2], #18, #2\n\t" + "bfi %[tmp], %[tmp2], #8, #8\n\t" + "ubfx %[tmp2], %[cbits2], #16, #2\n\t" + "bfi %[tmp], %[tmp2], #16, #8\n\t" + "ubfx %[tmp2], %[cbits2], #14, #2\n\t" + "bfi %[tmp], %[tmp2], #24, #8\n\t" + "muls %[tmp], %[colour2], %[tmp]\n\t" + "str %[tmp], [%[dst_ptr], #16]\n\t" + + "ubfx %[tmp], %[cbits2], #12, #2\n\t" + "ubfx %[tmp2], %[cbits2], #10, #2\n\t" + "bfi %[tmp], %[tmp2], #8, #8\n\t" + "ubfx %[tmp2], %[cbits2], #8, #2\n\t" + "bfi %[tmp], %[tmp2], #16, #8\n\t" + "ubfx %[tmp2], %[cbits2], #6, #2\n\t" + "bfi %[tmp], %[tmp2], #24, #8\n\t" + "muls %[tmp], %[colour2], %[tmp]\n\t" + "str %[tmp], [%[dst_ptr], #20]\n\t" + + "ubfx %[tmp], %[cbits2], #4, #2\n\t" + "ubfx %[tmp2], %[cbits2], #2, #2\n\t" + "bfi %[tmp], %[tmp2], #8, #8\n\t" + "bfi %[tmp], %[cbits2], #16, #2\n\t" + "muls %[tmp], %[colour2], %[tmp]\n\t" + "str %[tmp], [%[dst_ptr], #24]\n\t" + : [tmp] "=&l" (tmp), + [tmp2] "=&l" (tmp2) + : [cbits] "r" (bits), + [colour] "l" (colour), + [cbits2] "r" (bits2), + [colour2] "l" (colour2), + [dst_ptr] "r" (dst_ptr) + : "cc", "memory" ); + dst_ptr += 14 * 2; + } + if (i != frame_width) { + const uint8_t c = (*src_ptr++ - 0x20); + uint32_t bits = (c < 95) ? font_cache[c * 24 + char_y] : 0; + const uint8_t colour = *colour_ptr++; + + *dst_ptr++ = colour * ((bits >> 24) & 3); + *dst_ptr++ = colour * ((bits >> 22) & 3); + *dst_ptr++ = colour * ((bits >> 20) & 3); + *dst_ptr++ = colour * ((bits >> 18) & 3); + *dst_ptr++ = colour * ((bits >> 16) & 3); + *dst_ptr++ = colour * ((bits >> 14) & 3); + *dst_ptr++ = colour * ((bits >> 12) & 3); + *dst_ptr++ = colour * ((bits >> 10) & 3); + *dst_ptr++ = colour * ((bits >> 8) & 3); + *dst_ptr++ = colour * ((bits >> 6) & 3); + *dst_ptr++ = colour * ((bits >> 4) & 3); + *dst_ptr++ = colour * ((bits >> 2) & 3); + *dst_ptr++ = colour * (bits & 3); + *dst_ptr++ = 0; + } + } + } + + if (++v_scanline == v_total_active_lines) { + v_scanline = 0; + line_num = -1; + if (flip_next) { + flip_next = false; + display->flip_now(); + } + __sev(); + } +} + +// ---------------------------------------------------------------------------- +// Experimental clock config + +static void __no_inline_not_in_flash_func(set_qmi_timing)() { + // Make sure flash is deselected - QMI doesn't appear to have a busy flag(!) + while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) + ; + + qmi_hw->m[0].timing = 0x40000202; + //qmi_hw->m[0].timing = 0x40000101; + // Force a read through XIP to ensure the timing is applied + volatile uint32_t* ptr = (volatile uint32_t*)0x14000000; + (void) *ptr; +} + +void DVHSTX::display_setup_clock() { + // Before messing with clock speeds ensure QSPI clock is nice and slow + hw_write_masked(&qmi_hw->m[0].timing, 6, QMI_M0_TIMING_CLKDIV_BITS); + + // We're going to go fast, boost the voltage a little + vreg_set_voltage(VREG_VOLTAGE_1_15); + + // Force a read through XIP to ensure the timing is applied before raising the clock rate + volatile uint32_t* ptr = (volatile uint32_t*)0x14000000; + (void) *ptr; + + const uint32_t dvi_clock_khz = timing_mode->bit_clk_khz >> 1; + uint vco_freq, post_div1, post_div2; + if (!check_sys_clock_khz(dvi_clock_khz, &vco_freq, &post_div1, &post_div2)) + panic("System clock of %u kHz cannot be exactly achieved", dvi_clock_khz); + const uint32_t freq = vco_freq / (post_div1 * post_div2); + + // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. + hw_clear_bits(&clocks_hw->clk[clk_sys].ctrl, CLOCKS_CLK_SYS_CTRL_SRC_BITS); + while (clocks_hw->clk[clk_sys].selected != 0x1) + tight_loop_contents(); + hw_write_masked(&clocks_hw->clk[clk_ref].ctrl, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, CLOCKS_CLK_REF_CTRL_SRC_BITS); + while (clocks_hw->clk[clk_ref].selected != 0x4) + tight_loop_contents(); + + // Stop the other clocks so we don't worry about overspeed + clock_stop(clk_usb); + clock_stop(clk_adc); + clock_stop(clk_peri); + clock_stop(clk_hstx); + + // Set the sys PLL to the requested freq, set USB PLL to 528MHz + pll_init(pll_sys, PLL_COMMON_REFDIV, vco_freq, post_div1, post_div2); + pll_init(pll_usb, PLL_COMMON_REFDIV, 1584 * MHZ, 3, 1); + + const uint32_t usb_pll_freq = 528 * MHZ; + + // CLK SYS = PLL USB 528MHz / 2 = 264MHz + clock_configure(clk_sys, + CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX, + CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB, + usb_pll_freq, usb_pll_freq / 2); + + // CLK PERI = PLL USB 528MHz / 4 = 132MHz + clock_configure(clk_peri, + 0, // Only AUX mux on ADC + CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB, + usb_pll_freq, usb_pll_freq / 4); + + // CLK USB = PLL USB 528MHz / 11 = 48MHz + clock_configure(clk_usb, + 0, // No GLMUX + CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB, + usb_pll_freq, + USB_CLK_KHZ * KHZ); + + // CLK ADC = PLL USB 528MHz / 11 = 48MHz + clock_configure(clk_adc, + 0, // No GLMUX + CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB, + usb_pll_freq, + USB_CLK_KHZ * KHZ); + + // CLK HSTX = Requested freq + clock_configure(clk_hstx, + 0, + CLOCKS_CLK_HSTX_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS, + freq, freq); + + // Now we are running fast set fast QSPI clock and read delay + set_qmi_timing(); +} + +void DVHSTX::write_pixel(const Point &p, uint16_t colour) +{ + *point_to_ptr16(p) = colour; +} + +void DVHSTX::write_pixel_span(const Point &p, uint l, uint16_t colour) +{ + uint16_t* ptr = point_to_ptr16(p); + for (uint i = 0; i < l; ++i) ptr[i] = colour; +} + +void DVHSTX::write_pixel_span(const Point &p, uint l, uint16_t *data) +{ + uint16_t* ptr = point_to_ptr16(p); + for (uint i = 0; i < l; ++i) ptr[i] = data[i]; +} + +void DVHSTX::read_pixel_span(const Point &p, uint l, uint16_t *data) +{ + const uint16_t* ptr = point_to_ptr16(p); + for (uint i = 0; i < l; ++i) data[i] = ptr[i]; +} + +void DVHSTX::set_palette(RGB888 new_palette[PALETTE_SIZE]) +{ + memcpy(palette, new_palette, PALETTE_SIZE * sizeof(RGB888)); +} + +void DVHSTX::set_palette_colour(uint8_t entry, RGB888 colour) +{ + palette[entry] = colour; +} + +RGB888* DVHSTX::get_palette() +{ + return palette; +} + +void DVHSTX::write_palette_pixel(const Point &p, uint8_t colour) +{ + *point_to_ptr_palette(p) = colour; +} + +void DVHSTX::write_palette_pixel_span(const Point &p, uint l, uint8_t colour) +{ + uint8_t* ptr = point_to_ptr_palette(p); + memset(ptr, colour, l); +} + +void DVHSTX::write_palette_pixel_span(const Point &p, uint l, uint8_t* data) +{ + uint8_t* ptr = point_to_ptr_palette(p); + memcpy(ptr, data, l); +} + +void DVHSTX::read_palette_pixel_span(const Point &p, uint l, uint8_t *data) +{ + const uint8_t* ptr = point_to_ptr_palette(p); + memcpy(data, ptr, l); +} + +void DVHSTX::write_text(const Point &p, const char* text, TextColour colour, bool immediate) +{ + char* ptr = (char*)point_to_ptr_text(p, immediate); + int len = std::min((int)(frame_width - p.x), (int)strlen(text)); + memcpy(ptr, text, len); + if (mode == MODE_TEXT_RGB111) memset(ptr + frame_width * frame_height, (uint8_t)colour, len); +} + +void DVHSTX::clear() +{ + memset(frame_buffer_back, 0, frame_width * frame_height * frame_bytes_per_pixel); +} + +bool DVHSTX::init(uint16_t width, uint16_t height, Mode mode_) +{ + display_width = width; + display_height = height; + frame_width = width; + frame_height = height; + mode = mode_; + + timing_mode = nullptr; + if (mode == MODE_TEXT_MONO || mode == MODE_TEXT_RGB111) { + width = 1280; + height = 720; + display_width = 91; + frame_width = 91; + display_height = 30; + frame_height = 30; + h_repeat_shift = 0; + v_repeat_shift = 0; + timing_mode = &dvi_timing_1280x720p_rb_50hz; + } + else if (width == 320 && height == 180) { + h_repeat_shift = 2; + v_repeat_shift = 2; + timing_mode = &dvi_timing_1280x720p_rb_50hz; + } + else if (width == 640 && height == 360) { + h_repeat_shift = 1; + v_repeat_shift = 1; + timing_mode = &dvi_timing_1280x720p_rb_50hz; + } + else if (width == 480 && height == 270) { + h_repeat_shift = 2; + v_repeat_shift = 2; + timing_mode = &dvi_timing_1920x1080p_rb2_30hz; + } + else + { + uint16_t full_width = display_width; + uint16_t full_height = display_height; + h_repeat_shift = 0; + v_repeat_shift = 0; + + if (display_width < 640) { + h_repeat_shift = 1; + full_width *= 2; + } + + if (display_height < 400) { + v_repeat_shift = 1; + full_height *= 2; + } + + if (full_width == 640) { + if (full_height == 480) timing_mode = &dvi_timing_640x480p_60hz; + } + else if (full_width == 720) { + if (full_height == 480) timing_mode = &dvi_timing_720x480p_60hz; + else if (full_height == 400) timing_mode = &dvi_timing_720x400p_70hz; + else if (full_height == 576) timing_mode = &dvi_timing_720x576p_50hz; + } + else if (full_width == 800) { + if (full_height == 600) timing_mode = &dvi_timing_800x600p_60hz; + else if (full_height == 480) timing_mode = &dvi_timing_800x480p_60hz; + else if (full_height == 450) timing_mode = &dvi_timing_800x450p_60hz; + } + else if (full_width == 960) { + if (full_height == 540) timing_mode = &dvi_timing_960x540p_60hz; + } + else if (full_width == 1024) { + if (full_height == 768) timing_mode = &dvi_timing_1024x768_rb_60hz; + } + } + + if (!timing_mode) { + printf("Unsupported resolution %dx%d", width, height); + return false; + } + + display = this; + display_palette = get_palette(); + + display_setup_clock(); + + stdio_init_all(); + + v_inactive_total = timing_mode->v_front_porch + timing_mode->v_sync_width + timing_mode->v_back_porch; + v_total_active_lines = v_inactive_total + timing_mode->v_active_lines; + v_repeat = 1 << v_repeat_shift; + h_repeat = 1 << h_repeat_shift; + + vblank_line_vsync_off[0] |= timing_mode->h_front_porch; + vblank_line_vsync_off[2] |= timing_mode->h_sync_width; + vblank_line_vsync_off[4] |= timing_mode->h_back_porch + timing_mode->h_active_pixels; + + vblank_line_vsync_on[0] |= timing_mode->h_front_porch; + vblank_line_vsync_on[2] |= timing_mode->h_sync_width; + vblank_line_vsync_on[4] |= timing_mode->h_back_porch + timing_mode->h_active_pixels; + + vactive_line_header[0] |= timing_mode->h_front_porch; + vactive_line_header[2] |= timing_mode->h_sync_width; + vactive_line_header[4] |= timing_mode->h_back_porch; + vactive_line_header[6] |= timing_mode->h_active_pixels; + + vactive_text_line_header[0] |= timing_mode->h_front_porch; + vactive_text_line_header[2] |= timing_mode->h_sync_width; + vactive_text_line_header[4] |= timing_mode->h_back_porch; + vactive_text_line_header[7+6] |= timing_mode->h_active_pixels - 6; + + switch (mode) { + case MODE_RGB565: + frame_bytes_per_pixel = 2; + line_bytes_per_pixel = 2; + break; + case MODE_PALETTE: + frame_bytes_per_pixel = 1; + line_bytes_per_pixel = 4; + break; + case MODE_RGB888: + frame_bytes_per_pixel = 4; + line_bytes_per_pixel = 4; + break; + case MODE_TEXT_MONO: + frame_bytes_per_pixel = 1; + line_bytes_per_pixel = 4; + break; + case MODE_TEXT_RGB111: + frame_bytes_per_pixel = 2; + line_bytes_per_pixel = 14; + break; + default: + printf("Unsupported mode %d", (int)mode); + return false; + } + + frame_buffer_display = (uint8_t*)malloc(frame_width * frame_height * frame_bytes_per_pixel); + frame_buffer_back = (uint8_t*)malloc(frame_width * frame_height * frame_bytes_per_pixel); + memset(frame_buffer_display, 0, frame_width * frame_height * frame_bytes_per_pixel); + memset(frame_buffer_back, 0, frame_width * frame_height * frame_bytes_per_pixel); + + memset(palette, 0, PALETTE_SIZE); + + frame_buffer_display = frame_buffer_display; + + const bool is_text_mode = (mode == MODE_TEXT_MONO || mode == MODE_TEXT_RGB111); + const int frame_pixel_words = (frame_width * h_repeat * line_bytes_per_pixel + 3) >> 2; + const int frame_line_words = frame_pixel_words + (is_text_mode ? count_of(vactive_text_line_header) : count_of(vactive_line_header)); + const int frame_lines = (v_repeat == 1) ? NUM_CHANS : NUM_FRAME_LINES; + line_buffers = (uint32_t*)malloc(frame_line_words * 4 * frame_lines); + + for (int i = 0; i < frame_lines; ++i) + { + if (is_text_mode) memcpy(&line_buffers[i * frame_line_words], vactive_text_line_header, count_of(vactive_text_line_header) * sizeof(uint32_t)); + else memcpy(&line_buffers[i * frame_line_words], vactive_line_header, count_of(vactive_line_header) * sizeof(uint32_t)); + } + + if (mode == MODE_TEXT_RGB111) { + // Need to pre-render the font to RAM to be fast enough. + font_cache = (uint32_t*)malloc(4 * FONT->line_height * 95); + uint32_t* font_cache_ptr = font_cache; + for (int c = 0x20; c < 128; ++c) { + for (int y = 0; y < FONT->line_height; ++y) { + *font_cache_ptr++ = render_char_line(c, y); + } + } + } + + switch (mode) { + case MODE_RGB565: + // Configure HSTX's TMDS encoder for RGB565 + hstx_ctrl_hw->expand_tmds = + 4 << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB | + 8 << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB | + 5 << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB | + 3 << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB | + 4 << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB | + 29 << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB; + + // Pixels (TMDS) come in 2 16-bit chunks. Control symbols (RAW) are an + // entire 32-bit word. + hstx_ctrl_hw->expand_shift = + 2 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB | + 16 << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB | + 1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB | + 0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB; + break; + + case MODE_PALETTE: + // Configure HSTX's TMDS encoder for RGB888 + hstx_ctrl_hw->expand_tmds = + 7 << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB | + 16 << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB | + 7 << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB | + 8 << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB | + 7 << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB | + 0 << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB; + + // Pixels and control symbols (RAW) are an + // entire 32-bit word. + hstx_ctrl_hw->expand_shift = + 1 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB | + 0 << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB | + 1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB | + 0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB; + break; + + case MODE_TEXT_MONO: + // Configure HSTX's TMDS encoder for 2bpp + hstx_ctrl_hw->expand_tmds = + 1 << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB | + 18 << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB | + 1 << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB | + 18 << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB | + 1 << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB | + 18 << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB; + + // Pixels and control symbols (RAW) are an + // entire 32-bit word. + hstx_ctrl_hw->expand_shift = + 14 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB | + 30 << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB | + 1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB | + 0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB; + break; + + case MODE_TEXT_RGB111: + // Configure HSTX's TMDS encoder for RGB222 + hstx_ctrl_hw->expand_tmds = + 1 << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB | + 0 << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB | + 1 << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB | + 29 << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB | + 1 << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB | + 26 << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB; + + // Pixels (TMDS) come in 4 8-bit chunks. Control symbols (RAW) are an + // entire 32-bit word. + hstx_ctrl_hw->expand_shift = + 4 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB | + 8 << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB | + 1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB | + 0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB; + break; + + default: + printf("Unsupported mode %d", (int)mode); + return false; + } + + // Serial output config: clock period of 5 cycles, pop from command + // expander every 5 cycles, shift the output shiftreg by 2 every cycle. + hstx_ctrl_hw->csr = 0; + hstx_ctrl_hw->csr = + HSTX_CTRL_CSR_EXPAND_EN_BITS | + 5u << HSTX_CTRL_CSR_CLKDIV_LSB | + 5u << HSTX_CTRL_CSR_N_SHIFTS_LSB | + 2u << HSTX_CTRL_CSR_SHIFT_LSB | + HSTX_CTRL_CSR_EN_BITS; + + // HSTX outputs 0 through 7 appear on GPIO 12 through 19. + + // Assign clock pair to two neighbouring pins: + hstx_ctrl_hw->bit[1] = HSTX_CTRL_BIT0_CLK_BITS; + hstx_ctrl_hw->bit[0] = HSTX_CTRL_BIT0_CLK_BITS | HSTX_CTRL_BIT0_INV_BITS; + for (uint lane = 0; lane < 3; ++lane) { + // For each TMDS lane, assign it to the correct GPIO pair based on the + // desired pinout: + static const int lane_to_output_bit[3] = {2, 4, 6}; + int bit = lane_to_output_bit[lane]; + // Output even bits during first half of each HSTX cycle, and odd bits + // during second half. The shifter advances by two bits each cycle. + uint32_t lane_data_sel_bits = + (lane * 10 ) << HSTX_CTRL_BIT0_SEL_P_LSB | + (lane * 10 + 1) << HSTX_CTRL_BIT0_SEL_N_LSB; + // The two halves of each pair get identical data, but one pin is inverted. + hstx_ctrl_hw->bit[bit ] = lane_data_sel_bits | HSTX_CTRL_BIT0_INV_BITS; + hstx_ctrl_hw->bit[bit + 1] = lane_data_sel_bits; + } + + for (int i = 12; i <= 19; ++i) { + gpio_set_function(i, GPIO_FUNC_HSTX); + gpio_set_drive_strength(i, GPIO_DRIVE_STRENGTH_4MA); + } + + // Always use the bottom channels + dma_claim_mask((1 << NUM_CHANS) - 1); + + // The channels are set up identically, to transfer a whole scanline and + // then chain to the next channel. Each time a channel finishes, we + // reconfigure the one that just finished, meanwhile the other channel(s) + // are already making progress. + // Using just 2 channels was insufficient to avoid issues with the IRQ. + dma_channel_config c; + c = dma_channel_get_default_config(0); + channel_config_set_chain_to(&c, 1); + channel_config_set_dreq(&c, DREQ_HSTX); + dma_channel_configure( + 0, + &c, + &hstx_fifo_hw->fifo, + vblank_line_vsync_off, + count_of(vblank_line_vsync_off), + false + ); + c = dma_channel_get_default_config(1); + channel_config_set_chain_to(&c, 2); + channel_config_set_dreq(&c, DREQ_HSTX); + dma_channel_configure( + 1, + &c, + &hstx_fifo_hw->fifo, + vblank_line_vsync_off, + count_of(vblank_line_vsync_off), + false + ); + for (int i = 2; i < NUM_CHANS; ++i) { + c = dma_channel_get_default_config(i); + channel_config_set_chain_to(&c, (i+1) % NUM_CHANS); + channel_config_set_dreq(&c, DREQ_HSTX); + dma_channel_configure( + i, + &c, + &hstx_fifo_hw->fifo, + vblank_line_vsync_off, + count_of(vblank_line_vsync_off), + false + ); + } + + dma_hw->ints0 = (1 << NUM_CHANS) - 1; + dma_hw->inte0 = (1 << NUM_CHANS) - 1; + if (is_text_mode) irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler_text); + else irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler); + irq_set_enabled(DMA_IRQ_0, true); + + dma_channel_start(0); + + for (int i = 0; i < frame_height; ++i) { + memset(&frame_buffer_display[i * frame_width * frame_bytes_per_pixel], i, frame_width * frame_bytes_per_pixel); + } + + return true; +} + +void DVHSTX::flip_blocking() { + wait_for_vsync(); + flip_now(); +} + +void DVHSTX::flip_now() { + std::swap(frame_buffer_display, frame_buffer_back); +} + +void DVHSTX::wait_for_vsync() { + while (v_scanline >= timing_mode->v_front_porch) __wfe(); +} + +void DVHSTX::flip_async() { + flip_next = true; +} + +void DVHSTX::wait_for_flip() { + while (flip_next) __wfe(); +} \ No newline at end of file diff --git a/drivers/dvhstx/dvhstx.hpp b/drivers/dvhstx/dvhstx.hpp new file mode 100644 index 0000000..6924bfa --- /dev/null +++ b/drivers/dvhstx/dvhstx.hpp @@ -0,0 +1,157 @@ +#pragma once + +#include + +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "common/pimoroni_common.hpp" +#include "common/pimoroni_i2c.hpp" +#include "libraries/pico_graphics/pico_graphics.hpp" + +// DVI HSTX driver for use with Pimoroni PicoGraphics + +namespace pimoroni { + + // Digital Video using HSTX + // Valid screen modes are: + // Pixel doubled: 640x480 (60Hz), 720x480 (60Hz), 720x400 (70Hz), 720x576 (50Hz), + // 800x600 (60Hz), 800x480 (60Hz), 800x450 (60Hz), 960x540 (60Hz), 1024x768 (60Hz) + // Pixel doubled or quadrupled: 1280x720 (50Hz) + // + // Giving valid resolutions: + // 320x180, 640x360 (well supported, square pixels on a 16:9 display) + // 480x270, 400x225 (sometimes supported, square pixels on a 16:9 display) + // 320x240, 360x240, 360x200, 360x288, 400x300, 512x384 (well supported, but pixels aren't square) + // 400x240 (sometimes supported, pixels aren't square) + // + // Note that the double buffer is in RAM, so 640x360 uses almost all of the available RAM. + class DVHSTX { + public: + static constexpr int PALETTE_SIZE = 256; + + enum Mode { + MODE_PALETTE = 2, + MODE_RGB565 = 1, + MODE_RGB888 = 3, + MODE_TEXT_MONO = 4, + MODE_TEXT_RGB111 = 5, + }; + + enum TextColour { + TEXT_BLACK = 0, + TEXT_RED = 0b1000000, + TEXT_GREEN = 0b0001000, + TEXT_BLUE = 0b0000001, + TEXT_YELLOW = 0b1001000, + TEXT_MAGENTA = 0b1000001, + TEXT_CYAN = 0b0001001, + TEXT_WHITE = 0b1001001, + }; + + //-------------------------------------------------- + // Variables + //-------------------------------------------------- + protected: + friend void vsync_callback(); + + uint16_t display_width = 320; + uint16_t display_height = 180; + uint16_t frame_width = 320; + uint16_t frame_height = 180; + uint8_t frame_bytes_per_pixel = 2; + uint8_t bank = 0; + uint8_t h_repeat = 4; + uint8_t v_repeat = 4; + Mode mode = MODE_RGB565; + + public: + DVHSTX() + {} + + //-------------------------------------------------- + // Methods + //-------------------------------------------------- + public: + // 16bpp interface + void write_pixel(const Point &p, uint16_t colour); + void write_pixel_span(const Point &p, uint l, uint16_t colour); + void write_pixel_span(const Point &p, uint l, uint16_t *data); + void read_pixel_span(const Point &p, uint l, uint16_t *data); + + // 256 colour palette mode. + void set_palette(RGB888 new_palette[PALETTE_SIZE]); + void set_palette_colour(uint8_t entry, RGB888 colour); + RGB888* get_palette(); + + void write_palette_pixel(const Point &p, uint8_t colour); + void write_palette_pixel_span(const Point &p, uint l, uint8_t colour); + void write_palette_pixel_span(const Point &p, uint l, uint8_t* data); + void read_palette_pixel_span(const Point &p, uint l, uint8_t *data); + + // Text mode (91 x 30) + // Immediate writes to the active buffer instead of the back buffer + void write_text(const Point &p, const char* text, TextColour colour = TEXT_WHITE, bool immediate = false); + + void clear(); + + bool init(uint16_t width, uint16_t height, Mode mode = MODE_RGB565); + + // Wait for vsync and then flip the buffers + void flip_blocking(); + + // Flip immediately without waiting for vsync + void flip_now(); + + void wait_for_vsync(); + + // flip_async queues a flip to happen next vsync but returns without blocking. + // You should call wait_for_flip before doing any more reads or writes, defining sprites, etc. + void flip_async(); + void wait_for_flip(); + + // DMA handlers, should not be called externally + void gfx_dma_handler(); + void text_dma_handler(); + + private: + RGB888 palette[PALETTE_SIZE]; + + uint8_t* frame_buffer_display; + uint8_t* frame_buffer_back; + uint32_t* font_cache; + + uint16_t* point_to_ptr16(const Point &p) const { + return ((uint16_t*)frame_buffer_back) + (p.y * (uint32_t)frame_width) + p.x; + } + + uint8_t* point_to_ptr_palette(const Point &p) const { + return frame_buffer_back + (p.y * (uint32_t)frame_width) + p.x; + } + + uint8_t* point_to_ptr_text(const Point &p, bool immediate) const { + const uint32_t offset = (p.y * (uint32_t)frame_width) + p.x; + if (immediate) return frame_buffer_display + offset; + return frame_buffer_back + offset; + } + + void display_setup_clock(); + + // DMA scanline filling + uint ch_num = 0; + int line_num = -1; + + volatile int v_scanline = 2; + volatile bool flip_next; + + uint32_t* line_buffers; + const struct dvi_timing* timing_mode; + uint v_inactive_total; + uint v_total_active_lines; + + uint h_repeat_shift; + uint v_repeat_shift; + int line_bytes_per_pixel; + + uint32_t* display_palette = nullptr; + }; +} diff --git a/drivers/dvhstx/dvi.cpp b/drivers/dvhstx/dvi.cpp new file mode 100644 index 0000000..5d4011e --- /dev/null +++ b/drivers/dvhstx/dvi.cpp @@ -0,0 +1,265 @@ +#include + +#include "dvi.hpp" + +// VGA -- we do this mode properly, with a pretty comfortable clk_sys (252 MHz) +const struct dvi_timing dvi_timing_640x480p_60hz = { + .h_sync_polarity = false, + .h_front_porch = 16, + .h_sync_width = 96, + .h_back_porch = 48, + .h_active_pixels = 640, + + .v_sync_polarity = false, + .v_front_porch = 10, + .v_sync_width = 2, + .v_back_porch = 33, + .v_active_lines = 480, + + .bit_clk_khz = 252000 +}; + +// SVGA -- completely by-the-book but requires 400 MHz clk_sys +const struct dvi_timing dvi_timing_800x600p_60hz = { + .h_sync_polarity = false, + .h_front_porch = 44, + .h_sync_width = 128, + .h_back_porch = 88, + .h_active_pixels = 800, + + .v_sync_polarity = false, + .v_front_porch = 1, + .v_sync_width = 4, + .v_back_porch = 23, + .v_active_lines = 600, + + .bit_clk_khz = 400000 +}; + +// 720x480 - timings from dumping the EDID of my monitor +const struct dvi_timing dvi_timing_720x480p_60hz = { + .h_sync_polarity = false, + .h_front_porch = 16, + .h_sync_width = 62, + .h_back_porch = 60, + .h_active_pixels = 720, + + .v_sync_polarity = false, + .v_front_porch = 9, + .v_sync_width = 6, + .v_back_porch = 30, + .v_active_lines = 480, + + .bit_clk_khz = 270000 +}; + +// 720x576@50Hz - CEA timing +const struct dvi_timing dvi_timing_720x576p_50hz = { + .h_sync_polarity = false, + .h_front_porch = 12, + .h_sync_width = 64, + .h_back_porch = 68, + .h_active_pixels = 720, + + .v_sync_polarity = false, + .v_front_porch = 5, + .v_sync_width = 5, + .v_back_porch = 39, + .v_active_lines = 576, + + .bit_clk_khz = 270000 +}; + +// 720x400@70Hz - "IBM" timing +const struct dvi_timing dvi_timing_720x400p_70hz = { + .h_sync_polarity = false, + .h_front_porch = 18, + .h_sync_width = 108, + .h_back_porch = 54, + .h_active_pixels = 720, + + .v_sync_polarity = true, + .v_front_porch = 13, + .v_sync_width = 2, + .v_back_porch = 34, + .v_active_lines = 400, + + .bit_clk_khz = 283200 +}; + +// 800x480p 60 Hz (note this doesn't seem to be a CEA mode, I just used the +// output of `cvt 800 480 60`), 295 MHz bit clock +const struct dvi_timing dvi_timing_800x480p_60hz = { + .h_sync_polarity = false, + .h_front_porch = 24, + .h_sync_width = 72, + .h_back_porch = 96, + .h_active_pixels = 800, + + .v_sync_polarity = true, + .v_front_porch = 3, + .v_sync_width = 10, + .v_back_porch = 7, + .v_active_lines = 480, + + .bit_clk_khz = 295200 +}; + +// 800x450p 60 Hz Similarly not a CEA mode, but is 16:9 +const struct dvi_timing dvi_timing_800x450p_60hz = { + .h_sync_polarity = false, + .h_front_porch = 24, + .h_sync_width = 72, + .h_back_porch = 96, + .h_active_pixels = 800, + + .v_sync_polarity = true, + .v_front_porch = 3, + .v_sync_width = 5, + .v_back_porch = 10, + .v_active_lines = 450, + + .bit_clk_khz = 278400 +}; + +// SVGA reduced blanking (355 MHz bit clock) -- valid CVT mode, less common +// than fully-blanked SVGA, but doesn't require such a high system clock +const struct dvi_timing dvi_timing_800x600p_reduced_60hz = { + .h_sync_polarity = true, + .h_front_porch = 48, + .h_sync_width = 32, + .h_back_porch = 80, + .h_active_pixels = 800, + + .v_sync_polarity = false, + .v_front_porch = 3, + .v_sync_width = 4, + .v_back_porch = 11, + .v_active_lines = 600, + + .bit_clk_khz = 354000 +}; + +// Also known as qHD, bit uncommon, but it's a nice modest-resolution 16:9 +// aspect mode. Pixel clock 40.75 MHz for full CVT mode (no reduced blanking) +const struct dvi_timing dvi_timing_960x540p_60hz = { + .h_sync_polarity = false, + .h_front_porch = 32, + .h_sync_width = 96, + .h_back_porch = 128, + .h_active_pixels = 960, + + .v_sync_polarity = true, + .v_front_porch = 3, + .v_sync_width = 5, + .v_back_porch = 14, + .v_active_lines = 540, + + .bit_clk_khz = 408000 +}; + +// Also known as qHD, bit uncommon, but it's a nice modest-resolution 16:9 +// aspect mode. Pixel clock 33.5 MHz for 50Hz CVT mode (no reduced blanking) +const struct dvi_timing dvi_timing_960x540p_50hz = { + .h_sync_polarity = false, + .h_front_porch = 24, + .h_sync_width = 96, + .h_back_porch = 120, + .h_active_pixels = 960, + + .v_sync_polarity = true, + .v_front_porch = 3, + .v_sync_width = 5, + .v_back_porch = 11, + .v_active_lines = 540, + + .bit_clk_khz = 336000 +}; + +// 1024x768, CVT-RB +const struct dvi_timing dvi_timing_1024x768_rb_60hz = { + .h_sync_polarity = true, + .h_front_porch = 48, + .h_sync_width = 32, + .h_back_porch = 80, + .h_active_pixels = 1024, + + .v_sync_polarity = false, + .v_front_porch = 3, + .v_sync_width = 4, + .v_back_porch = 15, + .v_active_lines = 768, + + .bit_clk_khz = 560000 +}; + +// 720p50, this is a standard HD mode, the CVT-RB variant +// should be widely supported +const struct dvi_timing dvi_timing_1280x720p_rb_50hz = { + .h_sync_polarity = true, + .h_front_porch = 48, + .h_sync_width = 32, + .h_back_porch = 80, + .h_active_pixels = 1280, + + .v_sync_polarity = false, + .v_front_porch = 3, + .v_sync_width = 5, + .v_back_porch = 9, + .v_active_lines = 720, + + .bit_clk_khz = 528000 +}; + +// 720p60, this is the CVT-RB variant, again should be widely supported +const struct dvi_timing dvi_timing_1280x720p_rb_60hz = { + .h_sync_polarity = true, + .h_front_porch = 48, + .h_sync_width = 32, + .h_back_porch = 80, + .h_active_pixels = 1280, + + .v_sync_polarity = false, + .v_front_porch = 3, + .v_sync_width = 5, + .v_back_porch = 13, + .v_active_lines = 720, + + .bit_clk_khz = 640000 +}; + +// 1080p30 - not a normal mode but seems to work on a wide variety of hardware +// Strictly speaking RB2 should have a clock speed matching the target frequency more closely +// but it seems to work! +const struct dvi_timing dvi_timing_1920x1080p_rb2_30hz = { + .h_sync_polarity = true, + .h_front_porch = 8, + .h_sync_width = 32, + .h_back_porch = 40, + .h_active_pixels = 1920, + + .v_sync_polarity = false, + .v_front_porch = 7, + .v_sync_width = 8, + .v_back_porch = 6, + .v_active_lines = 1080, + + .bit_clk_khz = 660000 +}; + +// 1440p24 YOLO - works on my Dell Ultrasharp, that most forgiving of monitors. May require a little more than 1.3V +const struct dvi_timing dvi_timing_2560x1440p_yolo_24hz = { + .h_sync_polarity = true, + .h_front_porch = 8, + .h_sync_width = 32, + .h_back_porch = 20, + .h_active_pixels = 2560, + + .v_sync_polarity = false, + .v_front_porch = 2, + .v_sync_width = 6, + .v_back_porch = 2, + .v_active_lines = 1440, + + .bit_clk_khz = 912000 +}; diff --git a/drivers/dvhstx/dvi.hpp b/drivers/dvhstx/dvi.hpp new file mode 100644 index 0000000..9d8221a --- /dev/null +++ b/drivers/dvhstx/dvi.hpp @@ -0,0 +1,62 @@ +#pragma once + +// ---------------------------------------------------------------------------- +// DVI constants + +#define TMDS_CTRL_00 0x354u +#define TMDS_CTRL_01 0x0abu +#define TMDS_CTRL_10 0x154u +#define TMDS_CTRL_11 0x2abu + +#define TMDS_BALANCED_LOW 0x307u +#define TMDS_BALANCED_HIGH 0x2f0u + +#define TMDS_BLACK_A 0x100u +#define TMDS_BLACK_B 0x1ffu + +#define SYNC_V0_H0 (TMDS_CTRL_00 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) +#define SYNC_V0_H1 (TMDS_CTRL_01 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) +#define SYNC_V1_H0 (TMDS_CTRL_10 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) +#define SYNC_V1_H1 (TMDS_CTRL_11 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) +#define MISSING_PIXEL (TMDS_BALANCED_LOW | (TMDS_BALANCED_LOW << 10) | (TMDS_BALANCED_HIGH << 20)) +#define BLACK_PIXEL (TMDS_BALANCED_LOW | (TMDS_BALANCED_LOW << 10) | (TMDS_BALANCED_LOW << 20)) +#define BLACK_PIXEL_A (TMDS_BLACK_A | (TMDS_BLACK_A << 10) | (TMDS_BLACK_A << 20)) +#define BLACK_PIXEL_B (TMDS_BLACK_B | (TMDS_BLACK_B << 10) | (TMDS_BLACK_B << 20)) + +#define HSTX_CMD_RAW (0x0u << 12) +#define HSTX_CMD_RAW_REPEAT (0x1u << 12) +#define HSTX_CMD_TMDS (0x2u << 12) +#define HSTX_CMD_TMDS_REPEAT (0x3u << 12) +#define HSTX_CMD_NOP (0xfu << 12) + +struct dvi_timing { + bool h_sync_polarity; + uint h_front_porch; + uint h_sync_width; + uint h_back_porch; + uint h_active_pixels; + + bool v_sync_polarity; + uint v_front_porch; + uint v_sync_width; + uint v_back_porch; + uint v_active_lines; + + uint bit_clk_khz; +}; + +extern const struct dvi_timing dvi_timing_640x480p_60hz; +extern const struct dvi_timing dvi_timing_720x480p_60hz; +extern const struct dvi_timing dvi_timing_720x576p_50hz; +extern const struct dvi_timing dvi_timing_720x400p_70hz; +extern const struct dvi_timing dvi_timing_800x450p_60hz; +extern const struct dvi_timing dvi_timing_800x480p_60hz; +extern const struct dvi_timing dvi_timing_800x600p_60hz; +extern const struct dvi_timing dvi_timing_960x540p_60hz; +extern const struct dvi_timing dvi_timing_960x540p_50hz; +extern const struct dvi_timing dvi_timing_1024x768_rb_60hz; +extern const struct dvi_timing dvi_timing_1280x720p_rb_50hz; +extern const struct dvi_timing dvi_timing_1280x720p_rb_60hz; +extern const struct dvi_timing dvi_timing_1920x1080p_rb2_30hz; +extern const struct dvi_timing dvi_timing_1920x1080p_yolo_50hz; +extern const struct dvi_timing dvi_timing_1920x1080p_yolo_60hz; diff --git a/drivers/dvhstx/font.h b/drivers/dvhstx/font.h new file mode 100644 index 0000000..cab34e1 --- /dev/null +++ b/drivers/dvhstx/font.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _FONT_H +#define _FONT_H + +#include "pico/types.h" + +typedef struct { + uint16_t bitmap_index; + uint16_t adv_w; + int8_t box_w, box_h, ofs_x, ofs_y; +} __attribute__((packed)) lv_font_fmt_txt_glyph_dsc_t; + +typedef struct { + uint16_t range_start, range_length, glyph_id_start, list_length; + void *unicode_list, *glyph_id_ofs_list; + enum { + LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + } type; +} lv_font_fmt_txt_cmap_t; + +typedef struct { + const uint8_t *glyph_bitmap; + const lv_font_fmt_txt_glyph_dsc_t *glyph_dsc; + const lv_font_fmt_txt_cmap_t *cmaps; + uint8_t cmap_num, bpp, kern_scale, kern_classes; + void *kern_dsc; +} lv_font_fmt_txt_dsc_t; + +typedef struct { + lv_font_fmt_txt_dsc_t *dsc; + uint8_t line_height, base_line; +} lv_font_t; + +#define FONT_HEIGHT 23 +extern const lv_font_t intel_one_mono; + +#endif //SOFTWARE_FONT_H \ No newline at end of file diff --git a/drivers/dvhstx/intel_one_mono_2bpp.c b/drivers/dvhstx/intel_one_mono_2bpp.c new file mode 100644 index 0000000..58e1698 --- /dev/null +++ b/drivers/dvhstx/intel_one_mono_2bpp.c @@ -0,0 +1,895 @@ +/******************************************************************************* + * Size: 22 px + * Bpp: 2 + * Opts: --bpp 2 --size 22 --no-compress --font IntelOneMono-Medium.ttf --range 32-127 --format lvgl -o intel_one_mono.c + ******************************************************************************/ + +// The Intel One Mono font is licensed under the SIL Open Font License, Version 1.1: +// https://github.com/intel/intel-one-mono/blob/main/OFL.txt + +// This file is created using https://lvgl.io/tools/fontconverter + +#include "font.h" + +#ifndef INTEL_ONE_MONO +#define INTEL_ONE_MONO 1 +#endif + +#if INTEL_ONE_MONO + +/*----------------- + * BITMAPS + *----------------*/ + +/*Store the image of the glyphs*/ +static const uint8_t glyph_bitmap[] = { + /* U+0020 " " */ + + /* U+0021 "!" */ + 0x7f, 0xc7, 0xfc, 0x3f, 0x83, 0xf8, 0x3f, 0x42, + 0xf4, 0x2f, 0x1, 0xf0, 0x1f, 0x0, 0xe0, 0x0, + 0x0, 0x0, 0x5, 0x3, 0xf8, 0x7f, 0xc3, 0xf4, + + /* U+0022 "\"" */ + 0xbd, 0xb, 0xd7, 0xd0, 0xbd, 0x7d, 0xb, 0xc7, + 0xd0, 0x7c, 0x7d, 0x7, 0xc3, 0xc0, 0x7c, 0x3c, + 0x7, 0xc1, 0x40, 0x14, + + /* U+0023 "#" */ + 0x0, 0x3c, 0x3c, 0x0, 0xe, 0xe, 0x0, 0x3, + 0x43, 0x80, 0x16, 0xe6, 0xe5, 0x1f, 0xff, 0xff, + 0x87, 0xf8, 0x2f, 0xe0, 0xd, 0xb, 0x0, 0x3, + 0x43, 0x80, 0x16, 0xc0, 0xe5, 0xf, 0xfa, 0xbf, + 0xc3, 0xff, 0xff, 0xf0, 0xf, 0xf, 0x0, 0x3, + 0x83, 0x80, 0x0, 0xd0, 0xe0, 0x0, 0x74, 0x74, + 0x0, + + /* U+0024 "$" */ + 0x0, 0x3c, 0x0, 0x0, 0xf0, 0x0, 0x3, 0xc0, + 0x0, 0xbf, 0xf8, 0xf, 0xff, 0xf4, 0xfe, 0x56, + 0xc3, 0xd0, 0x0, 0xf, 0x40, 0x0, 0x3f, 0x40, + 0x0, 0x7f, 0xe4, 0x0, 0x7f, 0xfd, 0x0, 0xb, + 0xfd, 0x0, 0x1, 0xf8, 0x0, 0x2, 0xf0, 0x0, + 0xb, 0xde, 0x40, 0x7e, 0xbf, 0xff, 0xf0, 0x6f, + 0xfd, 0x0, 0x3, 0xc0, 0x0, 0xf, 0x0, 0x0, + 0x3c, 0x0, 0x0, 0x50, 0x0, + + /* U+0025 "%" */ + 0xb, 0xe0, 0xb, 0xc2, 0xeb, 0xc0, 0xf0, 0x38, + 0x1c, 0x2d, 0x3, 0x81, 0xd3, 0x80, 0x38, 0x1d, + 0x70, 0x3, 0xc2, 0xcd, 0x0, 0xf, 0xf4, 0x0, + 0x0, 0x14, 0x1f, 0xc0, 0x0, 0x57, 0xef, 0x0, + 0xc, 0xb0, 0x74, 0x2, 0xcf, 0x3, 0x80, 0x74, + 0xf0, 0x38, 0xf, 0xb, 0x7, 0x42, 0xe0, 0x7e, + 0xf0, 0x7c, 0x1, 0xfc, 0x0, + + /* U+0026 "&" */ + 0x0, 0xbf, 0xd0, 0x3, 0xff, 0xd0, 0xb, 0xc0, + 0x0, 0xb, 0x80, 0x0, 0xb, 0xc0, 0x0, 0x3, + 0xd0, 0x10, 0x2, 0xf0, 0x3c, 0xb, 0xfc, 0x78, + 0x3f, 0x7d, 0xf0, 0xbc, 0x2f, 0xe0, 0xf4, 0xf, + 0xd0, 0xf4, 0x7, 0xe0, 0xbc, 0xf, 0xf0, 0x3f, + 0xfd, 0xbc, 0x1f, 0xf4, 0x3e, + + /* U+0027 "'" */ + 0xfd, 0xfd, 0xfd, 0xbc, 0xbc, 0xbc, 0xbc, 0x14, + + /* U+0028 "(" */ + 0x0, 0x0, 0x0, 0x0, 0x2c, 0x0, 0x1f, 0xd0, + 0x7, 0xf0, 0x2, 0xf8, 0x0, 0x7e, 0x0, 0xf, + 0x80, 0x2, 0xf0, 0x0, 0x3e, 0x0, 0x7, 0xc0, + 0x0, 0xbc, 0x0, 0xb, 0xc0, 0x0, 0xbc, 0x0, + 0x7, 0xc0, 0x0, 0x3d, 0x0, 0x2, 0xf0, 0x0, + 0xf, 0x80, 0x0, 0x7d, 0x0, 0x2, 0xf8, 0x0, + 0xb, 0xe0, 0x0, 0x1f, 0xd0, 0x0, 0x2c, 0x0, + 0x0, 0x0, + + /* U+0029 ")" */ + 0x0, 0x0, 0x0, 0xb4, 0x0, 0x3, 0xf8, 0x0, + 0x1, 0xfc, 0x0, 0x0, 0xfc, 0x0, 0x0, 0xfc, + 0x0, 0x0, 0xf8, 0x0, 0x1, 0xf0, 0x0, 0x3, + 0xe0, 0x0, 0xb, 0xc0, 0x0, 0x1f, 0x0, 0x0, + 0x7c, 0x0, 0x1, 0xf0, 0x0, 0xb, 0xc0, 0x0, + 0x3e, 0x0, 0x1, 0xf0, 0x0, 0xf, 0x80, 0x0, + 0xfc, 0x0, 0xf, 0xc0, 0x1, 0xfc, 0x0, 0x3f, + 0x80, 0x0, 0xb4, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+002A "*" */ + 0x0, 0x7c, 0x0, 0x0, 0x7c, 0x0, 0x0, 0x38, + 0x0, 0x38, 0x34, 0x78, 0xbf, 0x66, 0xfc, 0x16, + 0xbe, 0x50, 0x0, 0xbc, 0x0, 0x3, 0xcb, 0x40, + 0xf, 0x83, 0xe0, 0xf, 0x1, 0xe0, 0x1, 0x0, + 0x40, + + /* U+002B "+" */ + 0x0, 0x0, 0x0, 0x0, 0x7c, 0x0, 0x0, 0x7c, + 0x0, 0x0, 0x7c, 0x0, 0x0, 0x7c, 0x0, 0xbf, + 0xff, 0xfc, 0xbf, 0xff, 0xfc, 0x0, 0x7c, 0x0, + 0x0, 0x7c, 0x0, 0x0, 0x7c, 0x0, 0x0, 0x7c, + 0x0, 0x0, 0x7c, 0x0, + + /* U+002C "," */ + 0x3, 0xfc, 0x3, 0xf8, 0x3, 0xf4, 0x7, 0xf0, + 0xf, 0xd0, 0x2f, 0x40, 0x3d, 0x0, + + /* U+002D "-" */ + 0x15, 0x55, 0x5f, 0xff, 0xf7, 0xff, 0xfc, + + /* U+002E "." */ + 0x0, 0x3, 0xf8, 0x7f, 0xc7, 0xfc, 0x2f, 0x40, + + /* U+002F "/" */ + 0x0, 0x0, 0x6, 0x0, 0x0, 0x3, 0xc0, 0x0, + 0x2, 0xe0, 0x0, 0x0, 0xf0, 0x0, 0x0, 0xb8, + 0x0, 0x0, 0x3c, 0x0, 0x0, 0x2e, 0x0, 0x0, + 0xf, 0x40, 0x0, 0xb, 0xc0, 0x0, 0x3, 0xd0, + 0x0, 0x2, 0xf0, 0x0, 0x0, 0xf4, 0x0, 0x0, + 0xbc, 0x0, 0x0, 0x3d, 0x0, 0x0, 0x1f, 0x0, + 0x0, 0xf, 0x40, 0x0, 0x7, 0xc0, 0x0, 0x3, + 0xd0, 0x0, 0x1, 0xf0, 0x0, 0x0, 0xf8, 0x0, + 0x0, + + /* U+0030 "0" */ + 0x1, 0xfe, 0x0, 0xb, 0xff, 0xd0, 0x1f, 0x87, + 0xf0, 0x3e, 0x0, 0xf4, 0x3c, 0x0, 0xfc, 0x7c, + 0x3, 0xfc, 0x7c, 0x1f, 0xbc, 0xbc, 0x7c, 0x7c, + 0xbe, 0xe0, 0x7c, 0x7f, 0x80, 0x7c, 0x3d, 0x0, + 0xb8, 0x3e, 0x0, 0xf4, 0x2f, 0x87, 0xf0, 0xf, + 0xff, 0xd0, 0x2, 0xfe, 0x0, + + /* U+0031 "1" */ + 0x0, 0x74, 0x0, 0x7f, 0x40, 0x7f, 0xf4, 0xb, + 0xef, 0x40, 0xa0, 0xf4, 0x0, 0xf, 0x40, 0x0, + 0xf4, 0x0, 0xf, 0x40, 0x0, 0xf4, 0x0, 0xf, + 0x40, 0x0, 0xf4, 0x0, 0xf, 0x40, 0x0, 0xf4, + 0xb, 0xff, 0xfe, 0xbf, 0xff, 0xe0, + + /* U+0032 "2" */ + 0x1, 0xbf, 0x40, 0xb, 0xff, 0xf0, 0x3f, 0x96, + 0xf4, 0x7d, 0x0, 0xf8, 0x10, 0x0, 0xf8, 0x0, + 0x0, 0xf4, 0x0, 0x3, 0xf0, 0x0, 0xb, 0xc0, + 0x0, 0x2f, 0x40, 0x0, 0xbd, 0x0, 0x2, 0xf4, + 0x0, 0xb, 0xd0, 0x0, 0x2f, 0x40, 0x0, 0x7f, + 0xff, 0xfc, 0x7f, 0xff, 0xfc, + + /* U+0033 "3" */ + 0x7f, 0xff, 0xf5, 0xff, 0xff, 0xc0, 0x0, 0x7e, + 0x0, 0x3, 0xe0, 0x0, 0x3e, 0x0, 0x3, 0xf0, + 0x0, 0x3f, 0xf8, 0x0, 0xff, 0xfc, 0x0, 0x2, + 0xf8, 0x0, 0x2, 0xf0, 0x0, 0xb, 0xc0, 0x0, + 0x7e, 0x5, 0x6f, 0xf4, 0xff, 0xff, 0x43, 0xff, + 0x90, 0x0, + + /* U+0034 "4" */ + 0x0, 0x1f, 0xc0, 0x0, 0x3f, 0xc0, 0x0, 0xb7, + 0xc0, 0x1, 0xf3, 0xc0, 0x3, 0xc3, 0xc0, 0xb, + 0x83, 0xc0, 0xf, 0x3, 0xc0, 0x3c, 0x3, 0xc0, + 0x78, 0x3, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0, 0x3, 0xc0, 0x0, 0x3, 0xc0, 0x0, + 0x3, 0xc0, 0x0, 0x3, 0xc0, + + /* U+0035 "5" */ + 0x1f, 0xff, 0xf0, 0x7f, 0xff, 0xc2, 0xe0, 0x0, + 0xb, 0x80, 0x0, 0x2e, 0x0, 0x0, 0xba, 0xfe, + 0x3, 0xff, 0xff, 0xb, 0x90, 0xbe, 0x0, 0x0, + 0xfc, 0x0, 0x2, 0xf0, 0x0, 0xf, 0x80, 0x1, + 0xfd, 0x1a, 0xff, 0xe0, 0xff, 0xf9, 0x3, 0xa5, + 0x0, 0x0, + + /* U+0036 "6" */ + 0x0, 0x1f, 0xc0, 0x0, 0x7f, 0x0, 0x1, 0xfc, + 0x0, 0x3, 0xf0, 0x0, 0xf, 0xc0, 0x0, 0x1f, + 0xff, 0x80, 0x3f, 0xff, 0xf0, 0x3d, 0x1, 0xf8, + 0x7c, 0x0, 0xfc, 0x7c, 0x0, 0xbc, 0x7c, 0x0, + 0xbc, 0x3e, 0x0, 0xfc, 0x3f, 0x43, 0xf4, 0xf, + 0xff, 0xe0, 0x2, 0xfe, 0x40, + + /* U+0037 "7" */ + 0xbf, 0xff, 0xfe, 0xff, 0xff, 0xf0, 0x0, 0xf, + 0x80, 0x0, 0x7c, 0x0, 0x3, 0xe0, 0x0, 0x1f, + 0x0, 0x0, 0xfc, 0x0, 0x7, 0xd0, 0x0, 0x3f, + 0x0, 0x1, 0xf4, 0x0, 0xf, 0xc0, 0x0, 0x7d, + 0x0, 0x3, 0xf0, 0x0, 0x1f, 0x80, 0x0, 0xfc, + 0x0, 0x0, + + /* U+0038 "8" */ + 0x2, 0xff, 0x40, 0x3f, 0xff, 0x83, 0xf4, 0x2f, + 0x4f, 0x40, 0x3e, 0x3d, 0x0, 0xf4, 0x7c, 0x7, + 0xc0, 0x7f, 0xf8, 0x1, 0xff, 0xe0, 0x2f, 0x42, + 0xf0, 0xf0, 0x2, 0xe7, 0xc0, 0x7, 0xdf, 0x0, + 0x2f, 0x3f, 0x2, 0xf8, 0x7f, 0xff, 0xc0, 0x2f, + 0xf4, 0x0, + + /* U+0039 "9" */ + 0x2, 0xff, 0x40, 0xf, 0xff, 0xe0, 0x3f, 0x43, + 0xf4, 0x3d, 0x0, 0xf8, 0x7c, 0x0, 0xbc, 0x7c, + 0x0, 0xbc, 0x7d, 0x0, 0xbc, 0x3f, 0x0, 0xf8, + 0x1f, 0xff, 0xf4, 0x6, 0xff, 0xf0, 0x0, 0x7, + 0xd0, 0x0, 0x1f, 0x80, 0x0, 0x7e, 0x0, 0x1, + 0xf8, 0x0, 0xb, 0xe0, 0x0, + + /* U+003A ":" */ + 0x2f, 0x47, 0xfc, 0x7f, 0xc2, 0xf4, 0x0, 0x0, + 0x0, 0x0, 0x2, 0xf4, 0x7f, 0xc7, 0xfc, 0x2f, + 0x40, + + /* U+003B ";" */ + 0x1, 0xf8, 0x3, 0xfc, 0x3, 0xfc, 0x1, 0xf8, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x54, + 0x3, 0xf8, 0x3, 0xf4, 0x7, 0xf0, 0xf, 0xe0, + 0x1f, 0x80, 0x3e, 0x0, 0x14, 0x0, + + /* U+003C "<" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0x0, 0x2f, + 0xc0, 0xb, 0xfd, 0x7, 0xff, 0x41, 0xff, 0x80, + 0xb, 0xd0, 0x0, 0x2f, 0xe0, 0x0, 0x1f, 0xf9, + 0x0, 0x2, 0xff, 0x40, 0x0, 0xbf, 0xc0, 0x0, + 0x1f, 0x0, 0x0, 0x4, + + /* U+003D "=" */ + 0x3f, 0xff, 0xf8, 0xff, 0xff, 0xe0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0xe3, 0xff, 0xff, 0x80, + + /* U+003E ">" */ + 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x7f, 0x40, + 0x0, 0x2f, 0xf4, 0x0, 0x2, 0xff, 0x80, 0x0, + 0x1f, 0xf8, 0x0, 0x0, 0xbc, 0x0, 0x6, 0xfc, + 0x0, 0x7f, 0xe0, 0xb, 0xfd, 0x0, 0x7f, 0xd0, + 0x0, 0x78, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+003F "?" */ + 0xa, 0xff, 0x81, 0xff, 0xff, 0xd7, 0x94, 0x1f, + 0xc0, 0x0, 0x2f, 0x0, 0x0, 0xfc, 0x0, 0xf, + 0xd0, 0x7, 0xfd, 0x0, 0x7f, 0x80, 0x1, 0xf0, + 0x0, 0x7, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x50, 0x0, 0x1f, 0xd0, 0x0, 0xbf, + 0x80, 0x0, 0xfc, 0x0, + + /* U+0040 "@" */ + 0x1, 0xbf, 0x80, 0xb, 0x41, 0xb0, 0x1c, 0x0, + 0x34, 0x34, 0x0, 0x1c, 0x30, 0x0, 0xc, 0x70, + 0x2f, 0xfc, 0xa0, 0xbe, 0xad, 0xa0, 0xf0, 0xd, + 0xa1, 0xd0, 0xd, 0xa1, 0xd0, 0xd, 0xa1, 0xe0, + 0xc, 0xa0, 0xf0, 0x3c, 0x70, 0x7f, 0xf4, 0x70, + 0xa, 0x40, 0x34, 0x0, 0x0, 0x2c, 0x0, 0x0, + 0xf, 0x40, 0x0, 0x2, 0xff, 0xe0, 0x0, 0x0, + 0x0, + + /* U+0041 "A" */ + 0x0, 0x3f, 0x40, 0x0, 0xf, 0xe0, 0x0, 0xb, + 0xfc, 0x0, 0x3, 0xdf, 0x40, 0x0, 0xf2, 0xe0, + 0x0, 0x7c, 0x7c, 0x0, 0x2e, 0xf, 0x0, 0xf, + 0x3, 0xe0, 0x7, 0xc0, 0xbc, 0x2, 0xff, 0xff, + 0x0, 0xff, 0xff, 0xd0, 0x3c, 0x0, 0xf8, 0x1f, + 0x0, 0x2f, 0xf, 0x80, 0x7, 0xd3, 0xd0, 0x0, + 0xf8, + + /* U+0042 "B" */ + 0x3f, 0xff, 0x80, 0x3f, 0xff, 0xf0, 0x3d, 0x1, + 0xf8, 0x3d, 0x0, 0xbc, 0x3d, 0x0, 0xbc, 0x3d, + 0x0, 0xf4, 0x3d, 0x2, 0xe0, 0x3f, 0xff, 0x40, + 0x3f, 0xff, 0xe0, 0x3d, 0x1, 0xfc, 0x3d, 0x0, + 0x3d, 0x3d, 0x0, 0x3d, 0x3d, 0x0, 0xbd, 0x3f, + 0xff, 0xfc, 0x3f, 0xff, 0xe0, + + /* U+0043 "C" */ + 0x0, 0xbf, 0x90, 0x7, 0xff, 0xf8, 0x1f, 0xd0, + 0xbc, 0x3f, 0x0, 0x4, 0x7d, 0x0, 0x0, 0xbc, + 0x0, 0x0, 0xfc, 0x0, 0x0, 0xf8, 0x0, 0x0, + 0xfc, 0x0, 0x0, 0xbc, 0x0, 0x0, 0x7c, 0x0, + 0x0, 0x3f, 0x0, 0x4, 0x2f, 0xd1, 0xbd, 0xb, + 0xff, 0xf8, 0x1, 0xff, 0x80, + + /* U+0044 "D" */ + 0x3f, 0xf8, 0x0, 0x3f, 0xff, 0x80, 0x3d, 0x1b, + 0xf0, 0x3d, 0x1, 0xf8, 0x3d, 0x0, 0xbc, 0x3d, + 0x0, 0x7c, 0x3d, 0x0, 0x3d, 0x3d, 0x0, 0x3d, + 0x3d, 0x0, 0x3d, 0x3d, 0x0, 0x7c, 0x3d, 0x0, + 0xfc, 0x3d, 0x2, 0xf4, 0x3d, 0x5f, 0xe0, 0x3f, + 0xff, 0x80, 0x3f, 0xe4, 0x0, + + /* U+0045 "E" */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x0, 0xf, + 0x80, 0x0, 0xf8, 0x0, 0xf, 0x80, 0x0, 0xf8, + 0x0, 0xf, 0xff, 0xfc, 0xff, 0xff, 0xcf, 0x80, + 0x0, 0xf8, 0x0, 0xf, 0x80, 0x0, 0xf8, 0x0, + 0xf, 0xff, 0xff, 0xff, 0xff, 0xf0, + + /* U+0046 "F" */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x0, 0xf, + 0x80, 0x0, 0xf8, 0x0, 0xf, 0x80, 0x0, 0xf8, + 0x0, 0xf, 0xff, 0xfc, 0xff, 0xff, 0xcf, 0x80, + 0x0, 0xf8, 0x0, 0xf, 0x80, 0x0, 0xf8, 0x0, + 0xf, 0x80, 0x0, 0xf8, 0x0, 0x0, + + /* U+0047 "G" */ + 0x0, 0x6f, 0xf0, 0x7, 0xff, 0xf0, 0x1f, 0xe4, + 0x0, 0x3f, 0x0, 0x0, 0x7d, 0x0, 0x0, 0xbc, + 0x0, 0x0, 0xfc, 0xf, 0xfc, 0xf8, 0xf, 0xfc, + 0xfc, 0x0, 0x3c, 0xbc, 0x0, 0x3c, 0x7c, 0x0, + 0x3c, 0x3f, 0x0, 0x3c, 0x2f, 0xd0, 0x7c, 0xb, + 0xff, 0xfc, 0x1, 0xbf, 0xf8, + + /* U+0048 "H" */ + 0x3d, 0x0, 0xf8, 0xf4, 0x3, 0xe3, 0xd0, 0xf, + 0x8f, 0x40, 0x3e, 0x3d, 0x0, 0xf8, 0xf4, 0x3, + 0xe3, 0xd0, 0xf, 0x8f, 0xff, 0xfe, 0x3f, 0xff, + 0xf8, 0xf4, 0x3, 0xe3, 0xd0, 0xf, 0x8f, 0x40, + 0x3e, 0x3d, 0x0, 0xf8, 0xf4, 0x3, 0xe3, 0xd0, + 0xf, 0x80, + + /* U+0049 "I" */ + 0x7f, 0xff, 0xca, 0xff, 0x90, 0x1f, 0x0, 0x7, + 0xc0, 0x1, 0xf0, 0x0, 0x7c, 0x0, 0x1f, 0x0, + 0x7, 0xc0, 0x1, 0xf0, 0x0, 0x7c, 0x0, 0x1f, + 0x0, 0x7, 0xc0, 0x1, 0xf0, 0xa, 0xff, 0x97, + 0xff, 0xfc, + + /* U+004A "J" */ + 0x0, 0x0, 0x3e, 0x0, 0x0, 0x3e, 0x0, 0x0, + 0x3e, 0x0, 0x0, 0x3e, 0x0, 0x0, 0x3e, 0x0, + 0x0, 0x3e, 0x0, 0x0, 0x3e, 0x0, 0x0, 0x3e, + 0x0, 0x0, 0x3e, 0x0, 0x0, 0x3e, 0xc, 0x0, + 0x3e, 0x3f, 0x40, 0x3e, 0x1f, 0xe5, 0xfc, 0x3, + 0xff, 0xf8, 0x0, 0x7f, 0xd0, + + /* U+004B "K" */ + 0x3d, 0x0, 0x7e, 0xf, 0x40, 0x3f, 0x3, 0xd0, + 0x3f, 0x0, 0xf4, 0x2f, 0x0, 0x3d, 0x1f, 0x40, + 0xf, 0x5f, 0x80, 0x3, 0xef, 0xc0, 0x0, 0xff, + 0xd0, 0x0, 0x3e, 0xfc, 0x0, 0xf, 0x4f, 0xc0, + 0x3, 0xd1, 0xf8, 0x0, 0xf4, 0x2f, 0x40, 0x3d, + 0x3, 0xf0, 0xf, 0x40, 0x3f, 0x3, 0xd0, 0x7, + 0xe0, + + /* U+004C "L" */ + 0xf8, 0x0, 0x3, 0xe0, 0x0, 0xf, 0x80, 0x0, + 0x3e, 0x0, 0x0, 0xf8, 0x0, 0x3, 0xe0, 0x0, + 0xf, 0x80, 0x0, 0x3e, 0x0, 0x0, 0xf8, 0x0, + 0x3, 0xe0, 0x0, 0xf, 0x80, 0x0, 0x3e, 0x0, + 0x0, 0xf8, 0x0, 0x3, 0xff, 0xff, 0xcf, 0xff, + 0xff, 0x0, + + /* U+004D "M" */ + 0x3e, 0x0, 0xf8, 0x3f, 0x1, 0xf8, 0x3f, 0x42, + 0xf8, 0x7f, 0x83, 0xfc, 0x7e, 0xc7, 0xbc, 0x7d, + 0xdf, 0x7c, 0x7c, 0xfd, 0x7c, 0x7c, 0xbc, 0x7c, + 0x78, 0x7c, 0x7c, 0x78, 0x34, 0x3c, 0xb8, 0x20, + 0x3c, 0xb8, 0x0, 0x3c, 0xb8, 0x0, 0x3c, 0xb8, + 0x0, 0x3c, 0xb8, 0x0, 0x3c, + + /* U+004E "N" */ + 0x3f, 0x0, 0xb8, 0xfc, 0x2, 0xe3, 0xf8, 0xb, + 0x8f, 0xf0, 0x2e, 0x3f, 0xe0, 0xb8, 0xf7, 0xc2, + 0xe3, 0xcf, 0x8b, 0x8f, 0x1f, 0x2e, 0x3c, 0x3d, + 0xb8, 0xf0, 0x7e, 0xe3, 0xc0, 0xff, 0x8f, 0x2, + 0xfe, 0x3c, 0x3, 0xf8, 0xf0, 0xb, 0xe3, 0xc0, + 0xf, 0x80, + + /* U+004F "O" */ + 0x2, 0xfe, 0x40, 0xf, 0xff, 0xd0, 0x2f, 0x47, + 0xf0, 0x3d, 0x0, 0xf8, 0x7c, 0x0, 0xbc, 0xbc, + 0x0, 0x7c, 0xf8, 0x0, 0x3d, 0xf8, 0x0, 0x3d, + 0xf8, 0x0, 0x3d, 0xbc, 0x0, 0x7c, 0xbc, 0x0, + 0xbc, 0x3d, 0x0, 0xf8, 0x2f, 0x43, 0xf0, 0xf, + 0xff, 0xd0, 0x2, 0xfe, 0x40, + + /* U+0050 "P" */ + 0x3f, 0xff, 0x80, 0x3f, 0xff, 0xf4, 0x3d, 0x1, + 0xfc, 0x3d, 0x0, 0x7d, 0x3d, 0x0, 0x3e, 0x3d, + 0x0, 0x3e, 0x3d, 0x0, 0x7d, 0x3d, 0x1, 0xfc, + 0x3f, 0xff, 0xf0, 0x3f, 0xff, 0x80, 0x3d, 0x0, + 0x0, 0x3d, 0x0, 0x0, 0x3d, 0x0, 0x0, 0x3d, + 0x0, 0x0, 0x3d, 0x0, 0x0, + + /* U+0051 "Q" */ + 0x2, 0xfe, 0x40, 0x3, 0xff, 0xf4, 0x2, 0xf4, + 0x7f, 0x0, 0xf4, 0x3, 0xe0, 0x7c, 0x0, 0xbc, + 0x2f, 0x0, 0x1f, 0xf, 0x80, 0x3, 0xd3, 0xe0, + 0x0, 0xf4, 0xf8, 0x0, 0x3d, 0x2f, 0x0, 0x1f, + 0xb, 0xc0, 0x7, 0xc0, 0xf4, 0x3, 0xe0, 0x2f, + 0x42, 0xf0, 0x3, 0xff, 0xf8, 0x0, 0x2f, 0xef, + 0x40, 0x0, 0x1, 0xf0, 0x0, 0x0, 0x3f, 0x0, + 0x0, 0x7, 0xc0, 0x0, 0x0, 0x40, + + /* U+0052 "R" */ + 0x3f, 0xff, 0x80, 0x3f, 0xff, 0xf0, 0x3d, 0x1, + 0xf8, 0x3d, 0x0, 0xbc, 0x3d, 0x0, 0xbc, 0x3d, + 0x0, 0xbc, 0x3d, 0x2, 0xf4, 0x3e, 0xbf, 0xd0, + 0x3f, 0xf9, 0x0, 0x3e, 0xbd, 0x0, 0x3d, 0x1f, + 0x40, 0x3d, 0xb, 0xd0, 0x3d, 0x3, 0xf0, 0x3d, + 0x0, 0xfc, 0x3d, 0x0, 0x7e, + + /* U+0053 "S" */ + 0x2, 0xff, 0xe0, 0x3f, 0xff, 0xd2, 0xf4, 0x16, + 0xf, 0x80, 0x0, 0x3e, 0x0, 0x0, 0xfd, 0x0, + 0x0, 0xff, 0x90, 0x0, 0xbf, 0xf4, 0x0, 0x1f, + 0xf4, 0x0, 0x3, 0xf0, 0x0, 0xb, 0xc0, 0x0, + 0x2f, 0x39, 0x2, 0xf9, 0xff, 0xff, 0xc1, 0xbf, + 0xf4, 0x0, + + /* U+0054 "T" */ + 0x3f, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xe0, 0x2, + 0xf0, 0x0, 0x0, 0x7c, 0x0, 0x0, 0x1f, 0x0, + 0x0, 0x7, 0xc0, 0x0, 0x1, 0xf0, 0x0, 0x0, + 0x7c, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x7, 0xc0, + 0x0, 0x1, 0xf0, 0x0, 0x0, 0x7c, 0x0, 0x0, + 0x1f, 0x0, 0x0, 0x7, 0xc0, 0x0, 0x1, 0xf0, + 0x0, + + /* U+0055 "U" */ + 0x7c, 0x0, 0xbd, 0xf0, 0x2, 0xf7, 0xc0, 0xb, + 0xdf, 0x0, 0x2f, 0x7c, 0x0, 0xbd, 0xf0, 0x2, + 0xf7, 0xc0, 0xb, 0xdf, 0x0, 0x2f, 0x7c, 0x0, + 0xbd, 0xf0, 0x2, 0xf7, 0xc0, 0xb, 0xcf, 0x80, + 0x3e, 0x3f, 0x97, 0xf4, 0x3f, 0xff, 0x80, 0x2f, + 0xf4, 0x0, + + /* U+0056 "V" */ + 0x3d, 0x0, 0xf, 0x8f, 0xc0, 0x3, 0xd1, 0xf0, + 0x1, 0xf0, 0x3d, 0x0, 0xb8, 0xf, 0x80, 0x3d, + 0x2, 0xf0, 0x1f, 0x0, 0x7c, 0xb, 0xc0, 0xf, + 0x43, 0xd0, 0x2, 0xf0, 0xf0, 0x0, 0x7c, 0x7c, + 0x0, 0xf, 0x6e, 0x0, 0x3, 0xef, 0x40, 0x0, + 0x7f, 0xc0, 0x0, 0xf, 0xe0, 0x0, 0x3, 0xf4, + 0x0, + + /* U+0057 "W" */ + 0x3d, 0x0, 0xf, 0x8f, 0x40, 0x3, 0xd3, 0xe0, + 0x0, 0xf4, 0xb8, 0x20, 0x3c, 0x2e, 0xd, 0xf, + 0x7, 0xc3, 0x87, 0xc1, 0xf1, 0xf1, 0xf0, 0x3c, + 0xbc, 0xb8, 0xf, 0x3b, 0x6e, 0x3, 0xdc, 0xeb, + 0x40, 0xff, 0x2f, 0xd0, 0x2f, 0xc7, 0xf0, 0xb, + 0xe0, 0xfc, 0x1, 0xf4, 0x3f, 0x0, 0x7c, 0xb, + 0xc0, + + /* U+0058 "X" */ + 0x2f, 0x0, 0x2f, 0x3, 0xe0, 0xf, 0x40, 0x7c, + 0xb, 0xc0, 0xf, 0xc7, 0xd0, 0x1, 0xf7, 0xe0, + 0x0, 0x2f, 0xf0, 0x0, 0x3, 0xf8, 0x0, 0x0, + 0xfd, 0x0, 0x0, 0x7f, 0xc0, 0x0, 0x3e, 0xf4, + 0x0, 0x2f, 0x2f, 0x0, 0xf, 0x43, 0xe0, 0xb, + 0xc0, 0x7c, 0x7, 0xd0, 0xf, 0xc3, 0xe0, 0x1, + 0xf4, + + /* U+0059 "Y" */ + 0x3e, 0x0, 0xf, 0x8b, 0xd0, 0xb, 0xc0, 0xfc, + 0x3, 0xd0, 0x1f, 0x42, 0xf0, 0x3, 0xf1, 0xf4, + 0x0, 0x7d, 0xf8, 0x0, 0xb, 0xfc, 0x0, 0x0, + 0xfe, 0x0, 0x0, 0x2f, 0x0, 0x0, 0x7, 0xc0, + 0x0, 0x1, 0xf0, 0x0, 0x0, 0x7c, 0x0, 0x0, + 0x1f, 0x0, 0x0, 0x7, 0xc0, 0x0, 0x1, 0xf0, + 0x0, + + /* U+005A "Z" */ + 0x7f, 0xff, 0xfc, 0x7f, 0xff, 0xfc, 0x15, 0x56, + 0xf4, 0x0, 0x3, 0xe0, 0x0, 0xf, 0xc0, 0x0, + 0x2f, 0x40, 0x0, 0x3e, 0x0, 0x0, 0xfc, 0x0, + 0x2, 0xf4, 0x0, 0x3, 0xe0, 0x0, 0xf, 0xc0, + 0x0, 0x1f, 0x40, 0x0, 0x3f, 0x0, 0x0, 0xbf, + 0xff, 0xfc, 0xbf, 0xff, 0xfc, + + /* U+005B "[" */ + 0x0, 0x0, 0xf, 0xff, 0xe3, 0xff, 0xf8, 0xf4, + 0x0, 0x3d, 0x0, 0xf, 0x40, 0x3, 0xd0, 0x0, + 0xf4, 0x0, 0x3d, 0x0, 0xf, 0x40, 0x3, 0xd0, + 0x0, 0xf4, 0x0, 0x3d, 0x0, 0xf, 0x40, 0x3, + 0xd0, 0x0, 0xf4, 0x0, 0x3d, 0x0, 0xf, 0x40, + 0x3, 0xd0, 0x0, 0xf4, 0x0, 0x3f, 0xff, 0x8f, + 0xff, 0xe0, + + /* U+005C "\\" */ + 0x18, 0x0, 0x0, 0xb, 0xc0, 0x0, 0x0, 0xf0, + 0x0, 0x0, 0x2e, 0x0, 0x0, 0x3, 0xc0, 0x0, + 0x0, 0xb8, 0x0, 0x0, 0xf, 0x0, 0x0, 0x3, + 0xe0, 0x0, 0x0, 0x7c, 0x0, 0x0, 0xf, 0x80, + 0x0, 0x1, 0xf0, 0x0, 0x0, 0x3e, 0x0, 0x0, + 0x7, 0xc0, 0x0, 0x0, 0xf4, 0x0, 0x0, 0x1f, + 0x0, 0x0, 0x3, 0xd0, 0x0, 0x0, 0x7c, 0x0, + 0x0, 0xf, 0x40, 0x0, 0x2, 0xf0, 0x0, 0x0, + 0x3d, + + /* U+005D "]" */ + 0x0, 0x0, 0xf, 0xff, 0xe3, 0xff, 0xf8, 0x0, + 0x3e, 0x0, 0xf, 0x80, 0x3, 0xe0, 0x0, 0xf8, + 0x0, 0x3e, 0x0, 0xf, 0x80, 0x3, 0xe0, 0x0, + 0xf8, 0x0, 0x3e, 0x0, 0xf, 0x80, 0x3, 0xe0, + 0x0, 0xf8, 0x0, 0x3e, 0x0, 0xf, 0x80, 0x3, + 0xe0, 0x0, 0xf8, 0x0, 0x3e, 0x3f, 0xff, 0x8f, + 0xff, 0xe0, + + /* U+005E "^" */ + 0x0, 0x1a, 0x0, 0x0, 0xf, 0xe0, 0x0, 0xf, + 0xbd, 0x0, 0x7, 0xcb, 0xc0, 0x3, 0xe0, 0xf4, + 0x1, 0xf0, 0x2f, 0x0, 0xf8, 0x3, 0xd0, 0x7c, + 0x0, 0xbc, 0x3e, 0x0, 0xf, 0x40, + + /* U+005F "_" */ + 0x15, 0x55, 0x55, 0xff, 0xff, 0xf7, 0xff, 0xff, + 0xc0, + + /* U+0060 "`" */ + 0x1a, 0x40, 0x1f, 0xc0, 0xf, 0xd0, 0x3, 0xf0, + 0x2, 0xf4, 0x0, 0xf8, 0x0, 0x7c, 0x0, 0x3e, + + /* U+0061 "a" */ + 0x1f, 0xff, 0x40, 0x2f, 0xff, 0xe0, 0x4, 0x2, + 0xf0, 0x0, 0x1, 0xf0, 0x1, 0x6b, 0xf0, 0x2f, + 0xff, 0xf0, 0x7e, 0x41, 0xf0, 0xb8, 0x1, 0xf0, + 0xbc, 0xb, 0xf0, 0x7f, 0xff, 0xfe, 0x1f, 0xe4, + 0xbe, + + /* U+0062 "b" */ + 0x3d, 0x0, 0x0, 0x3d, 0x0, 0x0, 0x3d, 0x0, + 0x0, 0x3d, 0x0, 0x0, 0x3d, 0x0, 0x0, 0x3d, + 0x7f, 0x80, 0x3f, 0xff, 0xf0, 0x3f, 0x82, 0xf8, + 0x3d, 0x0, 0xbc, 0x3d, 0x0, 0x7d, 0x3d, 0x0, + 0x3d, 0x3d, 0x0, 0x7c, 0x3d, 0x0, 0xfc, 0x3e, + 0x47, 0xf4, 0x3f, 0xff, 0xe0, 0x36, 0xfe, 0x40, + + /* U+0063 "c" */ + 0x1, 0xff, 0x80, 0xb, 0xff, 0xf8, 0x2f, 0x91, + 0xb8, 0x3e, 0x0, 0x0, 0xbc, 0x0, 0x0, 0xbc, + 0x0, 0x0, 0xbc, 0x0, 0x0, 0x7e, 0x0, 0x0, + 0x3f, 0x81, 0xb8, 0xf, 0xff, 0xf8, 0x2, 0xff, + 0x80, + + /* U+0064 "d" */ + 0x0, 0x0, 0xf8, 0x0, 0x3, 0xe0, 0x0, 0xf, + 0x80, 0x0, 0x3e, 0x0, 0x0, 0xf8, 0xb, 0xf7, + 0xe1, 0xff, 0xff, 0x8f, 0xd0, 0xbe, 0x7d, 0x0, + 0xfb, 0xf0, 0x3, 0xef, 0x80, 0xf, 0xbf, 0x0, + 0x3e, 0xbc, 0x0, 0xf8, 0xfd, 0x1f, 0xe2, 0xff, + 0xff, 0x81, 0xfe, 0x2e, + + /* U+0065 "e" */ + 0x1, 0xff, 0x40, 0xf, 0xff, 0xf0, 0x2f, 0x41, + 0xf8, 0x3c, 0x0, 0xbc, 0x7f, 0xff, 0xfc, 0xbf, + 0xff, 0xfc, 0x7c, 0x0, 0x0, 0x7d, 0x0, 0x0, + 0x3f, 0x40, 0x0, 0x1f, 0xff, 0xc0, 0x2, 0xff, + 0xc0, + + /* U+0066 "f" */ + 0x0, 0x1b, 0xfc, 0x0, 0x7f, 0xfc, 0x0, 0xfd, + 0x0, 0x0, 0xf4, 0x0, 0x0, 0xf4, 0x0, 0x7f, + 0xff, 0xf4, 0x7f, 0xff, 0xf4, 0x0, 0xf4, 0x0, + 0x0, 0xf4, 0x0, 0x0, 0xf4, 0x0, 0x0, 0xf4, + 0x0, 0x0, 0xf4, 0x0, 0x0, 0xf4, 0x0, 0x0, + 0xf4, 0x0, 0x0, 0xf4, 0x0, 0x0, 0xf4, 0x0, + + /* U+0067 "g" */ + 0x2, 0xff, 0x68, 0x3f, 0xff, 0xe3, 0xf9, 0x1f, + 0x9f, 0x40, 0x3e, 0xbc, 0x0, 0xfb, 0xe0, 0x3, + 0xef, 0xc0, 0xf, 0xaf, 0x0, 0x3e, 0x3f, 0x47, + 0xf8, 0xbf, 0xff, 0xe0, 0x7f, 0x8f, 0x80, 0x0, + 0x3e, 0x0, 0x0, 0xf8, 0x0, 0x1f, 0xd0, 0xff, + 0xfe, 0x3, 0xff, 0xd0, + + /* U+0068 "h" */ + 0x3d, 0x0, 0x0, 0xf4, 0x0, 0x3, 0xd0, 0x0, + 0xf, 0x40, 0x0, 0x3d, 0x0, 0x0, 0xf4, 0x7f, + 0x43, 0xdb, 0xff, 0xf, 0xfd, 0xbe, 0x3f, 0x40, + 0xf8, 0xf8, 0x3, 0xe3, 0xd0, 0xf, 0x8f, 0x40, + 0x3e, 0x3d, 0x0, 0xf8, 0xf4, 0x3, 0xe3, 0xd0, + 0xf, 0x8f, 0x40, 0x3e, + + /* U+0069 "i" */ + 0x0, 0x50, 0x0, 0x3f, 0x80, 0x3, 0xfc, 0x0, + 0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, + 0xf4, 0x7, 0xff, 0x40, 0x1, 0xf4, 0x0, 0xf, + 0x40, 0x0, 0xf4, 0x0, 0xf, 0x40, 0x0, 0xf4, + 0x0, 0xf, 0x40, 0x1, 0xf4, 0xb, 0xff, 0xff, + 0xbf, 0xff, 0xf0, + + /* U+006A "j" */ + 0x0, 0x14, 0x0, 0x1f, 0xc0, 0xb, 0xf0, 0x0, + 0xa4, 0x0, 0x0, 0x0, 0x0, 0xb, 0xff, 0xe2, + 0xff, 0xf8, 0x0, 0x3e, 0x0, 0xf, 0x80, 0x3, + 0xe0, 0x0, 0xf8, 0x0, 0x3e, 0x0, 0xf, 0x80, + 0x3, 0xe0, 0x0, 0xf8, 0x0, 0x3e, 0x0, 0xf, + 0x80, 0x3, 0xe0, 0x2, 0xf4, 0xff, 0xfc, 0x2f, + 0xf8, 0x0, + + /* U+006B "k" */ + 0xf8, 0x0, 0x3, 0xe0, 0x0, 0xf, 0x80, 0x0, + 0x3e, 0x0, 0x0, 0xf8, 0x0, 0x3, 0xe0, 0xf, + 0xcf, 0x80, 0xfc, 0x3e, 0xf, 0xc0, 0xf8, 0xfc, + 0x3, 0xef, 0xc0, 0xf, 0xfd, 0x0, 0x3e, 0xbd, + 0x0, 0xf8, 0xfd, 0x3, 0xe0, 0xfc, 0xf, 0x80, + 0xfc, 0x3e, 0x0, 0xfc, + + /* U+006C "l" */ + 0xbf, 0xf8, 0x2, 0xff, 0xe0, 0x0, 0xf, 0x80, + 0x0, 0x3e, 0x0, 0x0, 0xf8, 0x0, 0x3, 0xe0, + 0x0, 0xf, 0x80, 0x0, 0x3e, 0x0, 0x0, 0xf8, + 0x0, 0x3, 0xe0, 0x0, 0xf, 0x80, 0x0, 0x3e, + 0x0, 0x0, 0xf8, 0x0, 0x3, 0xf4, 0x0, 0x7, + 0xff, 0xc0, 0xb, 0xfe, + + /* U+006D "m" */ + 0xf0, 0xf4, 0x7c, 0xf3, 0xf8, 0xfd, 0xfb, 0xff, + 0xfe, 0xfc, 0x7e, 0x2e, 0xf0, 0x3c, 0x2e, 0xf0, + 0x3c, 0x2e, 0xf0, 0x3c, 0x2e, 0xf0, 0x3c, 0x2e, + 0xf0, 0x3c, 0x2e, 0xf0, 0x3c, 0x2e, 0xf0, 0x3c, + 0x2e, + + /* U+006E "n" */ + 0x3c, 0x1f, 0xd0, 0xf7, 0xff, 0xc3, 0xfe, 0x5f, + 0x8f, 0xd0, 0x3e, 0x3d, 0x0, 0xf8, 0xf4, 0x3, + 0xe3, 0xd0, 0xf, 0x8f, 0x40, 0x3e, 0x3d, 0x0, + 0xf8, 0xf4, 0x3, 0xe3, 0xd0, 0xf, 0x80, + + /* U+006F "o" */ + 0x2, 0xfe, 0x40, 0xf, 0xff, 0xe0, 0x3f, 0x46, + 0xf4, 0x7d, 0x0, 0xfc, 0xbc, 0x0, 0x7c, 0xbc, + 0x0, 0x7d, 0xbc, 0x0, 0x7c, 0x7d, 0x0, 0xfc, + 0x3f, 0x42, 0xf4, 0xf, 0xff, 0xe0, 0x2, 0xfe, + 0x40, + + /* U+0070 "p" */ + 0x3c, 0x7f, 0x80, 0x3e, 0xff, 0xf0, 0x3f, 0x41, + 0xf8, 0x3d, 0x0, 0xbc, 0x3d, 0x0, 0x7d, 0x3d, + 0x0, 0x3d, 0x3d, 0x0, 0x7c, 0x3d, 0x0, 0xfc, + 0x3f, 0x42, 0xf8, 0x3f, 0xff, 0xf0, 0x3d, 0xbf, + 0x40, 0x3d, 0x0, 0x0, 0x3d, 0x0, 0x0, 0x3d, + 0x0, 0x0, 0x3d, 0x0, 0x0, 0x3d, 0x0, 0x0, + + /* U+0071 "q" */ + 0x2, 0xff, 0x68, 0x3f, 0xff, 0xe3, 0xf8, 0x1f, + 0x9f, 0x40, 0x3e, 0xbc, 0x0, 0xfb, 0xe0, 0x3, + 0xef, 0xc0, 0xf, 0xaf, 0x0, 0x3e, 0x3f, 0x47, + 0xf8, 0xbf, 0xff, 0xe0, 0x7f, 0x8f, 0x80, 0x0, + 0x3e, 0x0, 0x0, 0xf8, 0x0, 0x3, 0xe0, 0x0, + 0xf, 0x80, 0x0, 0x3e, + + /* U+0072 "r" */ + 0x0, 0x0, 0x0, 0x7f, 0xc1, 0xfd, 0x7f, 0xcb, + 0xfd, 0x7, 0xdf, 0xe8, 0x7, 0xfc, 0x0, 0x7, + 0xf0, 0x0, 0x7, 0xd0, 0x0, 0x7, 0xc0, 0x0, + 0x7, 0xc0, 0x0, 0x7, 0xc0, 0x0, 0xff, 0xff, + 0x0, 0xff, 0xff, 0x0, + + /* U+0073 "s" */ + 0x2, 0xff, 0xe0, 0x7f, 0xff, 0xc3, 0xf0, 0x1, + 0xf, 0xc0, 0x0, 0x2f, 0xf9, 0x0, 0x1f, 0xff, + 0x80, 0x1, 0xbf, 0x40, 0x0, 0x3e, 0x14, 0x1, + 0xf8, 0xff, 0xff, 0xc2, 0xff, 0xf4, 0x0, + + /* U+0074 "t" */ + 0x1, 0xf0, 0x0, 0x7, 0xc0, 0x0, 0x1f, 0x0, + 0x0, 0x7c, 0x0, 0xff, 0xff, 0xf7, 0xff, 0xff, + 0xd0, 0x1f, 0x0, 0x0, 0x7c, 0x0, 0x1, 0xf0, + 0x0, 0x7, 0xc0, 0x0, 0x1f, 0x0, 0x0, 0x7c, + 0x0, 0x1, 0xf8, 0x0, 0x3, 0xff, 0xf0, 0x2, + 0xff, 0xc0, + + /* U+0075 "u" */ + 0x3d, 0x0, 0xf8, 0xf4, 0x3, 0xe3, 0xd0, 0xf, + 0x8f, 0x40, 0x3e, 0x3d, 0x0, 0xf8, 0xf4, 0x3, + 0xe3, 0xd0, 0xf, 0x8f, 0x40, 0xbe, 0x3f, 0x1f, + 0xf8, 0xbf, 0xf6, 0xe0, 0xbe, 0xb, 0x80, + + /* U+0076 "v" */ + 0xfc, 0x0, 0x7d, 0x7d, 0x0, 0xbc, 0x3e, 0x0, + 0xf8, 0x2f, 0x1, 0xf0, 0xf, 0x42, 0xf0, 0xf, + 0x83, 0xd0, 0x7, 0xc7, 0xc0, 0x3, 0xdf, 0x80, + 0x2, 0xff, 0x0, 0x1, 0xff, 0x0, 0x0, 0xfd, + 0x0, + + /* U+0077 "w" */ + 0x3c, 0x0, 0xb, 0x8f, 0x41, 0x3, 0xd2, 0xe0, + 0xd0, 0xf4, 0xb8, 0x38, 0x3c, 0x1f, 0x1f, 0x1f, + 0x3, 0xcf, 0xdb, 0x80, 0xf7, 0xba, 0xe0, 0x3e, + 0xcf, 0xf4, 0xb, 0xf1, 0xfc, 0x1, 0xf8, 0x3f, + 0x0, 0x7c, 0xf, 0xc0, + + /* U+0078 "x" */ + 0x7e, 0x0, 0xf8, 0x2f, 0x43, 0xf0, 0xf, 0xc7, + 0xd0, 0x3, 0xef, 0x80, 0x1, 0xff, 0x0, 0x0, + 0xfd, 0x0, 0x2, 0xff, 0x0, 0x7, 0xdf, 0xc0, + 0xf, 0x83, 0xe0, 0x3f, 0x2, 0xf4, 0xbd, 0x0, + 0xfc, + + /* U+0079 "y" */ + 0x3f, 0x0, 0x1f, 0x47, 0xd0, 0xb, 0xc0, 0xf8, + 0x3, 0xe0, 0x2f, 0x1, 0xf0, 0x3, 0xd0, 0xbc, + 0x0, 0xfc, 0x3d, 0x0, 0x1f, 0x1f, 0x0, 0x3, + 0xef, 0x80, 0x0, 0xbf, 0xc0, 0x0, 0xf, 0xf0, + 0x0, 0x3, 0xf4, 0x0, 0x0, 0xbc, 0x0, 0x0, + 0x3e, 0x0, 0x0, 0x2f, 0x0, 0x3, 0xff, 0x80, + 0x0, 0xff, 0x80, 0x0, + + /* U+007A "z" */ + 0x3f, 0xff, 0xf8, 0xff, 0xff, 0xd0, 0x0, 0x7f, + 0x0, 0x3, 0xf0, 0x0, 0x3f, 0x0, 0x3, 0xf4, + 0x0, 0x2f, 0x40, 0x2, 0xf4, 0x0, 0x2f, 0x80, + 0x0, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xc0, + + /* U+007B "{" */ + 0x0, 0x0, 0x0, 0x0, 0x1a, 0xfc, 0x1, 0xff, + 0xfd, 0x3, 0xf9, 0x40, 0x3, 0xf0, 0x0, 0x0, + 0xf8, 0x0, 0x0, 0x7e, 0x0, 0x0, 0x1f, 0x40, + 0x0, 0x1f, 0x80, 0x1, 0xbf, 0x0, 0x7f, 0xe4, + 0x0, 0xbd, 0x0, 0x0, 0x7f, 0xe4, 0x0, 0x1, + 0xff, 0x0, 0x0, 0x1f, 0x80, 0x0, 0x1f, 0x40, + 0x0, 0x7e, 0x0, 0x0, 0xf8, 0x0, 0x3, 0xf0, + 0x0, 0x3, 0xf9, 0x40, 0x1, 0xff, 0xfd, 0x0, + 0x1a, 0xfc, 0x0, 0x0, 0x0, + + /* U+007C "|" */ + 0x15, 0xf7, 0xdf, 0x7d, 0xf7, 0xdf, 0x7d, 0xf7, + 0xdf, 0x7d, 0xf7, 0xdf, 0x7d, 0xf7, 0xdf, + + /* U+007D "}" */ + 0x0, 0x0, 0x0, 0xbf, 0x90, 0x0, 0xff, 0xff, + 0x0, 0x5, 0xbf, 0x40, 0x0, 0x1f, 0x40, 0x0, + 0x7e, 0x0, 0x0, 0xfc, 0x0, 0x3, 0xf0, 0x0, + 0x3, 0xe0, 0x0, 0x2, 0xfe, 0x40, 0x0, 0x1f, + 0xf8, 0x0, 0x0, 0xbc, 0x0, 0x1f, 0xf8, 0x2, + 0xfe, 0x40, 0x3, 0xf0, 0x0, 0x3, 0xf0, 0x0, + 0x0, 0xfc, 0x0, 0x0, 0x7e, 0x0, 0x0, 0x1f, + 0x40, 0x1, 0xbf, 0x40, 0xff, 0xff, 0x0, 0xbf, + 0x94, 0x0, 0x0, 0x0, 0x0, + + /* U+007E "~" */ + 0x0, 0x40, 0x1, 0x1, 0xfc, 0x2, 0xe0, 0xff, + 0xd0, 0xf0, 0xbc, 0xbe, 0xbc, 0x3d, 0x7, 0xfd, + 0xf, 0x0, 0x3e, 0x0, 0x0, 0x0, 0x0 +}; + + +/*--------------------- + * GLYPH DESCRIPTION + *--------------------*/ + +static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { + {.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */, + {.bitmap_index = 0, .adv_w = 216, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 0, .adv_w = 216, .box_w = 6, .box_h = 16, .ofs_x = 4, .ofs_y = 0}, + {.bitmap_index = 24, .adv_w = 216, .box_w = 10, .box_h = 8, .ofs_x = 2, .ofs_y = 8}, + {.bitmap_index = 44, .adv_w = 216, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 93, .adv_w = 216, .box_w = 11, .box_h = 22, .ofs_x = 1, .ofs_y = -4}, + {.bitmap_index = 154, .adv_w = 216, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 207, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 252, .adv_w = 216, .box_w = 4, .box_h = 8, .ofs_x = 5, .ofs_y = 8}, + {.bitmap_index = 260, .adv_w = 216, .box_w = 10, .box_h = 23, .ofs_x = 2, .ofs_y = -4}, + {.bitmap_index = 318, .adv_w = 216, .box_w = 11, .box_h = 23, .ofs_x = 1, .ofs_y = -4}, + {.bitmap_index = 382, .adv_w = 216, .box_w = 12, .box_h = 11, .ofs_x = 1, .ofs_y = 3}, + {.bitmap_index = 415, .adv_w = 216, .box_w = 12, .box_h = 12, .ofs_x = 1, .ofs_y = 2}, + {.bitmap_index = 451, .adv_w = 216, .box_w = 8, .box_h = 7, .ofs_x = 2, .ofs_y = -4}, + {.bitmap_index = 465, .adv_w = 216, .box_w = 9, .box_h = 3, .ofs_x = 2, .ofs_y = 6}, + {.bitmap_index = 472, .adv_w = 216, .box_w = 6, .box_h = 5, .ofs_x = 4, .ofs_y = 0}, + {.bitmap_index = 480, .adv_w = 216, .box_w = 13, .box_h = 20, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 545, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 590, .adv_w = 216, .box_w = 10, .box_h = 15, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 628, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 673, .adv_w = 216, .box_w = 11, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 715, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 760, .adv_w = 216, .box_w = 11, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 802, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 847, .adv_w = 216, .box_w = 11, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 889, .adv_w = 216, .box_w = 11, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 931, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 976, .adv_w = 216, .box_w = 6, .box_h = 11, .ofs_x = 4, .ofs_y = 0}, + {.bitmap_index = 993, .adv_w = 216, .box_w = 8, .box_h = 15, .ofs_x = 2, .ofs_y = -4}, + {.bitmap_index = 1023, .adv_w = 216, .box_w = 11, .box_h = 13, .ofs_x = 1, .ofs_y = 1}, + {.bitmap_index = 1059, .adv_w = 216, .box_w = 11, .box_h = 7, .ofs_x = 1, .ofs_y = 4}, + {.bitmap_index = 1079, .adv_w = 216, .box_w = 12, .box_h = 13, .ofs_x = 1, .ofs_y = 1}, + {.bitmap_index = 1118, .adv_w = 216, .box_w = 11, .box_h = 16, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1162, .adv_w = 216, .box_w = 12, .box_h = 19, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 1219, .adv_w = 216, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1268, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1313, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1358, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1403, .adv_w = 216, .box_w = 10, .box_h = 15, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 1441, .adv_w = 216, .box_w = 10, .box_h = 15, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 1479, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1524, .adv_w = 216, .box_w = 11, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1566, .adv_w = 216, .box_w = 9, .box_h = 15, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 1600, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1645, .adv_w = 216, .box_w = 13, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1694, .adv_w = 216, .box_w = 11, .box_h = 15, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 1736, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1781, .adv_w = 216, .box_w = 11, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1823, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1868, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1913, .adv_w = 216, .box_w = 13, .box_h = 19, .ofs_x = 1, .ofs_y = -4}, + {.bitmap_index = 1975, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2020, .adv_w = 216, .box_w = 11, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2062, .adv_w = 216, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2111, .adv_w = 216, .box_w = 11, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2153, .adv_w = 216, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2202, .adv_w = 216, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2251, .adv_w = 216, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2300, .adv_w = 216, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2349, .adv_w = 216, .box_w = 12, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2394, .adv_w = 216, .box_w = 9, .box_h = 22, .ofs_x = 3, .ofs_y = -3}, + {.bitmap_index = 2444, .adv_w = 216, .box_w = 13, .box_h = 20, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 2509, .adv_w = 216, .box_w = 9, .box_h = 22, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 2559, .adv_w = 216, .box_w = 13, .box_h = 9, .ofs_x = 0, .ofs_y = 6}, + {.bitmap_index = 2589, .adv_w = 216, .box_w = 11, .box_h = 3, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 2598, .adv_w = 216, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 9}, + {.bitmap_index = 2614, .adv_w = 216, .box_w = 12, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2647, .adv_w = 216, .box_w = 12, .box_h = 16, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2695, .adv_w = 216, .box_w = 12, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2728, .adv_w = 216, .box_w = 11, .box_h = 16, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2772, .adv_w = 216, .box_w = 12, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2805, .adv_w = 216, .box_w = 12, .box_h = 16, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2853, .adv_w = 216, .box_w = 11, .box_h = 16, .ofs_x = 1, .ofs_y = -5}, + {.bitmap_index = 2897, .adv_w = 216, .box_w = 11, .box_h = 16, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2941, .adv_w = 216, .box_w = 10, .box_h = 17, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 2984, .adv_w = 216, .box_w = 9, .box_h = 22, .ofs_x = 2, .ofs_y = -5}, + {.bitmap_index = 3034, .adv_w = 216, .box_w = 11, .box_h = 16, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 3078, .adv_w = 216, .box_w = 11, .box_h = 16, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3122, .adv_w = 216, .box_w = 12, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3155, .adv_w = 216, .box_w = 11, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3186, .adv_w = 216, .box_w = 12, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3219, .adv_w = 216, .box_w = 12, .box_h = 16, .ofs_x = 1, .ofs_y = -5}, + {.bitmap_index = 3267, .adv_w = 216, .box_w = 11, .box_h = 16, .ofs_x = 1, .ofs_y = -5}, + {.bitmap_index = 3311, .adv_w = 216, .box_w = 12, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3347, .adv_w = 216, .box_w = 11, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3378, .adv_w = 216, .box_w = 11, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3420, .adv_w = 216, .box_w = 11, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3451, .adv_w = 216, .box_w = 12, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3484, .adv_w = 216, .box_w = 13, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3520, .adv_w = 216, .box_w = 12, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3553, .adv_w = 216, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -5}, + {.bitmap_index = 3605, .adv_w = 216, .box_w = 11, .box_h = 11, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3636, .adv_w = 216, .box_w = 12, .box_h = 23, .ofs_x = 1, .ofs_y = -4}, + {.bitmap_index = 3705, .adv_w = 216, .box_w = 3, .box_h = 20, .ofs_x = 5, .ofs_y = -2}, + {.bitmap_index = 3720, .adv_w = 216, .box_w = 12, .box_h = 23, .ofs_x = 1, .ofs_y = -4}, + {.bitmap_index = 3789, .adv_w = 216, .box_w = 13, .box_h = 7, .ofs_x = 0, .ofs_y = 4} +}; + +/*--------------------- + * CHARACTER MAPPING + *--------------------*/ + + + +/*Collect the unicode lists and glyph_id offsets*/ +static const lv_font_fmt_txt_cmap_t cmaps[] = +{ + { + .range_start = 32, .range_length = 95, .glyph_id_start = 1, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + } +}; + + + +/*-------------------- + * ALL CUSTOM DATA + *--------------------*/ + +static lv_font_fmt_txt_dsc_t font_dsc = { + .glyph_bitmap = glyph_bitmap, + .glyph_dsc = glyph_dsc, + .cmaps = cmaps, + .kern_dsc = NULL, + .kern_scale = 0, + .cmap_num = 1, + .bpp = 2, + .kern_classes = 0, +}; + + + +/*----------------- + * PUBLIC FONT + *----------------*/ + +/*Initialize a public general font descriptor*/ +const lv_font_t intel_one_mono = { + .dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */ + //.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/ + //.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/ + .line_height = 24, /*The maximum line height required by the font*/ + .base_line = 5, /*Baseline measured from the bottom of the line*/ +}; + + + +#endif /*#if INTEL_ONE_MONO*/ + diff --git a/examples/dvhstx/CMakeLists.txt b/examples/dvhstx/CMakeLists.txt new file mode 100644 index 0000000..74a0a9d --- /dev/null +++ b/examples/dvhstx/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable( + mandelbrot + mandelbrot.cpp + mandelf.c +) + +# Pull in pico libraries that we need +target_link_libraries(mandelbrot pico_stdlib pico_multicore pico_dvhstx) +pico_enable_stdio_usb(mandelbrot 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(mandelbrot) diff --git a/examples/dvhstx/mandelbrot.cpp b/examples/dvhstx/mandelbrot.cpp new file mode 100644 index 0000000..4fc3de6 --- /dev/null +++ b/examples/dvhstx/mandelbrot.cpp @@ -0,0 +1,128 @@ +#include +#include "hardware/uart.h" +#include "pico/multicore.h" +#include "drivers/dvhstx/dvhstx.hpp" +#include "libraries/pico_graphics/pico_graphics_dvhstx.hpp" + +extern "C" { +#include "mandelf.h" +} + +using namespace pimoroni; + +#define FRAME_WIDTH 640 +#define FRAME_HEIGHT 360 + +//#define FRAME_WIDTH 400 +//#define FRAME_HEIGHT 300 + +//#define FRAME_WIDTH 320 +//#define FRAME_HEIGHT 180 + +static DVHSTX display; +static PicoGraphics_PenDVHSTX_P8 graphics(FRAME_WIDTH, FRAME_HEIGHT, display); + +static FractalBuffer fractal; + +static void init_palette() { + graphics.create_pen(0, 0, 0); + for (int i = 0; i < 64; ++i) { + graphics.create_pen_hsv(i * (1.f / 63.f), 1.0f, 0.5f + (i & 7) * (0.5f / 7.f)); + } +} + +static void init_mandel() { + fractal.rows = FRAME_HEIGHT / 2; + fractal.cols = FRAME_WIDTH; + fractal.max_iter = 63; + fractal.iter_offset = 0; + fractal.minx = -2.25f; + fractal.maxx = 0.75f; + fractal.miny = -1.6f; + fractal.maxy = 0.f - (1.6f / FRAME_HEIGHT); // Half a row + fractal.use_cycle_check = true; + init_fractal(&fractal); +} + +#define NUM_ZOOMS 100 +static uint32_t zoom_count = 0; + +static void zoom_mandel() { + if (++zoom_count == NUM_ZOOMS) + { + init_mandel(); + zoom_count = 0; + sleep_ms(2000); + return; + } + + float zoomx = -.75f - .7f * ((float)zoom_count / (float)NUM_ZOOMS); + float sizex = fractal.maxx - fractal.minx; + float sizey = fractal.miny * -2.f; + float zoomr = 0.974f * 0.5f; + fractal.minx = zoomx - zoomr * sizex; + fractal.maxx = zoomx + zoomr * sizex; + fractal.miny = -zoomr * sizey; + fractal.maxy = 0.f + fractal.miny / FRAME_HEIGHT; + init_fractal(&fractal); +} + +static void display_row(int y, uint8_t* buf) { + display.write_palette_pixel_span({0, y}, FRAME_WIDTH, buf); + display.write_palette_pixel_span({0, FRAME_HEIGHT - 1 - y}, FRAME_WIDTH, buf); +} + +static uint8_t row_buf_core1[FRAME_WIDTH]; +void core1_main() { + while (true) { + int y = multicore_fifo_pop_blocking(); + generate_one_line(&fractal, row_buf_core1, y); + multicore_fifo_push_blocking(y); + } +} + +static uint8_t row_buf[FRAME_WIDTH]; +static void draw_two_rows(int y) { + multicore_fifo_push_blocking(y+1); + generate_one_line(&fractal, row_buf, y); + + display_row(y, row_buf); + + multicore_fifo_pop_blocking(); + display_row(y+1, row_buf_core1); +} + +void draw_mandel() { + //display.wait_for_flip(); + for (int y = 0; y < FRAME_HEIGHT / 2; y += 2) + { + draw_two_rows(y); + } + //display.flip_async(); + display.flip_now(); +} + +int main() { + stdio_init_all(); + + display.init(FRAME_WIDTH, FRAME_HEIGHT, DVHSTX::MODE_PALETTE); + + init_palette(); + + graphics.set_pen(0); + graphics.clear(); + display.flip_now(); + graphics.clear(); + + multicore_launch_core1(core1_main); + + init_mandel(); + draw_mandel(); + + while(true) { + absolute_time_t start_time = get_absolute_time(); + zoom_mandel(); + draw_mandel(); + printf("Drawing zoom %ld took %.2fms\n", zoom_count, absolute_time_diff_us(start_time, get_absolute_time()) * 0.001f); + } +} diff --git a/examples/dvhstx/mandelf.c b/examples/dvhstx/mandelf.c new file mode 100644 index 0000000..2eee393 --- /dev/null +++ b/examples/dvhstx/mandelf.c @@ -0,0 +1,63 @@ +// Copyright (C) Michael Bell 2021 + +#include +#include +#include +#include "pico/stdlib.h" + +#include "mandelf.h" + +// Cycle checking parameters +#define MAX_CYCLE_LEN 8 // Must be power of 2 +#define MIN_CYCLE_CHECK_ITER 24 // Must be multiple of max cycle len +#define CYCLE_TOLERANCE 4e-3 + +#define ESCAPE_SQUARE 4.f + +void init_fractal(FractalBuffer* f) +{ + f->done = false; + f->min_iter = f->max_iter - 1; + f->incx = (f->maxx - f->minx) / (f->cols - 1); + f->incy = (f->maxy - f->miny) / (f->rows - 1); + f->count_inside = 0; +} + +static inline void generate_one(FractalBuffer* f, float x0, float y0, uint8_t* buffptr) +{ + float x = x0; + float y = y0; + + uint16_t k = 1; + for (; k < f->max_iter; ++k) { + float x_square = x*x; + float y_square = y*y; + if (x_square + y_square > ESCAPE_SQUARE) break; + + float nextx = x_square - y_square + x0; + y = x*y*2.f + y0; + x = nextx; + } + if (k == f->max_iter) { + *buffptr = 0; + f->count_inside++; + } else { + if (k > f->iter_offset) k -= f->iter_offset; + else k = 1; + *buffptr = k; + if (f->min_iter > k) f->min_iter = k; + } +} + +void generate_one_line(FractalBuffer* f, uint8_t* buf, uint16_t ipos) +{ + if (f->done) return; + + float y0 = f->miny + ipos * f->incy; + float x0 = f->minx; + + for (int16_t j = 0; j < f->cols; ++j) { + generate_one(f, x0, y0, buf++); + x0 += f->incx; + } +} diff --git a/examples/dvhstx/mandelf.h b/examples/dvhstx/mandelf.h new file mode 100644 index 0000000..69fc1da --- /dev/null +++ b/examples/dvhstx/mandelf.h @@ -0,0 +1,24 @@ +#pragma once + +typedef struct { + // Configuration + int16_t rows; + int16_t cols; + + uint16_t max_iter; + uint16_t iter_offset; + float minx, miny, maxx, maxy; + bool use_cycle_check; + + // State + volatile bool done; + volatile uint16_t min_iter; + float incx, incy; + volatile uint32_t count_inside; +} FractalBuffer; + +// Generate a section of the fractal into buff +// Result written to buff is 0 for inside Mandelbrot set +// Otherwise iteration of escape minus min_iter (clamped to 1) +void init_fractal(FractalBuffer* fractal); +void generate_one_line(FractalBuffer* f, uint8_t* buf, uint16_t row); diff --git a/libraries/pico_graphics/pico_graphics_dvhstx.hpp b/libraries/pico_graphics/pico_graphics_dvhstx.hpp new file mode 100644 index 0000000..6bde2f8 --- /dev/null +++ b/libraries/pico_graphics/pico_graphics_dvhstx.hpp @@ -0,0 +1,88 @@ +#include "pico_graphics.hpp" +#include "dvhstx.hpp" + +namespace pimoroni { + enum BlendMode { + TARGET = 0, + FIXED = 1, + }; + + class PicoGraphicsDVHSTX : public PicoGraphics { + public: + DVHSTX &driver; + BlendMode blend_mode = BlendMode::TARGET; + + void set_blend_mode(BlendMode mode) { + blend_mode = mode; + } + + virtual void set_depth(uint8_t new_depth) {} + virtual void set_bg(uint c) {}; + + PicoGraphicsDVHSTX(uint16_t width, uint16_t height, DVHSTX &dv_display) + : PicoGraphics(width, height, nullptr), + driver(dv_display) + { + this->pen_type = PEN_DV_RGB555; + } + }; + + class PicoGraphics_PenDVHSTX_RGB565 : public PicoGraphicsDVHSTX { + public: + RGB565 color; + RGB565 background; + uint16_t depth = 0; + + PicoGraphics_PenDVHSTX_RGB565(uint16_t width, uint16_t height, DVHSTX &dv_display); + void set_pen(uint c) override; + void set_bg(uint c) override; + void set_depth(uint8_t new_depth) override; + void set_pen(uint8_t r, uint8_t g, uint8_t b) override; + int create_pen(uint8_t r, uint8_t g, uint8_t b) override; + int create_pen_hsv(float h, float s, float v) override; + void set_pixel(const Point &p) override; + void set_pixel_span(const Point &p, uint l) override; + void set_pixel_alpha(const Point &p, const uint8_t a) override; + + bool supports_alpha_blend() override {return true;} + + bool render_pico_vector_tile(const Rect &bounds, uint8_t* alpha_data, uint32_t stride, uint8_t alpha_type) override; + + static size_t buffer_size(uint w, uint h) { + return w * h * sizeof(RGB565); + } + }; + + class PicoGraphics_PenDVHSTX_P8 : public PicoGraphicsDVHSTX { + public: + static const uint16_t palette_size = 256; + uint8_t color; + uint8_t depth = 0; + bool used[palette_size]; + + std::array, 512> candidate_cache; + std::array candidates; + bool cache_built = false; + + PicoGraphics_PenDVHSTX_P8(uint16_t width, uint16_t height, DVHSTX &dv_display); + void set_pen(uint c) override; + void set_pen(uint8_t r, uint8_t g, uint8_t b) override; + void set_depth(uint8_t new_depth) override; + int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) override; + int create_pen(uint8_t r, uint8_t g, uint8_t b) override; + int create_pen_hsv(float h, float s, float v) override; + int reset_pen(uint8_t i) override; + + int get_palette_size() override {return 0;}; + RGB* get_palette() override {return nullptr;}; + + void set_pixel(const Point &p) override; + void set_pixel_span(const Point &p, uint l) override; + void get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array &candidates); + void set_pixel_dither(const Point &p, const RGB &c) override; + + static size_t buffer_size(uint w, uint h) { + return w * h; + } + }; +} \ No newline at end of file diff --git a/libraries/pico_graphics/pico_graphics_pen_dvhstx_p8.cpp b/libraries/pico_graphics/pico_graphics_pen_dvhstx_p8.cpp new file mode 100644 index 0000000..168f1a9 --- /dev/null +++ b/libraries/pico_graphics/pico_graphics_pen_dvhstx_p8.cpp @@ -0,0 +1,112 @@ +#include "pico_graphics_dvhstx.hpp" + +namespace pimoroni { + + inline constexpr uint32_t RGB_to_RGB888(const uint8_t r, const uint8_t g, const uint8_t b) { + return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; + } + + PicoGraphics_PenDVHSTX_P8::PicoGraphics_PenDVHSTX_P8(uint16_t width, uint16_t height, DVHSTX &dv_display) + : PicoGraphicsDVHSTX(width, height, dv_display) + { + this->pen_type = PEN_DV_P5; + for(auto i = 0u; i < palette_size; i++) { + driver.set_palette_colour(i, RGB_to_RGB888(i, i, i) << 3); + used[i] = false; + } + cache_built = false; + } + void PicoGraphics_PenDVHSTX_P8::set_pen(uint c) { + color = c; + } + void PicoGraphics_PenDVHSTX_P8::set_depth(uint8_t new_depth) { + depth = new_depth > 0 ? 1 : 0; + } + void PicoGraphics_PenDVHSTX_P8::set_pen(uint8_t r, uint8_t g, uint8_t b) { + RGB888 *driver_palette = driver.get_palette(); + RGB palette[palette_size]; + for(auto i = 0u; i < palette_size; i++) { + palette[i] = RGB((uint)driver_palette[i]); + } + + int pen = RGB(r, g, b).closest(palette, palette_size); + if(pen != -1) color = pen; + } + int PicoGraphics_PenDVHSTX_P8::update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) { + used[i] = true; + cache_built = false; + driver.set_palette_colour(i, RGB_to_RGB888(r, g, b)); + return i; + } + int PicoGraphics_PenDVHSTX_P8::create_pen(uint8_t r, uint8_t g, uint8_t b) { + // Create a colour and place it in the palette if there's space + for(auto i = 0u; i < palette_size; i++) { + if(!used[i]) { + used[i] = true; + cache_built = false; + driver.set_palette_colour(i, RGB_to_RGB888(r, g, b)); + return i; + } + } + return -1; + } + int PicoGraphics_PenDVHSTX_P8::create_pen_hsv(float h, float s, float v) { + RGB p = RGB::from_hsv(h, s, v); + return create_pen(p.r, p.g, p.b); + } + int PicoGraphics_PenDVHSTX_P8::reset_pen(uint8_t i) { + driver.set_palette_colour(i, 0); + used[i] = false; + cache_built = false; + return i; + } + void PicoGraphics_PenDVHSTX_P8::set_pixel(const Point &p) { + driver.write_palette_pixel(p, color); + } + + void PicoGraphics_PenDVHSTX_P8::set_pixel_span(const Point &p, uint l) { + driver.write_palette_pixel_span(p, l, color); + } + + void PicoGraphics_PenDVHSTX_P8::get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array &candidates) { + RGB error; + for(size_t i = 0; i < candidates.size(); i++) { + candidates[i] = (col + error).closest(palette, len); + error += (col - palette[candidates[i]]); + } + + // sort by a rough approximation of luminance, this ensures that neighbouring + // pixels in the dither matrix are at extreme opposites of luminence + // giving a more balanced output + std::sort(candidates.begin(), candidates.end(), [palette](int a, int b) { + return palette[a].luminance() > palette[b].luminance(); + }); + } + + void PicoGraphics_PenDVHSTX_P8::set_pixel_dither(const Point &p, const RGB &c) { + if(!bounds.contains(p)) return; + + if(!cache_built) { + RGB888 *driver_palette = driver.get_palette(); + RGB palette[palette_size]; + for(auto i = 0u; i < palette_size; i++) { + palette[i] = RGB((uint)driver_palette[i]); + } + + for(uint i = 0; i < 512; i++) { + RGB cache_col((i & 0x1C0) >> 1, (i & 0x38) << 2, (i & 0x7) << 5); + get_dither_candidates(cache_col, palette, palette_size, candidate_cache[i]); + } + cache_built = true; + } + + uint cache_key = ((c.r & 0xE0) << 1) | ((c.g & 0xE0) >> 2) | ((c.b & 0xE0) >> 5); + + // find the pattern coordinate offset + uint pattern_index = (p.x & 0b11) | ((p.y & 0b11) << 2); + + // set the pixel + color = candidate_cache[cache_key][dither16_pattern[pattern_index]]; + set_pixel(p); + } +} diff --git a/libraries/pico_graphics/pico_graphics_pen_dvhstx_rgb565.cpp b/libraries/pico_graphics/pico_graphics_pen_dvhstx_rgb565.cpp new file mode 100644 index 0000000..39658f4 --- /dev/null +++ b/libraries/pico_graphics/pico_graphics_pen_dvhstx_rgb565.cpp @@ -0,0 +1,72 @@ +#include "pico_graphics_dvhstx.hpp" + +#ifndef MICROPY_BUILD_TYPE +#define mp_printf(_, ...) printf(__VA_ARGS__); +#else +extern "C" { +#include "py/runtime.h" +} +#endif + +namespace pimoroni { + RGB565 to_rgb565_noswap(const RGB& color) { + uint16_t p = ((color.r & 0b11111000) << 8) | + ((color.g & 0b11111100) << 3) | + ((color.b & 0b11111000) >> 3); + + return p; + } + + PicoGraphics_PenDVHSTX_RGB565::PicoGraphics_PenDVHSTX_RGB565(uint16_t width, uint16_t height, DVHSTX &dv_display) + : PicoGraphicsDVHSTX(width, height, dv_display) + { + this->pen_type = PEN_DV_RGB555; + } + void PicoGraphics_PenDVHSTX_RGB565::set_pen(uint c) { + color = c; + } + void PicoGraphics_PenDVHSTX_RGB565::set_bg(uint c) { + background = c; + } + void PicoGraphics_PenDVHSTX_RGB565::set_depth(uint8_t new_depth) { + depth = new_depth > 0 ? 0x8000 : 0; + } + void PicoGraphics_PenDVHSTX_RGB565::set_pen(uint8_t r, uint8_t g, uint8_t b) { + RGB src_color{r, g, b}; + color = to_rgb565_noswap(src_color); + } + int PicoGraphics_PenDVHSTX_RGB565::create_pen(uint8_t r, uint8_t g, uint8_t b) { + return to_rgb565_noswap(RGB(r, g, b)); + } + int PicoGraphics_PenDVHSTX_RGB565::create_pen_hsv(float h, float s, float v) { + return to_rgb565_noswap(RGB::from_hsv(h, s, v)); + } + void PicoGraphics_PenDVHSTX_RGB565::set_pixel(const Point &p) { + driver.write_pixel(p, color); + } + void PicoGraphics_PenDVHSTX_RGB565::set_pixel_span(const Point &p, uint l) { + driver.write_pixel_span(p, l, color); + } + void PicoGraphics_PenDVHSTX_RGB565::set_pixel_alpha(const Point &p, const uint8_t a) { + uint16_t src = background; + if (blend_mode == BlendMode::TARGET) { + driver.read_pixel_span(p, 1, &src); + } + + uint8_t src_r = (src >> 8) & 0b11111000; + uint8_t src_g = (src >> 3) & 0b11111100; + uint8_t src_b = (src << 3) & 0b11111000; + + uint8_t dst_r = (color >> 8) & 0b11111000; + uint8_t dst_g = (color >> 3) & 0b11111100; + uint8_t dst_b = (color << 3) & 0b11111000; + + RGB565 blended = to_rgb565_noswap(RGB(src_r, src_g, src_b).blend(RGB(dst_r, dst_g, dst_b), a)); + + driver.write_pixel(p, blended); + } + + bool PicoGraphics_PenDVHSTX_RGB565::render_pico_vector_tile(const Rect &src_bounds, uint8_t* alpha_data, uint32_t stride, uint8_t alpha_type) { + return false; + } +} \ No newline at end of file diff --git a/pico_dvhstx.cmake b/pico_dvhstx.cmake new file mode 100644 index 0000000..e52074b --- /dev/null +++ b/pico_dvhstx.cmake @@ -0,0 +1,22 @@ + +add_subdirectory(drivers/dvhstx) + +set(LIB_NAME pico_dvhstx) +add_library(${LIB_NAME} INTERFACE) + +target_sources(${LIB_NAME} INTERFACE + ${PIMORONI_PICO_PATH}/libraries/pico_graphics/pico_graphics.cpp +# ${CMAKE_CURRENT_LIST_DIR}/libraries/pico_graphics/pico_graphics_pen_dvhstx_rgb888.cpp + ${CMAKE_CURRENT_LIST_DIR}/libraries/pico_graphics/pico_graphics_pen_dvhstx_rgb565.cpp + ${CMAKE_CURRENT_LIST_DIR}/libraries/pico_graphics/pico_graphics_pen_dvhstx_p8.cpp + ${PIMORONI_PICO_PATH}/libraries/pico_graphics/types.cpp +) + +target_include_directories(${LIB_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/libraries/pico_graphics # for pico_graphics_dv.hpp + ${PIMORONI_PICO_PATH}/libraries/pico_graphics # for pico_graphics.hpp + ${PIMORONI_PICO_PATH}/libraries/pngdec +) + +target_link_libraries(${LIB_NAME} INTERFACE dvhstx pico_stdlib hardware_i2c hardware_dma) diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..0a16a5c --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of Pico SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download Pico SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "Pico SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) \ No newline at end of file diff --git a/pimoroni_pico_import.cmake b/pimoroni_pico_import.cmake new file mode 100644 index 0000000..079cab6 --- /dev/null +++ b/pimoroni_pico_import.cmake @@ -0,0 +1,56 @@ +# This file can be dropped into a project to help locate the Pimoroni Pico libraries +# It will also set up the required include and module search paths. + +if (DEFINED ENV{PIMORONI_PICO_FETCH_FROM_GIT} AND (NOT PIMORONI_PICO_FETCH_FROM_GIT)) + set(PIMORONI_PICO_FETCH_FROM_GIT $ENV{PIMORONI_PICO_FETCH_FROM_GIT}) + message("Using PIMORONI_PICO_FETCH_FROM_GIT from environment ('${PIMORONI_PICO_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PIMORONI_PICO_FETCH_FROM_GIT_PATH} AND (NOT PIMORONI_PICO_FETCH_FROM_GIT_PATH)) + set(PIMORONI_PICO_FETCH_FROM_GIT_PATH $ENV{PIMORONI_PICO_FETCH_FROM_GIT_PATH}) + message("Using PIMORONI_PICO_FETCH_FROM_GIT_PATH from environment ('${PIMORONI_PICO_FETCH_FROM_GIT_PATH}')") +endif () + +if (NOT PIMORONI_PICO_PATH) + if (PIMORONI_PICO_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PIMORONI_PICO_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PIMORONI_PICO_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pimoroni_pico + GIT_REPOSITORY https://github.com/pimoroni/pimoroni-pico + GIT_TAG main + ) + if (NOT pimoroni_pico) + message("Downloading PIMORONI_PICO SDK") + FetchContent_Populate(pimoroni_pico) + set(PIMORONI_PICO_PATH ${pimoroni_pico_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + elseif(PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pimoroni-pico") + set(PIMORONI_PICO_PATH ${PICO_SDK_PATH}/../pimoroni-pico) + message("Defaulting PIMORONI_PICO_PATH as sibling of PICO_SDK_PATH: ${PIMORONI_PICO_PATH}") + elseif(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/../../pimoroni-pico/") + set(PIMORONI_PICO_PATH ${CMAKE_CURRENT_BINARY_DIR}/../../pimoroni-pico/) + else() + message(FATAL_ERROR "Pimoroni Pico location was not specified. Please set PIMORONI_PICO_PATH.") + endif() +endif() + +get_filename_component(PIMORONI_PICO_PATH "${PIMORONI_PICO_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PIMORONI_PICO_PATH}) + message(FATAL_ERROR "Directory '${PIMORONI_PICO_PATH}' not found") +endif () + +if (NOT EXISTS ${PIMORONI_PICO_PATH}/pimoroni_pico_import.cmake) + message(FATAL_ERROR "Directory '${PIMORONI_PICO_PATH}' does not appear to contain the Pimoroni Pico libraries") +endif () + +message("PIMORONI_PICO_PATH is ${PIMORONI_PICO_PATH}") + +set(PIMORONI_PICO_PATH ${PIMORONI_PICO_PATH} CACHE PATH "Path to the Pimoroni Pico libraries" FORCE) + +include_directories(${PIMORONI_PICO_PATH}) +list(APPEND CMAKE_MODULE_PATH ${PIMORONI_PICO_PATH})