diff --git a/src/cgram.c b/src/cgram.c index da6c22e..9a14aea 100644 --- a/src/cgram.c +++ b/src/cgram.c @@ -4,7 +4,7 @@ #include "cgram.h" -const struct cgram_pattern CGRAM_CZ[] = { +const struct LcdBuf_CGRAM_Symbol CGRAM_CZ[] = { { .symbol = "ě", .fallback = 'e', diff --git a/src/cgram.h b/src/cgram.h index da3c1f6..7f1bc9c 100644 --- a/src/cgram.h +++ b/src/cgram.h @@ -9,7 +9,7 @@ #include /** Pattern for CGRAM */ -struct cgram_pattern { +struct LcdBuf_CGRAM_Symbol { /** The symbol displayed */ struct Utf8Char symbol; @@ -20,6 +20,6 @@ struct cgram_pattern { uint8_t data[8]; }; -extern const struct cgram_pattern CGRAM_CZ[]; +extern const struct LcdBuf_CGRAM_Symbol CGRAM_CZ[]; #endif //HD44780UTF_CGRAM_H diff --git a/src/cgrom.c b/src/cgrom.c index 03b78a4..3874cd4 100644 --- a/src/cgrom.c +++ b/src/cgrom.c @@ -2,7 +2,7 @@ #include "utf8.h" #include "cgrom.h" -const struct cgrom_entry CGROM_A00[] = { +const struct LcdBuf_CGROM_Entry CGROM_A00[] = { {.address = 32, .symbol = " "}, {.address = 33, .symbol = "!"}, {.address = 34, .symbol = "\""}, diff --git a/src/cgrom.h b/src/cgrom.h index c9f091d..eddac95 100644 --- a/src/cgrom.h +++ b/src/cgrom.h @@ -9,7 +9,7 @@ #include "utf8.h" /** CGROM look-up table entry */ -struct cgrom_entry { +struct LcdBuf_CGROM_Entry { /** Address in the CGROM */ uint8_t address; @@ -19,6 +19,6 @@ struct cgrom_entry { /** The standard japanese lookup table, terminated by an empty entry */ -extern const struct cgrom_entry CGROM_A00[]; +extern const struct LcdBuf_CGROM_Entry CGROM_A00[]; #endif //HD44780UTF_CGROM_H diff --git a/src/lcdbuf.c b/src/lcdbuf.c index 451a01d..751f691 100644 --- a/src/lcdbuf.c +++ b/src/lcdbuf.c @@ -1,14 +1,15 @@ /** - * TODO file description + * HD44780 utf8-capable display buffer */ #include #include +//#include #include "lcdbuf.h" /** 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(cgrom); @@ -25,15 +26,17 @@ void LcdBuffer_Clear(struct LcdBuffer *self) assert(self); 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)); - 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 */ 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) { LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data); self->cgram[i].dirty = false; @@ -41,13 +44,29 @@ void LcdBuffer_Flush(struct LcdBuffer *self) } 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++) { LcdBuffer_IO_WriteAt(r, 0, self->screen[r], LINE_LEN); } memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); } else { - for (int e = 0; e < BUFLEN_DIRTY_LIST; e++) { - struct DirtyExtent *ext = &self->dirty_extents[e]; + for (int e = 0; e < LCDBUF_DIRTY_LIST_LEN; e++) { + struct LcdBuf_DirtyExtent *ext = &self->dirty_extents[e]; if (!ext->count) { continue; } @@ -60,7 +79,7 @@ void LcdBuffer_Flush(struct LcdBuffer *self) /** Fully write everything to the display */ 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) { 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; } -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; - for (int i = 0; i < BUFLEN_DIRTY_LIST; i++) { - struct DirtyExtent *ext = &self->dirty_extents[i]; + for (int i = 0; i < LCDBUF_DIRTY_LIST_LEN; i++) { + struct LcdBuf_DirtyExtent *ext = &self->dirty_extents[i]; if (ext->count == 0) { // unused 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 - if (ext->col < col && ext->col + ext->count > col) { + if ((ext->col <= col) && (ext->col + ext->count > col)) { // already in this extent +// show_dirty_slots(self); return; } - if (col < ext->col && (ext->col - col) <= 5) { - ext->count += (ext->col - col); + if ((col < ext->col) && ((ext->col - col) <= LCDBUF_DIRTY_EXTEND)) { + ext->count += ext->col - col; ext->col = col; +// show_dirty_slots(self); return; } - if (col >= ext->col + ext->count && (col - ext->col + ext->count) <= 5) { - ext->count += (col - ext->col + ext->count); + if ((col >= ext->col + ext->count) && ((col - (ext->col + ext->count)) <= LCDBUF_DIRTY_EXTEND)) { + ext->count += col - (ext->col + ext->count) + 1; +// show_dirty_slots(self); return; } } if (first_empty_extent_slot == -1) { +// printf("Give up on dirty extents\n"); self->full_repaint_required = true; } 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].row = row; self->dirty_extents[first_empty_extent_slot].count = 1; } + +// show_dirty_slots(self); } /** 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(row < LINE_NUM); 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 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 // normal ASCII - if (oldchar < 8) { - // release refcount on the CGRAM cell - self->cgram[oldchar].refcount -= 1; - } - self->screen[row][col] = ch.uint; goto done_dirty; } // Find if it's in CGROM - const struct cgrom_entry *rom = self->cgrom; + const struct LcdBuf_CGROM_Entry *rom = self->cgrom; for (;;) { if (rom->symbol.uint == 0) { // 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) { // found it! - if (oldchar < 8) { - // release refcount on the CGRAM cell - self->cgram[oldchar].refcount -= 1; - } - self->screen[row][col] = rom->address; 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 int first_empty_custom_slot = -1; - for (int i = 0; i < 8; i++) { - if (self->cgram[i].refcount > 0) { - if (self->cgram[i].uint == ch.uint) { - if (oldchar == i) { + for (uint8_t slot = 0; slot < LCDBUF_CGRAM_CAPACITY; slot++) { + if (self->cgram[slot].refcount > 0) { + if (self->cgram[slot].uint == ch.uint) { + if (oldchar == slot) { // No change, was already the same custom return; } - if (oldchar < 8) { - // release refcount on the CGRAM cell - self->cgram[oldchar].refcount -= 1; - } - - self->cgram[i].refcount += 1; - self->screen[row][col] = i; + self->cgram[slot].refcount++; + self->screen[row][col] = slot; goto done_dirty; } } else if (first_empty_custom_slot == -1) { - first_empty_custom_slot = i; + first_empty_custom_slot = slot; } } // New custom pattern is needed - if (oldchar < 8) { - // release refcount on the CGRAM cell - self->cgram[oldchar].refcount -= 1; - } - - uint32_t index = 0; - const struct cgram_pattern *pattern = self->custom_symbols; + uint16_t index = 0; + const struct LcdBuf_CGRAM_Symbol *pattern = self->custom_symbols; for (;;) { if (pattern->symbol.uint == 0) { // End of the lookup table @@ -212,8 +235,14 @@ void LcdBuffer_Set(struct LcdBuffer *self, uint8_t row, uint8_t col, struct Utf8 if (first_empty_custom_slot == -1) { // Whoops, out of slots. Show a fallback glyph - if (oldchar != pattern->fallback) { - self->screen[row][col] = pattern->fallback; + if (pattern->fallback != 0) { + if (oldchar != pattern->fallback) { + self->screen[row][col] = pattern->fallback; + 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; @@ -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 - self->screen[row][col] = '?'; + self->screen[row][col] = LCDBUF_SUBSTITUTION_CHAR; 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); } /** 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; Utf8Iterator_Init(&iter, utf_string); diff --git a/src/lcdbuf.h b/src/lcdbuf.h index 53f8022..a0342ca 100644 --- a/src/lcdbuf.h +++ b/src/lcdbuf.h @@ -1,5 +1,5 @@ /** - * TODO file description + * HD44780 utf8-capable display buffer */ #ifndef HD44780UTF_LCDBUF_H @@ -10,28 +10,48 @@ #include "cgram.h" #include "cgrom.h" +// --- settings --- + #define LINE_NUM 4 #define LINE_LEN 20 -#define BUFLEN_DIRTY_LIST 8 +/// number of remembered dirty regions before giving up and just writing full display +#define LCDBUF_DIRTY_LIST_LEN 8 + +/// if a cell is marked dirty closer than this to an existing extent, +/// the extent can grows in the direction rather than creating a new one +#define LCDBUF_DIRTY_EXTEND 5 + +/// Char shown if the requested symbol is missing in lookup tables +#define LCDBUF_SUBSTITUTION_CHAR 0xDB // this is the katakana box (ro ロ?) in the codepage A00 + +/// this must contain the full width or height +typedef uint8_t lcdbuf_pos_t; +/// this must contain the total number of cells in the display +typedef uint8_t lcdbuf_count_t; _Static_assert(LINE_NUM * LINE_LEN < 256, "LINE_NUM * LINE_LEN must fit in u8"); +// ---------------- + +/// Number of custom CGRAM slots (always 8) +#define LCDBUF_CGRAM_CAPACITY 8 + /** Indicates a range of screen cells that were changed and must be written to HW */ -struct DirtyExtent { - uint8_t row; - uint8_t col; - uint8_t count; +struct LcdBuf_DirtyExtent { + lcdbuf_pos_t row; + lcdbuf_pos_t col; + lcdbuf_count_t count; }; /** Struct for one CGRAM slot */ -struct CgramState { +struct LcdBuf_CgramState { /** UTF8 uint shown in this slot */ uint32_t uint; /** Array index in the custom symbols table, use for look-up when writing the font data to HW */ - uint32_t symbol_index; + uint16_t symbol_index; /** Number of occurrences of this symbol in the screen array */ - uint8_t refcount; + lcdbuf_count_t refcount; /** This CGRAM slot needs to be written to HW */ bool dirty; }; @@ -40,21 +60,21 @@ struct LcdBuffer { /** The raw screen buffer. Custom symbols are 0x00-0x07 */ uint8_t screen[LINE_NUM][LINE_LEN]; /** CGRAM state array */ - struct CgramState cgram[8]; + struct LcdBuf_CgramState cgram[LCDBUF_CGRAM_CAPACITY]; /** Hardware CGROM lookup table, used to map UTF8 to existing ROM symbols */ - const struct cgrom_entry *cgrom; + const struct LcdBuf_CGROM_Entry *cgrom; /** Defined custom display pattern of utf8 symbols */ - const struct cgram_pattern *custom_symbols; + const struct LcdBuf_CGRAM_Symbol *custom_symbols; /** Array of dirty extents - ranges in the display that need to be flushed to HW */ - struct DirtyExtent dirty_extents[BUFLEN_DIRTY_LIST]; + struct LcdBuf_DirtyExtent dirty_extents[LCDBUF_DIRTY_LIST_LEN]; /** If the dirty extents array was not sufficient to hold all changes, this flag is set, * indicating the dirty_extents array should be disregarded. */ bool full_repaint_required; }; /** 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); /** Clear the screen */ void LcdBuffer_Clear(struct LcdBuffer *self); @@ -66,15 +86,18 @@ void LcdBuffer_Flush(struct LcdBuffer *self); void LcdBuffer_FlushAll(struct LcdBuffer *self); /** 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); /** 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); /* Callbacks - need to be implemented by the application! */ +/** Clear the entire screen (CGRAM can be left unchanged, but they will be written anew if needed) */ +void LcdBuffer_IO_Clear(); + /** Write character data at position */ -void LcdBuffer_IO_WriteAt(uint8_t row, uint8_t col, const uint8_t *buf, uint8_t len); +void LcdBuffer_IO_WriteAt(lcdbuf_pos_t row, lcdbuf_pos_t col, const uint8_t *buf, lcdbuf_count_t len); /** Write CGRAM data. Data is always 8 bytes long. */ void LcdBuffer_IO_WriteCGRAM(uint8_t position, const uint8_t* data); diff --git a/src/main.c b/src/main.c index 212de0d..24285a8 100644 --- a/src/main.c +++ b/src/main.c @@ -7,22 +7,75 @@ int main() struct LcdBuffer buf; LcdBuffer_Init(&buf, CGROM_A00, CGRAM_CZ); - LcdBuffer_Write(&buf, 0, 0, "Ahoj"); + LcdBuffer_Write(&buf, 2, 10, "Ahoj"); + LcdBuffer_Write(&buf, 2, 9, "x"); + LcdBuffer_Write(&buf, 2, 6, "mm"); + LcdBuffer_Write(&buf, 0, 0, "ěščžýíéů"); + LcdBuffer_Write(&buf, 0, 0, "MEOWžýíéůěščřžýíéů"); LcdBuffer_Flush(&buf); + LcdBuffer_Write(&buf, 0, 5, "ĚŠČŘŽÝ"); + + LcdBuffer_Flush(&buf); + + LcdBuffer_Clear(&buf); + LcdBuffer_Flush(&buf); + return 0; } /** Write character data at position */ -void LcdBuffer_IO_WriteAt(uint8_t row, uint8_t col, const uint8_t *buf, uint8_t len) +void LcdBuffer_IO_Clear() { - printf("W@%d,%d: \"%.*s\" (len %d)\n", row, col, len, buf, len); + printf("ClearScreen\n"); } +/** Write character data at position */ +void LcdBuffer_IO_WriteAt(lcdbuf_pos_t row, lcdbuf_pos_t col, const uint8_t *buf, lcdbuf_count_t len) +{ + printf("W@%d,%d: (len %d) \"", row, col, len); // "\n + for(int i=0; i= 32 && c < 127) { + printf("%c", c); + } else { + printf("\\%d", c); + } + } + printf("\"\n"); +} + +#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" +#define BYTE_TO_BINARY(byte) \ + ((byte) & 0x80 ? '#' : '.'), \ + ((byte) & 0x40 ? '#' : '.'), \ + ((byte) & 0x20 ? '#' : '.'), \ + ((byte) & 0x10 ? '#' : '.'), \ + ((byte) & 0x08 ? '#' : '.'), \ + ((byte) & 0x04 ? '#' : '.'), \ + ((byte) & 0x02 ? '#' : '.'), \ + ((byte) & 0x01 ? '#' : '.') + /** Write CGRAM data. Data is always 8 bytes long. */ -void LcdBuffer_IO_WriteCGRAM(uint8_t position, const uint8_t *data) +void LcdBuffer_IO_WriteCGRAM(lcdbuf_pos_t position, const uint8_t *data) { - printf("G@%d: %02x %02x %02x %02x %02x %02x %02x %02x\n", - position, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); + printf("G@%d:\n " + BYTE_TO_BINARY_PATTERN"\n " + BYTE_TO_BINARY_PATTERN"\n " + BYTE_TO_BINARY_PATTERN"\n " + BYTE_TO_BINARY_PATTERN"\n " + BYTE_TO_BINARY_PATTERN"\n " + BYTE_TO_BINARY_PATTERN"\n " + BYTE_TO_BINARY_PATTERN"\n " + BYTE_TO_BINARY_PATTERN"\n\n", + position, + BYTE_TO_BINARY(data[0]), + BYTE_TO_BINARY(data[1]), + BYTE_TO_BINARY(data[2]), + BYTE_TO_BINARY(data[3]), + BYTE_TO_BINARY(data[4]), + BYTE_TO_BINARY(data[5]), + BYTE_TO_BINARY(data[6]), + BYTE_TO_BINARY(data[7])); }