diff --git a/CMakeLists.txt b/CMakeLists.txt index 9204b74..9132a78 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,10 +20,13 @@ add_executable(zavlaha src/lcd/cgram.c src/lcd/cgrom.c src/lcd/lcdbuf.c -) + src/screens/app_gui.c + src/screens/screen_home.c src/app_io.c) # Add pico_stdlib library which aggregates commonly used features target_link_libraries(zavlaha pico_stdlib hardware_i2c hardware_adc hardware_irq) +target_include_directories(zavlaha PRIVATE src) + # create map/bin/hex/uf2 file in addition to ELF. pico_add_extra_outputs(zavlaha) diff --git a/src/app_io.c b/src/app_io.c new file mode 100644 index 0000000..bfb7f63 --- /dev/null +++ b/src/app_io.c @@ -0,0 +1,30 @@ +/** + * TODO file description + */ + +#include +#include +#include +#include "app_io.h" +#include "pinout.h" + + +void set_relays(bool one, bool two, bool three, bool four) { + gpio_put(PIN_RE1, one); + gpio_put(PIN_RE2, two); + gpio_put(PIN_RE3, three); + gpio_put(PIN_RE1, four); +} + +void open_valve(uint8_t num) { + gpio_put(PIN_RE1, num == 1); + gpio_put(PIN_RE2, num == 2); + gpio_put(PIN_RE3, num == 3); + gpio_put(PIN_RE4, num == 4); +} + +uint16_t moisture_read() { + adc_select_input(ADC_NUM_MOISTURE); + return adc_read(); +} + diff --git a/src/app_io.h b/src/app_io.h new file mode 100644 index 0000000..ee5281e --- /dev/null +++ b/src/app_io.h @@ -0,0 +1,17 @@ +/** + * TODO file description + */ + +#ifndef ZAVLAHA_APP_IO_H +#define ZAVLAHA_APP_IO_H + +#include +#include + +void set_relays(bool one, bool two, bool three, bool four); + +void open_valve(uint8_t num); + +uint16_t moisture_read(); + +#endif //ZAVLAHA_APP_IO_H diff --git a/src/main.c b/src/main.c index eda1cca..2e7fc2d 100644 --- a/src/main.c +++ b/src/main.c @@ -4,11 +4,13 @@ #include #include #include +#include #include "pinout.h" #include "lcd.h" #include "ds_rtc.h" #include "ee.h" #include "lcd/lcdbuf.h" +#include "screens/app_gui.h" /* @@ -49,37 +51,26 @@ void setup_output(uint num) gpio_set_function(num, GPIO_FUNC_SIO); } +#define KEYPAD_BUFFER_LEN 10 +static uint8_t keypad_buffer[KEYPAD_BUFFER_LEN]; +static uint8_t keypad_buffer_wp = 0; +static uint8_t keypad_buffer_rp = 0; + // RX interrupt handler void irq_uart() { while (uart_is_readable(uart0)) { uint8_t ch = uart_getc(uart0); - // TODO do something useful - if (uart_is_writable(uart0)) { - uart_putc(uart0, ch); + if (keypad_buffer[keypad_buffer_wp] == 0) { + keypad_buffer[keypad_buffer_wp] = ch; + keypad_buffer_wp++; + if (keypad_buffer_wp >= KEYPAD_BUFFER_LEN) { + keypad_buffer_wp = 0; + } } } } -void set_relays(bool one, bool two, bool three, bool four) { - gpio_put(PIN_RE1, one); - gpio_put(PIN_RE2, two); - gpio_put(PIN_RE3, three); - gpio_put(PIN_RE1, four); -} - -void open_valve(uint num) { - gpio_put(PIN_RE1, num == 1); - gpio_put(PIN_RE2, num == 2); - gpio_put(PIN_RE3, num == 3); - gpio_put(PIN_RE4, num == 4); -} - -uint16_t moisture_read() { - adc_select_input(ADC_NUM_MOISTURE); - return adc_read(); -} - int main() { // picotool binary info @@ -134,24 +125,44 @@ int main() lcd_init(); - struct LcdBuffer lcd; - LcdBuffer_Init(&lcd, CGROM_A00, CGRAM_CZ); - - LcdBuffer_Write(&lcd, 0, 0, "ěščřŽÝÁÍ pyčo!"); - LcdBuffer_Flush(&lcd); - - char buf[100]; - while (1) { - struct rtc_time time; - if (0 == rtc_get_time(&time)) { - sprintf(buf, "čas: %02d:%02d:%02d", time.hour, time.minute, time.second); - LcdBuffer_Write(&lcd, 2, 2, buf); + gui_init(); + + for(;;) { + uint32_t interrupts = save_and_disable_interrupts(); + GuiEvent event = GUI_EVENT_NONE; + if (keypad_buffer[keypad_buffer_rp] != 0) { + char ch = keypad_buffer[keypad_buffer_rp]; + keypad_buffer[keypad_buffer_rp] = 0; + keypad_buffer_rp++; + if (keypad_buffer_rp >= KEYPAD_BUFFER_LEN) { + keypad_buffer_rp = 0; + } + event = ch; // there is 1:1 mapping for keypad ASCII } - LcdBuffer_Flush(&lcd); + restore_interrupts(interrupts); - sleep_ms(1000); + gui_loop_iter(event); } +// +// struct LcdBuffer lcd; +// LcdBuffer_Init(&lcd, CGROM_A00, CGRAM_CZ); +// +// LcdBuffer_Write(&lcd, 0, 0, "ěščřŽÝÁÍ pyčo!"); +// LcdBuffer_Flush(&lcd); +// +// char buf[100]; +// while (1) { +// struct rtc_time time; +// if (0 == rtc_get_time(&time)) { +// sprintf(buf, "čas: %02d:%02d:%02d", time.hour, time.minute, time.second); +// LcdBuffer_Write(&lcd, 2, 2, buf); +// } +// LcdBuffer_Flush(&lcd); +// +// sleep_ms(1000); +// } + #if 0 // while (1) { diff --git a/src/screens/app_gui.c b/src/screens/app_gui.c new file mode 100644 index 0000000..633ecca --- /dev/null +++ b/src/screens/app_gui.c @@ -0,0 +1,83 @@ +/** + * TODO file description + */ + +#include +#include +#include "app_gui.h" +#include "../lcd/lcdbuf.h" + +struct State s_app = {}; + +struct LcdBuffer lcd = {}; + +/** Schedule paint (the screen func will be called with the PAINT event argument */ +void request_paint() { + s_app.paint_needed = true; +} + +/** Draw the common overlay / HUD (with temperatures and heater status) */ +static void draw_common_overlay(); + +char stmp[100]; + +/** Main loop */ +void gui_init() +{ + switch_screen(screen_home, true); + s_app.last_tick_time = timestamp(); + + LcdBuffer_Init(&lcd, CGROM_A00, CGRAM_CZ); +} + +void gui_loop_iter(GuiEvent message) { + uint32_t tickNow = timestamp(); + + // 10ms tick event + if (tickNow - s_app.last_tick_time > 10) { + s_app.screen(GUI_EVENT_SCREEN_TICK); + s_app.last_tick_time = tickNow; + } + + if (message != GUI_EVENT_NONE) { + s_app.screen(message); + } + + if (message >= 32) { // lazy shortcut so we dont have to list all of them + // key was pressed + input_sound_effect(); + } + + if (s_app.paint_needed) { + s_app.paint_needed = false; + + draw_common_overlay(); + s_app.screen(GUI_EVENT_PAINT); + + // If there is anything to print, do it + LcdBuffer_Flush(&lcd); + } +} + +/** Switch to a different screen handler. + * If "init" is true, immediately call it with the init event. */ +void switch_screen(screen_t pScreen, bool init) { + s_app.screen = pScreen; + + LcdBuffer_Clear(&lcd); + request_paint(); + + if (init) { + pScreen(GUI_EVENT_SCREEN_INIT); + } +} + +/** Draw GUI common to all screens */ +static void draw_common_overlay() { + // TODO +} + +/** Play input sound effect if this is an input event */ +void input_sound_effect() { + // TODO +} diff --git a/src/screens/app_gui.h b/src/screens/app_gui.h new file mode 100644 index 0000000..77e00c6 --- /dev/null +++ b/src/screens/app_gui.h @@ -0,0 +1,63 @@ +/** + * TODO file description + */ + +#ifndef ZAVLAHA_APP_GUI_H +#define ZAVLAHA_APP_GUI_H + +#include +#include +#include +#include "gui_event.h" +#include "lcd/lcdbuf.h" + +/// Temporary scratch buffer +extern char stmp[100]; + +extern struct LcdBuffer lcd; + +/** + * Screen callback type. The event is either INIT, PAINT, or one of the input events. + */ +typedef void (*screen_t)(GuiEvent event); + + +static inline uint32_t timestamp() { + return to_ms_since_boot(get_absolute_time()); +} + +/** Input beep (push or knob turn) */ +void input_sound_effect(); + + +void gui_loop_iter(GuiEvent message); +void gui_init(); + +/** Switch to a different screen. Handles initial push state handling (so release + * does not cause a "click" event). + * + * @param pScreen - screen to switch to + * @param init - call the INIT event immediately after + */ +void switch_screen(screen_t pScreen, bool init); + +void request_paint(); + +// prototypes for screen handlers + +void screen_home(GuiEvent event); +// XXX other prototypes + +struct State { + /// Repaint was requested from the screen code + bool paint_needed; + + /// Pointer to the currently active screen func + screen_t screen; + + uint32_t last_tick_time; +}; + +extern struct State s_app; + +#endif //ZAVLAHA_APP_GUI_H diff --git a/src/screens/gui_event.h b/src/screens/gui_event.h new file mode 100644 index 0000000..51c1b4a --- /dev/null +++ b/src/screens/gui_event.h @@ -0,0 +1,38 @@ +// +// Created by MightyPork on 2023/04/09. +// + +#ifndef ZAVLAHA_GUI_EVENT_H +#define ZAVLAHA_GUI_EVENT_H + +// sent through the notify queue +typedef enum GuiEvent { + /// No event, zero; This is a default value. + GUI_EVENT_NONE = 0, + /// Cause redraw + GUI_EVENT_PAINT = 0, + /// Used as the argument when initing a screen + GUI_EVENT_SCREEN_INIT = 1, + /// Time tick, used to carry timing to the screen functions. + /// This tick has 10ms interval + GUI_EVENT_SCREEN_TICK = 2, + /// Keypad + GUI_EVENT_KEY_0 = '0', + GUI_EVENT_KEY_1, + GUI_EVENT_KEY_2, + GUI_EVENT_KEY_3, + GUI_EVENT_KEY_4, + GUI_EVENT_KEY_5, + GUI_EVENT_KEY_6, + GUI_EVENT_KEY_7, + GUI_EVENT_KEY_8, + GUI_EVENT_KEY_9, + GUI_EVENT_KEY_A = 'A', + GUI_EVENT_KEY_B, + GUI_EVENT_KEY_C, + GUI_EVENT_KEY_D, + GUI_EVENT_KEY_STAR = '*', + GUI_EVENT_KEY_HASH = '#', +} GuiEvent; + +#endif //ZAVLAHA_GUI_EVENT_H diff --git a/src/screens/screen_home.c b/src/screens/screen_home.c new file mode 100644 index 0000000..44e47cd --- /dev/null +++ b/src/screens/screen_home.c @@ -0,0 +1,59 @@ +/** + * TODO file description + */ +// +// Created by MightyPork on 2023/04/09. +// + +#include +#include +#include "app_gui.h" +#include "gui_event.h" +#include "ds_rtc.h" +#include "app_io.h" + +static uint32_t last_time = 0; +static struct rtc_time rtc_time = {}; +static uint16_t moisture = 0; + +static char showbuf[20]; +static uint8_t showbuf_wp = 0; + +void screen_home(GuiEvent event) +{ + uint32_t now = timestamp(); + uint32_t elapsed = now - last_time; + + switch (event) { + case GUI_EVENT_SCREEN_INIT: + memset(showbuf, ' ', sizeof(showbuf)); + // pass + case GUI_EVENT_SCREEN_TICK: + if (elapsed >= 100) { + last_time = now; + rtc_get_time(&rtc_time); + moisture = moisture_read(); + request_paint(); + } + break; + + case GUI_EVENT_PAINT:; + LcdBuffer_Write(&lcd, 0, 0, showbuf); + + sprintf(stmp, "Čas: %02d:%02d:%02d", rtc_time.hour, rtc_time.minute, rtc_time.second); + LcdBuffer_Write(&lcd, 1, 0, stmp); + + sprintf(stmp, "Vlhkost: %4d", moisture); + LcdBuffer_Write(&lcd, 2, 0, stmp); + break; + + default: + if (event >= 32) { + showbuf[showbuf_wp++] = event; + if (showbuf_wp == 20) { + showbuf_wp = 0; + } + request_paint(); + } + } +}