7 Commits
Author SHA1 Message Date
Slavasil 6bd8429fee edit CMakeLists: remove explicit SDK path and exclude beep.c from project 2026-10-30 09:19:41 +03:00
Slavasil 6370cfc891 move GUI code and unit conversion coefficients to corresponding separate files 2026-09-30 14:54:30 +03:00
Slavasil 50a893f536 complete the switch to SS49E hall sensor 2026-09-26 22:18:51 +03:00
Slavasil d0fd39cffa remove some dead code and comments 2026-09-16 13:00:28 +03:00
Slavasil c2c261a1c8 remove non-existent header include 2026-09-15 21:43:58 +03:00
Slavasil 6586bcfa69 fix font; add separate counter for distance from power-on 2026-09-13 22:47:07 +03:00
Slavasil 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
9 changed files with 508 additions and 293 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.27) cmake_minimum_required(VERSION 3.27)
set(PICO_SDK_PATH /home/sl/pico-sdk)
set(PICO_PLATFORM rp2350) set(PICO_PLATFORM rp2350)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include(pico_sdk_import.cmake) include(pico_sdk_import.cmake)
project(GPIOTest) project(GPIOTest)
@@ -9,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") set(PICO_PSRAM_SIZE_BYTES 8388608 CACHE STRING "PSRAM size in bytes")
pico_sdk_init() pico_sdk_init()
add_executable(program main.c st7789.c graphics.c utf8.c ds1302.c beep.c) 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_gpio hardware_pwm hardware_spi hardware_regs hardware_psram pico_stdio_usb pico_time pico_sync) 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=${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) target_compile_definitions(program PRIVATE PICO_PSRAM_CS_PIN=0 PICO_PSRAM_SIZE_BYTES=8388608)
+4 -4
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, 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, 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}; 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, const char GLYPH_CYRILLIC_SMALL_LETTER_SHCHA[] = {
255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 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,
7, 7, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6, 6, 6, 6, 7}; 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, 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, 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}; 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, 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, 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, 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] = { const char *GLYPH_ADDR[143] = {
GLYPH_EXCLAMATION_MARK, GLYPH_EXCLAMATION_MARK,
+3 -3
View File
@@ -111,7 +111,7 @@ 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, static const uint8_t DIGIT_MASKS[10] = {0b1110111, 0b0100100, 0b1011101, 0b1101101, 0b0101110,
0b1101011, 0b1111011, 0b0100101, 0b1111111, 0b1101111}; 0b1101011, 0b1111011, 0b0100101, 0b1111111, 0b1101111};
void lcd_draw_digit(uint8_t digit, uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor) { void lcd_draw_digit(uint8_t digit, uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor) {
@@ -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)); st7789_write(glyphPixels, 24 * (glyphWidth + LETTER_SPACING) * sizeof(uint16_t));
x += glyphWidth + LETTER_SPACING; x += glyphWidth + LETTER_SPACING;
} else { } else {
lcd_fill_rectangle(x, y, 12, 24, bgColor); _fill_rectangle(x, y, 12, 24, bgColor);
x += 12; x += 12;
} }
} }
@@ -215,7 +215,7 @@ int lcd_string_width(const char *string) {
} else { } else {
int glyphIndex = glyph_index_for_code_point(character); int glyphIndex = glyph_index_for_code_point(character);
if (glyphIndex != -1) { if (glyphIndex != -1) {
width += GLYPH_WIDTH[glyphIndex] + 1; width += GLYPH_WIDTH[glyphIndex] + LETTER_SPACING;
} else { } else {
width += 12; width += 12;
} }
+10 -6
View File
@@ -4,7 +4,7 @@
#define LCD_OFFSET_X 2 #define LCD_OFFSET_X 2
#define LCD_OFFSET_Y 34 #define LCD_OFFSET_Y 34
#define LCD_WIDTH 320 #define LCD_WIDTH 318
#define LCD_HEIGHT 172 #define LCD_HEIGHT 172
#define LETTER_SPACING 2 #define LETTER_SPACING 2
@@ -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) { inline void lcd_set_pixel(uint16_t x, uint16_t y, uint16_t color) {
x += LCD_OFFSET_X; x += LCD_OFFSET_X;
y += LCD_OFFSET_Y; 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) { inline void lcd_fill_rectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
x += LCD_OFFSET_X; x += LCD_OFFSET_X;
y += LCD_OFFSET_Y; y += LCD_OFFSET_Y;
st7789_caset(x, x + w - 1); _fill_rectangle(x, y, w, h, color);
st7789_raset(y, y + h - 1);
for (uint16_t i = 0; i < w * h; ++i) {
st7789_put(color);
}
} }
void lcd_draw_horizontal_digit_segment(uint16_t x, uint16_t y, uint16_t color); void lcd_draw_horizontal_digit_segment(uint16_t x, uint16_t y, uint16_t color);
+199
View File
@@ -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);
}
}
}
+91
View File
@@ -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
+130 -277
View File
@@ -1,4 +1,8 @@
#include "config.h"
#include <boot/picobin.h> #include <boot/picobin.h>
#include <hardware/adc.h>
#include <hardware/dma.h>
#include <hardware/flash.h> #include <hardware/flash.h>
#include <hardware/gpio.h> #include <hardware/gpio.h>
#include <hardware/irq.h> #include <hardware/irq.h>
@@ -13,56 +17,22 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "beep.h"
#include "ds1302.h" #include "ds1302.h"
#include "graphics.h" #include "graphics.h"
#include "gui.h"
#include "pins.h" #include "pins.h"
#include "ss49e.h"
#include "st7789.h" #include "st7789.h"
#include "units.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,
};
struct GUIState {
uint screen;
union {
struct {
uint displayMode;
bool displayModeChanged;
} mainScreen;
struct {
//...
} settingsScreen;
} screenState;
};
void on_gpio_interrupt(uint pin, uint32_t eventMask); void on_gpio_interrupt(uint pin, uint32_t eventMask);
int64_t hall_sensor_debounce_callback(alarm_id_t alarmId, void *userData); static void __not_in_flash_func(dma_irq_handler)();
int64_t button_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); 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_short_press(uint button);
void on_button_long_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 init_button_states();
void save_to_rtc(); void save_to_rtc();
bool load_from_rtc(); bool load_from_rtc();
@@ -74,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 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 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) { int16_t convert_brightness(int16_t input) {
for (int i = 0; i < BRIGHTNESS_CONVERT_LABLE_LEN - 1; ++i) { for (int i = 0; i < BRIGHTNESS_CONVERT_LABLE_LEN - 1; ++i) {
if (input < BRIGHTNESS_CONVERT_TABLE_X[i + 1]) { if (input < BRIGHTNESS_CONVERT_TABLE_X[i + 1]) {
@@ -92,45 +57,37 @@ int16_t convert_brightness(int16_t input) {
return -1; return -1;
} }
// for debouncing struct PushButtonState {
alarm_id_t hallSensorDebounceAlarm = 0;
bool hallSensorLastState = 0;
bool hallSensorPrevState = 0;
struct {
alarm_id_t debounceAlarm, holdAlarm; alarm_id_t debounceAlarm, holdAlarm;
bool currentState, prevState, isHeld; bool currentState, prevState;
} buttonStates[4]; };
static struct PushButtonState buttonStates[4];
static uint heldButtons;
// for debug (and for the future) // for debug (and for the future)
absolute_time_t lastWheelTickTime = 0; absolute_time_t lastWheelTickTime = 0;
// for hall sensors
int adcDmaChannelA, adcDmaChannelB;
uint16_t adcBufferA[1600];
uint16_t adcBufferB[1600];
SS49E_State wheelSensorState;
// for speed measurement // for speed measurement
uint8_t intervalWheelTicks[SPEED_MEASUREMENT_WINDOW_SIZE]; uint8_t intervalWheelTicks[SPEED_MEASUREMENT_WINDOW_SIZE];
uint8_t currentInterval = 0; uint8_t currentInterval = 0;
uint16_t ticksPerWindow = 0; 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; DateTime currentDatetime;
// unused currently // unused currently
// repeating_timer_t audioPlaybackTimer; // 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() { int main() {
#ifdef LOGGING #ifdef LOGGING
stdio_usb_init(); stdio_usb_init();
@@ -167,18 +124,46 @@ int main() {
} }
#endif #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_set_irq_callback(on_gpio_interrupt);
gpio_init(PIN_BEEPER); // hall sensor (wheel only for now)
gpio_set_dir(PIN_BEEPER, GPIO_OUT); adc_init();
adc_gpio_init(42);
adc_select_input(2);
adc_set_round_robin(1 << 2);
adc_set_clkdiv(3000.0f / 1.0f); // 16kHz for 1 sensor
adc_fifo_setup(true, true, 1, false, false);
// hall sensor // wheel sensor (with N-S alternating magnet arrangement)
gpio_init(PIN_MAIN_HALL_SENSOR); ss49e_init(&wheelSensorState, MAGNET_POLTYPE_BIPOLAR, on_wheel_hall_sensor_tick);
gpio_pull_up(PIN_MAIN_HALL_SENSOR);
gpio_set_irq_enabled(PIN_MAIN_HALL_SENSOR, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true); // DMA for hall sensor
adcDmaChannelA = dma_claim_unused_channel(true);
adcDmaChannelB = dma_claim_unused_channel(true);
dma_channel_config dmaCfg = dma_channel_get_default_config(adcDmaChannelA);
channel_config_set_transfer_data_size(&dmaCfg, DMA_SIZE_16);
channel_config_set_read_increment(&dmaCfg, false);
channel_config_set_write_increment(&dmaCfg, true);
channel_config_set_dreq(&dmaCfg, DREQ_ADC);
channel_config_set_chain_to(&dmaCfg, adcDmaChannelB);
dma_channel_configure(adcDmaChannelA, &dmaCfg, adcBufferA, &adc_hw->fifo, 1600, false);
dmaCfg = dma_channel_get_default_config(adcDmaChannelB);
channel_config_set_transfer_data_size(&dmaCfg, DMA_SIZE_16);
channel_config_set_read_increment(&dmaCfg, false);
channel_config_set_write_increment(&dmaCfg, true);
channel_config_set_dreq(&dmaCfg, DREQ_ADC);
channel_config_set_chain_to(&dmaCfg, adcDmaChannelA);
dma_channel_configure(adcDmaChannelB, &dmaCfg, adcBufferB, &adc_hw->fifo, 1600, false);
dma_channel_set_irq0_enabled(adcDmaChannelA, true);
dma_channel_set_irq0_enabled(adcDmaChannelB, true);
irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler);
irq_set_enabled(DMA_IRQ_0, true);
adc_run(true);
dma_channel_start(adcDmaChannelA);
// on-board LED // on-board LED
gpio_init(25); gpio_init(25);
@@ -196,7 +181,7 @@ int main() {
ds1302_init(); ds1302_init();
DateTime tmp = {26, 9, 9, 15, 10, 0, DOW_WED}; // DateTime tmp = {26, 9, 9, 15, 10, 0, DOW_WED};
// ds1302_set_datetime(&tmp); // ds1302_set_datetime(&tmp);
load_from_rtc(); load_from_rtc();
@@ -211,7 +196,7 @@ int main() {
pwm_set_chan_level(3, PWM_CHAN_B, 0); pwm_set_chan_level(3, PWM_CHAN_B, 0);
pwm_set_enabled(3, true); pwm_set_enabled(3, true);
gui_init(&guiState); gui_init();
absolute_time_t nextLcdUpdateTime = delayed_by_ms(get_absolute_time(), 50); absolute_time_t nextLcdUpdateTime = delayed_by_ms(get_absolute_time(), 50);
absolute_time_t nextClockUpdateTime = get_absolute_time(); absolute_time_t nextClockUpdateTime = get_absolute_time();
@@ -243,17 +228,7 @@ int main() {
} }
void on_gpio_interrupt(uint pin, uint32_t eventMask) { void on_gpio_interrupt(uint pin, uint32_t eventMask) {
if (pin == PIN_MAIN_HALL_SENSOR) { if (pin == PIN_BUTTON_0 || pin == PIN_BUTTON_1 || pin == PIN_BUTTON_2 || pin == PIN_BUTTON_3) {
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; uint buttonIndex;
switch (pin) { switch (pin) {
case PIN_BUTTON_0: case PIN_BUTTON_0:
@@ -268,12 +243,7 @@ void on_gpio_interrupt(uint pin, uint32_t eventMask) {
case PIN_BUTTON_3: case PIN_BUTTON_3:
buttonIndex = 3; buttonIndex = 3;
} }
auto buttonState = &buttonStates[buttonIndex]; struct PushButtonState *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) if (buttonState->debounceAlarm != -1)
cancel_alarm(buttonState->debounceAlarm); cancel_alarm(buttonState->debounceAlarm);
buttonState->debounceAlarm = buttonState->debounceAlarm =
@@ -282,25 +252,63 @@ void on_gpio_interrupt(uint pin, uint32_t eventMask) {
} }
} }
int64_t hall_sensor_debounce_callback(alarm_id_t alarmId, void *userData) { static void __not_in_flash_func(dma_irq_handler)() {
(void)alarmId; if (dma_hw->ints0 & (1u << adcDmaChannelA)) {
(void)userData; dma_hw->ints0 = 1u << adcDmaChannelA;
if (hallSensorLastState != hallSensorPrevState) { for (uint i = 0; i < 1600; ++i)
absolute_time_t now = get_absolute_time(); ss49e_process_sample(&wheelSensorState, adcBufferA[i]);
lastWheelTickTime = now; float sum = 0.0f, max = 0.0f, min = 9999.0f;
++intervalWheelTicks[currentInterval]; for (uint i = 0; i < 1600; ++i) {
++totalTicks; sum += adcBufferA[i];
if (adcBufferA[i] > max)
gpio_put(25, !gpio_get(25)); max = adcBufferA[i];
hallSensorPrevState = hallSensorLastState; if (adcBufferA[i] < min)
min = adcBufferA[i];
}
sum /= 1600;
printf("%f ~ %f ~ %f\n", min, sum, max);
dma_channel_set_write_addr(adcDmaChannelA, adcBufferA, false);
dma_channel_set_trans_count(adcDmaChannelA, 1600, false);
sum = 0;
for (int i = 0; i < 16; ++i) {
sum += intervalWheelTicks[i];
}
currentInterval = (currentInterval + 1) % 16;
intervalWheelTicks[currentInterval] = 0;
ticksPerWindow = sum;
}
if (dma_hw->ints0 & (1u << adcDmaChannelB)) {
dma_hw->ints0 = 1u << adcDmaChannelB;
for (uint i = 0; i < 1600; ++i)
ss49e_process_sample(&wheelSensorState, adcBufferB[i]);
float sum = 0.0f, max = 0.0f, min = 9999.0f;
for (uint i = 0; i < 1600; ++i) {
sum += adcBufferB[i];
if (adcBufferB[i] > max)
max = adcBufferB[i];
if (adcBufferB[i] < min)
min = adcBufferB[i];
}
sum /= 1600;
printf("%f ~ %f ~ %f\n", min, sum, max);
dma_channel_set_write_addr(adcDmaChannelB, adcBufferB, false);
dma_channel_set_trans_count(adcDmaChannelB, 1600, false);
sum = 0;
for (int i = 0; i < 16; ++i) {
sum += intervalWheelTicks[i];
}
currentInterval = (currentInterval + 1) % 16;
intervalWheelTicks[currentInterval] = 0;
ticksPerWindow = sum;
} }
return 0;
} }
int64_t button_debounce_callback(alarm_id_t alarmId, void *userData) { int64_t button_debounce_callback(alarm_id_t alarmId, void *userData) {
(void)alarmId; (void)alarmId;
uint buttonIndex = (uint)userData; uint buttonIndex = (uint)userData;
auto buttonState = &buttonStates[buttonIndex]; struct PushButtonState *buttonState = &buttonStates[buttonIndex];
bool currentState = !gpio_get(FRONT_BUTTON_PINS[buttonIndex]); bool currentState = !gpio_get(FRONT_BUTTON_PINS[buttonIndex]);
if (currentState != buttonState->prevState) { if (currentState != buttonState->prevState) {
buttonState->prevState = currentState; buttonState->prevState = currentState;
@@ -311,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); buttonState->holdAlarm = add_alarm_in_ms(BUTTON_HOLD_TIME_ms, button_hold_callback, (void *)buttonIndex, false);
} else { } else {
buttonState->holdAlarm = -1; buttonState->holdAlarm = -1;
if (!buttonState->isHeld) if (!(heldButtons & (1 << buttonIndex)))
on_button_short_press(buttonIndex); on_button_input(buttonIndex, BUTTON_SHORT_PRESS, heldButtons);
else else
buttonState->isHeld = false; heldButtons &= ~(1 << buttonIndex);
} }
} }
return 0; return 0;
@@ -323,169 +331,18 @@ int64_t button_debounce_callback(alarm_id_t alarmId, void *userData) {
int64_t button_hold_callback(alarm_id_t alarmId, void *userData) { int64_t button_hold_callback(alarm_id_t alarmId, void *userData) {
(void)alarmId; (void)alarmId;
uint buttonIndex = (uint)userData; uint buttonIndex = (uint)userData;
auto buttonState = &buttonStates[buttonIndex]; struct PushButtonState *buttonState = &buttonStates[buttonIndex];
buttonState->holdAlarm = -1; buttonState->holdAlarm = -1;
buttonState->isHeld = true; heldButtons |= 1 << buttonIndex;
on_button_long_press(buttonIndex); on_button_input(buttonIndex, BUTTON_LONG_PRESS, heldButtons);
return 0; return 0;
} }
void on_button_short_press(uint button) { void on_wheel_hall_sensor_tick() {
// beep(60, 900); ++intervalWheelTicks[currentInterval];
#ifdef LOGGING ++totalWheelTicks;
printf("button %i short press\n", button); ++wheelTicksFromPowerOn;
#endif printf("\t\t\tTICK\7\n");
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;
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;
}
// 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;
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;
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);
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);
#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_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 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, 0x0000);
lcd_draw_digit((value / 1000) % 10, DIGITS_START_X + DIGIT_WIDTH + DIGIT_SPACING, DIGITS_START_Y, 0xffff, 0x0000);
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);
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);
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);
#undef DIGITS_START_X
#undef DIGITS_START_Y
#undef DIGIT_WIDTH
#undef DIGIT_SPACING
}
void update_lcd() {
if (guiState.screen == MAIN_SCREEN) {
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("Скорость", 20, 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);
}
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);
} 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.displayModeChanged = true;
} }
void init_button_states() { void init_button_states() {
@@ -494,8 +351,8 @@ void init_button_states() {
buttonStates[i].holdAlarm = -1; buttonStates[i].holdAlarm = -1;
buttonStates[i].currentState = 0; buttonStates[i].currentState = 0;
buttonStates[i].prevState = 0; buttonStates[i].prevState = 0;
buttonStates[i].isHeld = 0;
} }
heldButtons = 0;
} }
void reset() { void reset() {
@@ -509,7 +366,7 @@ void save_to_rtc() {
static uint savedDistance; static uint savedDistance;
uint8_t buffer[31]; uint8_t buffer[31];
memset(buffer, 0, 31); memset(buffer, 0, 31);
uint newDistance = totalTicks * cmPerTick / 10; uint newDistance = totalWheelTicks * cmPerTick / 10;
if (savedBefore && newDistance == savedDistance) { if (savedBefore && newDistance == savedDistance) {
printf("skipped saving\n"); printf("skipped saving\n");
return; return;
@@ -528,10 +385,6 @@ void save_to_rtc() {
do { do {
printf("\nattempting to write...\n"); printf("\nattempting to write...\n");
ds1302_set_write_protection(0); 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); ds1302_write_ram_bulk(buffer, 31);
for (int i = 0; i < 30; ++i) for (int i = 0; i < 30; ++i)
printf("%02x ", buffer[i]); printf("%02x ", buffer[i]);
@@ -581,7 +434,7 @@ bool load_from_rtc() {
} }
uint savedDistance = *(uint *)(buffer + 0); uint savedDistance = *(uint *)(buffer + 0);
totalTicks = savedDistance * 10 / cmPerTick; totalWheelTicks = savedDistance * 10 / cmPerTick;
#ifdef LOGGING #ifdef LOGGING
printf("successfully loaded data from RTC memory\n"); printf("successfully loaded data from RTC memory\n");
#endif #endif
+38
View File
@@ -0,0 +1,38 @@
#include "ss49e.h"
void ss49e_init(SS49E_State *self, enum MagnetPolarityType magnetPolarityType, ss49e_tick_callback_t tickCallback) {
self->workingPhase = SS49E_WORKING_PHASE_NORMAL;
self->tickCallback = tickCallback;
self->magnetPolType = magnetPolarityType;
const float level0 = 2445.0f / 4095.0f;
const float deviation = 380.0f / 4095.0f;
if (magnetPolarityType == MAGNET_POLTYPE_BIPOLAR) {
// self->threshold01 = level0 + deviation * 0.75;
// self->threshold10 = level0 - deviation * 0.75;
self->threshold01 = (2447.0 + 520.0 * 2.0 / 3.0) / 4095.0;
self->threshold10 = (2447.0 - 800.0 * 2.0 / 3.0) / 4095.0;
} else {
float activeLevel = level0 + (magnetPolarityType == MAGNET_POLTYPE_UNIPOLAR_POSITIVE ? deviation : -deviation);
self->threshold01 = level0 * 1 / 3 + activeLevel * 2 / 3;
self->threshold10 = activeLevel * 1 / 3 + level0 * 2 / 3;
}
self->currentLevel = 0;
}
void ss49e_process_sample(SS49E_State *self, uint16_t sample) {
float normalized = (float)sample / 4095.0f;
if (self->currentLevel == 0) {
if (normalized >= self->threshold01) {
self->currentLevel = 1;
if (self->magnetPolType == MAGNET_POLTYPE_BIPOLAR || self->magnetPolType == MAGNET_POLTYPE_UNIPOLAR_POSITIVE)
self->tickCallback();
}
} else {
if (normalized <= self->threshold10) {
self->currentLevel = 0;
if (self->magnetPolType == MAGNET_POLTYPE_BIPOLAR || self->magnetPolType == MAGNET_POLTYPE_UNIPOLAR_NEGATIVE)
self->tickCallback();
}
}
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef VELOMETER_SS49E_H
#define VELOMETER_SS49E_H
#include <stdbool.h>
#include <stdint.h>
enum SS49E_WorkingPhase {
SS49E_WORKING_PHASE_NORMAL,
SS49E_WORKING_PHASE_CALIBRATING,
};
enum MagnetPolarityType {
MAGNET_POLTYPE_BIPOLAR,
MAGNET_POLTYPE_UNIPOLAR_POSITIVE,
MAGNET_POLTYPE_UNIPOLAR_NEGATIVE,
};
typedef void (*ss49e_tick_callback_t)();
typedef struct {
enum SS49E_WorkingPhase workingPhase;
enum MagnetPolarityType magnetPolType;
ss49e_tick_callback_t tickCallback;
float threshold01, threshold10;
bool currentLevel;
} SS49E_State;
void ss49e_init(SS49E_State *self, enum MagnetPolarityType magnetPolarityType, ss49e_tick_callback_t tickCallback);
void ss49e_process_sample(SS49E_State *self, uint16_t sample);
#endif