656 lines
21 KiB
C
656 lines
21 KiB
C
#include <boot/picobin.h>
|
|
#include <hardware/flash.h>
|
|
#include <hardware/gpio.h>
|
|
#include <hardware/irq.h>
|
|
#include <hardware/psram.h>
|
|
#include <hardware/pwm.h>
|
|
#include <hardware/regs/intctrl.h>
|
|
#include <pico/bootrom.h>
|
|
#include <pico/critical_section.h>
|
|
#include <pico/stdlib.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "beep.h"
|
|
#include "ds1302.h"
|
|
#include "graphics.h"
|
|
#include "pins.h"
|
|
#include "st7789.h"
|
|
|
|
#define LOGGING
|
|
|
|
#define HALL_SENSOR_DEBOUNCE_DELAY_us 10000
|
|
#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;
|
|
};
|
|
|
|
void on_gpio_interrupt(uint pin, uint32_t eventMask);
|
|
int64_t hall_sensor_debounce_callback(alarm_id_t alarmId, void *userData);
|
|
int64_t button_debounce_callback(alarm_id_t alarmId, void *userData);
|
|
int64_t button_hold_callback(alarm_id_t alarmId, void *userData);
|
|
bool speed_measure_timer_callback(repeating_timer_t *rt);
|
|
void on_button_short_press(uint button);
|
|
void on_button_long_press(uint button);
|
|
|
|
void update_lcd();
|
|
void gui_init(struct GUIState *state);
|
|
void init_button_states();
|
|
void save_to_rtc();
|
|
bool load_from_rtc();
|
|
void reset();
|
|
|
|
const uint8_t FRONT_BUTTON_PINS[4] = {PIN_BUTTON_0, PIN_BUTTON_1, PIN_BUTTON_2, PIN_BUTTON_3};
|
|
|
|
const uint16_t BRIGHTNESS_CONVERT_TABLE_X[] = {0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
|
|
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]) {
|
|
int16_t x0 = BRIGHTNESS_CONVERT_TABLE_X[i];
|
|
int16_t x1 = BRIGHTNESS_CONVERT_TABLE_X[i + 1];
|
|
int16_t y0 = BRIGHTNESS_CONVERT_TABLE_Y[i];
|
|
int16_t y1 = BRIGHTNESS_CONVERT_TABLE_Y[i + 1];
|
|
return y0 + (y1 - y0) * (input - x0) / (x1 - x0);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// for debouncing
|
|
alarm_id_t hallSensorDebounceAlarm = 0;
|
|
bool hallSensorLastState = 0;
|
|
bool hallSensorPrevState = 0;
|
|
|
|
struct {
|
|
alarm_id_t debounceAlarm, holdAlarm;
|
|
bool currentState, prevState, isHeld;
|
|
} buttonStates[4];
|
|
|
|
// for debug (and for the future)
|
|
absolute_time_t lastWheelTickTime = 0;
|
|
|
|
// for speed measurement
|
|
uint8_t intervalWheelTicks[SPEED_MEASUREMENT_WINDOW_SIZE];
|
|
uint8_t currentInterval = 0;
|
|
uint16_t ticksPerWindow = 0;
|
|
// critical_section_t intervalWheelTicksCritSec;
|
|
repeating_timer_t speedMeasureTimer;
|
|
|
|
// odometer counters
|
|
uint totalWheelTicks = 0;
|
|
uint wheelTicksFromPowerOn = 0;
|
|
|
|
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();
|
|
#endif
|
|
|
|
// display init
|
|
const struct st7789_config displayConfig = {
|
|
.spi = PICO_DEFAULT_SPI_INSTANCE(),
|
|
.gpio_din = PIN_LCD_SDA,
|
|
.gpio_clk = PIN_LCD_SCL,
|
|
.gpio_cs = PIN_LCD_CS,
|
|
.gpio_dc = PIN_LCD_DATA_COMMAND,
|
|
.gpio_rst = PIN_LCD_RESET,
|
|
.gpio_bl = PIN_LCD_BACKLIGHT,
|
|
};
|
|
st7789_init(&displayConfig, LCD_WIDTH + LCD_OFFSET_X, LCD_HEIGHT + LCD_OFFSET_Y);
|
|
|
|
// splash image (just for fun)
|
|
const uint16_t *splashImage = (const uint16_t *)(XIP_NOCACHE_NOALLOC_NOTRANSLATE_BASE + 1581056);
|
|
if (splashImage) {
|
|
st7789_caset(LCD_OFFSET_X, LCD_OFFSET_X + 317);
|
|
st7789_raset(LCD_OFFSET_Y, LCD_OFFSET_Y + 171);
|
|
st7789_write(splashImage, 318 * 172 * sizeof(uint16_t));
|
|
} else {
|
|
rom_reset_usb_boot(0, 0);
|
|
}
|
|
sleep_ms(1000);
|
|
|
|
#ifdef LOGGING
|
|
if (psram_is_available()) {
|
|
printf("PSRAM available. detecting size... ");
|
|
size_t psramSize = psram_detect_size();
|
|
printf("%u\n", psramSize);
|
|
}
|
|
#endif
|
|
|
|
// critical_section_init(&intervalWheelTicksCritSec);
|
|
add_repeating_timer_ms(SPEED_MEASUREMENT_INTERVAL_ms, speed_measure_timer_callback, NULL, &speedMeasureTimer);
|
|
|
|
gpio_set_irq_callback(on_gpio_interrupt);
|
|
|
|
gpio_init(PIN_BEEPER);
|
|
gpio_set_dir(PIN_BEEPER, GPIO_OUT);
|
|
|
|
// hall sensor
|
|
gpio_init(PIN_MAIN_HALL_SENSOR);
|
|
gpio_pull_up(PIN_MAIN_HALL_SENSOR);
|
|
gpio_set_irq_enabled(PIN_MAIN_HALL_SENSOR, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true);
|
|
|
|
// on-board LED
|
|
gpio_init(25);
|
|
gpio_set_dir(25, true);
|
|
|
|
// front buttons
|
|
init_button_states();
|
|
gpio_init_mask(0b1111 << 15);
|
|
for (int pin = 15; pin <= 18; ++pin) {
|
|
gpio_pull_up(pin);
|
|
gpio_set_irq_enabled(pin, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true);
|
|
}
|
|
|
|
irq_set_enabled(IO_IRQ_BANK0, 1);
|
|
|
|
ds1302_init();
|
|
|
|
DateTime tmp = {26, 9, 9, 15, 10, 0, DOW_WED};
|
|
// ds1302_set_datetime(&tmp);
|
|
|
|
load_from_rtc();
|
|
|
|
gpio_set_function(PIN_LCD_BACKLIGHT, GPIO_FUNC_PWM);
|
|
pwm_set_wrap(BACKLIGHT_PWM_SLICE, 1024);
|
|
pwm_set_chan_level(BACKLIGHT_PWM_SLICE, BACKLIGHT_PWM_CHANNEL, 1024);
|
|
pwm_set_enabled(BACKLIGHT_PWM_SLICE, true);
|
|
|
|
gpio_set_function(7, GPIO_FUNC_PWM);
|
|
pwm_set_wrap(3, 256);
|
|
pwm_set_chan_level(3, PWM_CHAN_B, 0);
|
|
pwm_set_enabled(3, true);
|
|
|
|
gui_init(&guiState);
|
|
|
|
absolute_time_t nextLcdUpdateTime = delayed_by_ms(get_absolute_time(), 50);
|
|
absolute_time_t nextClockUpdateTime = get_absolute_time();
|
|
absolute_time_t nextSaveToRtcTime = delayed_by_ms(get_absolute_time(), 10000);
|
|
|
|
while (true) {
|
|
absolute_time_t t = get_absolute_time();
|
|
if (t >= nextClockUpdateTime) {
|
|
nextClockUpdateTime = delayed_by_ms(t, 1000);
|
|
ds1302_get_datetime(¤tDatetime);
|
|
printf("time is %i %i %i %i %i %i %i\n", currentDatetime.year, currentDatetime.month, currentDatetime.day,
|
|
currentDatetime.dow, currentDatetime.hour, currentDatetime.minute, currentDatetime.second);
|
|
}
|
|
if (t >= nextLcdUpdateTime) {
|
|
nextLcdUpdateTime = delayed_by_ms(t, 50);
|
|
update_lcd();
|
|
if (gpio_get(PIN_BUTTON_0) == 0 && gpio_get(PIN_BUTTON_3) == 0) {
|
|
rom_reset_usb_boot(0, 0);
|
|
}
|
|
}
|
|
if (t >= nextSaveToRtcTime) {
|
|
nextSaveToRtcTime = delayed_by_ms(t, 10000);
|
|
save_to_rtc();
|
|
}
|
|
sleep_until(nextLcdUpdateTime);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void on_gpio_interrupt(uint pin, uint32_t eventMask) {
|
|
if (pin == PIN_MAIN_HALL_SENSOR) {
|
|
if (eventMask & GPIO_IRQ_EDGE_RISE)
|
|
hallSensorLastState = 0;
|
|
if (eventMask & GPIO_IRQ_EDGE_FALL)
|
|
hallSensorLastState = 1;
|
|
if (hallSensorDebounceAlarm)
|
|
cancel_alarm(hallSensorDebounceAlarm);
|
|
hallSensorDebounceAlarm =
|
|
add_alarm_in_us(HALL_SENSOR_DEBOUNCE_DELAY_us, hall_sensor_debounce_callback, NULL, false);
|
|
gpio_acknowledge_irq(pin, eventMask);
|
|
} else if (pin == PIN_BUTTON_0 || pin == PIN_BUTTON_1 || pin == PIN_BUTTON_2 || pin == PIN_BUTTON_3) {
|
|
uint buttonIndex;
|
|
switch (pin) {
|
|
case PIN_BUTTON_0:
|
|
buttonIndex = 0;
|
|
break;
|
|
case PIN_BUTTON_1:
|
|
buttonIndex = 1;
|
|
break;
|
|
case PIN_BUTTON_2:
|
|
buttonIndex = 2;
|
|
break;
|
|
case PIN_BUTTON_3:
|
|
buttonIndex = 3;
|
|
}
|
|
auto buttonState = &buttonStates[buttonIndex];
|
|
// if (eventMask & GPIO_IRQ_EDGE_RISE)
|
|
// buttonState->currentState = 0;
|
|
// if (eventMask & GPIO_IRQ_EDGE_FALL)
|
|
// buttonState->currentState = 1;
|
|
// buttonState->currentState = !gpio_get(pin);
|
|
if (buttonState->debounceAlarm != -1)
|
|
cancel_alarm(buttonState->debounceAlarm);
|
|
buttonState->debounceAlarm =
|
|
add_alarm_in_us(BUTTON_DEBOUNCE_DELAY_us, button_debounce_callback, (void *)buttonIndex, false);
|
|
gpio_acknowledge_irq(pin, eventMask);
|
|
}
|
|
}
|
|
|
|
int64_t hall_sensor_debounce_callback(alarm_id_t alarmId, void *userData) {
|
|
(void)alarmId;
|
|
(void)userData;
|
|
if (hallSensorLastState != hallSensorPrevState) {
|
|
absolute_time_t now = get_absolute_time();
|
|
lastWheelTickTime = now;
|
|
++intervalWheelTicks[currentInterval];
|
|
++totalWheelTicks;
|
|
++wheelTicksFromPowerOn;
|
|
|
|
gpio_put(25, !gpio_get(25));
|
|
hallSensorPrevState = hallSensorLastState;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int64_t button_debounce_callback(alarm_id_t alarmId, void *userData) {
|
|
(void)alarmId;
|
|
uint buttonIndex = (uint)userData;
|
|
auto buttonState = &buttonStates[buttonIndex];
|
|
bool currentState = !gpio_get(FRONT_BUTTON_PINS[buttonIndex]);
|
|
if (currentState != buttonState->prevState) {
|
|
buttonState->prevState = currentState;
|
|
if (buttonState->holdAlarm != -1) {
|
|
cancel_alarm(buttonState->holdAlarm);
|
|
}
|
|
if (currentState == 1) {
|
|
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);
|
|
else
|
|
buttonState->isHeld = false;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int64_t button_hold_callback(alarm_id_t alarmId, void *userData) {
|
|
(void)alarmId;
|
|
uint buttonIndex = (uint)userData;
|
|
auto buttonState = &buttonStates[buttonIndex];
|
|
buttonState->holdAlarm = -1;
|
|
buttonState->isHeld = true;
|
|
on_button_long_press(buttonIndex);
|
|
return 0;
|
|
}
|
|
|
|
void on_button_short_press(uint button) {
|
|
// beep(60, 900);
|
|
#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) {
|
|
// beep(60, 1273);
|
|
#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();
|
|
}
|
|
}
|
|
|
|
bool speed_measure_timer_callback(repeating_timer_t *rt) {
|
|
(void)rt;
|
|
|
|
uint16_t sum = 0;
|
|
/*
|
|
printf("window: ");
|
|
for (int i = 0; i < 16; ++i) {
|
|
sum += intervalWheelTicks[i];
|
|
if (currentInterval == i)
|
|
printf("[%i] ", intervalWheelTicks[i]);
|
|
else
|
|
printf("%i ", intervalWheelTicks[i]);
|
|
}
|
|
printf("\n");
|
|
*/
|
|
for (int i = 0; i < 16; ++i) {
|
|
sum += intervalWheelTicks[i];
|
|
}
|
|
currentInterval = (currentInterval + 1) % 16;
|
|
intervalWheelTicks[currentInterval] = 0;
|
|
ticksPerWindow = sum;
|
|
// printf("Total %i \r", sum);
|
|
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
|
|
#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;
|
|
}
|
|
}
|
|
|
|
void reset() {
|
|
pwm_set_chan_level(BACKLIGHT_PWM_SLICE, BACKLIGHT_PWM_CHANNEL, 0); // turn off the screen
|
|
(*((volatile uint32_t *)(PPB_BASE + 0x0ED0C))) = 0x5FA0004; // weird trick to actually reset the controller
|
|
}
|
|
|
|
void save_to_rtc() {
|
|
printf("saving distance to RTC... ");
|
|
static bool savedBefore = false;
|
|
static uint savedDistance;
|
|
uint8_t buffer[31];
|
|
memset(buffer, 0, 31);
|
|
uint newDistance = totalWheelTicks * cmPerTick / 10;
|
|
if (savedBefore && newDistance == savedDistance) {
|
|
printf("skipped saving\n");
|
|
return;
|
|
}
|
|
|
|
*(uint *)(buffer + 0) = newDistance;
|
|
|
|
uint16_t checksum = 0;
|
|
for (int i = 0; i < 29; ++i) {
|
|
checksum += buffer[i];
|
|
}
|
|
*(uint16_t *)(buffer + 29) = checksum;
|
|
|
|
uint8_t checkBuffer[31];
|
|
int limit = 10;
|
|
do {
|
|
printf("\nattempting to write...\n");
|
|
ds1302_set_write_protection(0);
|
|
// for (int i = 0; i < 31; ++i) {
|
|
// ds1302_set_ram_byte(0xC0 + i * 2, buffer + i);
|
|
// sleep_us(2);
|
|
// }
|
|
ds1302_write_ram_bulk(buffer, 31);
|
|
for (int i = 0; i < 30; ++i)
|
|
printf("%02x ", buffer[i]);
|
|
printf("%02x\n", buffer[30]);
|
|
ds1302_read_ram_bulk(checkBuffer, 31);
|
|
printf("attempting to read back...\n");
|
|
for (int i = 0; i < 30; ++i)
|
|
printf("%02x ", checkBuffer[i]);
|
|
printf("%02x\n", checkBuffer[30]);
|
|
if (limit-- == 0)
|
|
break;
|
|
} while (memcmp(buffer, checkBuffer, 31) != 0);
|
|
|
|
#ifdef LOGGING
|
|
for (int i = 0; i < 30; ++i)
|
|
printf("%02x ", buffer[i]);
|
|
printf("%02x\n", buffer[30]);
|
|
#endif
|
|
|
|
savedDistance = newDistance;
|
|
savedBefore = true;
|
|
#ifdef LOGGING
|
|
printf("saved\n");
|
|
#endif
|
|
}
|
|
|
|
bool load_from_rtc() {
|
|
uint8_t buffer[31];
|
|
ds1302_read_ram_bulk(buffer, 31);
|
|
|
|
#ifdef LOGGING
|
|
for (int i = 0; i < 30; ++i)
|
|
printf("%02x ", buffer[i]);
|
|
printf("%02x\n", buffer[30]);
|
|
#endif
|
|
|
|
uint16_t checksum = *(uint16_t *)(buffer + 29);
|
|
uint16_t actualChecksum = 0;
|
|
for (int i = 0; i < 29; ++i) {
|
|
actualChecksum += buffer[i];
|
|
}
|
|
if (actualChecksum != checksum) {
|
|
#ifdef LOGGING
|
|
printf("discarding invalid data in RTC memory\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
uint savedDistance = *(uint *)(buffer + 0);
|
|
totalWheelTicks = savedDistance * 10 / cmPerTick;
|
|
#ifdef LOGGING
|
|
printf("successfully loaded data from RTC memory\n");
|
|
#endif
|
|
return true;
|
|
}
|