|
|
@ -1,14 +1,15 @@ |
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* TODO file description |
|
|
|
* HD44780 utf8-capable display buffer |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
#include <string.h> |
|
|
|
#include <string.h> |
|
|
|
#include <assert.h> |
|
|
|
#include <assert.h> |
|
|
|
|
|
|
|
//#include <stdio.h>
|
|
|
|
#include "lcdbuf.h" |
|
|
|
#include "lcdbuf.h" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Initialize the struct */ |
|
|
|
/** Initialize the struct */ |
|
|
|
void LcdBuffer_Init(struct LcdBuffer *self, const struct cgrom_entry *cgrom, const struct cgram_pattern *custom_symbols) |
|
|
|
void LcdBuffer_Init(struct LcdBuffer *self, const struct LcdBuf_CGROM_Entry *cgrom, const struct LcdBuf_CGRAM_Symbol *custom_symbols) |
|
|
|
{ |
|
|
|
{ |
|
|
|
assert(self); |
|
|
|
assert(self); |
|
|
|
assert(cgrom); |
|
|
|
assert(cgrom); |
|
|
@ -25,15 +26,17 @@ void LcdBuffer_Clear(struct LcdBuffer *self) |
|
|
|
assert(self); |
|
|
|
assert(self); |
|
|
|
|
|
|
|
|
|
|
|
memset(self->cgram, 0, sizeof(self->cgram)); |
|
|
|
memset(self->cgram, 0, sizeof(self->cgram)); |
|
|
|
memset(self->screen, 32, sizeof(self->screen)); |
|
|
|
memset(self->screen, ' ', sizeof(self->screen)); |
|
|
|
memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); |
|
|
|
memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); |
|
|
|
self->full_repaint_required = false; |
|
|
|
|
|
|
|
|
|
|
|
// everything must be written, who knows what was left on the display before!
|
|
|
|
|
|
|
|
self->full_repaint_required = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** Write what needs to be written to the HW, clear all dirty marks */ |
|
|
|
/** Write what needs to be written to the HW, clear all dirty marks */ |
|
|
|
void LcdBuffer_Flush(struct LcdBuffer *self) |
|
|
|
void LcdBuffer_Flush(struct LcdBuffer *self) |
|
|
|
{ |
|
|
|
{ |
|
|
|
for (int i = 0; i < 8; i++) { |
|
|
|
for (int i = 0; i < LCDBUF_CGRAM_CAPACITY; i++) { |
|
|
|
if (self->cgram[i].refcount > 0 && self->cgram[i].dirty) { |
|
|
|
if (self->cgram[i].refcount > 0 && self->cgram[i].dirty) { |
|
|
|
LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data); |
|
|
|
LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data); |
|
|
|
self->cgram[i].dirty = false; |
|
|
|
self->cgram[i].dirty = false; |
|
|
@ -41,13 +44,29 @@ void LcdBuffer_Flush(struct LcdBuffer *self) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (self->full_repaint_required) { |
|
|
|
if (self->full_repaint_required) { |
|
|
|
|
|
|
|
self->full_repaint_required = false; |
|
|
|
|
|
|
|
// Check if we have anything on the display - if not, just clear screen
|
|
|
|
|
|
|
|
bool any_nonspace = false; |
|
|
|
|
|
|
|
for (int r = 0; !any_nonspace && r < LINE_NUM; r++) { |
|
|
|
|
|
|
|
for (int c = 0; !any_nonspace && c < LINE_LEN; c++) { |
|
|
|
|
|
|
|
if (self->screen[r][c] != ' ') { |
|
|
|
|
|
|
|
any_nonspace = true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!any_nonspace) { |
|
|
|
|
|
|
|
LcdBuffer_IO_Clear(); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (int r = 0; r < LINE_NUM; r++) { |
|
|
|
for (int r = 0; r < LINE_NUM; r++) { |
|
|
|
LcdBuffer_IO_WriteAt(r, 0, self->screen[r], LINE_LEN); |
|
|
|
LcdBuffer_IO_WriteAt(r, 0, self->screen[r], LINE_LEN); |
|
|
|
} |
|
|
|
} |
|
|
|
memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); |
|
|
|
memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
for (int e = 0; e < BUFLEN_DIRTY_LIST; e++) { |
|
|
|
for (int e = 0; e < LCDBUF_DIRTY_LIST_LEN; e++) { |
|
|
|
struct DirtyExtent *ext = &self->dirty_extents[e]; |
|
|
|
struct LcdBuf_DirtyExtent *ext = &self->dirty_extents[e]; |
|
|
|
if (!ext->count) { |
|
|
|
if (!ext->count) { |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
@ -60,7 +79,7 @@ void LcdBuffer_Flush(struct LcdBuffer *self) |
|
|
|
/** Fully write everything to the display */ |
|
|
|
/** Fully write everything to the display */ |
|
|
|
void LcdBuffer_FlushAll(struct LcdBuffer *self) |
|
|
|
void LcdBuffer_FlushAll(struct LcdBuffer *self) |
|
|
|
{ |
|
|
|
{ |
|
|
|
for (int i = 0; i < 8; i++) { |
|
|
|
for (int i = 0; i < LCDBUF_CGRAM_CAPACITY; i++) { |
|
|
|
if (self->cgram[i].refcount > 0) { |
|
|
|
if (self->cgram[i].refcount > 0) { |
|
|
|
LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data); |
|
|
|
LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data); |
|
|
|
} |
|
|
|
} |
|
|
@ -75,11 +94,27 @@ void LcdBuffer_FlushAll(struct LcdBuffer *self) |
|
|
|
self->full_repaint_required = false; |
|
|
|
self->full_repaint_required = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void mark_dirty(struct LcdBuffer *self, uint8_t row, uint8_t col) |
|
|
|
//static void show_dirty_slots(const struct LcdBuffer *self) {
|
|
|
|
|
|
|
|
// printf("\n\n");
|
|
|
|
|
|
|
|
// for (int i = 0; i < LCDBUF_DIRTY_LIST_LEN; i++) {
|
|
|
|
|
|
|
|
// if (self->dirty_extents[i].count == 0) {
|
|
|
|
|
|
|
|
// continue;
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// printf("dirty_extent(%d): col %d, row %d, len %d\n", i, self->dirty_extents[i].row, self->dirty_extents[i].col, self->dirty_extents[i].count);
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// printf("\n");
|
|
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void mark_dirty(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col) |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
// partial updates are not needed if everything will be written anyway
|
|
|
|
|
|
|
|
if (self->full_repaint_required) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int first_empty_extent_slot = -1; |
|
|
|
int first_empty_extent_slot = -1; |
|
|
|
for (int i = 0; i < BUFLEN_DIRTY_LIST; i++) { |
|
|
|
for (int i = 0; i < LCDBUF_DIRTY_LIST_LEN; i++) { |
|
|
|
struct DirtyExtent *ext = &self->dirty_extents[i]; |
|
|
|
struct LcdBuf_DirtyExtent *ext = &self->dirty_extents[i]; |
|
|
|
if (ext->count == 0) { |
|
|
|
if (ext->count == 0) { |
|
|
|
// unused
|
|
|
|
// unused
|
|
|
|
if (first_empty_extent_slot == -1) { |
|
|
|
if (first_empty_extent_slot == -1) { |
|
|
@ -94,60 +129,63 @@ static void mark_dirty(struct LcdBuffer *self, uint8_t row, uint8_t col) |
|
|
|
|
|
|
|
|
|
|
|
// this is a filled extent
|
|
|
|
// this is a filled extent
|
|
|
|
|
|
|
|
|
|
|
|
if (ext->col < col && ext->col + ext->count > col) { |
|
|
|
if ((ext->col <= col) && (ext->col + ext->count > col)) { |
|
|
|
// already in this extent
|
|
|
|
// already in this extent
|
|
|
|
|
|
|
|
// show_dirty_slots(self);
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (col < ext->col && (ext->col - col) <= 5) { |
|
|
|
if ((col < ext->col) && ((ext->col - col) <= LCDBUF_DIRTY_EXTEND)) { |
|
|
|
ext->count += (ext->col - col); |
|
|
|
ext->count += ext->col - col; |
|
|
|
ext->col = col; |
|
|
|
ext->col = col; |
|
|
|
|
|
|
|
// show_dirty_slots(self);
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (col >= ext->col + ext->count && (col - ext->col + ext->count) <= 5) { |
|
|
|
if ((col >= ext->col + ext->count) && ((col - (ext->col + ext->count)) <= LCDBUF_DIRTY_EXTEND)) { |
|
|
|
ext->count += (col - ext->col + ext->count); |
|
|
|
ext->count += col - (ext->col + ext->count) + 1; |
|
|
|
|
|
|
|
// show_dirty_slots(self);
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (first_empty_extent_slot == -1) { |
|
|
|
if (first_empty_extent_slot == -1) { |
|
|
|
|
|
|
|
// printf("Give up on dirty extents\n");
|
|
|
|
self->full_repaint_required = true; |
|
|
|
self->full_repaint_required = true; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
|
|
|
|
// printf("New dirty extent: #%d\n", first_empty_extent_slot);
|
|
|
|
self->dirty_extents[first_empty_extent_slot].col = col; |
|
|
|
self->dirty_extents[first_empty_extent_slot].col = col; |
|
|
|
self->dirty_extents[first_empty_extent_slot].row = row; |
|
|
|
self->dirty_extents[first_empty_extent_slot].row = row; |
|
|
|
self->dirty_extents[first_empty_extent_slot].count = 1; |
|
|
|
self->dirty_extents[first_empty_extent_slot].count = 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// show_dirty_slots(self);
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** Set one utf8 character at a position */ |
|
|
|
/** Set one utf8 character at a position */ |
|
|
|
void LcdBuffer_Set(struct LcdBuffer *self, uint8_t row, uint8_t col, struct Utf8Char ch) |
|
|
|
void LcdBuffer_Set(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col, struct Utf8Char ch) |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
// printf("set %d:%d\n", row, col);
|
|
|
|
assert(self); |
|
|
|
assert(self); |
|
|
|
assert(row < LINE_NUM); |
|
|
|
assert(row < LINE_NUM); |
|
|
|
assert(col < LINE_LEN); |
|
|
|
assert(col < LINE_LEN); |
|
|
|
|
|
|
|
|
|
|
|
uint8_t oldchar = self->screen[row][col]; |
|
|
|
const uint8_t oldchar = self->screen[row][col]; |
|
|
|
|
|
|
|
|
|
|
|
if (oldchar >= 8 && oldchar == ch.uint) { |
|
|
|
if (oldchar >= LCDBUF_CGRAM_CAPACITY && oldchar == ch.uint) { |
|
|
|
// No change
|
|
|
|
// No change
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Fast path for standard ASCII
|
|
|
|
// Fast path for standard ASCII - assuming this extent of the table is always the same!
|
|
|
|
if (ch.uint >= 32 && ch.uint < 126 && ch.uint != '\\') { // A00 has YEN in place of BACKSLASH
|
|
|
|
if (ch.uint >= 32 && ch.uint < 126 && ch.uint != '\\') { // A00 has YEN in place of BACKSLASH
|
|
|
|
// normal ASCII
|
|
|
|
// normal ASCII
|
|
|
|
if (oldchar < 8) { |
|
|
|
|
|
|
|
// release refcount on the CGRAM cell
|
|
|
|
|
|
|
|
self->cgram[oldchar].refcount -= 1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self->screen[row][col] = ch.uint; |
|
|
|
self->screen[row][col] = ch.uint; |
|
|
|
goto done_dirty; |
|
|
|
goto done_dirty; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Find if it's in CGROM
|
|
|
|
// Find if it's in CGROM
|
|
|
|
const struct cgrom_entry *rom = self->cgrom; |
|
|
|
const struct LcdBuf_CGROM_Entry *rom = self->cgrom; |
|
|
|
for (;;) { |
|
|
|
for (;;) { |
|
|
|
if (rom->symbol.uint == 0) { |
|
|
|
if (rom->symbol.uint == 0) { |
|
|
|
// End of the lookup table
|
|
|
|
// End of the lookup table
|
|
|
@ -156,11 +194,6 @@ void LcdBuffer_Set(struct LcdBuffer *self, uint8_t row, uint8_t col, struct Utf8 |
|
|
|
|
|
|
|
|
|
|
|
if (rom->symbol.uint == ch.uint) { |
|
|
|
if (rom->symbol.uint == ch.uint) { |
|
|
|
// found it!
|
|
|
|
// found it!
|
|
|
|
if (oldchar < 8) { |
|
|
|
|
|
|
|
// release refcount on the CGRAM cell
|
|
|
|
|
|
|
|
self->cgram[oldchar].refcount -= 1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self->screen[row][col] = rom->address; |
|
|
|
self->screen[row][col] = rom->address; |
|
|
|
goto done_dirty; |
|
|
|
goto done_dirty; |
|
|
|
} |
|
|
|
} |
|
|
@ -170,37 +203,27 @@ void LcdBuffer_Set(struct LcdBuffer *self, uint8_t row, uint8_t col, struct Utf8 |
|
|
|
|
|
|
|
|
|
|
|
// Check if the same custom char is already used - if so, increment refcount and reuse it
|
|
|
|
// Check if the same custom char is already used - if so, increment refcount and reuse it
|
|
|
|
int first_empty_custom_slot = -1; |
|
|
|
int first_empty_custom_slot = -1; |
|
|
|
for (int i = 0; i < 8; i++) { |
|
|
|
for (uint8_t slot = 0; slot < LCDBUF_CGRAM_CAPACITY; slot++) { |
|
|
|
if (self->cgram[i].refcount > 0) { |
|
|
|
if (self->cgram[slot].refcount > 0) { |
|
|
|
if (self->cgram[i].uint == ch.uint) { |
|
|
|
if (self->cgram[slot].uint == ch.uint) { |
|
|
|
if (oldchar == i) { |
|
|
|
if (oldchar == slot) { |
|
|
|
// No change, was already the same custom
|
|
|
|
// No change, was already the same custom
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (oldchar < 8) { |
|
|
|
self->cgram[slot].refcount++; |
|
|
|
// release refcount on the CGRAM cell
|
|
|
|
self->screen[row][col] = slot; |
|
|
|
self->cgram[oldchar].refcount -= 1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self->cgram[i].refcount += 1; |
|
|
|
|
|
|
|
self->screen[row][col] = i; |
|
|
|
|
|
|
|
goto done_dirty; |
|
|
|
goto done_dirty; |
|
|
|
} |
|
|
|
} |
|
|
|
} else if (first_empty_custom_slot == -1) { |
|
|
|
} else if (first_empty_custom_slot == -1) { |
|
|
|
first_empty_custom_slot = i; |
|
|
|
first_empty_custom_slot = slot; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// New custom pattern is needed
|
|
|
|
// New custom pattern is needed
|
|
|
|
|
|
|
|
|
|
|
|
if (oldchar < 8) { |
|
|
|
uint16_t index = 0; |
|
|
|
// release refcount on the CGRAM cell
|
|
|
|
const struct LcdBuf_CGRAM_Symbol *pattern = self->custom_symbols; |
|
|
|
self->cgram[oldchar].refcount -= 1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t index = 0; |
|
|
|
|
|
|
|
const struct cgram_pattern *pattern = self->custom_symbols; |
|
|
|
|
|
|
|
for (;;) { |
|
|
|
for (;;) { |
|
|
|
if (pattern->symbol.uint == 0) { |
|
|
|
if (pattern->symbol.uint == 0) { |
|
|
|
// End of the lookup table
|
|
|
|
// End of the lookup table
|
|
|
@ -212,10 +235,16 @@ void LcdBuffer_Set(struct LcdBuffer *self, uint8_t row, uint8_t col, struct Utf8 |
|
|
|
|
|
|
|
|
|
|
|
if (first_empty_custom_slot == -1) { |
|
|
|
if (first_empty_custom_slot == -1) { |
|
|
|
// Whoops, out of slots. Show a fallback glyph
|
|
|
|
// Whoops, out of slots. Show a fallback glyph
|
|
|
|
|
|
|
|
if (pattern->fallback != 0) { |
|
|
|
if (oldchar != pattern->fallback) { |
|
|
|
if (oldchar != pattern->fallback) { |
|
|
|
self->screen[row][col] = pattern->fallback; |
|
|
|
self->screen[row][col] = pattern->fallback; |
|
|
|
goto done_dirty; |
|
|
|
goto done_dirty; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// TODO kick out some other CGRAM entry that has fallback?
|
|
|
|
|
|
|
|
self->screen[row][col] = LCDBUF_SUBSTITUTION_CHAR; |
|
|
|
|
|
|
|
goto done_dirty; |
|
|
|
|
|
|
|
} |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -234,14 +263,20 @@ void LcdBuffer_Set(struct LcdBuffer *self, uint8_t row, uint8_t col, struct Utf8 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Fallback, no way to show this glyph
|
|
|
|
// Fallback, no way to show this glyph
|
|
|
|
self->screen[row][col] = '?'; |
|
|
|
self->screen[row][col] = LCDBUF_SUBSTITUTION_CHAR; |
|
|
|
|
|
|
|
|
|
|
|
done_dirty: |
|
|
|
done_dirty: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (oldchar < LCDBUF_CGRAM_CAPACITY) { |
|
|
|
|
|
|
|
// release refcount on the CGRAM cell
|
|
|
|
|
|
|
|
assert(self->cgram[oldchar].refcount > 0); |
|
|
|
|
|
|
|
self->cgram[oldchar].refcount--; |
|
|
|
|
|
|
|
} |
|
|
|
mark_dirty(self, row, col); |
|
|
|
mark_dirty(self, row, col); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** Write a UTF8 string at a position */ |
|
|
|
/** Write a UTF8 string at a position */ |
|
|
|
void LcdBuffer_Write(struct LcdBuffer *self, uint8_t row, uint8_t col, char *utf_string) |
|
|
|
void LcdBuffer_Write(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col, char *utf_string) |
|
|
|
{ |
|
|
|
{ |
|
|
|
struct Utf8Iterator iter; |
|
|
|
struct Utf8Iterator iter; |
|
|
|
Utf8Iterator_Init(&iter, utf_string); |
|
|
|
Utf8Iterator_Init(&iter, utf_string); |
|
|
|