Compare commits

...

4 commits

Author SHA1 Message Date
5839abece1 this code gets super sad if F_CPU is not 150MHz in the tools menu 2025-02-21 10:43:21 -06:00
c2318b9fd3 fix column alignment 2025-02-21 10:43:10 -06:00
924b1897f2 Add "very low intensity" 2025-02-21 08:31:39 -06:00
c4bb02cc0b Add background color support
RP2350 HSTX RGB111 text mode theory:

 * Cached font in memory is 13 pixels across, organized as the low 26 bits of 32-bit words
 * A 14th pixel is always black
 * Pixels in the cache are all adjacent
 * Each output pixel is 1 byte
 * "R2G2B2" values are created by selecting 2 bits out of the font data and multiplying them by the color value (SWAR)

The old implementation performed one multiply per output pixel, or 13
8-bit multiplies per character.  The new implementation carefully
re-orders the data in the font cache so that 32-bit multiplies can be
performed instead. In this case, 4 multiplies per character are needed.

Each two characters make 28 bytes (7 32-bit values) in the output buffer,
so the character generator is unrolled manually once, making all stores
to the output buffer 32 bits at a time.

This gains enough efficiency that the loop can be written in C instead
of assembler and also there's enough time to add background color. The
background color is XOR'd into each output pixel.

The final new trick is reduced intensity: When reduced intensity is
selected, the low bit of the font data is masked away, so that instead
of intensities 0/1/2/3, the possible intensities are 0/0/2/2.

As neither the regular nor reduced intensity text are visible on the
matching background color, there are effectively 8 * 14 = 112 useful
combinations.
2025-02-21 08:20:03 -06:00
5 changed files with 175 additions and 152 deletions

View file

@ -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.set_color(TextColor::TEXT_BLACK, TextColor::BG_WHITE);
display.clear();
display.show_cursor();
display.print("display initialized\n\n\n\n\n");
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.set_color(c, d);
display.write('*');
display.write('*');
display.write('*');
display.set_color(c, d, TextColor::ATTR_LOW_INTEN);
display.write('*');
display.write('*');
display.write('*');
display.set_color(c, d, TextColor::ATTR_V_LOW_INTEN);
display.write('*');
display.write('*');
display.write('*');
display.set_color(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.set_color(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++];

View file

@ -79,7 +79,7 @@ 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...
@ -88,12 +88,12 @@ size_t DVHSTXText3::write(uint8_t c) {
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++;

View file

@ -192,7 +192,8 @@ public:
void clear();
void set_color(TextColor a) { attr = a; }
void set_color(uint8_t a) { attr = a; }
void set_color(TextColor fg, TextColor bg, TextColor inten = TextColor::ATTR_NORMAL_INTEN) { attr = fg | bg | inten; }
void hide_cursor() {
cursor_visible = false;
@ -226,7 +227,7 @@ private:
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() {

View file

@ -1,6 +1,10 @@
#include <string.h>
#include <pico/stdlib.h>
#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 <pico/lock_core.h>
}
@ -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);

View file

@ -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,
};
//--------------------------------------------------