Compare commits

...

2 Commits

Author SHA1 Message Date
6586bcfa69 fix font; add separate counter for distance from power-on 2026-09-13 22:47:07 +03:00
2073575f54 ui changes
speedometer: digit color now changes gradually with speed
odometer: the last digit is now aligned to bottom
2026-09-12 21:27:55 +03:00
4 changed files with 126 additions and 56 deletions

View File

@@ -326,9 +326,9 @@ const char GLYPH_CYRILLIC_SMALL_LETTER_CHE[] = {224, 224, 0, 0, 0, 0, 0, 0,
const char GLYPH_CYRILLIC_SMALL_LETTER_SHA[] = {224, 224, 0, 0, 0, 0, 0, 0, 224, 224, 0, 0, 0, 0, 0, 224,
255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255,
7, 7, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6, 6, 6, 6, 7};
const char GLYPH_CYRILLIC_SMALL_LETTER_SHCHA[] = {224, 224, 0, 0, 0, 0, 0, 0, 224, 224, 0, 0, 0, 0, 0, 224,
255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255,
7, 7, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6, 6, 6, 6, 7};
const char GLYPH_CYRILLIC_SMALL_LETTER_SHCHA[] = {
224, 224, 0, 0, 0, 0, 0, 0, 224, 224, 0, 0, 0, 0, 0, 224, 224, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255,
0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 7, 7, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6, 6, 6, 6, 7, 7, 126, 126, 0};
const char GLYPH_CYRILLIC_SMALL_LETTER_HARD_SIGN[] = {96, 96, 96, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 255, 255, 12, 12, 12, 12, 12, 12, 28, 24, 248, 240,
0, 0, 0, 7, 7, 6, 6, 6, 6, 6, 6, 6, 3, 3, 1};
@@ -351,7 +351,7 @@ const char GLYPH_WIDTH[143] = {
13, 9, 16, 17, 14, 13, 15, 12, 11, 13, 14, 2, 11, 14, 12, 16, 15, 16, 13, 16, 14, 12, 14, 14, 16, 17, 14, 14, 13,
6, 11, 6, 13, 13, 5, 11, 12, 10, 12, 13, 8, 12, 11, 2, 4, 12, 4, 16, 11, 13, 12, 12, 8, 10, 8, 11, 12, 18,
13, 12, 10, 7, 2, 7, 14, 12, 11, 18, 12, 17, 13, 15, 15, 14, 16, 14, 14, 17, 16, 12, 16, 16, 17, 16, 12, 13, 16,
14, 13, 10, 9, 15, 13, 18, 11, 12, 12, 12, 14, 15, 11, 11, 10, 16, 12, 10, 16, 16, 15, 16, 10, 10, 16, 11};
14, 13, 10, 9, 15, 13, 18, 11, 12, 12, 12, 14, 15, 11, 11, 10, 16, 12, 10, 16, 20, 15, 16, 10, 10, 16, 11};
const char *GLYPH_ADDR[143] = {
GLYPH_EXCLAMATION_MARK,

View File

@@ -111,8 +111,8 @@ void lcd_draw_vertical_xsmall_digit_segment(uint16_t x, uint16_t y, uint16_t col
}
}
const uint8_t DIGIT_MASKS[10] = {0b1110111, 0b0100100, 0b1011101, 0b1101101, 0b0101110,
0b1101011, 0b1111011, 0b0100101, 0b1111111, 0b1101111};
static const uint8_t DIGIT_MASKS[10] = {0b1110111, 0b0100100, 0b1011101, 0b1101101, 0b0101110,
0b1101011, 0b1111011, 0b0100101, 0b1111111, 0b1101111};
void lcd_draw_digit(uint8_t digit, uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor) {
const uint16_t SEGMENT_OFFSETS[7][2] = {{9, 0}, {0, 9}, {49, 9}, {9, 49}, {0, 58}, {49, 58}, {9, 99}};
@@ -199,7 +199,7 @@ void lcd_write_string(const char *string, uint16_t x, uint16_t y, uint16_t color
st7789_write(glyphPixels, 24 * (glyphWidth + LETTER_SPACING) * sizeof(uint16_t));
x += glyphWidth + LETTER_SPACING;
} else {
lcd_fill_rectangle(x, y, 12, 24, bgColor);
_fill_rectangle(x, y, 12, 24, bgColor);
x += 12;
}
}
@@ -215,7 +215,7 @@ int lcd_string_width(const char *string) {
} else {
int glyphIndex = glyph_index_for_code_point(character);
if (glyphIndex != -1) {
width += GLYPH_WIDTH[glyphIndex] + 1;
width += GLYPH_WIDTH[glyphIndex] + LETTER_SPACING;
} else {
width += 12;
}

View File

@@ -29,6 +29,14 @@ static inline void _draw_v_strip(uint16_t x, uint16_t y, uint16_t len, uint16_t
}
}
static inline void _fill_rectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
st7789_caset(x, x + w - 1);
st7789_raset(y, y + h - 1);
for (uint16_t i = 0; i < w * h; ++i) {
st7789_put(color);
}
}
inline void lcd_set_pixel(uint16_t x, uint16_t y, uint16_t color) {
x += LCD_OFFSET_X;
y += LCD_OFFSET_Y;
@@ -39,11 +47,7 @@ inline void lcd_set_pixel(uint16_t x, uint16_t y, uint16_t color) {
inline void lcd_fill_rectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
x += LCD_OFFSET_X;
y += LCD_OFFSET_Y;
st7789_caset(x, x + w - 1);
st7789_raset(y, y + h - 1);
for (uint16_t i = 0; i < w * h; ++i) {
st7789_put(color);
}
_fill_rectangle(x, y, w, h, color);
}
void lcd_draw_horizontal_digit_segment(uint16_t x, uint16_t y, uint16_t color);

152
main.c
View File

@@ -37,7 +37,8 @@ enum GUIScreen {
enum DisplayMode {
DISPLAY_MODE_SPEED,
DISPLAY_MODE_DISTANCE,
DISPLAY_MODE_DISTANCE_FROM_POWER_ON,
DISPLAY_MODE_TOTAL_DISTANCE,
};
struct GUIState {
@@ -51,6 +52,9 @@ struct GUIState {
//...
} settingsScreen;
} screenState;
uint8_t drawnTimeHours;
uint8_t drawnTimeMinutes;
uint16_t drawnTimeTextWidth;
};
void on_gpio_interrupt(uint pin, uint32_t eventMask);
@@ -112,7 +116,9 @@ uint16_t ticksPerWindow = 0;
// critical_section_t intervalWheelTicksCritSec;
repeating_timer_t speedMeasureTimer;
uint totalTicks = 0;
// odometer counters
uint totalWheelTicks = 0;
uint wheelTicksFromPowerOn = 0;
DateTime currentDatetime;
@@ -289,7 +295,8 @@ int64_t hall_sensor_debounce_callback(alarm_id_t alarmId, void *userData) {
absolute_time_t now = get_absolute_time();
lastWheelTickTime = now;
++intervalWheelTicks[currentInterval];
++totalTicks;
++totalWheelTicks;
++wheelTicksFromPowerOn;
gpio_put(25, !gpio_get(25));
hallSensorPrevState = hallSensorLastState;
@@ -338,7 +345,9 @@ void on_button_short_press(uint button) {
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;
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;
@@ -381,6 +390,14 @@ bool speed_measure_timer_callback(repeating_timer_t *rt) {
return 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) {
#define DIGITS_START_X 20
@@ -391,24 +408,23 @@ void draw_speed(uint value) {
if (value > 999)
value = 999;
uint16_t color;
if (value >= 300)
color = 0xfd04;
else if (value >= 200)
color = 0xfe84;
else if (value >= 150)
color = 0xdfe4;
else if (value >= 100)
color = 0x6fe4;
else if (value >= 50)
color = 0x27fb;
else
color = 0x04ff;
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;
lcd_draw_digit(value / 100, DIGITS_START_X, DIGITS_START_Y, color, 0x0000);
lcd_draw_digit((value / 10) % 10, DIGITS_START_X + DIGIT_WIDTH + DIGIT_SPACING, DIGITS_START_Y, color, 0x0000);
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, 0x0000);
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
@@ -417,11 +433,11 @@ void draw_speed(uint value) {
}
// distance is in "2.3" decimal fixed-point, in kilometers
void draw_distance(uint value) {
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 28
#define XSMALL_DIGITS_Y_OFFSET 60
#define DIGIT_WIDTH 64
#define SMALL_DIGIT_WIDTH 48
#define XSMALL_DIGIT_WIDTH 30
@@ -432,28 +448,64 @@ void draw_distance(uint value) {
if (value > 99999)
value = 99999;
lcd_draw_digit(value / 10000, DIGITS_START_X, DIGITS_START_Y, 0xffff, 0x0000);
lcd_draw_digit((value / 1000) % 10, DIGITS_START_X + DIGIT_WIDTH + DIGIT_SPACING, DIGITS_START_Y, 0xffff, 0x0000);
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, 0x0000);
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, 0x0000);
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 + XSMALL_DIGIT_SPACING + 10,
DIGITS_START_Y + XSMALL_DIGITS_Y_OFFSET, 0xffff, 0x0000);
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);
@@ -463,29 +515,43 @@ void update_lcd() {
if (guiState.screenState.mainScreen.displayMode == DISPLAY_MODE_SPEED) {
uint convertedSpeed = ticksPerWindow * cmphPerTpw / 10000;
draw_speed(convertedSpeed);
lcd_write_string("Скорость", 20, 0, 0xffff, 0x0000);
lcd_write_string("Скорость", 10, 0, 0xffff, 0x0000);
lcd_write_string("км/ч", 241, 126, 0xffff, 0x0000);
} else if (guiState.screenState.mainScreen.displayMode == DISPLAY_MODE_DISTANCE) {
uint convertedDistance = totalTicks * cmPerTick / 100;
draw_distance(convertedDistance);
lcd_write_string("Расстояние", 20, 0, 0xffff, 0x0000);
lcd_write_string("км", 280, 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);
}
char timeDisplay[6];
memset(timeDisplay, ' ', sizeof(timeDisplay));
timeDisplay[5] = 0;
sprintf(timeDisplay, "%i:%02i", currentDatetime.hour, currentDatetime.minute);
lcd_write_string(timeDisplay, 320 - 20 - lcd_string_width(timeDisplay), 0, 0xffff, 0x0000);
lcd_fill_rectangle(320 - 160, 0, 140 - lcd_string_width(timeDisplay), 24, 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;
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() {
@@ -509,7 +575,7 @@ void save_to_rtc() {
static uint savedDistance;
uint8_t buffer[31];
memset(buffer, 0, 31);
uint newDistance = totalTicks * cmPerTick / 10;
uint newDistance = totalWheelTicks * cmPerTick / 10;
if (savedBefore && newDistance == savedDistance) {
printf("skipped saving\n");
return;
@@ -581,7 +647,7 @@ bool load_from_rtc() {
}
uint savedDistance = *(uint *)(buffer + 0);
totalTicks = savedDistance * 10 / cmPerTick;
totalWheelTicks = savedDistance * 10 / cmPerTick;
#ifdef LOGGING
printf("successfully loaded data from RTC memory\n");
#endif