Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bd8429fee | ||
|
|
6370cfc891 |
+2
-3
@@ -1,5 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
set(PICO_SDK_PATH /home/sl/pico-sdk)
|
||||
set(PICO_PLATFORM rp2350)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
include(pico_sdk_import.cmake)
|
||||
@@ -10,8 +9,8 @@ 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)
|
||||
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)
|
||||
add_executable(program main.c st7789.c gui.c graphics.c graphics_fb.c utf8.c ds1302.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 pico_util)
|
||||
#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)
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
#include "gui.h"
|
||||
|
||||
#include <pico/bootrom.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
#ifndef VELOMETER_GUI_H
|
||||
#define VELOMETER_GUI_H
|
||||
#include "config.h"
|
||||
#include <pico/stdlib.h>
|
||||
|
||||
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
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <boot/picobin.h>
|
||||
#include <hardware/adc.h>
|
||||
#include <hardware/dma.h>
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user