diff --git a/CMakeLists.txt b/CMakeLists.txt index 29a695a..99d5c09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(zavlaha src/screens/screen_home.c src/screens/screen_settings.c src/screens/screen_cyklus.c + src/screens/screen_set_time.c src/app_io.c src/app_config.c) diff --git a/src/lcd.c b/src/lcd.c index 276e305..bb6d405 100644 --- a/src/lcd.c +++ b/src/lcd.c @@ -49,6 +49,16 @@ void LcdBuffer_IO_WriteCGRAM(uint8_t position, const uint8_t* data) lcd_glyph(position, data); } +void LcdBuffer_IO_SetCursorPos(lcdbuf_pos_t cursor_row, lcdbuf_pos_t cursor_col) +{ + lcd_xy(cursor_col, cursor_row); +} + +void LcdBuffer_IO_SetCursorStyle(uint8_t cursor_style) +{ + lcd_set_cursor(cursor_style & 0b11); +} + // Internal prototypes void _lcd_mode_r(); @@ -128,18 +138,20 @@ void lcd_command(uint8_t bb); // 0 W, 1 R -bool _lcd_mode; +static bool lcd_mode; -struct { +static struct { uint8_t x; uint8_t y; -} _pos; +} lcd_pos; -enum { +static enum { TEXT = 0, CG = 1 -} _addrtype; +} lcd_addrtype; +static uint8_t lcd_old_cursor = CURSOR_NONE; +static bool lcd_enabled = false; /** Initialize the display */ void lcd_init() @@ -148,7 +160,7 @@ void lcd_init() as_output(LCD_E); as_output(LCD_RW); as_output(LCD_RS); - _lcd_mode = 1; // force data pins to output + lcd_mode = 1; // force data pins to output _lcd_mode_w(); // Magic sequence to invoke Cthulhu (or enter 4-bit mode) @@ -173,9 +185,9 @@ void lcd_init() // mark as enabled lcd_enable(); - _pos.x = 0; - _pos.y = 0; - _addrtype = TEXT; + lcd_pos.x = 0; + lcd_pos.y = 0; + lcd_addrtype = TEXT; } @@ -192,7 +204,7 @@ void _lcd_clk() /** Enter READ mode */ void _lcd_mode_r() { - if (_lcd_mode == 1) { return; } // already in R mode + if (lcd_mode == 1) { return; } // already in R mode pin_high(LCD_RW); @@ -201,14 +213,14 @@ void _lcd_mode_r() as_input_pu(LCD_D5); as_input_pu(LCD_D4); - _lcd_mode = 1; + lcd_mode = 1; } /** Enter WRITE mode */ void _lcd_mode_w() { - if (_lcd_mode == 0) { return; } // already in W mode + if (lcd_mode == 0) { return; } // already in W mode pin_low(LCD_RW); @@ -217,7 +229,7 @@ void _lcd_mode_w() as_output(LCD_D5); as_output(LCD_D4); - _lcd_mode = 0; + lcd_mode = 0; } @@ -250,22 +262,22 @@ void lcd_command(uint8_t bb) /** Write a data byte */ void lcd_write(uint8_t bb) { - if (_addrtype == TEXT) { + if (lcd_addrtype == TEXT) { if (bb == '\r') { // CR - _pos.x = 0; - lcd_xy(_pos.x, _pos.y); + lcd_pos.x = 0; + lcd_xy(lcd_pos.x, lcd_pos.y); return; } if (bb == '\n') { // LF - _pos.y++; - lcd_xy(_pos.x, _pos.y); + lcd_pos.y++; + lcd_xy(lcd_pos.x, lcd_pos.y); return; } - _pos.x++; + lcd_pos.x++; } _lcd_wait_bf(); @@ -285,7 +297,7 @@ uint8_t lcd_read_bf_addr() /** Read CGRAM or DDRAM */ uint8_t lcd_read() { - if (_addrtype == TEXT) { _pos.x++; } + if (lcd_addrtype == TEXT) { lcd_pos.x++; } pin_high(LCD_RS); return _lcd_read_byte(); @@ -341,21 +353,17 @@ void lcd_putc(const char c) /** Set cursor position */ void lcd_xy(const uint8_t x, const uint8_t y) { - _pos.x = x; - _pos.y = y; + lcd_pos.x = x; + lcd_pos.y = y; lcd_addr(LCD_ROW_ADDR[y] + (x)); } - -uint8_t _lcd_old_cursor = CURSOR_NONE; -bool _lcd_enabled = false; - /** Set LCD cursor. If not enabled, only remember it. */ -void lcd_cursor(uint8_t type) +void lcd_set_cursor(uint8_t type) { - _lcd_old_cursor = (type & CURSOR_BOTH); + lcd_old_cursor = (type & CURSOR_BOTH); - if (_lcd_enabled) { lcd_command(LCD_CURSOR_NONE | _lcd_old_cursor); } + if (lcd_enabled) { lcd_command(LCD_CURSOR_NONE | lcd_old_cursor); } } @@ -363,15 +371,15 @@ void lcd_cursor(uint8_t type) void lcd_disable() { lcd_command(LCD_DISABLE); - _lcd_enabled = false; + lcd_enabled = false; } /** Enable display (restoring cursor) */ void lcd_enable() { - _lcd_enabled = true; - lcd_cursor(_lcd_old_cursor); + lcd_enabled = true; + lcd_set_cursor(lcd_old_cursor); } @@ -379,9 +387,9 @@ void lcd_enable() void lcd_home() { lcd_command(LCD_HOME); - _pos.x = 0; - _pos.y = 0; - _addrtype = TEXT; + lcd_pos.x = 0; + lcd_pos.y = 0; + lcd_addrtype = TEXT; } @@ -389,9 +397,9 @@ void lcd_home() void lcd_clear() { lcd_command(LCD_CLEAR); - _pos.x = 0; - _pos.y = 0; - _addrtype = TEXT; + lcd_pos.x = 0; + lcd_pos.y = 0; + lcd_addrtype = TEXT; _delay_ms(1); // it tends to lose the first character otherwise! } @@ -405,15 +413,15 @@ void lcd_glyph(const uint8_t index, const uint8_t *array) } // restore previous position - lcd_xy(_pos.x, _pos.y); - _addrtype = TEXT; + lcd_xy(lcd_pos.x, lcd_pos.y); + lcd_addrtype = TEXT; } /** Set address in CGRAM */ void lcd_addr_cg(const uint8_t acg) { - _addrtype = CG; + lcd_addrtype = CG; lcd_command(0b01000000 | ((acg) & 0b00111111)); } @@ -421,6 +429,6 @@ void lcd_addr_cg(const uint8_t acg) /** Set address in DDRAM */ void lcd_addr(const uint8_t add) { - _addrtype = TEXT; + lcd_addrtype = TEXT; lcd_command(0b10000000 | ((add) & 0b01111111)); } diff --git a/src/lcd/lcdbuf.c b/src/lcd/lcdbuf.c index 751f691..978b6ab 100644 --- a/src/lcd/lcdbuf.c +++ b/src/lcd/lcdbuf.c @@ -36,8 +36,37 @@ void LcdBuffer_Clear(struct LcdBuffer *self) /** 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; } @@ -57,7 +86,7 @@ void LcdBuffer_Flush(struct LcdBuffer *self) if (!any_nonspace) { LcdBuffer_IO_Clear(); - return; + goto done; } for (int r = 0; r < LINE_NUM; r++) { @@ -74,11 +103,21 @@ void LcdBuffer_Flush(struct LcdBuffer *self) 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); @@ -92,6 +131,14 @@ void LcdBuffer_FlushAll(struct LcdBuffer *self) 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) { @@ -289,3 +336,14 @@ void LcdBuffer_Write(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col, 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; + } +} diff --git a/src/lcd/lcdbuf.h b/src/lcd/lcdbuf.h index a24b689..239d9be 100644 --- a/src/lcd/lcdbuf.h +++ b/src/lcd/lcdbuf.h @@ -71,6 +71,12 @@ struct LcdBuffer { /** 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; + + /* Visible cursor - is restored after flushing */ + uint8_t cursor_style; // two bits - blink 0b01, bar 0b10 + lcdbuf_pos_t cursor_row; + lcdbuf_pos_t cursor_col; + bool cursor_dirty; }; /** Initialize the struct */ @@ -91,6 +97,9 @@ void LcdBuffer_Set(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col, s /** Write a UTF8 string at a position */ void LcdBuffer_Write(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col, char *utf_string); +/** Set visible cursor position and style. style is 2 bits: blink 0b01, bar 0b10 */ +void LcdBuffer_SetCursor(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col, uint8_t cursor_style); + /* 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) */ @@ -100,6 +109,10 @@ extern void LcdBuffer_IO_Clear(); extern 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. */ -extern void LcdBuffer_IO_WriteCGRAM(uint8_t position, const uint8_t* data); +extern void LcdBuffer_IO_WriteCGRAM(uint8_t position, const uint8_t *data); + +/** Set cursor pos & style */ +extern void LcdBuffer_IO_SetCursorPos(lcdbuf_pos_t cursor_row, lcdbuf_pos_t cursor_col); +extern void LcdBuffer_IO_SetCursorStyle(uint8_t cursor_style); #endif //HD44780UTF_LCDBUF_H diff --git a/src/screens/app_gui.c b/src/screens/app_gui.c index df06bdd..221cf43 100644 --- a/src/screens/app_gui.c +++ b/src/screens/app_gui.c @@ -6,6 +6,7 @@ #include #include "app_gui.h" #include "../lcd/lcdbuf.h" +#include "lcd.h" struct State s_app = {}; @@ -70,6 +71,7 @@ void switch_screen(screen_t pScreen, bool init) { s_app.screen = pScreen; LcdBuffer_Clear(&lcd); + LcdBuffer_SetCursor(&lcd, 0, 0, CURSOR_NONE); // always start with a hidden cursor. If the page wants a visible cursor, it should do that in PAINT request_paint(); if (init) { diff --git a/src/screens/app_gui.h b/src/screens/app_gui.h index 52f0f5d..3eb8c0d 100644 --- a/src/screens/app_gui.h +++ b/src/screens/app_gui.h @@ -50,6 +50,7 @@ void request_paint(); void screen_home(GuiEvent event); void screen_cyklus(GuiEvent event); void screen_settings(GuiEvent event); +void screen_set_time(GuiEvent event); // XXX other prototypes struct State { diff --git a/src/screens/screen_set_time.c b/src/screens/screen_set_time.c new file mode 100644 index 0000000..399bb5a --- /dev/null +++ b/src/screens/screen_set_time.c @@ -0,0 +1,71 @@ +#include +#include "app_gui.h" +#include "gui_event.h" +#include "ds_rtc.h" +#include "app_io.h" +#include "lcd.h" + +static struct rtc_time time; +static int cursor; + +void screen_set_time(GuiEvent event) +{ + char buf[100]; + + switch (event) { + case GUI_EVENT_SCREEN_INIT: + time.hour = 0; + time.minute = 0; + time.second = 0; + cursor = 0; + break; + + case GUI_EVENT_PAINT: + LcdBuffer_Write(&lcd, 0, 0, "Zadejte přesný čas"); + + LcdBuffer_SetCursor(&lcd, 1, cursor + (cursor >= 2), (cursor < 4) ? CURSOR_BOTH : CURSOR_NONE); + + snprintf(buf, 100, "%02d:%02d", time.hour, time.minute); + LcdBuffer_Write(&lcd, 1, 0, buf); + + LcdBuffer_Write(&lcd, 3, 0, "🅰Uložit 🅳Zrušit"); + break; + + case GUI_EVENT_KEY_A: // Confirm + rtc_set_time(&time); + switch_screen(screen_settings, false); + break; + + case GUI_EVENT_KEY_D: // CANCEL + switch_screen(screen_settings, false); + break; + + default: + if (event >= '0' && event <= '9') { + int digit = event - '0'; + if (cursor == 0) { + if (digit <= 2) { + time.hour += digit * 10; + cursor++; + request_paint(); + } + } else if (cursor == 1) { + if (time.hour < 20 || digit <= 3) { + time.hour += digit; + cursor++; + request_paint(); + } + } else if (cursor == 2) { + if (digit <= 5) { + time.minute += digit * 10; + cursor++; + request_paint(); + } + } else if (cursor == 3) { + time.minute += digit; + cursor++; // cursor disappears + request_paint(); + } + } + } +} diff --git a/src/screens/screen_settings.c b/src/screens/screen_settings.c index 82dc8e9..5d0703f 100644 --- a/src/screens/screen_settings.c +++ b/src/screens/screen_settings.c @@ -100,7 +100,7 @@ void screen_settings(GuiEvent event) break; case GUI_EVENT_KEY_3: // Nastavit cas - // TODO + switch_screen(screen_set_time, true); break; } break;