Files
velometer/main.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(&currentDatetime);
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;
}