/** * HD44780 utf8-capable display buffer */ #include #include //#include #include "lcdbuf.h" /** Initialize the struct */ void LcdBuffer_Init(struct LcdBuffer *self, const struct LcdBuf_CGROM_Entry *cgrom, const struct LcdBuf_CGRAM_Symbol *custom_symbols) { assert(self); assert(cgrom); assert(custom_symbols); memset(self, 0, sizeof(struct LcdBuffer)); LcdBuffer_Clear(self); self->cgrom = cgrom; self->custom_symbols = custom_symbols; } /** Clear the screen */ void LcdBuffer_Clear(struct LcdBuffer *self) { assert(self); memset(self->cgram, 0, sizeof(self->cgram)); memset(self->screen, ' ', sizeof(self->screen)); memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); // 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) { bool any_flush = self->full_repaint_required || self->cursor_dirty; uint8_t flush_cgram_mask = 0; // Check if any flushing is required for (int i = 0; i < LCDBUF_CGRAM_CAPACITY; i++) { if (self->cgram[i].refcount > 0 && self->cgram[i].dirty) { any_flush = true; flush_cgram_mask |= 1 << i; } } if (!any_flush) { // check if we have dirty areas for (int e = 0; e < LCDBUF_DIRTY_LIST_LEN; e++) { struct LcdBuf_DirtyExtent *ext = &self->dirty_extents[e]; if (ext->count) { any_flush = true; break; } } } if (!any_flush) { // really nothing to do return; } LcdBuffer_IO_SetCursorStyle(0); // hide cursor for the time of this function for (int i = 0; i < LCDBUF_CGRAM_CAPACITY; i++) { if (flush_cgram_mask & (1 << i)) { LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data); self->cgram[i].dirty = false; } } 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(); goto done; } 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 < LCDBUF_DIRTY_LIST_LEN; e++) { struct LcdBuf_DirtyExtent *ext = &self->dirty_extents[e]; if (!ext->count) { continue; } LcdBuffer_IO_WriteAt(ext->row, ext->col, &self->screen[ext->row][ext->col], ext->count); ext->count = 0; // mark the slot as free } } done: // Restore the visible cursor if (self->cursor_style != 0) { LcdBuffer_IO_SetCursorPos(self->cursor_row, self->cursor_col); LcdBuffer_IO_SetCursorStyle(self->cursor_style); // hide cursor for the time of this function } self->cursor_dirty = false; } /** Fully write everything to the display */ void LcdBuffer_FlushAll(struct LcdBuffer *self) { LcdBuffer_IO_SetCursorStyle(0); // hide cursor for the time of this function 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); } self->cgram[i].dirty = false; } 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)); self->full_repaint_required = false; // Restore the visible cursor if (self->cursor_style != 0) { LcdBuffer_IO_SetCursorPos(self->cursor_row, self->cursor_col); LcdBuffer_IO_SetCursorStyle(self->cursor_style); // hide cursor for the time of this function } self->cursor_dirty = false; } //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 < LCDBUF_DIRTY_LIST_LEN; i++) { struct LcdBuf_DirtyExtent *ext = &self->dirty_extents[i]; if (ext->count == 0) { // unused if (first_empty_extent_slot == -1) { first_empty_extent_slot = i; } continue; } if (ext->row != row) { // not this row continue; } // this is a filled extent 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) <= 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)) <= 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, 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); const uint8_t oldchar = self->screen[row][col]; if (oldchar >= LCDBUF_CGRAM_CAPACITY && oldchar == ch.uint) { // No change return; } // 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 self->screen[row][col] = ch.uint; goto done_dirty; } // Find if it's in CGROM const struct LcdBuf_CGROM_Entry *rom = self->cgrom; for (;;) { if (rom->symbol.uint == 0) { // End of the lookup table break; } if (rom->symbol.uint == ch.uint) { // found it! self->screen[row][col] = rom->address; goto done_dirty; } rom++; } // Check if the same custom char is already used - if so, increment refcount and reuse it int first_empty_custom_slot = -1; 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; } self->cgram[slot].refcount++; self->screen[row][col] = slot; goto done_dirty; } } else if (first_empty_custom_slot == -1) { first_empty_custom_slot = slot; } } // New custom pattern is needed uint16_t index = 0; const struct LcdBuf_CGRAM_Symbol *pattern = self->custom_symbols; for (;;) { if (pattern->symbol.uint == 0) { // End of the lookup table break; } if (pattern->symbol.uint == ch.uint) { // found it! if (first_empty_custom_slot == -1) { // Whoops, out of slots. Show a fallback glyph 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; } // Allocate a new slot in the CGRAM self->cgram[first_empty_custom_slot].refcount = 1; self->cgram[first_empty_custom_slot].uint = ch.uint; self->cgram[first_empty_custom_slot].dirty = true; // it should be flushed! self->cgram[first_empty_custom_slot].symbol_index = index; self->screen[row][col] = first_empty_custom_slot; goto done_dirty; } index++; pattern++; } // Fallback, no way to show this glyph 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, lcdbuf_pos_t row, lcdbuf_pos_t col, char *utf_string) { struct Utf8Iterator iter; Utf8Iterator_Init(&iter, utf_string); struct Utf8Char uchar; while ((uchar = Utf8Iterator_Next(&iter)).uint) { if (col >= LINE_LEN) { break; } LcdBuffer_Set(self, row, col, uchar); col++; } } void LcdBuffer_SetCursor(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col, uint8_t cursor_style) { if ((self->cursor_style != cursor_style) || (self->cursor_row != row) || (self->cursor_col != col)) { self->cursor_style = cursor_style; self->cursor_row = row; self->cursor_col = col; self->cursor_dirty = true; } }