diff --git a/CMakeLists.txt b/CMakeLists.txt index 991ed63..4a87bbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ set(PICO_PSRAM_CS_PIN 47 CACHE STRING "GPIO used as Chip Select for PSRAM") set(PICO_PSRAM_SIZE_BYTES 8388608 CACHE STRING "PSRAM size in bytes") pico_sdk_init() -add_executable(program main.c st7789.c graphics.c graphics_fb.c utf8.c ds1302.c beep.c ss49e.c) +add_executable(program main.c st7789.c gui.c graphics.c graphics_fb.c utf8.c ds1302.c beep.c ss49e.c) target_link_libraries(program pico_stdlib pico_bootrom pico_rand hardware_adc hardware_dma hardware_gpio hardware_pwm hardware_spi hardware_regs hardware_psram pico_stdio_usb pico_time pico_sync) #target_compile_definitions(program PRIVATE PICO_PSRAM_CS_PIN=${PICO_PSRAM_CS_PIN} PICO_PSRAM_SIZE_BYTES=${PICO_PSRAM_CS_PIN}) target_compile_definitions(program PRIVATE PICO_PSRAM_CS_PIN=0 PICO_PSRAM_SIZE_BYTES=8388608) diff --git a/graphics.h b/graphics.h index bbe082d..fc0b472 100644 --- a/graphics.h +++ b/graphics.h @@ -4,7 +4,7 @@ #define LCD_OFFSET_X 2 #define LCD_OFFSET_Y 34 -#define LCD_WIDTH 320 +#define LCD_WIDTH 318 #define LCD_HEIGHT 172 #define LETTER_SPACING 2 diff --git a/gui.c b/gui.c new file mode 100644 index 0000000..35c2b76 --- /dev/null +++ b/gui.c @@ -0,0 +1,199 @@ +#include "gui.h" + +#include +#include +#include + +#include "ds1302.h" +#include "graphics.h" +#include "st7789.h" +#include "units.h" + +extern uint16_t ticksPerWindow; +extern uint totalWheelTicks; +extern uint wheelTicksFromPowerOn; +extern DateTime currentDatetime; + +extern void reset(); + +static struct GUIState guiState; + +void gui_init() { + guiState.screen = 0; + guiState.screenState.mainScreen.displayMode = DISPLAY_MODE_DISTANCE_FROM_POWER_ON; + guiState.screenState.mainScreen.displayModeChanged = true; + guiState.drawnTimeTextWidth = 0; + guiState.drawnTimeHours = 24; + guiState.drawnTimeMinutes = 60; +} + +void on_button_input(short button, enum ButtonInputType type, uint heldButtons) { +#ifdef LOGGING + printf("button %i %s press\n", button, type == BUTTON_SHORT_PRESS ? "short" : "long"); +#endif + + // handle simultaneous long presses + if (button == 2 && (heldButtons & (1 << 3)) || button == 3 && (heldButtons & (1 << 2))) { + reset(); + } + if (button == 0 && (heldButtons & (1 << 3)) || button == 3 && (heldButtons & (1 << 0))) { + rom_reset_usb_boot(0, 0); + } + + // handle simple short and long presses + if (button == 3 && guiState.screen == MAIN_SCREEN) { + uint oldDispMode = guiState.screenState.mainScreen.displayMode; + if (oldDispMode == DISPLAY_MODE_SPEED) + guiState.screenState.mainScreen.displayMode = DISPLAY_MODE_DISTANCE_FROM_POWER_ON; + else if (oldDispMode == DISPLAY_MODE_DISTANCE_FROM_POWER_ON) + guiState.screenState.mainScreen.displayMode = DISPLAY_MODE_TOTAL_DISTANCE; + else + guiState.screenState.mainScreen.displayMode = DISPLAY_MODE_SPEED; + guiState.screenState.mainScreen.displayModeChanged = true; + } +} + +uint16_t color565_lerp(uint16_t a, uint16_t b, uint alphaPercent) { + uint alphaComplement = 100 - alphaPercent; + uint16_t red = ((b >> 11) * alphaPercent + (a >> 11) * alphaComplement) / 100; + uint16_t green = (((b >> 5) & 0x3F) * alphaPercent + ((a >> 5) & 0x3F) * alphaComplement) / 100; + uint16_t blue = ((b & 0x1F) * alphaPercent + (a & 0x1F) * alphaComplement) / 100; + return blue | (green << 5) | (red << 11); +} + +// speed is in "2.1" decimal fixed-point, in km/h +void draw_speed(uint value) { + enum { + DIGITS_START_X = 20, + DIGITS_START_Y = 37, + DIGIT_WIDTH = 64, + DIGIT_SPACING = 8, + }; + + if (value > 999) + value = 999; + + const uint16_t speedToColorX[] = {0, 50, 100, 150, 200, 300}; + const uint16_t speedToColorY[] = {0x04ff, 0x27fb, 0x6fe4, 0xdfe4, 0xfe84, 0xfd04}; + const size_t speedToColorPointCount = 6; + + uint16_t color = speedToColorY[speedToColorPointCount - 1]; + for (int i = 1; i < speedToColorPointCount; ++i) { + if (value <= speedToColorX[i]) { + color = color565_lerp(speedToColorY[i - 1], speedToColorY[i], + (value - speedToColorX[i - 1]) * 100 / (speedToColorX[i] - speedToColorX[i - 1])); + break; + } + } + + lcd_draw_digit(value / 100, DIGITS_START_X, DIGITS_START_Y, color, 0x528a); + lcd_draw_digit((value / 10) % 10, DIGITS_START_X + DIGIT_WIDTH + DIGIT_SPACING, DIGITS_START_Y, color, 0x528a); + lcd_draw_decimal_point(DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING, DIGITS_START_Y + 101, color); + lcd_draw_digit(value % 10, DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + 5, DIGITS_START_Y, color, 0x528a); +} + +// distance is in "2.3" decimal fixed-point, in kilometers +void draw_short_term_distance(uint value) { + enum { + DIGITS_START_X = 20, + DIGITS_START_Y = 37, + SMALL_DIGITS_Y_OFFSET = 28, + XSMALL_DIGITS_Y_OFFSET = 60, + DIGIT_WIDTH = 64, + SMALL_DIGIT_WIDTH = 48, + XSMALL_DIGIT_WIDTH = 30, + DIGIT_SPACING = 8, + SMALL_DIGIT_SPACING = 6, + XSMALL_DIGIT_SPACING = 4, + }; + + if (value > 99999) + value = 99999; + + lcd_draw_digit(value / 10000, DIGITS_START_X, DIGITS_START_Y, 0xffff, 0x528a); + lcd_draw_digit((value / 1000) % 10, DIGITS_START_X + DIGIT_WIDTH + DIGIT_SPACING, DIGITS_START_Y, 0xffff, 0x528a); + lcd_draw_decimal_point(DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING, DIGITS_START_Y + 101, 0xffff); + lcd_draw_small_digit((value / 100) % 10, DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + 10, + DIGITS_START_Y + SMALL_DIGITS_Y_OFFSET, 0xffff, 0x528a); + lcd_draw_small_digit((value / 10) % 10, + DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + SMALL_DIGIT_WIDTH + SMALL_DIGIT_SPACING + + 10, + DIGITS_START_Y + SMALL_DIGITS_Y_OFFSET, 0xffff, 0x528a); + lcd_draw_xsmall_digit(value % 10, + DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + SMALL_DIGIT_WIDTH * 2 + + SMALL_DIGIT_SPACING * 2 + 10, + DIGITS_START_Y + XSMALL_DIGITS_Y_OFFSET, 0xffff, 0x528a); +} + +// long term distance is in "4.2" decimal fixed-point, in kilometers +void draw_long_term_distance(uint value) { + enum { + DIGITS_START_X = 15, + DIGITS_START_Y = 50, + SMALL_DIGIT_WIDTH = 48, + XSMALL_DIGITS_Y_OFFSET = 32, + XSMALL_DIGIT_WIDTH = 30, + SMALL_DIGIT_SPACING = 6, + XSMALL_DIGIT_SPACING = 4, + }; + + if (value > 999999) + value = 999999; + + lcd_draw_small_digit(value / 100000, DIGITS_START_X, DIGITS_START_Y, 0xffff, 0x528a); + lcd_draw_small_digit((value / 10000) % 10, DIGITS_START_X + SMALL_DIGIT_WIDTH + SMALL_DIGIT_SPACING, DIGITS_START_Y, + 0xffff, 0x528a); + lcd_draw_small_digit((value / 1000) % 10, DIGITS_START_X + SMALL_DIGIT_WIDTH * 2 + SMALL_DIGIT_SPACING * 2, + DIGITS_START_Y, 0xffff, 0x528a); + lcd_draw_small_digit((value / 100) % 10, DIGITS_START_X + SMALL_DIGIT_WIDTH * 3 + SMALL_DIGIT_SPACING * 3, + DIGITS_START_Y, 0xffff, 0x528a); + lcd_draw_decimal_point(DIGITS_START_X + SMALL_DIGIT_WIDTH * 4 + SMALL_DIGIT_SPACING * 4 - 6, DIGITS_START_Y + 76, + 0xffff); + lcd_draw_xsmall_digit((value / 10) % 10, DIGITS_START_X + SMALL_DIGIT_WIDTH * 4 + SMALL_DIGIT_SPACING * 4 + 10, + DIGITS_START_Y + XSMALL_DIGITS_Y_OFFSET, 0xffff, 0x528a); + lcd_draw_xsmall_digit(value % 10, + DIGITS_START_X + SMALL_DIGIT_WIDTH * 4 + SMALL_DIGIT_SPACING * 4 + XSMALL_DIGIT_WIDTH + + XSMALL_DIGIT_SPACING + 10, + DIGITS_START_Y + XSMALL_DIGITS_Y_OFFSET, 0xffff, 0x528a); +} + +void update_lcd() { + if (guiState.screen == MAIN_SCREEN) { + bool forceRedrawClock = guiState.screenState.mainScreen.displayModeChanged; + if (guiState.screenState.mainScreen.displayModeChanged) { + // lcd_fill_rectangle(49, 27, 213, 113, 0x0000); + st7789_fill(0x0000); + guiState.screenState.mainScreen.displayModeChanged = false; + } + + if (guiState.screenState.mainScreen.displayMode == DISPLAY_MODE_SPEED) { + uint convertedSpeed = ticksPerWindow * cmphPerTpw / 10000; + draw_speed(convertedSpeed); + lcd_write_string("Скорость", 10, 0, 0xffff, 0x0000); + lcd_write_string("км/ч", 241, 126, 0xffff, 0x0000); + } else if (guiState.screenState.mainScreen.displayMode == DISPLAY_MODE_DISTANCE_FROM_POWER_ON) { + uint convertedDistance = wheelTicksFromPowerOn * cmPerTick / 100; + draw_short_term_distance(convertedDistance); + lcd_write_string("Текущий путь", 10, 0, 0xffff, 0x0000); + lcd_write_string("км", 280, 37, 0xffff, 0x0000); + } else if (guiState.screenState.mainScreen.displayMode == DISPLAY_MODE_TOTAL_DISTANCE) { + uint convertedDistance = totalWheelTicks * cmPerTick / 1000; + draw_long_term_distance(convertedDistance); + lcd_write_string("Общий путь", 10, 0, 0xffff, 0x0000); + lcd_write_string("км", 280, 37, 0xffff, 0x0000); + } + + if (forceRedrawClock || guiState.drawnTimeHours != currentDatetime.hour || + guiState.drawnTimeMinutes != currentDatetime.minute) { + lcd_fill_rectangle(305 - guiState.drawnTimeTextWidth, 0, guiState.drawnTimeTextWidth, 24, 0x0000); + guiState.drawnTimeHours = currentDatetime.hour; + guiState.drawnTimeMinutes = currentDatetime.minute; + char timeDisplay[6]; + memset(timeDisplay, ' ', sizeof(timeDisplay)); + timeDisplay[5] = 0; + sprintf(timeDisplay, "%i:%02i", currentDatetime.hour, currentDatetime.minute); + lcd_write_string(timeDisplay, 305 - lcd_string_width(timeDisplay), 0, 0xffff, 0x0000); + guiState.drawnTimeTextWidth = lcd_string_width(timeDisplay); + } + } +} diff --git a/gui.h b/gui.h new file mode 100644 index 0000000..3017cb9 --- /dev/null +++ b/gui.h @@ -0,0 +1,91 @@ +#ifndef VELOMETER_GUI_H +#define VELOMETER_GUI_H +#include "config.h" +#include + +enum ButtonInputType { + BUTTON_SHORT_PRESS, + BUTTON_LONG_PRESS, +}; + +enum GUIScreen { + MAIN_SCREEN, + MENU_SCREEN, +}; + +enum DisplayMode { + DISPLAY_MODE_SPEED, + DISPLAY_MODE_DISTANCE_FROM_POWER_ON, + DISPLAY_MODE_TOTAL_DISTANCE, +}; + +enum MenuOptionType { + MENU_OPTION_NUMBER, + MENU_OPTION_ONOFF, + MENU_OPTION_TEXT, + MENU_OPTION_LINK, +}; + +#define TEXT_EDIT_STATIC_BUFFER_SIZE 18 +struct TextEditState { + uint textLength; // number of characters in input + uint textByteLength; // number of bytes occupied by characters + + // fixed-size buffers for most cases + char staticTextBuffer[TEXT_EDIT_STATIC_BUFFER_SIZE]; + uint8_t staticCharWidthBuffer[TEXT_EDIT_STATIC_BUFFER_SIZE]; + + // the same thing but in heap for longer input + bool heapBufferInUse; + char *heapTextBuffer; + uint8_t *heapCharWidthBuffer; + uint heapTextBufferCapacity; + uint heapCharWidthBufferCapacity; + + // visual edit field properties + uint cursorPos; // in characters + uint cursorPosCharOffset; // for quicker character addressing inside a UTF-8 string + uint scrollOffset; // in pixels +}; + +struct MenuScreenState { + enum { + MENU_SCREEN_STATE_CHOOSING_OPTION, + MENU_SCREEN_STATE_EDITING_OPTION, + } subState; + union { + struct { + uint chosenOptionIndex; + short scrollAnimationProgress; + } choosingOption; + struct { + uint optionIndex; + enum MenuOptionType optionType; + union { + uint numberValue; + bool onOffValue; + struct TextEditState textEditState; + }; + } editingOption; + }; +}; + +struct GUIState { + uint screen; + union { + struct { + uint displayMode; + bool displayModeChanged; + } mainScreen; + struct MenuScreenState menuScreen; + } screenState; + uint8_t drawnTimeHours; + uint8_t drawnTimeMinutes; + uint16_t drawnTimeTextWidth; +}; + +void gui_init(); +void on_button_input(short button, enum ButtonInputType type, uint heldButtons); +void update_lcd(); + +#endif diff --git a/main.c b/main.c index 7e888a7..8bc9cc4 100644 --- a/main.c +++ b/main.c @@ -1,3 +1,5 @@ +#include "config.h" + #include #include #include @@ -17,46 +19,11 @@ #include "ds1302.h" #include "graphics.h" +#include "gui.h" #include "pins.h" #include "ss49e.h" #include "st7789.h" - -#define LOGGING - -#define BUTTON_DEBOUNCE_DELAY_us 3000 -#define BUTTON_HOLD_TIME_ms 1000 -#define TICKS_PER_REVOLUTION 6 -#define SPEED_MEASUREMENT_INTERVAL_ms 100 -#define SPEED_MEASUREMENT_WINDOW_SIZE 16 - -#define SPEED_MEASUREMENT_WINDOW_INTERVAL (SPEED_MEASUREMENT_INTERVAL_ms * SPEED_MEASUREMENT_WINDOW_SIZE) - -enum GUIScreen { - MAIN_SCREEN, - SETTINGS_SCREEN, -}; - -enum DisplayMode { - DISPLAY_MODE_SPEED, - DISPLAY_MODE_DISTANCE_FROM_POWER_ON, - DISPLAY_MODE_TOTAL_DISTANCE, -}; - -struct GUIState { - uint screen; - union { - struct { - uint displayMode; - bool displayModeChanged; - } mainScreen; - struct { - //... - } settingsScreen; - } screenState; - uint8_t drawnTimeHours; - uint8_t drawnTimeMinutes; - uint16_t drawnTimeTextWidth; -}; +#include "units.h" void on_gpio_interrupt(uint pin, uint32_t eventMask); static void __not_in_flash_func(dma_irq_handler)(); @@ -66,8 +33,6 @@ void on_button_short_press(uint button); void on_button_long_press(uint button); void on_wheel_hall_sensor_tick(); -void update_lcd(); -void gui_init(struct GUIState *state); void init_button_states(); void save_to_rtc(); bool load_from_rtc(); @@ -79,11 +44,6 @@ const uint16_t BRIGHTNESS_CONVERT_TABLE_X[] = {0, 10, 20, 30, 40, 50, 60, 70, 80 const uint16_t BRIGHTNESS_CONVERT_TABLE_Y[] = {0, 7, 12, 21, 37, 64, 111, 194, 338, 588, 1024}; const size_t BRIGHTNESS_CONVERT_LABLE_LEN = 11; -const uint millimetersPerRevolution = 1540; -const uint cmphPerTpw = 360000 * millimetersPerRevolution / SPEED_MEASUREMENT_WINDOW_INTERVAL / - TICKS_PER_REVOLUTION; // cm/hour per Ticks/window -const uint cmPerTick = millimetersPerRevolution / TICKS_PER_REVOLUTION / 10; // cm per tick - int16_t convert_brightness(int16_t input) { for (int i = 0; i < BRIGHTNESS_CONVERT_LABLE_LEN - 1; ++i) { if (input < BRIGHTNESS_CONVERT_TABLE_X[i + 1]) { @@ -99,10 +59,11 @@ int16_t convert_brightness(int16_t input) { struct PushButtonState { alarm_id_t debounceAlarm, holdAlarm; - bool currentState, prevState, isHeld; + bool currentState, prevState; }; -struct PushButtonState buttonStates[4]; +static struct PushButtonState buttonStates[4]; +static uint heldButtons; // for debug (and for the future) absolute_time_t lastWheelTickTime = 0; @@ -127,18 +88,6 @@ DateTime currentDatetime; // unused currently // repeating_timer_t audioPlaybackTimer; -struct GUIState guiState; - -const uint16_t *find_splash_image_offset(const uint16_t *start) { - const uint16_t marker[3] = {0x6f66, 0x626f, 0x7261}; - for (int offs = 0; offs < 2 * 1024 * 1024; ++offs) { - if (start[offs + 0] == marker[0] && start[offs + 1] == marker[1] && start[offs + 2] == marker[2]) { - return start + offs; - } - } - return NULL; -} - int main() { #ifdef LOGGING stdio_usb_init(); @@ -182,7 +131,7 @@ int main() { adc_gpio_init(42); adc_select_input(2); adc_set_round_robin(1 << 2); - adc_set_clkdiv(3000 / 1); // 16kHz for 1 sensor + adc_set_clkdiv(3000.0f / 1.0f); // 16kHz for 1 sensor adc_fifo_setup(true, true, 1, false, false); // wheel sensor (with N-S alternating magnet arrangement) @@ -247,7 +196,7 @@ int main() { pwm_set_chan_level(3, PWM_CHAN_B, 0); pwm_set_enabled(3, true); - gui_init(&guiState); + gui_init(); absolute_time_t nextLcdUpdateTime = delayed_by_ms(get_absolute_time(), 50); absolute_time_t nextClockUpdateTime = get_absolute_time(); @@ -370,10 +319,10 @@ int64_t button_debounce_callback(alarm_id_t alarmId, void *userData) { buttonState->holdAlarm = add_alarm_in_ms(BUTTON_HOLD_TIME_ms, button_hold_callback, (void *)buttonIndex, false); } else { buttonState->holdAlarm = -1; - if (!buttonState->isHeld) - on_button_short_press(buttonIndex); + if (!(heldButtons & (1 << buttonIndex))) + on_button_input(buttonIndex, BUTTON_SHORT_PRESS, heldButtons); else - buttonState->isHeld = false; + heldButtons &= ~(1 << buttonIndex); } } return 0; @@ -384,37 +333,11 @@ int64_t button_hold_callback(alarm_id_t alarmId, void *userData) { uint buttonIndex = (uint)userData; struct PushButtonState *buttonState = &buttonStates[buttonIndex]; buttonState->holdAlarm = -1; - buttonState->isHeld = true; - on_button_long_press(buttonIndex); + heldButtons |= 1 << buttonIndex; + on_button_input(buttonIndex, BUTTON_LONG_PRESS, heldButtons); return 0; } -void on_button_short_press(uint button) { -#ifdef LOGGING - printf("button %i short press\n", button); -#endif - if (button == 3 && guiState.screen == MAIN_SCREEN) { - uint oldDispMode = guiState.screenState.mainScreen.displayMode; - if (oldDispMode == DISPLAY_MODE_SPEED) - guiState.screenState.mainScreen.displayMode = DISPLAY_MODE_DISTANCE_FROM_POWER_ON; - else if (oldDispMode == DISPLAY_MODE_DISTANCE_FROM_POWER_ON) - guiState.screenState.mainScreen.displayMode = DISPLAY_MODE_TOTAL_DISTANCE; - else - guiState.screenState.mainScreen.displayMode = DISPLAY_MODE_SPEED; - guiState.screenState.mainScreen.displayModeChanged = true; - } -} - -void on_button_long_press(uint button) { -#ifdef LOGGING - printf("button %i long press\n"); -#endif - if (button == 2 && buttonStates[3].isHeld || - button == 3 && buttonStates[2].isHeld) { // if buttons 2 & 3 pressed and held simultaneously... - reset(); - } -} - void on_wheel_hall_sensor_tick() { ++intervalWheelTicks[currentInterval]; ++totalWheelTicks; @@ -422,178 +345,14 @@ void on_wheel_hall_sensor_tick() { printf("\t\t\tTICK\7\n"); } -uint16_t color565_lerp(uint16_t a, uint16_t b, uint alphaPercent) { - uint alphaComplement = 100 - alphaPercent; - uint16_t red = ((b >> 11) * alphaPercent + (a >> 11) * alphaComplement) / 100; - uint16_t green = (((b >> 5) & 0x3F) * alphaPercent + ((a >> 5) & 0x3F) * alphaComplement) / 100; - uint16_t blue = ((b & 0x1F) * alphaPercent + (a & 0x1F) * alphaComplement) / 100; - return blue | (green << 5) | (red << 11); -} - -// speed is in "2.1" decimal fixed-point, in km/h -void draw_speed(uint value) { -#define DIGITS_START_X 20 -#define DIGITS_START_Y 37 -#define DIGIT_WIDTH 64 -#define DIGIT_SPACING 8 - - if (value > 999) - value = 999; - - const uint16_t speedToColorX[] = {0, 50, 100, 150, 200, 300}; - const uint16_t speedToColorY[] = {0x04ff, 0x27fb, 0x6fe4, 0xdfe4, 0xfe84, 0xfd04}; - const size_t speedToColorPointCount = 6; - - uint16_t color = speedToColorY[speedToColorPointCount - 1]; - for (int i = 1; i < speedToColorPointCount; ++i) { - if (value <= speedToColorX[i]) { - color = color565_lerp(speedToColorY[i - 1], speedToColorY[i], - (value - speedToColorX[i - 1]) * 100 / (speedToColorX[i] - speedToColorX[i - 1])); - break; - } - } - - lcd_draw_digit(value / 100, DIGITS_START_X, DIGITS_START_Y, color, 0x528a); - lcd_draw_digit((value / 10) % 10, DIGITS_START_X + DIGIT_WIDTH + DIGIT_SPACING, DIGITS_START_Y, color, 0x528a); - lcd_draw_decimal_point(DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING, DIGITS_START_Y + 101, color); - lcd_draw_digit(value % 10, DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + 5, DIGITS_START_Y, color, 0x528a); - -#undef DIGITS_START_X -#undef DIGITS_START_Y -#undef DIGIT_WIDTH -#undef DIGIT_SPACING -} - -// distance is in "2.3" decimal fixed-point, in kilometers -void draw_short_term_distance(uint value) { -#define DIGITS_START_X 20 -#define DIGITS_START_Y 37 -#define SMALL_DIGITS_Y_OFFSET 28 -#define XSMALL_DIGITS_Y_OFFSET 60 -#define DIGIT_WIDTH 64 -#define SMALL_DIGIT_WIDTH 48 -#define XSMALL_DIGIT_WIDTH 30 -#define DIGIT_SPACING 8 -#define SMALL_DIGIT_SPACING 6 -#define XSMALL_DIGIT_SPACING 4 - - if (value > 99999) - value = 99999; - - lcd_draw_digit(value / 10000, DIGITS_START_X, DIGITS_START_Y, 0xffff, 0x528a); - lcd_draw_digit((value / 1000) % 10, DIGITS_START_X + DIGIT_WIDTH + DIGIT_SPACING, DIGITS_START_Y, 0xffff, 0x528a); - lcd_draw_decimal_point(DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING, DIGITS_START_Y + 101, 0xffff); - lcd_draw_small_digit((value / 100) % 10, DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + 10, - DIGITS_START_Y + SMALL_DIGITS_Y_OFFSET, 0xffff, 0x528a); - lcd_draw_small_digit((value / 10) % 10, - DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + SMALL_DIGIT_WIDTH + SMALL_DIGIT_SPACING + - 10, - DIGITS_START_Y + SMALL_DIGITS_Y_OFFSET, 0xffff, 0x528a); - lcd_draw_xsmall_digit(value % 10, - DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + SMALL_DIGIT_WIDTH * 2 + - SMALL_DIGIT_SPACING * 2 + 10, - DIGITS_START_Y + XSMALL_DIGITS_Y_OFFSET, 0xffff, 0x528a); - -#undef DIGITS_START_X -#undef DIGITS_START_Y -#undef DIGIT_WIDTH -#undef DIGIT_SPACING -#undef XSMALL_DIGITS_Y_OFFSET -} - -// long term distance is in "4.2" decimal fixed-point, in kilometers -void draw_long_term_distance(uint value) { -#define DIGITS_START_X 15 -#define DIGITS_START_Y 50 -#define XSMALL_DIGITS_Y_OFFSET 32 -#define XSMALL_DIGIT_WIDTH 30 -#define XSMALL_DIGIT_SPACING 4 - - if (value > 999999) - value = 999999; - - lcd_draw_small_digit(value / 100000, DIGITS_START_X, DIGITS_START_Y, 0xffff, 0x528a); - lcd_draw_small_digit((value / 10000) % 10, DIGITS_START_X + SMALL_DIGIT_WIDTH + SMALL_DIGIT_SPACING, DIGITS_START_Y, - 0xffff, 0x528a); - lcd_draw_small_digit((value / 1000) % 10, DIGITS_START_X + SMALL_DIGIT_WIDTH * 2 + SMALL_DIGIT_SPACING * 2, - DIGITS_START_Y, 0xffff, 0x528a); - lcd_draw_small_digit((value / 100) % 10, DIGITS_START_X + SMALL_DIGIT_WIDTH * 3 + SMALL_DIGIT_SPACING * 3, - DIGITS_START_Y, 0xffff, 0x528a); - lcd_draw_decimal_point(DIGITS_START_X + SMALL_DIGIT_WIDTH * 4 + SMALL_DIGIT_SPACING * 4 - 6, DIGITS_START_Y + 76, - 0xffff); - lcd_draw_xsmall_digit((value / 10) % 10, DIGITS_START_X + SMALL_DIGIT_WIDTH * 4 + SMALL_DIGIT_SPACING * 4 + 10, - DIGITS_START_Y + XSMALL_DIGITS_Y_OFFSET, 0xffff, 0x528a); - lcd_draw_xsmall_digit(value % 10, - DIGITS_START_X + SMALL_DIGIT_WIDTH * 4 + SMALL_DIGIT_SPACING * 4 + XSMALL_DIGIT_WIDTH + - XSMALL_DIGIT_SPACING + 10, - DIGITS_START_Y + XSMALL_DIGITS_Y_OFFSET, 0xffff, 0x528a); - -#undef DIGITS_START_X -#undef DIGITS_START_Y -#undef XSMALL_DIGITS_Y_OFFSET -#undef XSMALL_DIGIT_WIDTH -#undef XSMALL_DIGIT_SPACING -} - -void update_lcd() { - if (guiState.screen == MAIN_SCREEN) { - bool forceRedrawClock = guiState.screenState.mainScreen.displayModeChanged; - if (guiState.screenState.mainScreen.displayModeChanged) { - // lcd_fill_rectangle(49, 27, 213, 113, 0x0000); - st7789_fill(0x0000); - guiState.screenState.mainScreen.displayModeChanged = false; - } - - if (guiState.screenState.mainScreen.displayMode == DISPLAY_MODE_SPEED) { - uint convertedSpeed = ticksPerWindow * cmphPerTpw / 10000; - draw_speed(convertedSpeed); - lcd_write_string("Скорость", 10, 0, 0xffff, 0x0000); - lcd_write_string("км/ч", 241, 126, 0xffff, 0x0000); - } else if (guiState.screenState.mainScreen.displayMode == DISPLAY_MODE_DISTANCE_FROM_POWER_ON) { - uint convertedDistance = wheelTicksFromPowerOn * cmPerTick / 100; - draw_short_term_distance(convertedDistance); - lcd_write_string("Текущий путь", 10, 0, 0xffff, 0x0000); - lcd_write_string("км", 280, 37, 0xffff, 0x0000); - } else if (guiState.screenState.mainScreen.displayMode == DISPLAY_MODE_TOTAL_DISTANCE) { - uint convertedDistance = totalWheelTicks * cmPerTick / 1000; - draw_long_term_distance(convertedDistance); - lcd_write_string("Общий путь", 10, 0, 0xffff, 0x0000); - lcd_write_string("км", 280, 37, 0xffff, 0x0000); - } - - if (forceRedrawClock || guiState.drawnTimeHours != currentDatetime.hour || - guiState.drawnTimeMinutes != currentDatetime.minute) { - lcd_fill_rectangle(305 - guiState.drawnTimeTextWidth, 0, guiState.drawnTimeTextWidth, 24, 0x0000); - guiState.drawnTimeHours = currentDatetime.hour; - guiState.drawnTimeMinutes = currentDatetime.minute; - char timeDisplay[6]; - memset(timeDisplay, ' ', sizeof(timeDisplay)); - timeDisplay[5] = 0; - sprintf(timeDisplay, "%i:%02i", currentDatetime.hour, currentDatetime.minute); - lcd_write_string(timeDisplay, 305 - lcd_string_width(timeDisplay), 0, 0xffff, 0x0000); - guiState.drawnTimeTextWidth = lcd_string_width(timeDisplay); - } - } else if (guiState.screen == SETTINGS_SCREEN) { - } -} - -void gui_init(struct GUIState *state) { - state->screen = 0; - state->screenState.mainScreen.displayMode = DISPLAY_MODE_DISTANCE_FROM_POWER_ON; - state->screenState.mainScreen.displayModeChanged = true; - state->drawnTimeTextWidth = 0; - state->drawnTimeHours = 24; - state->drawnTimeMinutes = 60; -} - void init_button_states() { for (int i = 0; i < 4; ++i) { buttonStates[i].debounceAlarm = -100; buttonStates[i].holdAlarm = -1; buttonStates[i].currentState = 0; buttonStates[i].prevState = 0; - buttonStates[i].isHeld = 0; } + heldButtons = 0; } void reset() {