diff --git a/examples/02texttest/02texttest.ino b/examples/02texttest/02texttest.ino index 7ab1c5a..ab1600a 100644 --- a/examples/02texttest/02texttest.ino +++ b/examples/02texttest/02texttest.ino @@ -11,6 +11,24 @@ DVHSTXText3 display(DVHSTX_PINOUT_DEFAULT); // // DVHSTXText3 display({12, 14, 16, 18}); +const static TextColor colors[] = { + TextColor::TEXT_BLACK, + TextColor::TEXT_RED, TextColor::TEXT_GREEN, TextColor::TEXT_BLUE, + TextColor::TEXT_YELLOW, TextColor::TEXT_MAGENTA, TextColor::TEXT_CYAN, + TextColor::TEXT_WHITE, +}; + +const static TextColor background_colors[] = { + TextColor::BG_BLACK, + TextColor::BG_RED, TextColor::BG_GREEN, TextColor::BG_BLUE, + TextColor::BG_YELLOW, TextColor::BG_MAGENTA, TextColor::BG_CYAN, + TextColor::BG_WHITE, +}; + +const static TextColor intensity[] = { + TextColor::ATTR_NORMAL_INTEN, TextColor::ATTR_LOW_INTEN, TextColor::ATTR_V_LOW_INTEN +}; + void setup() { Serial.begin(115200); if (!display.begin()) { // Blink LED if insufficient RAM @@ -18,25 +36,59 @@ void setup() { for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1); } - display.show_cursor(); - display.print("display initialized\n\n\n\n\n"); + display.setColor(TextColor::TEXT_BLACK, TextColor::BG_WHITE); + display.clear(); + display.showCursor(); + display.print("display initialized (black on white background)\n\n\n\n\n"); + display.println("line wrap test. one line should be full of 'w's and the next line should start 'xy'."); + for (int i = 0; i < display.width(); i++) + display.write('w'); + display.println("xy"); + display.println("\n\nAttribute test\n"); + display.print(" "); + for (int d : background_colors) { + display.printf(" %d ri vli ", (int)d >> 3 ); + } + display.write('\n'); + for (TextColor c : colors) { + display.printf(" %d ", (int)c); + for (TextColor d : background_colors) { + display.setColor(c, d); + display.write('*'); + display.write('*'); + display.write('*'); + display.setColor(c, d, TextColor::ATTR_LOW_INTEN); + display.write('*'); + display.write('*'); + display.write('*'); + display.setColor(c, d, TextColor::ATTR_V_LOW_INTEN); + display.write('*'); + display.write('*'); + display.write('*'); + display.setColor(TextColor::TEXT_BLACK, TextColor::BG_WHITE); + display.write(' '); + } + display.write('\n'); + } + display.write('\n'); + display.write('\n'); } -const char message[] = "All work and no play makes Jack a dull boy\n"; +const char message[] = "All work and no play makes Jack a dull boy "; int cx, cy, i; void loop() { - const static TextColor colors[] = { - TextColor::TEXT_RED, TextColor::TEXT_GREEN, TextColor::TEXT_BLUE, - TextColor::TEXT_YELLOW, TextColor::TEXT_MAGENTA, TextColor::TEXT_CYAN, - TextColor::TEXT_WHITE, - }; - if (i == 0) { - auto attr = colors[random(std::size(colors))]; - for (int j = random(91 - sizeof(message)); j; j--) + static_assert(std::size(colors) == std::size(background_colors)); + auto fg_idx = random(std::size(colors)); + auto bg_idx = random(std::size(colors)) - 1; + auto inten_idx = random(std::size(intensity)); + if(bg_idx == fg_idx) bg_idx ++; // never bg == fg + for (int j = random(6); j; j--) + display.write(' '); + display.setColor(colors[fg_idx], background_colors[bg_idx], intensity[inten_idx]); + for (int j = random(6); j; j--) display.write(' '); - display.set_color(attr); } int ch = message[i++]; diff --git a/src/Adafruit_dvhstx.cpp b/src/Adafruit_dvhstx.cpp index 20b3711..80fe8ef 100644 --- a/src/Adafruit_dvhstx.cpp +++ b/src/Adafruit_dvhstx.cpp @@ -79,21 +79,23 @@ void DVHSTX8::swap(bool copy_framebuffer) { } void DVHSTXText3::clear() { - memset(getBuffer(), 0, WIDTH * HEIGHT * sizeof(uint16_t)); + std::fill(getBuffer(), getBuffer() + WIDTH*HEIGHT, attr << 8); } // Character framebuffer is actually a small GFXcanvas16, so... size_t DVHSTXText3::write(uint8_t c) { + if (!*this) return 0; + if (c == '\r') { // Carriage return cursor_x = 0; } else if ((c == '\n') || (c >= 32 && - cursor_x > WIDTH)) { // Newline OR right edge and printing + cursor_x >= WIDTH)) { // Newline OR right edge and printing cursor_x = 0; if (cursor_y >= (HEIGHT - 1)) { // Vert scroll? memmove(getBuffer(), getBuffer() + WIDTH, WIDTH * (HEIGHT - 1) * sizeof(uint16_t)); - drawFastHLine(0, HEIGHT - 1, WIDTH, ' '); // Clear bottom line + drawFastHLine(0, HEIGHT - 1, WIDTH, ' ' | (attr << 8)); // Clear bottom line cursor_y = HEIGHT - 1; } else { cursor_y++; diff --git a/src/Adafruit_dvhstx.h b/src/Adafruit_dvhstx.h index 0a846f4..d401c13 100644 --- a/src/Adafruit_dvhstx.h +++ b/src/Adafruit_dvhstx.h @@ -180,6 +180,8 @@ public: pinout(pinout), res{res}, attr{TextColor::TEXT_WHITE} {} ~DVHSTXText3() { end(); } + operator bool() const { return hstx.get_back_buffer(); } + bool begin() { bool result = hstx.init(91, 30, pimoroni::DVHSTX::MODE_TEXT_RGB111, false, pinout); @@ -192,19 +194,20 @@ public: void clear(); - void set_color(TextColor a) { attr = a; } + void setColor(uint8_t a) { attr = a; } + void setColor(TextColor fg, TextColor bg, TextColor inten = TextColor::ATTR_NORMAL_INTEN) { attr = fg | bg | inten; } - void hide_cursor() { + void hideCursor() { cursor_visible = false; hstx.cursor_off(); } - void show_cursor() { + void showCursor() { cursor_visible = true; sync_cursor_with_hstx(); } - void set_cursor(int x, int y) { + void setCursor(int x, int y) { if (x < 0) x = 0; if (x > _width) @@ -220,13 +223,15 @@ public: size_t write(uint8_t c); + int getCursorX() const { return cursor_x; } + int getCursorY() const { return cursor_y; } private: DVHSTXPinout pinout; DVHSTXResolution res; mutable pimoroni::DVHSTX hstx; bool double_buffered; bool cursor_visible = false; - TextColor attr; + uint8_t attr; uint8_t cursor_x = 0, cursor_y = 0; void sync_cursor_with_hstx() { diff --git a/src/drivers/dvhstx/dvhstx.cpp b/src/drivers/dvhstx/dvhstx.cpp index eb6bf7b..8e75e78 100644 --- a/src/drivers/dvhstx/dvhstx.cpp +++ b/src/drivers/dvhstx/dvhstx.cpp @@ -1,6 +1,10 @@ #include #include +#if F_CPU != 150000000 +#error "Adafruit_DVHSTX controls overclocking (setting CPU frequency to 264MHz). However, the Tools > CPU Speed selector *MUST* be set to 150MHz" +#endif + extern "C" { #include } @@ -232,6 +236,25 @@ void __scratch_x("display") dma_irq_handler_text() { display->text_dma_handler(); } +uint8_t color_lut[8] = { +#define CLUT_ENTRY(i) (i) +#define CLUT_R CLUT_ENTRY(1 << 6) +#define CLUT_G CLUT_ENTRY(1 << 3) +#define CLUT_B CLUT_ENTRY(1 << 0) + 0, + CLUT_R, + CLUT_G, + CLUT_R | CLUT_G, + CLUT_B, + CLUT_R | CLUT_B, + CLUT_G | CLUT_B, + CLUT_R | CLUT_G | CLUT_B, +#undef CLUT_R +#undef CLUT_G +#undef CLUT_B +#undef CLUT_ENTRY +}; + 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. @@ -262,139 +285,51 @@ void __scratch_x("display") DVHSTX::text_dma_handler() { } } 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 * 2]; -#ifdef __riscv - for (int i = 0; i < frame_width; ++i) { - const uint8_t c = (*src_ptr++ - 0x20); - uint32_t bits = (c < 95) ? font_cache[c * 24 + char_y] : 0; - const uint8_t colour = *src_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; - } -#else - int i = 0; - for (; i < frame_width-1; i += 2) { + uint32_t* dst_ptr = &line_buffers[ch_num * line_buf_total_len + count_of(vactive_text_line_header)]; + for (int i = 0; i < frame_width; i += 2) { + uint32_t tmp_h, tmp_l; + uint8_t c = (*src_ptr++ - 0x20); uint32_t bits = (c < 95) ? font_cache[c * 24 + char_y] : 0; - uint8_t colour = *src_ptr++; + uint8_t attr = *src_ptr++; + uint32_t bg = color_lut[(attr >> 3) & 7]; + uint32_t colour = color_lut[attr & 7] ^ bg; + uint32_t bg_xor = bg * 0x3030303; + if (attr & ATTR_LOW_INTEN) bits = bits & 0xaaaaaaaa; + if ((attr & ATTR_V_LOW_INTEN) == ATTR_V_LOW_INTEN) bits >>= 1; + + *dst_ptr++ = colour * ((bits >> 6) & 0x3030303) ^ bg_xor; + *dst_ptr++ = colour * ((bits >> 4) & 0x3030303) ^ bg_xor; + *dst_ptr++ = colour * ((bits >> 2) & 0x3030303) ^ bg_xor; + tmp_l = colour * ((bits >> 0) & 0x3030303) ^ bg_xor; + + if (i == frame_width - 1) { + *dst_ptr++ = tmp_l; + break; + } + c = (*src_ptr++ - 0x20); - uint32_t bits2 = (c < 95) ? font_cache[c * 24 + char_y] : 0; - uint8_t colour2 = *src_ptr++; + bits = (c < 95) ? font_cache[c * 24 + char_y] : 0; + attr = *src_ptr++; + if (attr & ATTR_LOW_INTEN) bits = bits & 0xaaaaaaaa; + if ((attr & ATTR_V_LOW_INTEN) == ATTR_V_LOW_INTEN) bits >>= 1; + bg = color_lut[(attr >> 3) & 7] ; + colour = color_lut[attr & 7] ^ bg; + bg_xor = bg * 0x3030303; - // 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; + tmp_h = colour * ((bits >> 6) & 0x3030303) ^ bg_xor; + *dst_ptr++ = tmp_l & 0xffff | (tmp_h << 16); + tmp_l = tmp_h >> 16; + tmp_h = colour * ((bits >> 4) & 0x3030303) ^ bg_xor; + *dst_ptr++ = tmp_l & 0xffff | (tmp_h << 16); + tmp_l = tmp_h >> 16; + tmp_h = colour * ((bits >> 2) & 0x3030303) ^ bg_xor; + *dst_ptr++ = tmp_l & 0xffff | (tmp_h << 16); + tmp_l = tmp_h >> 16; + tmp_h = colour * ((bits >> 0) & 0x3030303) ^ bg_xor; + *dst_ptr++ = tmp_l & 0xffff | (tmp_h << 16); } - 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 = *src_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; - } -#endif if (y / 24 == cursor_y) { uint8_t* dst_ptr = (uint8_t*)&line_buffers[ch_num * line_buf_total_len + count_of(vactive_text_line_header)] + 14 * cursor_x; *dst_ptr++ ^= 0xff; @@ -732,12 +667,33 @@ bool DVHSTX::init(uint16_t width, uint16_t height, Mode mode_, bool double_buffe } if (mode == MODE_TEXT_RGB111) { + auto scramble = [](uint32_t b) { + auto take = [b](int shift1, int shift2) { + return ((b >> shift1) & 3) << shift2; + }; + return + take( 0, 0) | + take( 2, 26) | + take( 4, 18) | + take( 6, 10) | + take( 8, 2) | + take(10, 28) | + take(12, 20) | + take(14, 12) | + take(16, 4) | + take(18, 30) | + take(20, 22) | + take(22, 14) | + take(24, 6) | + take(26, 28); + + }; // Need to pre-render the font to RAM to be fast enough. font_cache = (uint32_t*)malloc(4 * FONT->line_height * 96); 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); + *font_cache_ptr++ = scramble(render_char_line(c, y)); } } } @@ -915,6 +871,7 @@ bool DVHSTX::init(uint16_t width, uint16_t height, Mode mode_, bool double_buffe dma_hw->inte2 = (1 << NUM_CHANS) - 1; if (is_text_mode) irq_set_exclusive_handler(DMA_IRQ_2, dma_irq_handler_text); else irq_set_exclusive_handler(DMA_IRQ_2, dma_irq_handler); + irq_set_priority(DMA_IRQ_2, PICO_HIGHEST_IRQ_PRIORITY); irq_set_enabled(DMA_IRQ_2, true); dma_channel_start(0); diff --git a/src/drivers/dvhstx/dvhstx.hpp b/src/drivers/dvhstx/dvhstx.hpp index 47fd72a..f435883 100644 --- a/src/drivers/dvhstx/dvhstx.hpp +++ b/src/drivers/dvhstx/dvhstx.hpp @@ -42,14 +42,27 @@ namespace pimoroni { }; 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, + TEXT_BLACK = 0, + TEXT_RED, + TEXT_GREEN, + TEXT_BLUE, + TEXT_YELLOW, + TEXT_MAGENTA, + TEXT_CYAN, + TEXT_WHITE, + + BG_BLACK = 0, + BG_RED = TEXT_RED << 3, + BG_GREEN = TEXT_GREEN << 3, + BG_BLUE = TEXT_BLUE << 3, + BG_YELLOW = TEXT_YELLOW << 3, + BG_MAGENTA = TEXT_MAGENTA << 3, + BG_CYAN = TEXT_CYAN << 3, + BG_WHITE = TEXT_WHITE << 3, + + ATTR_NORMAL_INTEN = 0, + ATTR_LOW_INTEN = 1 << 6, + ATTR_V_LOW_INTEN = 1 << 7 | ATTR_LOW_INTEN, }; //--------------------------------------------------