First commit
This commit is contained in:
commit
4354a3ace5
.gitignore.gitmodulesCMakeLists.txtDockerfileLICENSEREADME.md
docs
gui.kra
images
assembled.jpgassembly_order.jpgassembly_order.kraassembly_pins.jpginstalled.jpginstalled_menu.jpgmain_screen.pngpcb_top.jpg
reference.ru.mdreference.ru.pdfthrottle_to_speed.odsthrottle_to_speed_raw.csvextra
font
libopencm3pcb
src
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
build
|
||||||
|
.vscode
|
||||||
|
font/font.c
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "libopencm3"]
|
||||||
|
path = libopencm3
|
||||||
|
url = https://github.com/libopencm3/libopencm3.git
|
120
CMakeLists.txt
Normal file
120
CMakeLists.txt
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
option(SWAP_BEEP_AND_SPEED "Swap speed and beep buttons" OFF)
|
||||||
|
option(WARNINGS_AS_ERRORS "Threeat warnings as errors" OFF)
|
||||||
|
option(USE_DEFAULT_TOOLCHAIN "Use ./extra/arm-gcc-toolchain.cmake toolcahin" ON)
|
||||||
|
|
||||||
|
if(USE_DEFAULT_TOOLCHAIN)
|
||||||
|
set(CMAKE_TOOLCHAIN_FILE ./extra/arm-gcc-toolchain.cmake)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
project(kugoo-s3-bluepill VERSION 0.1)
|
||||||
|
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE "MinSizeRel" CACHE STRING "Build type" FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(TARGET_BASENAME ${PROJECT_NAME}-${PROJECT_VERSION}-${CMAKE_BUILD_TYPE})
|
||||||
|
set(TARGET ${TARGET_BASENAME}.elf)
|
||||||
|
|
||||||
|
find_library(OPENCM3_LIBRARY opencm3_stm32f1
|
||||||
|
HINTS
|
||||||
|
ENV OPENCM3_ROOT
|
||||||
|
./libopencm3/lib
|
||||||
|
PATH_SUFFIXES lib
|
||||||
|
REQUIRED)
|
||||||
|
|
||||||
|
find_path(OPENCM3_INCLUDE_DIR libopencm3/stm32/gpio.h
|
||||||
|
HINTS
|
||||||
|
ENV OPENCM3_ROOT
|
||||||
|
./libopencm3
|
||||||
|
PATH_SUFFIXES include
|
||||||
|
REQUIRED)
|
||||||
|
|
||||||
|
find_program(OPENOCD openocd)
|
||||||
|
|
||||||
|
add_definitions(-DSTM32F1)
|
||||||
|
|
||||||
|
if(SWAP_BEEP_AND_SPEED)
|
||||||
|
add_definitions(-DSWAP_BEEP_AND_SPEED)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_definitions(-DFIRMWARE_VERSION="${PROJECT_VERSION}")
|
||||||
|
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS
|
||||||
|
"-T${PROJECT_SOURCE_DIR}/extra/stm32f103c8t6-opencm3.ld \
|
||||||
|
-mthumb \
|
||||||
|
-mcpu=cortex-m3 \
|
||||||
|
-nostartfiles \
|
||||||
|
-Wl,--gc-sections \
|
||||||
|
-Wl,-Map=${TARGET_BASENAME}.map")
|
||||||
|
|
||||||
|
# set(CMAKE_EXE_LINKER_FLAGS_DEBUG "-u _printf_float")
|
||||||
|
|
||||||
|
set(CMAKE_C_FLAGS
|
||||||
|
"-Wall \
|
||||||
|
-Wextra \
|
||||||
|
-std=gnu99 \
|
||||||
|
-mthumb \
|
||||||
|
-mcpu=cortex-m3 \
|
||||||
|
-mfloat-abi=soft \
|
||||||
|
-ffunction-sections \
|
||||||
|
-finline-functions \
|
||||||
|
-fdata-sections \
|
||||||
|
-specs=nano.specs \
|
||||||
|
-specs=nosys.specs")
|
||||||
|
|
||||||
|
if(WARNINGS_AS_ERRORS)
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include_directories(src)
|
||||||
|
include_directories(${OPENCM3_INCLUDE_DIR})
|
||||||
|
|
||||||
|
add_executable(${TARGET}
|
||||||
|
src/globals.c
|
||||||
|
src/utils.c
|
||||||
|
src/persistence.c
|
||||||
|
src/hardware.c
|
||||||
|
src/kugoo_s3.c
|
||||||
|
src/ssd1306.c
|
||||||
|
src/keyboard.c
|
||||||
|
src/gui.c
|
||||||
|
|
||||||
|
src/views/main_view.c
|
||||||
|
src/views/settings_view.c
|
||||||
|
src/views/detailed_view.c
|
||||||
|
src/views/trigger_calibration_view.c
|
||||||
|
src/views/last_trips_view.c
|
||||||
|
|
||||||
|
src/main.c)
|
||||||
|
|
||||||
|
target_link_libraries(${TARGET} ${OPENCM3_LIBRARY})
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET ${TARGET}
|
||||||
|
POST_BUILD
|
||||||
|
COMMAND ${CMAKE_OBJCOPY} -O ihex ${TARGET} ${TARGET_BASENAME}.hex
|
||||||
|
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(CMAKE_SIZE)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET ${TARGET}
|
||||||
|
POST_BUILD
|
||||||
|
COMMAND ${CMAKE_SIZE} --format=berkeley ${TARGET}
|
||||||
|
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message(WARNING "arm-none-eabi-size not found, please set the CMAKE_SIZE variable")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (OPENOCD)
|
||||||
|
add_custom_target(flash-openocd
|
||||||
|
COMMAND ${OPENOCD} -d0 -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c "program ${TARGET} verify reset exit"
|
||||||
|
DEPENDS ${TARGET}
|
||||||
|
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM debian:10-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install cmake make python3 gcc-arm-none-eabi dos2unix -y
|
||||||
|
|
||||||
|
COPY . /code/
|
||||||
|
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
# Fix CRLF line endings
|
||||||
|
RUN mkdir /build && mkdir /dist \
|
||||||
|
&& find libopencm3 \( -name '*.py' -o -iname 'Makefile' -o -iname 'irq2nvic_h' \) -exec dos2unix {} \;
|
||||||
|
|
||||||
|
CMD cd /code/libopencm3 && make TARGETS=stm32/f1 \
|
||||||
|
&& cd /build \
|
||||||
|
&& cmake -G "Unix Makefiles" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/dist /code \
|
||||||
|
&& cmake --build .
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 MultiMote
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
213
README.md
Normal file
213
README.md
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
# ПРЕДУПРЕЖДЕНИЕ
|
||||||
|
|
||||||
|
Это любительское устройство, созданное автором для личного электросамоката. В
|
||||||
|
случае ВНЕЗАПНОГО отказа устройства во время пути или другого странного поведения,
|
||||||
|
автор не несёт никакой ответственности за причиненный ущерб.
|
||||||
|
|
||||||
|
Вы делаете всё на свой страх и риск.
|
||||||
|
|
||||||
|
Ко всему прочему, проект не находится в стадии завершённого.
|
||||||
|
|
||||||
|
|
||||||
|
# Описание
|
||||||
|
|
||||||
|
[📖 Руководство пользователя](docs/reference.ru.md) [(pdf)](docs/reference.ru.pdf)
|
||||||
|
|
||||||
|
## Особенности
|
||||||
|
|
||||||
|
> В связи с тем, что штатная функция круиз-контроля таковым не является, далее
|
||||||
|
> буду называть эту функцию автоматической фиксации ускорения.
|
||||||
|
> Иначе не знаю как это назвать.
|
||||||
|
|
||||||
|
* Подавление неотключаемой автоматической фиксации ускорения. Осуществляется за счёт
|
||||||
|
подачи нулевого ускорения, затем плавного возврата в режим.
|
||||||
|
* Поддержание заданной скорости (П-регулятор, почти).
|
||||||
|
* Настоящий круиз-контроль (с поддержанием скорости, а не ускорения) с
|
||||||
|
включением по кнопке. Это значит что устройство будет поддерживать заданную
|
||||||
|
скорость независимо от уклона дороги.
|
||||||
|
* Переключение максимальной скорости в км/ч.
|
||||||
|
* Отображение ошибок: неподключенная ручка тормоза, неподключенная ручка газа,
|
||||||
|
перегрузка по току, проблема с двигателем, нет ответа от контроллера мотор-колеса.
|
||||||
|
* Программное ограничение тока.
|
||||||
|
* Плавный старт.
|
||||||
|
* История поездок (последние 8).
|
||||||
|
|
||||||
|
* Экранная заставка для предотвращения выгорания пикселей.
|
||||||
|
* Защита от зависаний при помощи сторожевого таймера.
|
||||||
|
* Внешняя EEPROM память. Настройки не сбрасываются при перепрошивке.
|
||||||
|
* UTF-8 для исходников и шрифта. Даже Emoji можно выводить. Зачем? Да захотелось.
|
||||||
|
* Односторонняя печатная плата.
|
||||||
|
|
||||||
|
|
||||||
|
## Прочее
|
||||||
|
|
||||||
|
* Штатная функция выбора "скорости" в контроллере мотор-колеса не используется и
|
||||||
|
установлена на уровне 3, так как после замеров оказалось, что это просто
|
||||||
|
ограничение хода ручки газа.
|
||||||
|
* В устройстве используется ШИМ на выходе для пищалки. То, что пищалка уже с генератором,
|
||||||
|
я понял слишком поздно. Но, в принципе, работает. Пищалку рекомендуется заменить на
|
||||||
|
пьезоизлучатель с примерно такой обвязкой:
|
||||||
|
|
||||||
|
```
|
||||||
|
R1
|
||||||
|
>----[ ]----
|
||||||
|
220R |
|
||||||
|
-----
|
||||||
|
L1 |3 C| BZ1
|
||||||
|
150mH |3 |
|
||||||
|
(154) -----
|
||||||
|
|
|
||||||
|
>--------------
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
* ⚠ Есть нерешённая проблема с резким ускорением на долю секунды во время пути.
|
||||||
|
Я не знаю, проблема в помехах во время передачи или же в моём контроллере мотор-колеса.
|
||||||
|
|
||||||
|
* При отсутствии конденсаторов параллельно высоковольтной линии, возникают
|
||||||
|
помехи, которые искажают пакет данных и самокат может ВНЕЗАПНО затормозить
|
||||||
|
или разогнаться.
|
||||||
|
|
||||||
|
* По каким-то причинам контроллер мотор-колеса может вернуть время оборота колеса
|
||||||
|
около 5мс, что равняется скорости примерно 400 км/ч.
|
||||||
|
Поэтому слишком низкие значения игнорируются.
|
||||||
|
|
||||||
|
* При слишком частом подаче пакетов на контроллер мотор-колеса,
|
||||||
|
последний начинает игнорировать некоторые из них.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
* ⚠ Довести до ума механизм подавления штатного круиз-контроля.
|
||||||
|
* ⚠ Разобраться с редким зависанием c последующей перезагрузкой.
|
||||||
|
Кажется, это связано с дисплеем. Серьёзно?
|
||||||
|
* Реализовать "расстояние с последней зарядки". То, что самокат
|
||||||
|
был на зарядке, определять по разнице напряжения между включениями.
|
||||||
|
* Редактирование конфигурации по USART/Bluetooth.
|
||||||
|
* Последовательности звуков.
|
||||||
|
* Моргание фары, шаблоны мигания.
|
||||||
|
* Как-нибудь применить стоп-сигнал.
|
||||||
|
* Немного зарефакторить код, не всё находится в логичных местах.
|
||||||
|
* Провести тесты на другом контроллере мотор-колеса. Возможно,
|
||||||
|
мой контроллер - источник проблем.
|
||||||
|
* Изменение шаблоноа значений ограничения скорости.
|
||||||
|
* Поддержка трёхпозиционного переключателя скоростей.
|
||||||
|
|
||||||
|
|
||||||
|
## Фото
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Сборка прошивки
|
||||||
|
|
||||||
|
## Сборка через Docker
|
||||||
|
|
||||||
|
Самый простой способ. Необходим [Docker](https://docker.com).
|
||||||
|
Параметры сборки можно изменить в Dockerfile.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker build -t kugoo-s3-bluepill .
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run -it --rm -v путь/куда/сохранить/результат:/dist kugoo-s3-bluepill
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сборка вручную
|
||||||
|
|
||||||
|
|
||||||
|
Для сборки необходимы:
|
||||||
|
|
||||||
|
* Python3
|
||||||
|
* [GNU Arm Embedded Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads)
|
||||||
|
* [CMake](https://cmake.org/download/)
|
||||||
|
* make
|
||||||
|
* [libopencm3 0d72e67](https://github.com/libopencm3/libopencm3/tree/0d72e6739c5f7c90f28350a8bb228722ff094806)
|
||||||
|
|
||||||
|
|
||||||
|
Дополнительные зависимости:
|
||||||
|
|
||||||
|
* openocd (прошивка и отладка)
|
||||||
|
|
||||||
|
Если вы используете Windows, то все эти пакеты можно поставить через [msys2](https://www.msys2.org/).
|
||||||
|
|
||||||
|
Сборка на примере msys2:
|
||||||
|
|
||||||
|
В корне msys2 запускаем `mingw32.exe`
|
||||||
|
|
||||||
|
Установка необходимых пакетов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pacman -S mingw-w64-i686-arm-none-eabi-gcc mingw-w64-i686-arm-none-eabi-gdb \
|
||||||
|
mingw-w64-i686-cmake mingw-w64-i686-make mingw-w64-i686-openocd
|
||||||
|
```
|
||||||
|
|
||||||
|
Собираем libopencm3.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd libopencm3
|
||||||
|
mingw32-make TARGETS=stm32/f1
|
||||||
|
```
|
||||||
|
|
||||||
|
Конфигурируем проект. Переходим в его корневой каталог, затем выполняем
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake -G "MinGW Makefiles" ..
|
||||||
|
```
|
||||||
|
|
||||||
|
Файл extra/arm-gcc-toolchain.cmake при этом можно скопировать
|
||||||
|
в любое расположение и настроить под себя
|
||||||
|
(использовать абсолютные пути, например):
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake -G "MinGW Makefiles" -DUSE_DEFAULT_TOOLCHAIN=OFF -DCMAKE_TOOLCHAIN_FILE=/path/to/custom/arm-gcc-toolchain.cmake ..
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Собираем
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake --build .
|
||||||
|
```
|
||||||
|
|
||||||
|
или
|
||||||
|
|
||||||
|
```
|
||||||
|
mingw32-make
|
||||||
|
```
|
||||||
|
|
||||||
|
# Точки интереса в файлах проекта
|
||||||
|
|
||||||
|
* `globals.h` - различные константы
|
||||||
|
* `settings_view.c` - объявление пунктов меню настроек
|
||||||
|
* `gui.c` - объявление экранов (например, главный экран, экран настроек,
|
||||||
|
экран калибровки ручек)
|
||||||
|
* `views/*` - обработчики экранов
|
||||||
|
|
||||||
|
# Порядок сборки устройства
|
||||||
|
|
||||||
|
[Печатная плата, схема, список компонентов](pcb)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Монтаж XL7005 и обвязки. Проверка напряжений.
|
||||||
|
2. Монтаж линейного стабилизатора. Проверка напряжений.
|
||||||
|
3. Монтаж цепи включения питания и временной перемычки для проверки.
|
||||||
|
При нажатой кнопке должны появится напряжения на стабилизаторах.
|
||||||
|
4. Монтаж остальных мелких компонентов.
|
||||||
|
5. Монтаж гребёнки для дисплея.
|
||||||
|
6. Монтаж штырьков для bluepill (**не** на плате bluepill).
|
||||||
|
Для удобства штырьки можно припаивать в пластиковой оправе, потом
|
||||||
|
её снять (см. выше)
|
||||||
|
7. Установка bluepill.
|
||||||
|
8. Установка дисплея.
|
||||||
|
|
BIN
docs/gui.kra
Normal file
BIN
docs/gui.kra
Normal file
Binary file not shown.
BIN
docs/images/assembled.jpg
Normal file
BIN
docs/images/assembled.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 201 KiB |
BIN
docs/images/assembly_order.jpg
Normal file
BIN
docs/images/assembly_order.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 174 KiB |
BIN
docs/images/assembly_order.kra
Normal file
BIN
docs/images/assembly_order.kra
Normal file
Binary file not shown.
BIN
docs/images/assembly_pins.jpg
Normal file
BIN
docs/images/assembly_pins.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 122 KiB |
BIN
docs/images/installed.jpg
Normal file
BIN
docs/images/installed.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 150 KiB |
BIN
docs/images/installed_menu.jpg
Normal file
BIN
docs/images/installed_menu.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 184 KiB |
BIN
docs/images/main_screen.png
Normal file
BIN
docs/images/main_screen.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 15 KiB |
BIN
docs/images/pcb_top.jpg
Normal file
BIN
docs/images/pcb_top.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 166 KiB |
214
docs/reference.ru.md
Normal file
214
docs/reference.ru.md
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
- [1. Краткие имена кнопок](#1-краткие-имена-кнопок)
|
||||||
|
- [2. Неочевидные комбинации клавиш](#2-неочевидные-комбинации-клавиш)
|
||||||
|
- [2.1 Выключение](#21-выключение)
|
||||||
|
- [2.2 Активация круиз-контроля](#22-активация-круиз-контроля)
|
||||||
|
- [2.3 Сброс настроек](#23-сброс-настроек)
|
||||||
|
- [2.4 Ограничение тока](#24-ограничение-тока)
|
||||||
|
- [3. Редактирование значений в меню настроек](#3-редактирование-значений-в-меню-настроек)
|
||||||
|
- [3.1 Логические значения (1|0)](#31-логические-значения-10)
|
||||||
|
- [3.2 Числовые значения](#32-числовые-значения)
|
||||||
|
- [4. Пункты меню настроек](#4-пункты-меню-настроек)
|
||||||
|
- [ОТКЛЮЧИТЬ ЗВУКИ (1|0)](#отключить-звуки-10)
|
||||||
|
- [ГРОМКОСТЬ КНОПОК](#громкость-кнопок)
|
||||||
|
- [ГРОМКОСТЬ СИГНАЛОВ](#громкость-сигналов)
|
||||||
|
- [АНТИ-ФИКСАЦИЯ УСКОР. (1|0)](#анти-фиксация-ускор-10)
|
||||||
|
- [ZERO-START (1|0)](#zero-start-10)
|
||||||
|
- [ПЛАВНОЕ УСКОРЕНИЕ (1|0)](#плавное-ускорение-10)
|
||||||
|
- [ИНКРЕМ. ПЛАВН. УСКОР](#инкрем-плавн-ускор)
|
||||||
|
- [ОГРАНИЧЕНИЕ ТОКА (1|0)](#ограничение-тока-10)
|
||||||
|
- [ЗНАЧ. ОГРАНИЧЕН. ТОКА](#знач-ограничен-тока)
|
||||||
|
- [КОЭФФ. СТАБ. ТОКА](#коэфф-стаб-тока)
|
||||||
|
- [СТАБИЛИЗАЦИЯ СКОР. (1|0)](#стабилизация-скор-10)
|
||||||
|
- [КОЭФФ. СТАБ. СКОРОСТИ](#коэфф-стаб-скорости)
|
||||||
|
- [КАЛИБРОВКА РУЧЕК](#калибровка-ручек)
|
||||||
|
- [АВТОКАЛИБРОВКА РУЧЕК (1|0)](#автокалибровка-ручек-10)
|
||||||
|
- [СОХРАНЯТЬ СКОРОСТЬ](#сохранять-скорость)
|
||||||
|
- [ДЛИНА ОКР. КОЛЕСА, ММ](#длина-окр-колеса-мм)
|
||||||
|
- [КОЛИЧЕСТВО МАГНИТОВ](#количество-магнитов)
|
||||||
|
- [ПОСЛЕДНИЕ ПОЕЗДКИ](#последние-поездки)
|
||||||
|
|
||||||
|
|
||||||
|
# 1. Краткие имена кнопок
|
||||||
|
|
||||||
|
`BEEP` - звуковой сигнал / возврат
|
||||||
|
|
||||||
|
`SET` - меню настроек / уменьшить значение
|
||||||
|
|
||||||
|
`POWER` - питание / подтверждения
|
||||||
|
|
||||||
|
`LIGHT` - фонарь / увеличить значение
|
||||||
|
|
||||||
|
`SPEED` - ограничение скорости
|
||||||
|
|
||||||
|
|
||||||
|
# 2. Неочевидные комбинации клавиш
|
||||||
|
|
||||||
|
## 2.1 Выключение
|
||||||
|
|
||||||
|
Выключение производится из главного экрана долгим нажатием кнопки `POWER`.
|
||||||
|
|
||||||
|
## 2.2 Активация круиз-контроля
|
||||||
|
|
||||||
|
Вывести ручку акселератора в ненулевое положение и нажать кнопку `SET`. Для
|
||||||
|
поддержания скорости используется значение ручки (не текущая скорость самоката).
|
||||||
|
|
||||||
|
Автоматической активации пока нет. И нужно ли?
|
||||||
|
|
||||||
|
## 2.3 Сброс настроек
|
||||||
|
|
||||||
|
При включении зажать `BEEP`. Далее нажать `LIGHT` чтобы сбросить все сохранённые
|
||||||
|
данные, или `SET`, чтобы сохранить при этом показания одометра.
|
||||||
|
Для отмены нажать `POWER`.
|
||||||
|
|
||||||
|
## 2.4 Ограничение тока
|
||||||
|
|
||||||
|
Вывести ручку тормоза в ненулевое положение и нажать кнопку `SPEED`.
|
||||||
|
|
||||||
|
|
||||||
|
# 3. Редактирование значений в меню настроек
|
||||||
|
|
||||||
|
## 3.1 Логические значения (1|0)
|
||||||
|
|
||||||
|
Выбрать пункт меню и нажать кнопку питания.
|
||||||
|
|
||||||
|
`✔` - включено
|
||||||
|
|
||||||
|
`▪` - выключено
|
||||||
|
|
||||||
|
## 3.2 Числовые значения
|
||||||
|
|
||||||
|
Выбрать пункт меню и нажать кнопку питания. Навигация между разрядами
|
||||||
|
происходит с помощью кнопки питания. Увеличить значение разряда - кнопка
|
||||||
|
`LIGHT`, уменьшить - кнопка `SET`. Для установки значения по-умолчанию
|
||||||
|
используется кнопка `SPEED`. Выход из редактирования осуществляется
|
||||||
|
кнопкой `BEEP`.
|
||||||
|
|
||||||
|
При редактирования значений с плавающей точкой, возможны странные явления
|
||||||
|
в связи с своеобразностью этого типа данных.
|
||||||
|
|
||||||
|
# 4. Пункты меню настроек
|
||||||
|
|
||||||
|
## ОТКЛЮЧИТЬ ЗВУКИ (1|0)
|
||||||
|
|
||||||
|
Отключить все звуки кроме звука на кнопке сигнала.
|
||||||
|
|
||||||
|
## ГРОМКОСТЬ КНОПОК
|
||||||
|
|
||||||
|
Громкость сигнала нажатия на кнопки.
|
||||||
|
|
||||||
|
## ГРОМКОСТЬ СИГНАЛОВ
|
||||||
|
|
||||||
|
Громкость различных оповещений (например, вход-выход из круиз-контроля).
|
||||||
|
Громкость "гудка" не регулируется этим параметром.
|
||||||
|
|
||||||
|
## АНТИ-ФИКСАЦИЯ УСКОР. (1|0)
|
||||||
|
|
||||||
|
Подавление фиксации ускорения.
|
||||||
|
|
||||||
|
Головная боль штатного контроллера мотор-колеса. Он же "круиз-контроль", коим
|
||||||
|
на самом деле не является. Не отключается. При долгом удержании (около 5 секунд)
|
||||||
|
ручки акселератора в одном положении её значение фиксируется, после чего ручку можно
|
||||||
|
отпускать. При отключении данной функции в меню данное устройство совершает
|
||||||
|
попытки подавить данный функционал методом периодической подачи нулевого
|
||||||
|
ускорения на контроллер мотор-колеса.
|
||||||
|
|
||||||
|
> ⚠️ При выключении данной функции становится невозможной какая-либо
|
||||||
|
> продолжительная регулировка ускорения (стабилизация скорости, ограничение тока).
|
||||||
|
|
||||||
|
## ZERO-START (1|0)
|
||||||
|
|
||||||
|
Двигатель начинает работать только после того, как начать его вращать.
|
||||||
|
|
||||||
|
> ⚠️ При использовании стабилизации скорости и функции zero-start
|
||||||
|
> гарантированы неожиданные результаты.
|
||||||
|
|
||||||
|
## ПЛАВНОЕ УСКОРЕНИЕ (1|0)
|
||||||
|
|
||||||
|
Включение функции плавного старта. При повышении значения ручки акселератора,
|
||||||
|
фактическое значение линейно нарастает. Хорошо работает вместе
|
||||||
|
со стабилизацией скорости.
|
||||||
|
|
||||||
|
## ИНКРЕМ. ПЛАВН. УСКОР
|
||||||
|
|
||||||
|
Инкремент плавного ускорения (км/ч).
|
||||||
|
|
||||||
|
При включенной функции плавного ускорения к текущему значению скорости каждые
|
||||||
|
50 мс добавляется данное значение до достижения требуемого.
|
||||||
|
|
||||||
|
## ОГРАНИЧЕНИЕ ТОКА (1|0)
|
||||||
|
|
||||||
|
Включить или выключить ограничение тока.
|
||||||
|
|
||||||
|
## ЗНАЧ. ОГРАНИЧЕН. ТОКА
|
||||||
|
|
||||||
|
Значение ограничения тока.
|
||||||
|
|
||||||
|
При превышении заданного значения устройство совершает попытки снизить скорость
|
||||||
|
движения, пока не понизится ток до доступного предела.
|
||||||
|
|
||||||
|
## КОЭФФ. СТАБ. ТОКА
|
||||||
|
|
||||||
|
Коэффициент стабилизации тока.
|
||||||
|
|
||||||
|
При превышении заданного значения разница тока умножается
|
||||||
|
на этот коэффициент и отнимается от значения скорости (км/ч).
|
||||||
|
Происходит это после приёма пакета от контроллера мотор-колеса.
|
||||||
|
|
||||||
|
При установке значения в 0 стабилизация тока работать не может.
|
||||||
|
|
||||||
|
## СТАБИЛИЗАЦИЯ СКОР. (1|0)
|
||||||
|
|
||||||
|
Включить или выключить стабилизацию скорости.
|
||||||
|
|
||||||
|
Как это работает? При нажатии на ручку акселератора на контроллер мотор-колеса
|
||||||
|
подаётся приблизительное значение ускорения для достижения данной скорости на
|
||||||
|
холостых оборотах. Затем реальная скорость сравнивается с ожидаемой, разница
|
||||||
|
значений умножается на коэффициент стабилизации скорости и прибавляется
|
||||||
|
к передаваемому значению. Происходит это после приёма
|
||||||
|
пакета от контроллера мотор-колеса.
|
||||||
|
|
||||||
|
## КОЭФФ. СТАБ. СКОРОСТИ
|
||||||
|
|
||||||
|
Коэффициент стабилизации скорости.
|
||||||
|
|
||||||
|
При установке значения в 0 стабилизация скорости работать не может.
|
||||||
|
|
||||||
|
## КАЛИБРОВКА РУЧЕК
|
||||||
|
|
||||||
|
Произвести установку минимальных и максимальных значений ручек тормоза/акселератора.
|
||||||
|
Калибровка происходит в следующем порядке:
|
||||||
|
|
||||||
|
1. Нулевое положение ручки акселератора
|
||||||
|
2. Максимальное положение ручки акселератора
|
||||||
|
3. Нулевое положение ручки тормоза
|
||||||
|
4. Максимальное положение ручки тормоза
|
||||||
|
|
||||||
|
## АВТОКАЛИБРОВКА РУЧЕК (1|0)
|
||||||
|
|
||||||
|
При запуске устройства установить текущие положения ручек как минимальные.
|
||||||
|
Минимальные значения ручной калибровки при этом игнорируются.
|
||||||
|
|
||||||
|
## СОХРАНЯТЬ СКОРОСТЬ
|
||||||
|
|
||||||
|
Сохранять ли выбранное значение ограничения скорости после перезапуска.
|
||||||
|
|
||||||
|
## ДЛИНА ОКР. КОЛЕСА, ММ
|
||||||
|
|
||||||
|
Длина окружности колеса в миллиметрах.
|
||||||
|
|
||||||
|
Формула: `2 × π × (диаметр_в_мм / 2)`
|
||||||
|
|
||||||
|
## КОЛИЧЕСТВО МАГНИТОВ
|
||||||
|
|
||||||
|
Количество магнитов в мотор-колесе. По умолчанию 30.
|
||||||
|
|
||||||
|
## ПОСЛЕДНИЕ ПОЕЗДКИ
|
||||||
|
|
||||||
|
Открывает список последних восьми поездок. Данные отображаются в формате
|
||||||
|
|
||||||
|
```
|
||||||
|
Время - Расстояние
|
||||||
|
```
|
||||||
|
|
||||||
|
При долгом нажатии кнопки `SPEED` список очищается.
|
||||||
|
Список обновляется при выключении устройства кнопкой `POWER`.
|
1037
docs/reference.ru.pdf
Normal file
1037
docs/reference.ru.pdf
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/throttle_to_speed.ods
Normal file
BIN
docs/throttle_to_speed.ods
Normal file
Binary file not shown.
493
docs/throttle_to_speed_raw.csv
Normal file
493
docs/throttle_to_speed_raw.csv
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
speed_lvl;data;battery_val;current;ms_per_rev
|
||||||
|
1;0;387;0;3000
|
||||||
|
1;0;387;0;3000
|
||||||
|
1;0;388;0;3000
|
||||||
|
1;0;387;0;3000
|
||||||
|
1;25;388;0;3000
|
||||||
|
1;25;388;0;3000
|
||||||
|
1;25;387;0;3000
|
||||||
|
1;25;387;0;3000
|
||||||
|
1;50;387;0;3000
|
||||||
|
1;50;388;0;3000
|
||||||
|
1;50;387;0;3000
|
||||||
|
1;50;387;0;3000
|
||||||
|
1;75;387;0;3000
|
||||||
|
1;75;387;0;3000
|
||||||
|
1;75;387;0;3000
|
||||||
|
1;75;388;0;3000
|
||||||
|
1;100;386;0;3000
|
||||||
|
1;100;387;0;3000
|
||||||
|
1;100;387;0;3000
|
||||||
|
1;100;387;0;3000
|
||||||
|
1;125;388;0;3000
|
||||||
|
1;125;387;0;3000
|
||||||
|
1;125;387;0;3000
|
||||||
|
1;125;388;0;3000
|
||||||
|
1;150;387;0;3000
|
||||||
|
1;150;387;0;3000
|
||||||
|
1;150;387;0;3000
|
||||||
|
1;150;387;0;3000
|
||||||
|
1;175;387;0;3000
|
||||||
|
1;175;387;0;3000
|
||||||
|
1;175;388;0;3000
|
||||||
|
1;175;388;0;3000
|
||||||
|
1;200;386;0;3000
|
||||||
|
1;200;387;0;3000
|
||||||
|
1;200;388;0;3000
|
||||||
|
1;200;386;0;3000
|
||||||
|
1;225;386;0;3000
|
||||||
|
1;225;386;0;929
|
||||||
|
1;225;387;0;929
|
||||||
|
1;225;386;0;926
|
||||||
|
1;250;386;0;618
|
||||||
|
1;250;386;1;618
|
||||||
|
1;250;386;0;619
|
||||||
|
1;250;386;1;618
|
||||||
|
1;275;386;1;471
|
||||||
|
1;275;386;1;470
|
||||||
|
1;275;386;1;470
|
||||||
|
1;275;385;1;471
|
||||||
|
1;300;386;1;371
|
||||||
|
1;300;386;1;370
|
||||||
|
1;300;386;1;371
|
||||||
|
1;300;386;1;370
|
||||||
|
1;325;386;2;321
|
||||||
|
1;325;385;1;326
|
||||||
|
1;325;386;1;321
|
||||||
|
1;325;386;2;321
|
||||||
|
1;350;386;1;281
|
||||||
|
1;350;385;1;285
|
||||||
|
1;350;385;1;285
|
||||||
|
1;350;386;1;285
|
||||||
|
1;375;384;2;251
|
||||||
|
1;375;385;2;251
|
||||||
|
1;375;385;2;250
|
||||||
|
1;375;385;2;251
|
||||||
|
1;400;385;2;220
|
||||||
|
1;400;385;2;220
|
||||||
|
1;400;385;2;220
|
||||||
|
1;400;385;2;220
|
||||||
|
1;425;385;2;200
|
||||||
|
1;425;385;2;201
|
||||||
|
1;425;385;2;201
|
||||||
|
1;425;385;2;200
|
||||||
|
1;450;385;2;182
|
||||||
|
1;450;385;3;182
|
||||||
|
1;450;385;3;182
|
||||||
|
1;450;384;2;182
|
||||||
|
1;475;385;3;166
|
||||||
|
1;475;385;3;167
|
||||||
|
1;475;385;3;167
|
||||||
|
1;475;384;3;167
|
||||||
|
1;500;385;4;153
|
||||||
|
1;500;384;3;153
|
||||||
|
1;500;384;4;153
|
||||||
|
1;500;385;3;153
|
||||||
|
1;525;385;4;143
|
||||||
|
1;525;384;4;143
|
||||||
|
1;525;385;4;142
|
||||||
|
1;525;385;4;143
|
||||||
|
1;550;384;5;133
|
||||||
|
1;550;383;3;133
|
||||||
|
1;550;384;4;133
|
||||||
|
1;550;384;3;133
|
||||||
|
1;575;383;5;124
|
||||||
|
1;575;384;6;125
|
||||||
|
1;575;384;5;125
|
||||||
|
1;575;384;4;125
|
||||||
|
1;600;383;5;117
|
||||||
|
1;600;383;5;118
|
||||||
|
1;600;384;4;117
|
||||||
|
1;600;384;4;117
|
||||||
|
1;625;384;4;110
|
||||||
|
1;625;383;5;111
|
||||||
|
1;625;383;5;110
|
||||||
|
1;625;384;4;111
|
||||||
|
1;650;383;6;111
|
||||||
|
1;650;383;5;111
|
||||||
|
1;650;384;5;110
|
||||||
|
1;650;383;5;111
|
||||||
|
1;675;384;6;110
|
||||||
|
1;675;383;5;110
|
||||||
|
1;675;383;6;111
|
||||||
|
1;675;384;7;111
|
||||||
|
1;700;383;6;111
|
||||||
|
1;700;384;6;111
|
||||||
|
1;700;383;6;111
|
||||||
|
1;700;383;6;110
|
||||||
|
1;725;383;6;111
|
||||||
|
1;725;383;5;110
|
||||||
|
1;725;383;4;111
|
||||||
|
1;725;383;5;110
|
||||||
|
1;750;383;5;111
|
||||||
|
1;750;383;6;110
|
||||||
|
1;750;383;6;110
|
||||||
|
1;750;383;6;111
|
||||||
|
1;775;383;6;111
|
||||||
|
1;775;383;5;111
|
||||||
|
1;775;383;5;110
|
||||||
|
1;775;383;6;111
|
||||||
|
1;800;383;6;111
|
||||||
|
1;800;383;5;111
|
||||||
|
1;800;383;5;111
|
||||||
|
1;800;383;6;110
|
||||||
|
1;825;383;5;111
|
||||||
|
1;825;383;6;111
|
||||||
|
1;825;383;5;111
|
||||||
|
1;825;383;6;110
|
||||||
|
1;850;383;5;110
|
||||||
|
1;850;382;5;110
|
||||||
|
1;850;382;5;111
|
||||||
|
1;850;382;6;111
|
||||||
|
1;875;383;5;111
|
||||||
|
1;875;383;6;111
|
||||||
|
1;875;383;6;110
|
||||||
|
1;875;382;6;111
|
||||||
|
1;900;382;6;111
|
||||||
|
1;900;383;6;111
|
||||||
|
1;900;383;5;111
|
||||||
|
1;900;383;5;111
|
||||||
|
1;925;383;6;111
|
||||||
|
1;925;383;6;111
|
||||||
|
1;925;382;5;111
|
||||||
|
1;925;382;6;110
|
||||||
|
1;950;383;6;111
|
||||||
|
1;950;382;5;110
|
||||||
|
1;950;382;5;111
|
||||||
|
1;950;382;6;111
|
||||||
|
1;975;382;3;111
|
||||||
|
1;975;382;5;111
|
||||||
|
1;975;382;6;111
|
||||||
|
1;975;382;6;111
|
||||||
|
1;1000;382;5;111
|
||||||
|
1;1000;383;5;111
|
||||||
|
1;1000;382;5;111
|
||||||
|
1;1000;383;5;110
|
||||||
|
2;0;385;0;256
|
||||||
|
2;0;385;0;3000
|
||||||
|
2;0;385;0;3000
|
||||||
|
2;0;385;0;3000
|
||||||
|
2;25;385;0;3000
|
||||||
|
2;25;386;0;3000
|
||||||
|
2;25;385;0;3000
|
||||||
|
2;25;385;0;3000
|
||||||
|
2;50;385;0;3000
|
||||||
|
2;50;385;0;3000
|
||||||
|
2;50;386;0;3000
|
||||||
|
2;50;385;0;3000
|
||||||
|
2;75;386;0;3000
|
||||||
|
2;75;385;0;3000
|
||||||
|
2;75;385;0;3000
|
||||||
|
2;75;384;0;3000
|
||||||
|
2;100;386;0;3000
|
||||||
|
2;100;385;0;3000
|
||||||
|
2;100;385;0;3000
|
||||||
|
2;100;385;0;3000
|
||||||
|
2;125;385;0;3000
|
||||||
|
2;125;385;0;3000
|
||||||
|
2;125;385;0;3000
|
||||||
|
2;125;385;0;3000
|
||||||
|
2;150;385;0;3000
|
||||||
|
2;150;385;0;3000
|
||||||
|
2;150;386;0;3000
|
||||||
|
2;150;385;0;3000
|
||||||
|
2;175;385;0;3000
|
||||||
|
2;175;385;0;3000
|
||||||
|
2;175;385;0;3000
|
||||||
|
2;175;386;0;3000
|
||||||
|
2;200;385;0;3000
|
||||||
|
2;200;385;0;3000
|
||||||
|
2;200;385;0;3000
|
||||||
|
2;200;385;0;3000
|
||||||
|
2;225;384;0;3000
|
||||||
|
2;225;384;0;3000
|
||||||
|
2;225;384;0;925
|
||||||
|
2;225;384;0;923
|
||||||
|
2;250;384;0;617
|
||||||
|
2;250;384;0;618
|
||||||
|
2;250;384;0;617
|
||||||
|
2;250;384;0;617
|
||||||
|
2;275;384;1;470
|
||||||
|
2;275;384;1;470
|
||||||
|
2;275;383;1;469
|
||||||
|
2;275;384;1;470
|
||||||
|
2;300;384;0;371
|
||||||
|
2;300;385;1;371
|
||||||
|
2;300;385;1;370
|
||||||
|
2;300;384;1;371
|
||||||
|
2;325;384;1;321
|
||||||
|
2;325;384;1;326
|
||||||
|
2;325;384;1;321
|
||||||
|
2;325;384;1;321
|
||||||
|
2;350;384;2;284
|
||||||
|
2;350;384;1;285
|
||||||
|
2;350;384;1;285
|
||||||
|
2;350;384;1;285
|
||||||
|
2;375;384;2;251
|
||||||
|
2;375;384;2;251
|
||||||
|
2;375;384;2;251
|
||||||
|
2;375;384;2;251
|
||||||
|
2;400;384;2;220
|
||||||
|
2;400;383;2;220
|
||||||
|
2;400;383;2;221
|
||||||
|
2;400;384;2;221
|
||||||
|
2;425;384;2;201
|
||||||
|
2;425;384;2;201
|
||||||
|
2;425;384;2;201
|
||||||
|
2;425;383;2;201
|
||||||
|
2;450;382;2;182
|
||||||
|
2;450;384;3;182
|
||||||
|
2;450;383;3;181
|
||||||
|
2;450;383;3;182
|
||||||
|
2;475;383;3;167
|
||||||
|
2;475;383;3;167
|
||||||
|
2;475;383;3;167
|
||||||
|
2;475;383;3;167
|
||||||
|
2;500;383;3;153
|
||||||
|
2;500;382;3;153
|
||||||
|
2;500;383;3;154
|
||||||
|
2;500;384;3;154
|
||||||
|
2;525;383;4;143
|
||||||
|
2;525;382;4;143
|
||||||
|
2;525;383;4;143
|
||||||
|
2;525;383;4;143
|
||||||
|
2;550;383;4;133
|
||||||
|
2;550;383;5;133
|
||||||
|
2;550;383;3;133
|
||||||
|
2;550;383;4;133
|
||||||
|
2;575;383;5;125
|
||||||
|
2;575;383;5;124
|
||||||
|
2;575;383;4;125
|
||||||
|
2;575;382;5;125
|
||||||
|
2;600;382;4;118
|
||||||
|
2;600;382;4;117
|
||||||
|
2;600;382;5;117
|
||||||
|
2;600;381;4;117
|
||||||
|
2;625;383;6;111
|
||||||
|
2;625;382;4;111
|
||||||
|
2;625;381;5;111
|
||||||
|
2;625;382;5;110
|
||||||
|
2;650;382;6;105
|
||||||
|
2;650;382;7;106
|
||||||
|
2;650;382;6;105
|
||||||
|
2;650;382;6;105
|
||||||
|
2;675;382;6;100
|
||||||
|
2;675;381;6;100
|
||||||
|
2;675;382;6;101
|
||||||
|
2;675;381;6;100
|
||||||
|
2;700;381;6;95
|
||||||
|
2;700;381;8;95
|
||||||
|
2;700;382;7;95
|
||||||
|
2;700;382;7;95
|
||||||
|
2;725;381;8;91
|
||||||
|
2;725;381;8;90
|
||||||
|
2;725;382;6;90
|
||||||
|
2;725;382;8;91
|
||||||
|
2;750;382;7;87
|
||||||
|
2;750;380;7;87
|
||||||
|
2;750;381;8;87
|
||||||
|
2;750;382;9;86
|
||||||
|
2;775;381;8;84
|
||||||
|
2;775;381;7;83
|
||||||
|
2;775;381;7;83
|
||||||
|
2;775;381;8;84
|
||||||
|
2;800;381;8;83
|
||||||
|
2;800;380;7;83
|
||||||
|
2;800;381;8;83
|
||||||
|
2;800;382;8;83
|
||||||
|
2;825;381;8;83
|
||||||
|
2;825;381;7;83
|
||||||
|
2;825;381;7;83
|
||||||
|
2;825;381;8;84
|
||||||
|
2;850;382;7;84
|
||||||
|
2;850;381;8;84
|
||||||
|
2;850;381;8;83
|
||||||
|
2;850;379;7;84
|
||||||
|
2;875;381;7;84
|
||||||
|
2;875;381;8;84
|
||||||
|
2;875;381;7;83
|
||||||
|
2;875;381;7;83
|
||||||
|
2;900;380;7;84
|
||||||
|
2;900;379;8;84
|
||||||
|
2;900;380;8;83
|
||||||
|
2;900;380;8;84
|
||||||
|
2;925;381;8;84
|
||||||
|
2;925;380;7;83
|
||||||
|
2;925;380;8;84
|
||||||
|
2;925;380;7;84
|
||||||
|
2;950;381;8;83
|
||||||
|
2;950;381;7;84
|
||||||
|
2;950;380;7;83
|
||||||
|
2;950;381;8;84
|
||||||
|
2;975;380;9;85
|
||||||
|
2;975;379;7;84
|
||||||
|
2;975;380;8;83
|
||||||
|
2;975;380;7;84
|
||||||
|
2;1000;381;8;83
|
||||||
|
2;1000;381;9;84
|
||||||
|
2;1000;380;9;84
|
||||||
|
2;1000;380;8;84
|
||||||
|
3;0;384;0;230
|
||||||
|
3;0;384;0;3000
|
||||||
|
3;0;384;0;3000
|
||||||
|
3;0;383;0;3000
|
||||||
|
3;25;384;0;3000
|
||||||
|
3;25;384;0;3000
|
||||||
|
3;25;384;0;3000
|
||||||
|
3;25;384;0;3000
|
||||||
|
3;50;384;0;3000
|
||||||
|
3;50;383;0;3000
|
||||||
|
3;50;384;0;3000
|
||||||
|
3;50;384;0;3000
|
||||||
|
3;75;383;0;3000
|
||||||
|
3;75;384;0;3000
|
||||||
|
3;75;384;0;3000
|
||||||
|
3;75;384;0;3000
|
||||||
|
3;100;384;0;3000
|
||||||
|
3;100;384;0;3000
|
||||||
|
3;100;384;0;3000
|
||||||
|
3;100;384;0;3000
|
||||||
|
3;125;385;0;3000
|
||||||
|
3;125;384;0;3000
|
||||||
|
3;125;384;0;3000
|
||||||
|
3;125;384;0;3000
|
||||||
|
3;150;383;0;3000
|
||||||
|
3;150;384;0;3000
|
||||||
|
3;150;384;0;3000
|
||||||
|
3;150;384;0;3000
|
||||||
|
3;175;384;0;3000
|
||||||
|
3;175;384;0;3000
|
||||||
|
3;175;385;0;3000
|
||||||
|
3;175;384;0;3000
|
||||||
|
3;200;384;0;3000
|
||||||
|
3;200;384;0;3000
|
||||||
|
3;200;384;0;3000
|
||||||
|
3;200;384;0;3000
|
||||||
|
3;225;383;0;3000
|
||||||
|
3;225;383;0;3000
|
||||||
|
3;225;383;0;924
|
||||||
|
3;225;384;0;922
|
||||||
|
3;250;383;0;618
|
||||||
|
3;250;383;0;617
|
||||||
|
3;250;383;0;617
|
||||||
|
3;250;383;0;617
|
||||||
|
3;275;384;1;470
|
||||||
|
3;275;383;0;471
|
||||||
|
3;275;383;0;469
|
||||||
|
3;275;382;1;470
|
||||||
|
3;300;384;1;371
|
||||||
|
3;300;383;1;371
|
||||||
|
3;300;383;1;370
|
||||||
|
3;300;384;0;371
|
||||||
|
3;325;383;1;321
|
||||||
|
3;325;383;1;327
|
||||||
|
3;325;383;1;322
|
||||||
|
3;325;384;1;321
|
||||||
|
3;350;383;1;279
|
||||||
|
3;350;383;1;285
|
||||||
|
3;350;383;1;286
|
||||||
|
3;350;383;1;286
|
||||||
|
3;375;383;1;251
|
||||||
|
3;375;383;2;251
|
||||||
|
3;375;382;2;251
|
||||||
|
3;375;383;2;251
|
||||||
|
3;400;383;2;220
|
||||||
|
3;400;383;2;221
|
||||||
|
3;400;382;2;221
|
||||||
|
3;400;382;2;220
|
||||||
|
3;425;382;2;201
|
||||||
|
3;425;383;2;202
|
||||||
|
3;425;383;2;201
|
||||||
|
3;425;383;2;201
|
||||||
|
3;450;383;2;182
|
||||||
|
3;450;382;3;182
|
||||||
|
3;450;384;3;182
|
||||||
|
3;450;383;3;182
|
||||||
|
3;475;383;3;167
|
||||||
|
3;475;383;3;167
|
||||||
|
3;475;382;3;167
|
||||||
|
3;475;382;3;168
|
||||||
|
3;500;383;3;153
|
||||||
|
3;500;382;3;153
|
||||||
|
3;500;382;3;153
|
||||||
|
3;500;382;3;154
|
||||||
|
3;525;383;3;143
|
||||||
|
3;525;382;4;143
|
||||||
|
3;525;382;4;143
|
||||||
|
3;525;382;4;143
|
||||||
|
3;550;382;4;133
|
||||||
|
3;550;382;4;133
|
||||||
|
3;550;382;5;133
|
||||||
|
3;550;382;3;133
|
||||||
|
3;575;382;4;126
|
||||||
|
3;575;381;4;125
|
||||||
|
3;575;382;4;125
|
||||||
|
3;575;382;4;125
|
||||||
|
3;600;382;4;117
|
||||||
|
3;600;382;5;118
|
||||||
|
3;600;382;4;118
|
||||||
|
3;600;381;4;118
|
||||||
|
3;625;382;5;111
|
||||||
|
3;625;382;4;111
|
||||||
|
3;625;381;5;111
|
||||||
|
3;625;381;6;111
|
||||||
|
3;650;381;6;105
|
||||||
|
3;650;381;7;105
|
||||||
|
3;650;382;6;106
|
||||||
|
3;650;381;6;106
|
||||||
|
3;675;381;5;100
|
||||||
|
3;675;381;6;100
|
||||||
|
3;675;381;6;100
|
||||||
|
3;675;381;7;100
|
||||||
|
3;700;381;7;95
|
||||||
|
3;700;381;6;95
|
||||||
|
3;700;381;8;95
|
||||||
|
3;700;381;6;95
|
||||||
|
3;725;381;6;91
|
||||||
|
3;725;380;7;91
|
||||||
|
3;725;381;8;91
|
||||||
|
3;725;380;6;90
|
||||||
|
3;750;380;7;87
|
||||||
|
3;750;380;7;87
|
||||||
|
3;750;380;7;87
|
||||||
|
3;750;379;7;87
|
||||||
|
3;775;380;7;84
|
||||||
|
3;775;380;7;84
|
||||||
|
3;775;380;7;83
|
||||||
|
3;775;380;8;84
|
||||||
|
3;800;380;9;80
|
||||||
|
3;800;380;9;80
|
||||||
|
3;800;380;11;80
|
||||||
|
3;800;379;9;80
|
||||||
|
3;825;379;7;77
|
||||||
|
3;825;379;9;78
|
||||||
|
3;825;380;7;78
|
||||||
|
3;825;379;9;78
|
||||||
|
3;850;381;10;75
|
||||||
|
3;850;379;11;74
|
||||||
|
3;850;379;8;75
|
||||||
|
3;850;380;9;75
|
||||||
|
3;875;379;8;71
|
||||||
|
3;875;379;9;72
|
||||||
|
3;875;380;8;72
|
||||||
|
3;875;379;10;72
|
||||||
|
3;900;378;9;69
|
||||||
|
3;900;379;11;69
|
||||||
|
3;900;379;11;69
|
||||||
|
3;900;379;14;70
|
||||||
|
3;925;379;11;64
|
||||||
|
3;925;379;11;63
|
||||||
|
3;925;379;13;64
|
||||||
|
3;925;379;9;64
|
||||||
|
3;950;379;11;64
|
||||||
|
3;950;378;10;64
|
||||||
|
3;950;378;14;63
|
||||||
|
3;950;378;11;64
|
||||||
|
3;975;379;0;64
|
||||||
|
3;975;379;11;64
|
||||||
|
3;975;378;14;64
|
||||||
|
3;975;377;12;64
|
||||||
|
3;1000;379;11;64
|
||||||
|
3;1000;378;8;63
|
||||||
|
3;1000;378;12;63
|
||||||
|
3;1000;378;11;63
|
|
13
extra/arm-gcc-toolchain.cmake
Normal file
13
extra/arm-gcc-toolchain.cmake
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
set(CMAKE_SYSTEM_NAME Generic)
|
||||||
|
|
||||||
|
# undefined reference to `_exit' fix on compiler test
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS_INIT "--specs=nosys.specs")
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
|
||||||
|
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
|
||||||
|
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
|
||||||
|
|
||||||
|
set(CMAKE_DEBUGGER arm-none-eabi-gdb)
|
||||||
|
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
|
||||||
|
set(CMAKE_OBJDUMP arm-none-eabi-objdump)
|
||||||
|
set(CMAKE_SIZE arm-none-eabi-size)
|
132
extra/stm32f103c8t6-opencm3.ld
Normal file
132
extra/stm32f103c8t6-opencm3.ld
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
rom (rx) : ORIGIN = 0x08000000, LENGTH = 64K
|
||||||
|
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the libopencm3 project.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
|
||||||
|
*
|
||||||
|
* This library is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a generic linker script for Cortex-M targets using libopencm3.
|
||||||
|
*
|
||||||
|
* Memory regions MUST be defined in the ld script which includes this one!
|
||||||
|
* Example:
|
||||||
|
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
rom (rx) : ORIGIN = 0x08000000, LENGTH = 256K
|
||||||
|
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 16K
|
||||||
|
}
|
||||||
|
|
||||||
|
INCLUDE cortex-m-generic.ld
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Enforce emmition of the vector table. */
|
||||||
|
EXTERN (vector_table)
|
||||||
|
|
||||||
|
/* Define the entry point of the output file. */
|
||||||
|
ENTRY(reset_handler)
|
||||||
|
|
||||||
|
/* Define sections. */
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
.text : {
|
||||||
|
*(.vectors) /* Vector table */
|
||||||
|
*(.text*) /* Program code */
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.rodata*) /* Read-only data */
|
||||||
|
. = ALIGN(4);
|
||||||
|
} >rom
|
||||||
|
|
||||||
|
/* C++ Static constructors/destructors, also used for __attribute__
|
||||||
|
* ((constructor)) and the likes */
|
||||||
|
.preinit_array : {
|
||||||
|
. = ALIGN(4);
|
||||||
|
__preinit_array_start = .;
|
||||||
|
KEEP (*(.preinit_array))
|
||||||
|
__preinit_array_end = .;
|
||||||
|
} >rom
|
||||||
|
.init_array : {
|
||||||
|
. = ALIGN(4);
|
||||||
|
__init_array_start = .;
|
||||||
|
KEEP (*(SORT(.init_array.*)))
|
||||||
|
KEEP (*(.init_array))
|
||||||
|
__init_array_end = .;
|
||||||
|
} >rom
|
||||||
|
.fini_array : {
|
||||||
|
. = ALIGN(4);
|
||||||
|
__fini_array_start = .;
|
||||||
|
KEEP (*(.fini_array))
|
||||||
|
KEEP (*(SORT(.fini_array.*)))
|
||||||
|
__fini_array_end = .;
|
||||||
|
} >rom
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Another section used by C++ stuff, appears when using newlib with
|
||||||
|
* 64bit (long long) printf support
|
||||||
|
*/
|
||||||
|
.ARM.extab : {
|
||||||
|
*(.ARM.extab*)
|
||||||
|
} >rom
|
||||||
|
.ARM.exidx : {
|
||||||
|
__exidx_start = .;
|
||||||
|
*(.ARM.exidx*)
|
||||||
|
__exidx_end = .;
|
||||||
|
} >rom
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
_etext = .;
|
||||||
|
|
||||||
|
/* ram, but not cleared on reset, eg boot/app comms */
|
||||||
|
.noinit (NOLOAD) : {
|
||||||
|
*(.noinit*)
|
||||||
|
} >ram
|
||||||
|
. = ALIGN(4);
|
||||||
|
|
||||||
|
.data : {
|
||||||
|
_data = .;
|
||||||
|
*(.data*) /* Read-write initialized data */
|
||||||
|
*(.ramtext*) /* "text" functions to run in ram */
|
||||||
|
. = ALIGN(4);
|
||||||
|
_edata = .;
|
||||||
|
} >ram AT >rom
|
||||||
|
_data_loadaddr = LOADADDR(.data);
|
||||||
|
|
||||||
|
.bss : {
|
||||||
|
*(.bss*) /* Read-write zero initialized data */
|
||||||
|
*(COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
_ebss = .;
|
||||||
|
} >ram
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The .eh_frame section appears to be used for C++ exception handling.
|
||||||
|
* You may need to fix this if you're using C++.
|
||||||
|
*/
|
||||||
|
/DISCARD/ : { *(.eh_frame) }
|
||||||
|
|
||||||
|
. = ALIGN(4);
|
||||||
|
end = .;
|
||||||
|
}
|
||||||
|
|
||||||
|
PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram));
|
||||||
|
|
145
font/font_convert.py
Normal file
145
font/font_convert.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
header_body = "#include <stdint.h>\n\n"
|
||||||
|
header_body += "#define FONT_WIDTH 5\n\n"
|
||||||
|
header_body += "static const uint8_t font_error_symbol[FONT_WIDTH] = {0xfe, 0xaa, 0x92, 0xaa, 0xfe};"
|
||||||
|
|
||||||
|
image_list = glob.glob("*.png")
|
||||||
|
|
||||||
|
char_table = []
|
||||||
|
|
||||||
|
def utf8_code(ch):
|
||||||
|
return int.from_bytes(ch.encode("utf-8"), "big")
|
||||||
|
|
||||||
|
for filename in image_list:
|
||||||
|
basename = os.path.splitext(filename)[0]
|
||||||
|
match_multi = re.search(r"sprites_(.)-(.).png", filename)
|
||||||
|
match_single = re.search(r"sprite_(.).png", filename)
|
||||||
|
|
||||||
|
if match_multi is not None:
|
||||||
|
start_character = match_multi.group(1)
|
||||||
|
end_character = match_multi.group(2)
|
||||||
|
elif match_single is not None:
|
||||||
|
start_character = match_single.group(1)
|
||||||
|
end_character = match_single.group(1)
|
||||||
|
else: continue
|
||||||
|
|
||||||
|
start_code = utf8_code(start_character)
|
||||||
|
end_code = utf8_code(end_character)
|
||||||
|
|
||||||
|
print("Processing %s (0x%08x - 0x%08x)" % (basename, start_code, end_code))
|
||||||
|
|
||||||
|
image = Image.open(filename)
|
||||||
|
image_rgb = image.convert("RGB")
|
||||||
|
|
||||||
|
table_size = ord(end_character) - ord(start_character) + 1
|
||||||
|
|
||||||
|
print("Table size: %d" % table_size)
|
||||||
|
|
||||||
|
if table_size < 1 or table_size > image.width / 5:
|
||||||
|
print("invalid table size")
|
||||||
|
continue
|
||||||
|
|
||||||
|
for i in range(0, table_size):
|
||||||
|
char_bytes = [];
|
||||||
|
for col in range(0, 5):
|
||||||
|
col_byte = 0
|
||||||
|
for row in range(0, 8):
|
||||||
|
r, g, b = image_rgb.getpixel((i*5 + col, row))
|
||||||
|
if (r + g + b) == 0:
|
||||||
|
col_byte |= (1 << row)
|
||||||
|
|
||||||
|
char_bytes.append(col_byte)
|
||||||
|
|
||||||
|
char_char = chr(ord(start_character) + i)
|
||||||
|
|
||||||
|
char_table.append({
|
||||||
|
"char": char_char,
|
||||||
|
"code": utf8_code(char_char),
|
||||||
|
"bytes": char_bytes
|
||||||
|
})
|
||||||
|
|
||||||
|
image.close()
|
||||||
|
|
||||||
|
if not char_table:
|
||||||
|
print("Nothing found")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
char_table.sort(key=lambda x: x["code"])
|
||||||
|
|
||||||
|
|
||||||
|
table_blocks = []
|
||||||
|
current_block = {}
|
||||||
|
prev_code = 0
|
||||||
|
# split to blocks if character in different ranges
|
||||||
|
for idx, item in enumerate(char_table):
|
||||||
|
code = item["code"]
|
||||||
|
if code - prev_code > 1:
|
||||||
|
if len(current_block) > 0:
|
||||||
|
current_block["end_code"] = prev_code
|
||||||
|
table_blocks.append(current_block)
|
||||||
|
current_block = {"start_code": code, "data": []}
|
||||||
|
|
||||||
|
current_block["data"].append(item)
|
||||||
|
prev_code = code
|
||||||
|
|
||||||
|
current_block["end_code"] = prev_code
|
||||||
|
table_blocks.append(current_block)
|
||||||
|
|
||||||
|
array_body = "static const uint8_t font_data[FONT_WIDTH * %d] = {\n" % len(char_table)
|
||||||
|
function_body = "void ssd1306_mbchar(uint32_t ch) {\n";
|
||||||
|
function_body += " uint16_t mapped_idx;\n\n";
|
||||||
|
idx = 0
|
||||||
|
|
||||||
|
for block in table_blocks:
|
||||||
|
start_code = block["start_code"]
|
||||||
|
end_code = block["end_code"]
|
||||||
|
array_body += " // 0x%08x - 0x%08x \n" % (start_code, end_code)
|
||||||
|
first_case = function_body.endswith("idx;\n\n")
|
||||||
|
|
||||||
|
function_body += " "
|
||||||
|
if not first_case:
|
||||||
|
function_body += "} else "
|
||||||
|
|
||||||
|
if start_code == end_code:
|
||||||
|
function_body += "if (ch == 0x%08x) {\n" % start_code
|
||||||
|
function_body += " mapped_idx = %d;\n" % idx
|
||||||
|
else:
|
||||||
|
function_body += "if (ch >= 0x%08x && ch <= 0x%08x) {\n" % (start_code, end_code)
|
||||||
|
function_body += " mapped_idx = %d + (ch - 0x%08x);\n" % (idx, start_code)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for char_data in block["data"]:
|
||||||
|
b = char_data["bytes"]
|
||||||
|
array_body += " 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, // %s (0x%08x) #%d\n" % (
|
||||||
|
b[0], b[1], b[2], b[3], b[4], char_data["char"], char_data["code"], idx
|
||||||
|
)
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
function_body += " } else {\n"
|
||||||
|
function_body += " draw_font_bytes(font_error_symbol);\n"
|
||||||
|
function_body += " return;\n"
|
||||||
|
function_body += " }\n\n"
|
||||||
|
function_body += " draw_font_bytes(font_data + mapped_idx * FONT_WIDTH);\n"
|
||||||
|
|
||||||
|
function_body += "}"
|
||||||
|
|
||||||
|
array_body += "};"
|
||||||
|
|
||||||
|
# out = open("font.json", "w", encoding="utf-8")
|
||||||
|
# out.write(json.dumps(table_blocks))
|
||||||
|
# out.close()
|
||||||
|
|
||||||
|
out = open("font.c", "w", encoding="utf-8")
|
||||||
|
out.write(header_body)
|
||||||
|
out.write("\n\n")
|
||||||
|
out.write(array_body)
|
||||||
|
out.write("\n\n")
|
||||||
|
out.write(function_body)
|
||||||
|
out.write("\n\n")
|
||||||
|
out.close()
|
BIN
font/sprite_Ё.png
Normal file
BIN
font/sprite_Ё.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 507 B |
BIN
font/sprite_▪.png
Normal file
BIN
font/sprite_▪.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 496 B |
BIN
font/sprite_✔.png
Normal file
BIN
font/sprite_✔.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 513 B |
BIN
font/sprites.kra
Normal file
BIN
font/sprites.kra
Normal file
Binary file not shown.
BIN
font/sprites_ -_.png
Normal file
BIN
font/sprites_ -_.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.3 KiB |
BIN
font/sprites_А-Я.png
Normal file
BIN
font/sprites_А-Я.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.0 KiB |
1
libopencm3
Submodule
1
libopencm3
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 44928416eacb4c8d7774b1385c7f38fb226d080b
|
1
pcb/README.md
Normal file
1
pcb/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Проект DipTrace.
|
BIN
pcb/kugoo-s3-bluepill-sch.png
Normal file
BIN
pcb/kugoo-s3-bluepill-sch.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 125 KiB |
BIN
pcb/kugoo-s3-bluepill.dch
Normal file
BIN
pcb/kugoo-s3-bluepill.dch
Normal file
Binary file not shown.
BIN
pcb/kugoo-s3-bluepill.dip
Normal file
BIN
pcb/kugoo-s3-bluepill.dip
Normal file
Binary file not shown.
BIN
pcb/rev0.3_LM2596/kugoo-s3-bluepill.dch
Normal file
BIN
pcb/rev0.3_LM2596/kugoo-s3-bluepill.dch
Normal file
Binary file not shown.
BIN
pcb/rev0.3_LM2596/kugoo-s3-bluepill.dip
Normal file
BIN
pcb/rev0.3_LM2596/kugoo-s3-bluepill.dip
Normal file
Binary file not shown.
122
src/font.h
Normal file
122
src/font.h
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
#ifndef FONT_H
|
||||||
|
#define FONT_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define FONT_WIDTH 5
|
||||||
|
|
||||||
|
static const uint8_t font_error_symbol[FONT_WIDTH] = {0xfe, 0xaa, 0x92, 0xaa, 0xfe};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Generated values below (use font_convert.py)
|
||||||
|
//
|
||||||
|
|
||||||
|
static const uint8_t font_data[FONT_WIDTH * 99] = {
|
||||||
|
// 0x00000020 - 0x0000005f
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, // (0x00000020) #0
|
||||||
|
0x00, 0x00, 0xbe, 0x00, 0x00, // ! (0x00000021) #1
|
||||||
|
0x00, 0x0c, 0x00, 0x0c, 0x00, // " (0x00000022) #2
|
||||||
|
0x28, 0xfe, 0x28, 0xfe, 0x28, // # (0x00000023) #3
|
||||||
|
0x4c, 0x92, 0xfe, 0x92, 0x64, // $ (0x00000024) #4
|
||||||
|
0x06, 0xe6, 0x10, 0xce, 0xc0, // % (0x00000025) #5
|
||||||
|
0x40, 0xac, 0x92, 0xac, 0x60, // & (0x00000026) #6
|
||||||
|
0x00, 0x00, 0x0c, 0x00, 0x00, // ' (0x00000027) #7
|
||||||
|
0x00, 0x7c, 0x82, 0x00, 0x00, // ( (0x00000028) #8
|
||||||
|
0x00, 0x00, 0x82, 0x7c, 0x00, // ) (0x00000029) #9
|
||||||
|
0x00, 0x08, 0x14, 0x08, 0x00, // * (0x0000002a) #10
|
||||||
|
0x00, 0x20, 0x70, 0x20, 0x00, // + (0x0000002b) #11
|
||||||
|
0x00, 0x00, 0x80, 0x40, 0x00, // , (0x0000002c) #12
|
||||||
|
0x00, 0x20, 0x20, 0x20, 0x00, // - (0x0000002d) #13
|
||||||
|
0x00, 0x00, 0x80, 0x00, 0x00, // . (0x0000002e) #14
|
||||||
|
0x00, 0xc0, 0x38, 0x06, 0x00, // / (0x0000002f) #15
|
||||||
|
0x7c, 0xc2, 0xba, 0x86, 0x7c, // 0 (0x00000030) #16
|
||||||
|
0x00, 0x8c, 0xfe, 0x80, 0x00, // 1 (0x00000031) #17
|
||||||
|
0x84, 0xc2, 0xa2, 0x92, 0x8c, // 2 (0x00000032) #18
|
||||||
|
0x44, 0x82, 0x92, 0xaa, 0x44, // 3 (0x00000033) #19
|
||||||
|
0x1e, 0x10, 0x10, 0x10, 0xfe, // 4 (0x00000034) #20
|
||||||
|
0x4e, 0x92, 0x92, 0x92, 0x62, // 5 (0x00000035) #21
|
||||||
|
0x7c, 0x92, 0x92, 0x92, 0x64, // 6 (0x00000036) #22
|
||||||
|
0x02, 0x02, 0xe2, 0x12, 0x0e, // 7 (0x00000037) #23
|
||||||
|
0x6c, 0x92, 0x92, 0x92, 0x6c, // 8 (0x00000038) #24
|
||||||
|
0x4c, 0x92, 0x92, 0x92, 0x7c, // 9 (0x00000039) #25
|
||||||
|
0x00, 0x00, 0x48, 0x00, 0x00, // : (0x0000003a) #26
|
||||||
|
0x00, 0x80, 0x48, 0x00, 0x00, // ; (0x0000003b) #27
|
||||||
|
0x00, 0x20, 0x50, 0x88, 0x00, // < (0x0000003c) #28
|
||||||
|
0x00, 0x50, 0x50, 0x50, 0x00, // = (0x0000003d) #29
|
||||||
|
0x00, 0x88, 0x50, 0x20, 0x00, // > (0x0000003e) #30
|
||||||
|
0x0c, 0x02, 0xa2, 0x12, 0x0c, // ? (0x0000003f) #31
|
||||||
|
0x7c, 0x82, 0x9a, 0xa2, 0xbc, // @ (0x00000040) #32
|
||||||
|
0xfc, 0x12, 0x12, 0x12, 0xfc, // A (0x00000041) #33
|
||||||
|
0xfe, 0x92, 0x92, 0x92, 0x7c, // B (0x00000042) #34
|
||||||
|
0x7c, 0x82, 0x82, 0x82, 0x44, // C (0x00000043) #35
|
||||||
|
0xfe, 0x82, 0x82, 0x82, 0x7c, // D (0x00000044) #36
|
||||||
|
0xfe, 0x92, 0x92, 0x82, 0x82, // E (0x00000045) #37
|
||||||
|
0xfe, 0x12, 0x12, 0x12, 0x02, // F (0x00000046) #38
|
||||||
|
0x7c, 0x82, 0x92, 0x92, 0x74, // G (0x00000047) #39
|
||||||
|
0xfe, 0x10, 0x10, 0x10, 0xfe, // H (0x00000048) #40
|
||||||
|
0x82, 0x82, 0xfe, 0x82, 0x82, // I (0x00000049) #41
|
||||||
|
0x40, 0x80, 0x82, 0x7e, 0x02, // J (0x0000004a) #42
|
||||||
|
0xfe, 0x10, 0x10, 0x28, 0xc6, // K (0x0000004b) #43
|
||||||
|
0xfe, 0x80, 0x80, 0x80, 0x80, // L (0x0000004c) #44
|
||||||
|
0xfe, 0x08, 0x30, 0x08, 0xfe, // M (0x0000004d) #45
|
||||||
|
0xfe, 0x08, 0x10, 0x20, 0xfe, // N (0x0000004e) #46
|
||||||
|
0x7c, 0x82, 0x82, 0x82, 0x7c, // O (0x0000004f) #47
|
||||||
|
0xfe, 0x12, 0x12, 0x12, 0x0c, // P (0x00000050) #48
|
||||||
|
0x7c, 0x82, 0x82, 0x42, 0xbc, // Q (0x00000051) #49
|
||||||
|
0xfe, 0x12, 0x12, 0x12, 0xec, // R (0x00000052) #50
|
||||||
|
0x4c, 0x92, 0x92, 0x92, 0x64, // S (0x00000053) #51
|
||||||
|
0x02, 0x02, 0xfe, 0x02, 0x02, // T (0x00000054) #52
|
||||||
|
0x7e, 0x80, 0x80, 0x80, 0x7e, // U (0x00000055) #53
|
||||||
|
0x1e, 0x60, 0x80, 0x60, 0x1e, // V (0x00000056) #54
|
||||||
|
0x7e, 0x80, 0x7e, 0x80, 0x7e, // W (0x00000057) #55
|
||||||
|
0xc6, 0x28, 0x10, 0x28, 0xc6, // X (0x00000058) #56
|
||||||
|
0x06, 0x08, 0xf0, 0x08, 0x06, // Y (0x00000059) #57
|
||||||
|
0xc2, 0xa2, 0x92, 0x8a, 0x86, // Z (0x0000005a) #58
|
||||||
|
0x00, 0xfe, 0x82, 0x00, 0x00, // [ (0x0000005b) #59
|
||||||
|
0x00, 0x06, 0x38, 0xc0, 0x00, // \ (0x0000005c) #60
|
||||||
|
0x00, 0x00, 0x82, 0xfe, 0x00, // ] (0x0000005d) #61
|
||||||
|
0x00, 0x08, 0x04, 0x08, 0x00, // ^ (0x0000005e) #62
|
||||||
|
0x00, 0x80, 0x80, 0x80, 0x00, // _ (0x0000005f) #63
|
||||||
|
// 0x0000d081 - 0x0000d081
|
||||||
|
0xfc, 0x95, 0x94, 0x85, 0x84, // Ё (0x0000d081) #64
|
||||||
|
// 0x0000d090 - 0x0000d0af
|
||||||
|
0xfc, 0x12, 0x12, 0x12, 0xfc, // А (0x0000d090) #65
|
||||||
|
0xfe, 0x92, 0x92, 0x92, 0xe2, // Б (0x0000d091) #66
|
||||||
|
0xfe, 0x92, 0x92, 0x92, 0x6c, // В (0x0000d092) #67
|
||||||
|
0xfe, 0x02, 0x02, 0x02, 0x02, // Г (0x0000d093) #68
|
||||||
|
0xc0, 0x7c, 0x42, 0x42, 0xfe, // Д (0x0000d094) #69
|
||||||
|
0xfe, 0x92, 0x92, 0x92, 0x82, // Е (0x0000d095) #70
|
||||||
|
0xc6, 0x28, 0xfe, 0x28, 0xc6, // Ж (0x0000d096) #71
|
||||||
|
0x44, 0x82, 0x82, 0x92, 0x6c, // З (0x0000d097) #72
|
||||||
|
0xfe, 0x40, 0x20, 0x10, 0xfe, // И (0x0000d098) #73
|
||||||
|
0xfe, 0x40, 0x23, 0x10, 0xfe, // Й (0x0000d099) #74
|
||||||
|
0xfe, 0x10, 0x10, 0x28, 0xc6, // К (0x0000d09a) #75
|
||||||
|
0xf8, 0x04, 0x02, 0x02, 0xfe, // Л (0x0000d09b) #76
|
||||||
|
0xfe, 0x08, 0x10, 0x08, 0xfe, // М (0x0000d09c) #77
|
||||||
|
0xfe, 0x10, 0x10, 0x10, 0xfe, // Н (0x0000d09d) #78
|
||||||
|
0x7c, 0x82, 0x82, 0x82, 0x7c, // О (0x0000d09e) #79
|
||||||
|
0xfe, 0x02, 0x02, 0x02, 0xfe, // П (0x0000d09f) #80
|
||||||
|
0xfe, 0x22, 0x22, 0x22, 0x1c, // Р (0x0000d0a0) #81
|
||||||
|
0x7c, 0x82, 0x82, 0x82, 0x82, // С (0x0000d0a1) #82
|
||||||
|
0x02, 0x02, 0xfe, 0x02, 0x02, // Т (0x0000d0a2) #83
|
||||||
|
0x4e, 0x90, 0x90, 0x90, 0x7e, // У (0x0000d0a3) #84
|
||||||
|
0x1c, 0x22, 0xfe, 0x22, 0x1c, // Ф (0x0000d0a4) #85
|
||||||
|
0xc6, 0x28, 0x10, 0x28, 0xc6, // Х (0x0000d0a5) #86
|
||||||
|
0xfe, 0x80, 0x80, 0xfe, 0x80, // Ц (0x0000d0a6) #87
|
||||||
|
0x0e, 0x10, 0x10, 0x10, 0xfe, // Ч (0x0000d0a7) #88
|
||||||
|
0xfe, 0x80, 0xfc, 0x80, 0xfe, // Ш (0x0000d0a8) #89
|
||||||
|
0x7e, 0x40, 0x7c, 0x40, 0xfe, // Щ (0x0000d0a9) #90
|
||||||
|
0x02, 0xfe, 0x90, 0x90, 0x60, // Ъ (0x0000d0aa) #91
|
||||||
|
0xfe, 0x90, 0x60, 0x00, 0xfe, // Ы (0x0000d0ab) #92
|
||||||
|
0xfe, 0x90, 0x90, 0x90, 0x60, // Ь (0x0000d0ac) #93
|
||||||
|
0x44, 0x82, 0x92, 0x92, 0x7c, // Э (0x0000d0ad) #94
|
||||||
|
0xfe, 0x10, 0x7c, 0x82, 0x7c, // Ю (0x0000d0ae) #95
|
||||||
|
0xec, 0x12, 0x12, 0x12, 0xfe, // Я (0x0000d0af) #96
|
||||||
|
// 0x00e296aa - 0x00e296aa
|
||||||
|
0x00, 0x00, 0x10, 0x00, 0x00, // ▪ (0x00e296aa) #97
|
||||||
|
// 0x00e29c94 - 0x00e29c94
|
||||||
|
0x10, 0x20, 0x10, 0x08, 0x04, // ✔ (0x00e29c94) #98
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* FONT_H */
|
10
src/globals.c
Normal file
10
src/globals.c
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#include "globals.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
char sprintf_buf[64] = { '\0' };
|
||||||
|
// float desired_speed_kmh = 0;
|
||||||
|
float cruise_ctl_speed_kmh = 0;
|
||||||
|
// float control_output_kmh = 0;
|
||||||
|
uint32_t reset_reason = 0;
|
||||||
|
|
||||||
|
enum cruise_control_status_t cruise_ctl_status = CRUISE_CTL_DISABLED;
|
73
src/globals.h
Normal file
73
src/globals.h
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#ifndef GLOBALS_H
|
||||||
|
#define GLOBALS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define TRIGGER_MAX 1000U
|
||||||
|
#define CONTROLLER_TRIGGER_MAX 1000U
|
||||||
|
|
||||||
|
#define PERSIST_DISTANCE_EVERY_METERS 100
|
||||||
|
#define DEFAULT_SPEED_LIMIT_KMH 15.0f
|
||||||
|
|
||||||
|
#define BATTERY_VOLTS_0_PERCENTS 310
|
||||||
|
#define BATTERY_VOLTS_100_PERCENTS 420
|
||||||
|
/// Calibration values saved +- this value
|
||||||
|
#define TRIGGER_ANTI_JITTER 30
|
||||||
|
/// GUI repain interval in milliseconds
|
||||||
|
#define DISPLAY_REFRESH_INTERVAL_MS 500
|
||||||
|
|
||||||
|
#define LOGIC_REFRESH_INTERVAL_MS 50
|
||||||
|
#define PACKET_SEND_INTERVAL_MS 50
|
||||||
|
#define ANTI_THROTTLE_LOCK_PERIOD_MS 1000
|
||||||
|
#define ANTI_THROTTLE_LOCK_ZERO_PACKETS 2
|
||||||
|
|
||||||
|
#define STATIC_BEEP_VOLUME 16
|
||||||
|
#define KEY_BEEP_FREQ 4000
|
||||||
|
|
||||||
|
// Show separators between menu categories
|
||||||
|
#define MENU_SEPARATORS
|
||||||
|
|
||||||
|
/// throttle value after which wheel starts to rotate
|
||||||
|
#define CONTROLLER_STOPPED_VAL 175
|
||||||
|
|
||||||
|
#define SCREENSAVER_INACTIVITY_PERIOD_MS (1000U * 60U)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* speed to throttle value ratio, approximately
|
||||||
|
* used in kugoo_s3_set_speed_approx
|
||||||
|
*
|
||||||
|
* see speed.ods
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* CONTROLLER_STOPPED_VAL + 10 km/H * 23.07046 = 175 + 230.7 = 405
|
||||||
|
*/
|
||||||
|
#define THROTTLE_TO_SPEED_COEFF 23.07046f
|
||||||
|
|
||||||
|
/// Used in anti throttle lock logic.
|
||||||
|
/// After throttle sets to 0, then it slowly rises to normal value.
|
||||||
|
#define THROTTLE_RECOVER_INCREMENT 3
|
||||||
|
|
||||||
|
// Print reset reason in detailed view instead of some indicators
|
||||||
|
// #define DEBUG_RESET_REASON
|
||||||
|
|
||||||
|
|
||||||
|
enum control_state_t {
|
||||||
|
CS_NORMAL,
|
||||||
|
CS_THROTTLE_RECOVER,
|
||||||
|
CS_FORCE_ZERO_THROTTLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum cruise_control_status_t {
|
||||||
|
CRUISE_CTL_DISABLED,
|
||||||
|
CRUISE_CTL_WAITING_RELEASE, ///< Waiting for throttle trigger value becomes 0
|
||||||
|
CRUISE_CTL_ENABLED,
|
||||||
|
};
|
||||||
|
|
||||||
|
extern char sprintf_buf[64];
|
||||||
|
// extern float desired_speed_kmh;
|
||||||
|
extern float cruise_ctl_speed_kmh;
|
||||||
|
// extern float control_output_kmh;
|
||||||
|
extern uint32_t reset_reason;
|
||||||
|
extern enum cruise_control_status_t cruise_ctl_status;
|
||||||
|
|
||||||
|
#endif /* GLOBALS_H */
|
162
src/gui.c
Normal file
162
src/gui.c
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
#include "gui.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "kugoo_s3.h"
|
||||||
|
#include "persistence.h"
|
||||||
|
#include "ssd1306.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "views/detailed_view.h"
|
||||||
|
#include "views/main_view.h"
|
||||||
|
#include "views/settings_view.h"
|
||||||
|
#include "views/trigger_calibration_view.h"
|
||||||
|
#include "views/last_trips_view.h"
|
||||||
|
|
||||||
|
static uint32_t next_display_refrash_time = 0;
|
||||||
|
static uint32_t last_activity_time = 0;
|
||||||
|
|
||||||
|
static const struct view_t views[] = {
|
||||||
|
{
|
||||||
|
.id = GUI_VIEW_MAIN,
|
||||||
|
.on_draw = main_view_redraw,
|
||||||
|
.on_key = main_view_keyhandler,
|
||||||
|
.on_open = NULL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.id = GUI_VIEW_MAIN_DETAILED,
|
||||||
|
.on_draw = detailed_view_redraw,
|
||||||
|
.on_key = main_view_keyhandler,
|
||||||
|
.on_open = NULL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.id = GUI_VIEW_SETTINGS,
|
||||||
|
.on_draw = settings_view_redraw,
|
||||||
|
.on_key = settings_view_keyhandler,
|
||||||
|
.on_open = NULL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.id = GUI_VIEW_TRIGGER_CALIBRATION,
|
||||||
|
.on_draw = trigger_calibration_view_redraw,
|
||||||
|
.on_key = trigger_calibration_view_keyhandler,
|
||||||
|
.on_open = trigger_calibration_view_reset
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.id = GUI_VIEW_LAST_TRIPS,
|
||||||
|
.on_draw = last_trips_view_redraw,
|
||||||
|
.on_key = last_trips_view_keyhandler,
|
||||||
|
.on_open = NULL
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static const uint8_t views_count = sizeof(views) / sizeof(struct view_t);
|
||||||
|
|
||||||
|
const struct view_t *current_view = views;
|
||||||
|
|
||||||
|
void process_events(void) {
|
||||||
|
keyboard_poll();
|
||||||
|
uint16_t key_evt = keyboard_pop_event();
|
||||||
|
|
||||||
|
if(key_evt ||
|
||||||
|
kugoo_s3_get_speed() > 0 ||
|
||||||
|
brake_value_normalized() > 0 ||
|
||||||
|
throttle_value_normalized() > 0) {
|
||||||
|
last_activity_time = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((gui_is_view(GUI_VIEW_MAIN) ||
|
||||||
|
gui_is_view(GUI_VIEW_MAIN_DETAILED)) &&
|
||||||
|
key_evt & KB_EVT_TYPE_REPEAT &&
|
||||||
|
key_evt & KB_EVT_KEY_POWER) {
|
||||||
|
|
||||||
|
force_persist_distance();
|
||||||
|
persist_last_trip();
|
||||||
|
eeprom_persist(&storage.speed_limit_last);
|
||||||
|
|
||||||
|
beep_blocking(500, 10, storage.keys_volume);
|
||||||
|
ssd1306_clear();
|
||||||
|
ssd1306_redraw();
|
||||||
|
power_disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_view->on_key) {
|
||||||
|
current_view->on_key(key_evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void error_message(const char *msg) {
|
||||||
|
ssd1306_framebuffer_setpos(1, 2);
|
||||||
|
ssd1306_font_scale(2);
|
||||||
|
ssd1306_string("ТОВАРИЩ!");
|
||||||
|
ssd1306_framebuffer_setpos(4, 0);
|
||||||
|
ssd1306_font_scale(1);
|
||||||
|
ssd1306_string(msg);
|
||||||
|
ssd1306_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_redraw(void) {
|
||||||
|
if (millis() < next_display_refrash_time) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
next_display_refrash_time = millis() + DISPLAY_REFRESH_INTERVAL_MS;
|
||||||
|
|
||||||
|
ssd1306_clear();
|
||||||
|
|
||||||
|
if((millis() - last_activity_time) > SCREENSAVER_INACTIVITY_PERIOD_MS) {
|
||||||
|
ssd1306_font_scale(2);
|
||||||
|
ssd1306_framebuffer_setpos(millis() / 5 % 7, (millis() / 3) % (SSD1306_WIDTH - 24));
|
||||||
|
ssd1306_string(":)");
|
||||||
|
ssd1306_redraw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssd1306_font_scale(1);
|
||||||
|
|
||||||
|
if (gui_is_view(GUI_VIEW_MAIN) || gui_is_view(GUI_VIEW_MAIN_DETAILED)) {
|
||||||
|
// throttle lock is not an error
|
||||||
|
if (kugoo_s3_rx.state & ~KUGOO_S3_STATE_THROTTLE_LOCKED) {
|
||||||
|
if (kugoo_s3_rx.state & KUGOO_S3_STATE_MOTOR_ERROR) {
|
||||||
|
error_message("ОТКАЗ\nМОТОР-КОЛЕСА!");
|
||||||
|
} else if (kugoo_s3_rx.state & KUGOO_S3_STATE_OVERCURRENT) {
|
||||||
|
error_message("ПЕРЕТОК!");
|
||||||
|
} else {
|
||||||
|
error_message("КОНТРОЛЛЕР\nМОТОР-КОЛЕСА\nИЗВЕЩАЕТ ОБ ОШИБКЕ!");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (millis() - kugoo_s3_last_packet_time() > 2000) {
|
||||||
|
error_message("КОНТРОЛЛЕР\nМОТОР-КОЛЕСА\nНЕ ОТВЕЧАЕТ!");
|
||||||
|
return;
|
||||||
|
} else if (brake_adc_value() < 500) {
|
||||||
|
error_message("ПОДКЛЮЧИ РУЧКУ\nТОРМОЗА!");
|
||||||
|
return;
|
||||||
|
} else if (throttle_adc_value() < 500) {
|
||||||
|
error_message("ПОДКЛЮЧИ РУЧКУ\nАКСЕЛЕРАТОРА!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_view->on_draw) {
|
||||||
|
current_view->on_draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
ssd1306_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_redraw_force(void) {
|
||||||
|
next_display_refrash_time = 0;
|
||||||
|
gui_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_set_view(enum view_id_t id) {
|
||||||
|
for (uint8_t i = 0; i < views_count; i++) {
|
||||||
|
if (views[i].id == id) {
|
||||||
|
current_view = &views[i];
|
||||||
|
if (current_view->on_open) {
|
||||||
|
current_view->on_open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t gui_is_view(enum view_id_t id) { return current_view->id == id; }
|
27
src/gui.h
Normal file
27
src/gui.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef GUI_H
|
||||||
|
#define GUI_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
enum view_id_t {
|
||||||
|
GUI_VIEW_MAIN,
|
||||||
|
GUI_VIEW_MAIN_DETAILED,
|
||||||
|
GUI_VIEW_SETTINGS,
|
||||||
|
GUI_VIEW_TRIGGER_CALIBRATION,
|
||||||
|
GUI_VIEW_LAST_TRIPS,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct view_t {
|
||||||
|
enum view_id_t id;
|
||||||
|
void (*on_key)(uint8_t);
|
||||||
|
void (*on_draw)();
|
||||||
|
void (*on_open)();
|
||||||
|
};
|
||||||
|
|
||||||
|
void process_events(void);
|
||||||
|
void gui_redraw(void);
|
||||||
|
void gui_redraw_force(void);
|
||||||
|
void gui_set_view(enum view_id_t id);
|
||||||
|
uint8_t gui_is_view(enum view_id_t id);
|
||||||
|
|
||||||
|
#endif /* GUI_H */
|
319
src/hardware.c
Normal file
319
src/hardware.c
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
#include "hardware.h"
|
||||||
|
#include "persistence.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static volatile uint32_t system_millis = 0;
|
||||||
|
static volatile uint16_t beep_time_left = 0;
|
||||||
|
#define BUZZER_PRESCALER 10U
|
||||||
|
|
||||||
|
struct adc_buf_t {
|
||||||
|
uint16_t vbat;
|
||||||
|
uint16_t throttle;
|
||||||
|
uint16_t brake;
|
||||||
|
};
|
||||||
|
static volatile struct adc_buf_t adc_buf;
|
||||||
|
|
||||||
|
void nops(uint32_t n) {
|
||||||
|
volatile uint32_t i;
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
__asm__("nop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_tick_handler(void) {
|
||||||
|
system_millis++;
|
||||||
|
|
||||||
|
if (beep_time_left > 0) {
|
||||||
|
if (beep_time_left == 1) {
|
||||||
|
buzzer_freq_vol(0, 0);
|
||||||
|
}
|
||||||
|
--beep_time_left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint32_t millis(void) { return system_millis; }
|
||||||
|
|
||||||
|
void msleep(uint32_t ms) {
|
||||||
|
uint32_t wake = system_millis + ms;
|
||||||
|
while (wake > system_millis) {
|
||||||
|
iwdg_reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void buzzer_freq_vol(uint16_t freq, uint8_t volume) {
|
||||||
|
if (freq == 0) {
|
||||||
|
timer_disable_oc_output(TIM1, TIM_OC4);
|
||||||
|
gpio_clear(GPIOA, GPIO_TIM1_CH4);
|
||||||
|
} else {
|
||||||
|
uint16_t period = (rcc_ahb_frequency / BUZZER_PRESCALER) / freq;
|
||||||
|
timer_enable_oc_output(TIM1, TIM_OC4);
|
||||||
|
timer_set_period(TIM1, period);
|
||||||
|
timer_set_oc_value(TIM1, TIM_OC4, convert_range_u16(volume, 100U, period / 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void beep_blocking(uint16_t freq, uint16_t ms, uint8_t volume) {
|
||||||
|
if(!storage.mute) {
|
||||||
|
beep_time_left = 0;
|
||||||
|
buzzer_freq_vol(freq, volume);
|
||||||
|
}
|
||||||
|
msleep(ms);
|
||||||
|
buzzer_freq_vol(0, volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
void beep(uint16_t freq, uint16_t ms, uint8_t volume) {
|
||||||
|
if(storage.mute) {
|
||||||
|
buzzer_freq_vol(0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buzzer_freq_vol(freq, volume);
|
||||||
|
beep_time_left = ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t throttle_adc_value(void) { return adc_buf.throttle; }
|
||||||
|
|
||||||
|
uint16_t brake_adc_value(void) { return adc_buf.brake; }
|
||||||
|
|
||||||
|
uint16_t throttle_value_normalized(void) {
|
||||||
|
uint16_t clamped = clamp_u16(throttle_adc_value(),
|
||||||
|
storage.throttle_trigger_min_value,
|
||||||
|
storage.throttle_trigger_max_value);
|
||||||
|
|
||||||
|
return ((clamped - storage.throttle_trigger_min_value) * TRIGGER_MAX) /
|
||||||
|
(storage.throttle_trigger_max_value - storage.throttle_trigger_min_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
float throttle_value_to_kmh(void){
|
||||||
|
return convert_range_float(throttle_value_normalized(), TRIGGER_MAX, storage.speed_limit_last);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t brake_value_normalized(void) {
|
||||||
|
uint16_t clamped = clamp_u16(brake_adc_value(),
|
||||||
|
storage.brake_trigger_min_value,
|
||||||
|
storage.brake_trigger_max_value);
|
||||||
|
|
||||||
|
return ((clamped - storage.brake_trigger_min_value) * TRIGGER_MAX) /
|
||||||
|
(storage.brake_trigger_max_value - storage.brake_trigger_min_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t battery_value(void) {
|
||||||
|
// volts per unit = 3.3v / 4096 = 0.0008056640625
|
||||||
|
// res divider = r2 / (r1 + r2) = 3.3k / (47k + 3.3k) = 0.06560636182902585
|
||||||
|
// coeff = (res divider / volts per unit)*100 = ~814
|
||||||
|
// voltage_int = (adc_val * 1000) / coeff
|
||||||
|
// volts = voltage_int / 10
|
||||||
|
// .1 volts = voltage_int % 10
|
||||||
|
return ((uint32_t)adc_buf.vbat * 100) / 814;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void enable_peripherals(void) {
|
||||||
|
rcc_periph_clock_enable(RCC_AFIO);
|
||||||
|
rcc_periph_clock_enable(RCC_GPIOA);
|
||||||
|
rcc_periph_clock_enable(RCC_GPIOB);
|
||||||
|
rcc_periph_clock_enable(RCC_GPIOC);
|
||||||
|
rcc_periph_clock_enable(RCC_ADC1);
|
||||||
|
rcc_periph_clock_enable(RCC_USART1);
|
||||||
|
rcc_periph_clock_enable(RCC_USART3);
|
||||||
|
rcc_periph_clock_enable(RCC_SPI2);
|
||||||
|
rcc_periph_clock_enable(RCC_I2C1);
|
||||||
|
rcc_periph_clock_enable(RCC_TIM1);
|
||||||
|
rcc_periph_clock_enable(RCC_DMA1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void systick_setup(void) {
|
||||||
|
rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
|
||||||
|
systick_set_frequency(1000, rcc_ahb_frequency); // 1 ms
|
||||||
|
systick_counter_enable();
|
||||||
|
systick_interrupt_enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void gpio_setup(void) {
|
||||||
|
// builtin led
|
||||||
|
gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL,
|
||||||
|
GPIO13);
|
||||||
|
gpio_set(GPIOC, GPIO13);
|
||||||
|
|
||||||
|
// power en
|
||||||
|
gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL,
|
||||||
|
GPIO8);
|
||||||
|
|
||||||
|
// power, light, speed buttons
|
||||||
|
gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN,
|
||||||
|
GPIO5 | GPIO6 | GPIO7);
|
||||||
|
gpio_set(GPIOA, GPIO5 | GPIO6 | GPIO7);
|
||||||
|
|
||||||
|
// set, beep buttons
|
||||||
|
gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN,
|
||||||
|
GPIO0 | GPIO1);
|
||||||
|
gpio_set(GPIOB, GPIO0 | GPIO1);
|
||||||
|
|
||||||
|
// adc
|
||||||
|
gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG,
|
||||||
|
GPIO0 | GPIO1 | GPIO2);
|
||||||
|
|
||||||
|
// usart1
|
||||||
|
gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,
|
||||||
|
GPIO_USART1_TX);
|
||||||
|
gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN,
|
||||||
|
GPIO_USART1_RX);
|
||||||
|
gpio_set(GPIOA, GPIO_USART1_RX);
|
||||||
|
|
||||||
|
// usart3
|
||||||
|
gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,
|
||||||
|
GPIO_USART3_TX);
|
||||||
|
gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN,
|
||||||
|
GPIO_USART3_RX);
|
||||||
|
gpio_set(GPIOB, GPIO_USART3_RX);
|
||||||
|
|
||||||
|
// i2c1
|
||||||
|
gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN,
|
||||||
|
GPIO_I2C1_SCL | GPIO_I2C1_SDA);
|
||||||
|
|
||||||
|
// buzzer pwm
|
||||||
|
gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,
|
||||||
|
GPIO_TIM1_CH4);
|
||||||
|
|
||||||
|
// light
|
||||||
|
gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL,
|
||||||
|
GPIO8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2c_setup(void) {
|
||||||
|
i2c_reset(I2C1);
|
||||||
|
i2c_peripheral_disable(I2C1);
|
||||||
|
i2c_enable_ack(I2C1);
|
||||||
|
i2c_set_fast_mode(I2C1);
|
||||||
|
i2c_set_speed(I2C1, i2c_speed_fm_400k, rcc_apb1_frequency / 1000000);
|
||||||
|
i2c_peripheral_enable(I2C1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pwm_setup(void) {
|
||||||
|
rcc_periph_reset_pulse(RST_TIM1);
|
||||||
|
|
||||||
|
// timer mode: no divider (72MHz), edge aligned, upcounting
|
||||||
|
timer_set_mode(TIM1, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
|
||||||
|
|
||||||
|
timer_set_prescaler(TIM1, BUZZER_PRESCALER-1);
|
||||||
|
|
||||||
|
// PWM mode 1 (output high if CNT > CCR1)
|
||||||
|
timer_set_oc_mode(TIM1, TIM_OC4, TIM_OCM_PWM1);
|
||||||
|
|
||||||
|
timer_enable_oc_preload(TIM1, TIM_OC4);
|
||||||
|
timer_enable_preload(TIM1);
|
||||||
|
|
||||||
|
timer_enable_break_main_output(TIM1);
|
||||||
|
|
||||||
|
timer_disable_oc_output(TIM1, TIM_OC4);
|
||||||
|
|
||||||
|
// timer_enable_oc_output(TIM1, TIM_OC4);
|
||||||
|
|
||||||
|
timer_enable_counter(TIM1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dma1_channel1_isr(void) {
|
||||||
|
dma_clear_interrupt_flags(DMA1, DMA_CHANNEL1, DMA_IFCR_CGIF1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void adc_setup_dma(void) {
|
||||||
|
uint8_t channel_seq[16];
|
||||||
|
|
||||||
|
dma_disable_channel(DMA1, DMA_CHANNEL1);
|
||||||
|
|
||||||
|
dma_enable_circular_mode(DMA1, DMA_CHANNEL1);
|
||||||
|
dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL1);
|
||||||
|
|
||||||
|
dma_set_peripheral_size(DMA1, DMA_CHANNEL1, DMA_CCR_PSIZE_16BIT);
|
||||||
|
dma_set_memory_size(DMA1, DMA_CHANNEL1, DMA_CCR_MSIZE_16BIT);
|
||||||
|
|
||||||
|
dma_set_read_from_peripheral(DMA1, DMA_CHANNEL1);
|
||||||
|
dma_set_peripheral_address(DMA1, DMA_CHANNEL1, (uint32_t)&ADC_DR(ADC1));
|
||||||
|
|
||||||
|
dma_set_memory_address(DMA1, DMA_CHANNEL1, (uint32_t)&adc_buf);
|
||||||
|
dma_set_number_of_data(DMA1, DMA_CHANNEL1, 3);
|
||||||
|
|
||||||
|
dma_enable_transfer_complete_interrupt(DMA1, DMA_CHANNEL1);
|
||||||
|
dma_enable_channel(DMA1, DMA_CHANNEL1);
|
||||||
|
|
||||||
|
adc_power_off(ADC1);
|
||||||
|
|
||||||
|
adc_enable_scan_mode(ADC1);
|
||||||
|
adc_set_continuous_conversion_mode(ADC1);
|
||||||
|
adc_disable_discontinuous_mode_regular(ADC1);
|
||||||
|
|
||||||
|
adc_enable_external_trigger_regular(ADC1, ADC_CR2_EXTSEL_SWSTART);
|
||||||
|
adc_set_right_aligned(ADC1);
|
||||||
|
adc_set_sample_time_on_all_channels(ADC1, ADC_SMPR_SMP_7DOT5CYC);
|
||||||
|
|
||||||
|
adc_power_on(ADC1);
|
||||||
|
|
||||||
|
nops(800000);
|
||||||
|
|
||||||
|
adc_reset_calibration(ADC1);
|
||||||
|
adc_calibrate(ADC1);
|
||||||
|
|
||||||
|
channel_seq[0] = ADC_CHANNEL0;
|
||||||
|
channel_seq[1] = ADC_CHANNEL1;
|
||||||
|
channel_seq[2] = ADC_CHANNEL2;
|
||||||
|
|
||||||
|
adc_set_regular_sequence(ADC1, 3, channel_seq);
|
||||||
|
|
||||||
|
adc_enable_dma(ADC1);
|
||||||
|
nops(100);
|
||||||
|
adc_start_conversion_regular(ADC1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usart_setup(void) {
|
||||||
|
nvic_enable_irq(NVIC_USART1_IRQ);
|
||||||
|
usart_set_baudrate(USART1, 9600);
|
||||||
|
usart_set_databits(USART1, 8);
|
||||||
|
usart_set_stopbits(USART1, USART_STOPBITS_1);
|
||||||
|
usart_set_mode(USART1, USART_MODE_TX_RX);
|
||||||
|
usart_set_parity(USART1, USART_PARITY_NONE);
|
||||||
|
usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
|
||||||
|
usart_enable_rx_interrupt(USART1);
|
||||||
|
usart_enable(USART1);
|
||||||
|
|
||||||
|
usart_set_baudrate(USART3, 9600);
|
||||||
|
usart_set_databits(USART3, 8);
|
||||||
|
usart_set_stopbits(USART3, USART_STOPBITS_1);
|
||||||
|
usart_set_mode(USART3, USART_MODE_TX_RX);
|
||||||
|
usart_set_parity(USART3, USART_PARITY_NONE);
|
||||||
|
usart_set_flow_control(USART3, USART_FLOWCONTROL_NONE);
|
||||||
|
usart_enable(USART3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* printf redirect */
|
||||||
|
int _write(int file, char *ptr, int len) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (file == 1) {
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
usart_send_blocking(USART3, ptr[i]);
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = EIO;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// extern void initialise_monitor_handles(void);
|
||||||
|
|
||||||
|
void hardware_setup(void) {
|
||||||
|
enable_peripherals();
|
||||||
|
systick_setup();
|
||||||
|
gpio_setup();
|
||||||
|
|
||||||
|
power_enable();
|
||||||
|
|
||||||
|
adc_setup_dma();
|
||||||
|
usart_setup();
|
||||||
|
i2c_setup();
|
||||||
|
pwm_setup();
|
||||||
|
iwdg_set_period_ms(2000); // 2s watchdog
|
||||||
|
iwdg_start();
|
||||||
|
}
|
66
src/hardware.h
Normal file
66
src/hardware.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#ifndef HARDWARE_H
|
||||||
|
#define HARDWARE_H
|
||||||
|
|
||||||
|
#include <libopencm3/cm3/systick.h>
|
||||||
|
#include <libopencm3/stm32/adc.h>
|
||||||
|
#include <libopencm3/stm32/dma.h>
|
||||||
|
#include <libopencm3/stm32/gpio.h>
|
||||||
|
#include <libopencm3/stm32/rcc.h>
|
||||||
|
#include <libopencm3/stm32/usart.h>
|
||||||
|
#include <libopencm3/stm32/spi.h>
|
||||||
|
#include <libopencm3/stm32/i2c.h>
|
||||||
|
#include <libopencm3/stm32/timer.h>
|
||||||
|
#include <libopencm3/stm32/iwdg.h>
|
||||||
|
#include <libopencm3/cm3/nvic.h>
|
||||||
|
|
||||||
|
void hardware_setup(void);
|
||||||
|
uint32_t millis(void);
|
||||||
|
void msleep(uint32_t ms);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Outputs frequency to the buzzer
|
||||||
|
*
|
||||||
|
* Do not set frequency lower than 150 Hz
|
||||||
|
*
|
||||||
|
* @param freq Frequency in hertz
|
||||||
|
* @param volume 0-100
|
||||||
|
*/
|
||||||
|
void buzzer_freq_vol(uint16_t freq, uint8_t volume);
|
||||||
|
void beep_blocking(uint16_t freq, uint16_t ms, uint8_t volume);
|
||||||
|
void beep(uint16_t freq, uint16_t ms, uint8_t volume);
|
||||||
|
|
||||||
|
uint16_t throttle_adc_value(void);
|
||||||
|
uint16_t brake_adc_value(void);
|
||||||
|
|
||||||
|
/// 0-1000, based on min and max value
|
||||||
|
uint16_t throttle_value_normalized(void);
|
||||||
|
/// Get throttle trigger value in range from 0 to speed limit
|
||||||
|
float throttle_value_to_kmh(void);
|
||||||
|
|
||||||
|
/// 0-1000, based on min and max value
|
||||||
|
uint16_t brake_value_normalized(void);
|
||||||
|
|
||||||
|
// No floating point, 39.2v is 392
|
||||||
|
uint16_t battery_value(void);
|
||||||
|
|
||||||
|
#define power_enable() gpio_set(GPIOB, GPIO8)
|
||||||
|
#define power_disable() do { gpio_clear(GPIOB, GPIO8); while(1){} } while(0)
|
||||||
|
|
||||||
|
#define light_on() gpio_set(GPIOA, GPIO8)
|
||||||
|
#define light_off() gpio_clear(GPIOA, GPIO8)
|
||||||
|
#define light_toggle() gpio_toggle(GPIOA, GPIO8)
|
||||||
|
|
||||||
|
|
||||||
|
#define set_button_pressed() (!gpio_get(GPIOB, GPIO0))
|
||||||
|
#define power_button_pressed() (!gpio_get(GPIOA, GPIO7))
|
||||||
|
#define light_button_pressed() (!gpio_get(GPIOA, GPIO6))
|
||||||
|
|
||||||
|
#ifdef SWAP_BEEP_AND_SPEED
|
||||||
|
#define beep_button_pressed() (!gpio_get(GPIOA, GPIO5))
|
||||||
|
#define speed_button_pressed() (!gpio_get(GPIOB, GPIO1))
|
||||||
|
#else
|
||||||
|
#define beep_button_pressed() (!gpio_get(GPIOB, GPIO1))
|
||||||
|
#define speed_button_pressed() (!gpio_get(GPIOA, GPIO5))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* HARDWARE_H */
|
69
src/keyboard.c
Normal file
69
src/keyboard.c
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#include "keyboard.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
|
||||||
|
static uint16_t last_key_event = KB_EVT_NONE;
|
||||||
|
|
||||||
|
static uint32_t beep_btn_pressed_at = 0;
|
||||||
|
static uint32_t set_btn_pressed_at = 0;
|
||||||
|
static uint32_t power_btn_pressed_at = 0;
|
||||||
|
static uint32_t light_btn_pressed_at = 0;
|
||||||
|
static uint32_t speed_btn_pressed_at = 0;
|
||||||
|
|
||||||
|
static uint8_t check_key_event(uint8_t key_state, uint8_t key_id,
|
||||||
|
uint32_t *pressed_at) {
|
||||||
|
uint32_t ms_presssed = millis() - *pressed_at;
|
||||||
|
if (key_state) {
|
||||||
|
if (*pressed_at == 0) {
|
||||||
|
*pressed_at = millis();
|
||||||
|
last_key_event = key_id | KB_EVT_TYPE_KEYDOWN;
|
||||||
|
} else if ((ms_presssed > KEY_REPEAT_DELAY) &&
|
||||||
|
(ms_presssed > (KEY_REPEAT_INTERVAL + KEY_REPEAT_DELAY))) {
|
||||||
|
*pressed_at += KEY_REPEAT_INTERVAL;
|
||||||
|
last_key_event = key_id | KB_EVT_TYPE_REPEAT;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
} else if (!key_state && *pressed_at > 0) {
|
||||||
|
last_key_event = key_id | KB_EVT_TYPE_KEYUP;
|
||||||
|
*pressed_at = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void keyboard_poll(void) {
|
||||||
|
if (last_key_event != KB_EVT_NONE) { // no poll if last event unprocessed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_key_event(beep_button_pressed(), KB_EVT_KEY_BEEP,
|
||||||
|
&beep_btn_pressed_at)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_key_event(set_button_pressed(), KB_EVT_KEY_SET,
|
||||||
|
&set_btn_pressed_at)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_key_event(power_button_pressed(), KB_EVT_KEY_POWER,
|
||||||
|
&power_btn_pressed_at)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_key_event(light_button_pressed(), KB_EVT_KEY_LIGHT,
|
||||||
|
&light_btn_pressed_at)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_key_event(speed_button_pressed(), KB_EVT_KEY_SPEED,
|
||||||
|
&speed_btn_pressed_at)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t keyboard_pop_event(void)
|
||||||
|
{
|
||||||
|
uint8_t val = last_key_event;
|
||||||
|
last_key_event = KB_EVT_NONE;
|
||||||
|
return val;
|
||||||
|
}
|
26
src/keyboard.h
Normal file
26
src/keyboard.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#ifndef KEYBOARD_H
|
||||||
|
#define KEYBOARD_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define KEY_REPEAT_DELAY 750
|
||||||
|
#define KEY_REPEAT_INTERVAL 100
|
||||||
|
|
||||||
|
#define KB_EVT_MASK_KEYS 0x1f
|
||||||
|
|
||||||
|
#define KB_EVT_NONE 0x00
|
||||||
|
|
||||||
|
#define KB_EVT_KEY_BEEP 0x01
|
||||||
|
#define KB_EVT_KEY_SET 0x02
|
||||||
|
#define KB_EVT_KEY_POWER 0x04
|
||||||
|
#define KB_EVT_KEY_LIGHT 0x08
|
||||||
|
#define KB_EVT_KEY_SPEED 0x10
|
||||||
|
|
||||||
|
#define KB_EVT_TYPE_KEYDOWN 0x20
|
||||||
|
#define KB_EVT_TYPE_REPEAT 0x40
|
||||||
|
#define KB_EVT_TYPE_KEYUP 0x80
|
||||||
|
|
||||||
|
void keyboard_poll(void);
|
||||||
|
uint8_t keyboard_pop_event(void);
|
||||||
|
|
||||||
|
#endif /* KEYBOARD_H */
|
149
src/kugoo_s3.c
Normal file
149
src/kugoo_s3.c
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
#include "kugoo_s3.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
#include "persistence.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
volatile struct kugoo_s3_packet_in kugoo_s3_rx;
|
||||||
|
struct kugoo_s3_packet_out kugoo_s3_tx;
|
||||||
|
volatile uint8_t kugoo_s3_tx_ready = false;
|
||||||
|
|
||||||
|
static volatile uint8_t packet_in_buf[8];
|
||||||
|
static volatile uint8_t packet_in_buf_pos = 0;
|
||||||
|
static volatile uint32_t last_packet_time = 0;
|
||||||
|
|
||||||
|
void kugoo_s3_packet_init(struct kugoo_s3_packet_out *packet) {
|
||||||
|
packet->header = KUGOO_S3_PACKET_OUT_HEADER;
|
||||||
|
packet->features = KUGOO_S3_FEATURE_UNLOCK;
|
||||||
|
packet->n_a_1 = 0U;
|
||||||
|
packet->magnets_count = storage.magnets_count;
|
||||||
|
packet->config_batt_voltage1 = KUGOO_S3_BATTERY_VOLTAGE;
|
||||||
|
packet->brake = 0U;
|
||||||
|
packet->throttle = 0U;
|
||||||
|
packet->config_batt_voltage2 = KUGOO_S3_BATTERY_VOLTAGE;
|
||||||
|
packet->n_a_2 = 0U;
|
||||||
|
packet->speed_limit = 0U;
|
||||||
|
packet->speed_level = 3U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kugoo_s3_packet_update_checksum(struct kugoo_s3_packet_out *p) {
|
||||||
|
uint8_t *ptr = (uint8_t*) p;
|
||||||
|
|
||||||
|
p->checksum = 0;
|
||||||
|
for(uint8_t i = 0; i < sizeof(struct kugoo_s3_packet_out) - 1; ++i) {
|
||||||
|
p->checksum ^= *ptr;
|
||||||
|
++ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kugoo_s3_packet_send(struct kugoo_s3_packet_out *p) {
|
||||||
|
kugoo_s3_packet_update_checksum(p);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->header);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->features);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->n_a_1);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->magnets_count);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, (p->config_batt_voltage1 >> 8) & 0xFF);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->config_batt_voltage1 & 0xFF);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, (p->brake >> 8) & 0xFF);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->brake & 0xFF);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, (p->throttle >> 8) & 0xFF);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->throttle & 0xFF);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, (p->config_batt_voltage2 >> 8) & 0xFF);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->config_batt_voltage2 & 0xFF);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->n_a_2);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->speed_limit);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->speed_level);
|
||||||
|
usart_send_blocking(KUGOO_S3_USART, p->checksum);
|
||||||
|
//printf("%X %X\r\n", (p->throttle >> 8) & 0xFF, p->throttle & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float kugoo_s3_get_speed(void) {
|
||||||
|
// last_packet.ms_per_rev - время в миллисекундах, за которое колесо делает полный
|
||||||
|
// оборот диаметр колеса - 19 см 1 оборот - 2*PI*r = 2 * 3.141592653589793 *
|
||||||
|
// (19/2)
|
||||||
|
// = 59.690260418206066 см = 0.5969026041820606м
|
||||||
|
// допустим, время оборота = 500мс, 0.5969м за 500мс
|
||||||
|
// важно умножить сначала на 1000, а не на (1000/500), чтобы не потерять
|
||||||
|
// точность сантиметры в секунду = (59.69 * 1000) / 500 = 119.38 см/с
|
||||||
|
|
||||||
|
// Как перевести метры в секунду в километры в час:
|
||||||
|
// Нужно 1 м/с разделить на 1000 (количество метров в километре)
|
||||||
|
// и умножить на 3600 (количество секунд в часе) получаем 3.6 километра в час;
|
||||||
|
// ((59.69 * 1000 / (double)last_packet.ms_per_rev) / 100.0) * 3.6;
|
||||||
|
// упрощаем
|
||||||
|
// (((596.9mm * 10) / last_packet.ms_per_rev * 36) / 100)
|
||||||
|
// или для float
|
||||||
|
// (596.9mm / (float) last_packet.ms_per_rev) * 3.6f
|
||||||
|
// ух.
|
||||||
|
|
||||||
|
if (kugoo_s3_rx.ms_per_rev == 0) { // is this possible?
|
||||||
|
return 0;
|
||||||
|
} else if (kugoo_s3_rx.ms_per_rev == KUGOO_S3_SPEED_VAL_STOPPED) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((float) storage.wheel_length_mm / (float) kugoo_s3_rx.ms_per_rev) * 3.6f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kugoo_s3_set_speed_approx(float kmh) {
|
||||||
|
float new_throttle = 0;
|
||||||
|
if(kmh <= 0) {
|
||||||
|
kugoo_s3_tx.throttle = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new_throttle = CONTROLLER_STOPPED_VAL + kmh * THROTTLE_TO_SPEED_COEFF;
|
||||||
|
kugoo_s3_tx.throttle = clamp_u16((uint16_t)new_throttle, 0, CONTROLLER_TRIGGER_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void update_distance(void) {
|
||||||
|
if (kugoo_s3_rx.ms_per_rev == 0) {
|
||||||
|
return;
|
||||||
|
} else if (kugoo_s3_rx.ms_per_rev == KUGOO_S3_SPEED_VAL_STOPPED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// получаем сантиметры в секунду как описано выше,
|
||||||
|
// но так как мы получаем пакеты чаще, чем 1 раз в секунду,
|
||||||
|
// то делим на количество пакетов в секунду
|
||||||
|
uint32_t cm =
|
||||||
|
((storage.wheel_length_mm * 100) / kugoo_s3_rx.ms_per_rev) /
|
||||||
|
KUGOO_S3_PACKETS_IN_PER_SECOND;
|
||||||
|
session_add_distance(cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint32_t kugoo_s3_last_packet_time(void) { return last_packet_time; }
|
||||||
|
|
||||||
|
void kugoo_s3_byte_received(uint8_t b) {
|
||||||
|
static uint16_t ms_per_rev = 0;
|
||||||
|
if (b == KUGOO_S3_PACKET_IN_HEADER) {
|
||||||
|
packet_in_buf_pos = 0;
|
||||||
|
} else if (++packet_in_buf_pos > 7) {
|
||||||
|
packet_in_buf_pos = 7;
|
||||||
|
}
|
||||||
|
packet_in_buf[packet_in_buf_pos] = b;
|
||||||
|
|
||||||
|
// @todo ?
|
||||||
|
if (packet_in_buf_pos == 7) {
|
||||||
|
uint8_t crc = 0;
|
||||||
|
for (uint8_t i = 0; i < 7; i++) {
|
||||||
|
crc ^= packet_in_buf[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crc == packet_in_buf[7]) {
|
||||||
|
kugoo_s3_rx.header = packet_in_buf[0];
|
||||||
|
kugoo_s3_rx.state = packet_in_buf[1];
|
||||||
|
kugoo_s3_rx.n_a_1 = packet_in_buf[2];
|
||||||
|
kugoo_s3_rx.current =
|
||||||
|
(((uint16_t)packet_in_buf[3]) << 8) | packet_in_buf[4];
|
||||||
|
ms_per_rev = (((uint16_t)packet_in_buf[5]) << 8) | packet_in_buf[6];
|
||||||
|
if (ms_per_rev > 20) { // 20ms is ~100km/h (trash measured or you are an idiot)
|
||||||
|
kugoo_s3_rx.ms_per_rev = ms_per_rev;
|
||||||
|
}
|
||||||
|
kugoo_s3_rx.checksum = crc;
|
||||||
|
|
||||||
|
last_packet_time = millis();
|
||||||
|
update_distance();
|
||||||
|
kugoo_s3_tx_ready = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
92
src/kugoo_s3.h
Normal file
92
src/kugoo_s3.h
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#ifndef KUGOO_S3_H
|
||||||
|
#define KUGOO_S3_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define KUGOO_S3_USART USART1
|
||||||
|
|
||||||
|
#define KUGOO_S3_PACKET_OUT_HEADER 0x2F
|
||||||
|
#define KUGOO_S3_PACKET_IN_HEADER 0x28
|
||||||
|
#define KUGOO_S3_SPEED_VAL_STOPPED 0xbb8
|
||||||
|
|
||||||
|
/// 1000ms in second / 200ms period, for speed calculation
|
||||||
|
#define KUGOO_S3_PACKETS_IN_PER_SECOND 5
|
||||||
|
|
||||||
|
|
||||||
|
enum kugoo_s3_battery_voltage_t {
|
||||||
|
KUGOO_S3_BATTERY_VOLTAGE_24V = 0x00D2,
|
||||||
|
KUGOO_S3_BATTERY_VOLTAGE_36V = 0x0136,
|
||||||
|
KUGOO_S3_BATTERY_VOLTAGE_48V = 0x019A
|
||||||
|
};
|
||||||
|
|
||||||
|
#define KUGOO_S3_BATTERY_VOLTAGE KUGOO_S3_BATTERY_VOLTAGE_36V
|
||||||
|
|
||||||
|
enum kugoo_s3_state_t {
|
||||||
|
KUGOO_S3_STATE_MOTOR_ERROR = 0x01,
|
||||||
|
KUGOO_S3_STATE_THROTTLE_LOCKED = 0x02, ///< Not an error
|
||||||
|
KUGOO_S3_STATE_OVERCURRENT = 0x08
|
||||||
|
};
|
||||||
|
|
||||||
|
enum kugoo_s3_feature_t {
|
||||||
|
KUGOO_S3_FEATURE_UNLOCK = 0x01,
|
||||||
|
KUGOO_S3_FEATURE_ZERO_START = 0x02,
|
||||||
|
KUGOO_S3_FEATURE_REAR_LIGHT = 0x04,
|
||||||
|
// KUGOO_S3_FEATURE_4 = 0x08,
|
||||||
|
// KUGOO_S3_FEATURE_5 = 0x10,
|
||||||
|
// KUGOO_S3_FEATURE_6 = 0x20,
|
||||||
|
// KUGOO_S3_FEATURE_7 = 0x40,
|
||||||
|
// KUGOO_S3_FEATURE_8 = 0x80,
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
/// Packet from me to the wheel controller
|
||||||
|
struct kugoo_s3_packet_out {
|
||||||
|
uint8_t header; ///< @see KUGOO_S3_PACKET_OUT_HEADER
|
||||||
|
uint8_t features; ///< @see kugoo_s3_feature_t
|
||||||
|
uint8_t n_a_1;
|
||||||
|
uint8_t magnets_count;
|
||||||
|
uint16_t config_batt_voltage1; ///< @see kugoo_s3_battery_voltage_t
|
||||||
|
uint16_t brake; ///< 0-1000
|
||||||
|
uint16_t throttle; ///< 0-1000
|
||||||
|
uint16_t config_batt_voltage2; ///< @see kugoo_s3_battery_voltage_t
|
||||||
|
uint8_t n_a_2;
|
||||||
|
uint8_t speed_limit; ///< Unused
|
||||||
|
uint8_t speed_level; ///< 1-3
|
||||||
|
uint8_t checksum; ///< All bytes XOR
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Packet from wheel controller to me
|
||||||
|
struct kugoo_s3_packet_in {
|
||||||
|
uint8_t header; ///< @see KUGOO_S3_PACKET_IN _HEADER
|
||||||
|
uint8_t state; ///< @see kugoo_s3_state_t
|
||||||
|
uint8_t n_a_1;
|
||||||
|
uint16_t current; ///< Current * 10 (95 = 9.5A)
|
||||||
|
uint16_t ms_per_rev; ///< Milliseconds per full wheel revolution
|
||||||
|
uint8_t checksum; ///< All bytes XOR
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
/// Last received packet
|
||||||
|
extern volatile struct kugoo_s3_packet_in kugoo_s3_rx;
|
||||||
|
/// Last sent packet
|
||||||
|
extern struct kugoo_s3_packet_out kugoo_s3_tx;
|
||||||
|
/// Sets to true when packet successfully processed
|
||||||
|
extern volatile uint8_t kugoo_s3_tx_ready;
|
||||||
|
|
||||||
|
void kugoo_s3_packet_init(struct kugoo_s3_packet_out *packet);
|
||||||
|
void kugoo_s3_packet_send(struct kugoo_s3_packet_out *packet);
|
||||||
|
void kugoo_s3_try_persist_distance(void);
|
||||||
|
void kugoo_s3_force_persist_distance(void);
|
||||||
|
|
||||||
|
void kugoo_s3_byte_received(uint8_t b);
|
||||||
|
|
||||||
|
/// fuck everything, I want to float
|
||||||
|
float kugoo_s3_get_speed(void);
|
||||||
|
|
||||||
|
/// @see KUGOO_S3_SPEED_COEFF
|
||||||
|
void kugoo_s3_set_speed_approx(float kmh);
|
||||||
|
|
||||||
|
uint32_t kugoo_s3_last_packet_time(void);
|
||||||
|
|
||||||
|
#endif /* KUGOO_S3_H */
|
231
src/main.c
Normal file
231
src/main.c
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "globals.h"
|
||||||
|
#include "gui.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
#include "kugoo_s3.h"
|
||||||
|
#include "persistence.h"
|
||||||
|
#include "ssd1306.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
|
||||||
|
void usart1_isr(void) {
|
||||||
|
// while data is not empty
|
||||||
|
while (USART_SR(USART1) & USART_SR_RXNE) {
|
||||||
|
kugoo_s3_byte_received(usart_recv(USART1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
reset_reason = RCC_CSR;
|
||||||
|
RCC_CSR |= RCC_CSR_RMVF;
|
||||||
|
|
||||||
|
memset((void *)&kugoo_s3_rx, 0, sizeof(struct kugoo_s3_packet_in));
|
||||||
|
uint32_t next_packet_send_time;
|
||||||
|
uint32_t next_logic_refresh;
|
||||||
|
uint32_t next_throttle_lock_cancellation = 0;
|
||||||
|
uint16_t throttle_trigger = 0;
|
||||||
|
uint8_t prev_throttle_lock_state = 0;
|
||||||
|
uint8_t throttle_locked = 0;
|
||||||
|
uint8_t zero_packets_left = 0; // anti throttle lock zero packets
|
||||||
|
enum control_state_t control_state = CS_NORMAL;
|
||||||
|
|
||||||
|
float desired_speed_kmh = 0;
|
||||||
|
float control_output_kmh = 0;
|
||||||
|
float speed_error = 0;
|
||||||
|
float current_error = 0;
|
||||||
|
float speed = 0;
|
||||||
|
|
||||||
|
hardware_setup();
|
||||||
|
|
||||||
|
eeprom_read_and_validate();
|
||||||
|
|
||||||
|
beep_blocking(1000, 10, storage.keys_volume);
|
||||||
|
|
||||||
|
ssd1306_init();
|
||||||
|
ssd1306_contrast(0xf0);
|
||||||
|
ssd1306_blend_mode(SSD1306_BLEND_MODE_LIGHTEN);
|
||||||
|
ssd1306_fill(0xff); // screen test
|
||||||
|
ssd1306_redraw();
|
||||||
|
msleep(200);
|
||||||
|
|
||||||
|
|
||||||
|
if (beep_button_pressed()) {
|
||||||
|
ssd1306_clear();
|
||||||
|
ssd1306_string("СБРОС НАСТРОЕК\n\n"
|
||||||
|
"СОХРАНИТЬ ОДОМЕТР:\n"
|
||||||
|
"SET\n\n"
|
||||||
|
"СТЕРЕТЬ ВСЁ:\n"
|
||||||
|
"LIGHT");
|
||||||
|
ssd1306_redraw();
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
if (set_button_pressed()) {
|
||||||
|
uint32_t distance = storage.total_distance_meters;
|
||||||
|
eeprom_reset_all(false);
|
||||||
|
storage.total_distance_meters = distance;
|
||||||
|
eeprom_write_all();
|
||||||
|
beep_blocking(2000, 300, STATIC_BEEP_VOLUME);
|
||||||
|
power_disable();
|
||||||
|
} else if (light_button_pressed()) {
|
||||||
|
eeprom_reset_all(true);
|
||||||
|
beep_blocking(2000, 300, STATIC_BEEP_VOLUME);
|
||||||
|
power_disable();
|
||||||
|
} else if (power_button_pressed()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
msleep(20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (power_button_pressed()) {
|
||||||
|
msleep(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
kugoo_s3_packet_init(&kugoo_s3_tx);
|
||||||
|
next_packet_send_time = millis() + PACKET_SEND_INTERVAL_MS;
|
||||||
|
next_logic_refresh = millis() + LOGIC_REFRESH_INTERVAL_MS;
|
||||||
|
|
||||||
|
beep_blocking(2000, 10, storage.keys_volume);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
// update only when new speed and current received
|
||||||
|
if (kugoo_s3_tx_ready) {
|
||||||
|
kugoo_s3_tx_ready = false;
|
||||||
|
|
||||||
|
// control loop
|
||||||
|
speed = kugoo_s3_get_speed();
|
||||||
|
speed_error = desired_speed_kmh - speed;
|
||||||
|
current_error = storage.current_limit - ((float) kugoo_s3_rx.current / 10.0f);
|
||||||
|
|
||||||
|
// when current overflows maximum current, switch to current limiting
|
||||||
|
// instead of speed stabilization
|
||||||
|
if(control_state == CS_NORMAL) {
|
||||||
|
if (storage.current_limit_enabled &&
|
||||||
|
storage.current_limit > 0 &&
|
||||||
|
current_error < 0) {
|
||||||
|
control_output_kmh += current_error * storage.current_stabilization_Kp;
|
||||||
|
} else if(storage.speed_stabilization_enabled) {
|
||||||
|
control_output_kmh += speed_error * storage.speed_stabilization_Kp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try_persist_distance();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (millis() >= next_logic_refresh) {
|
||||||
|
next_logic_refresh = millis() + LOGIC_REFRESH_INTERVAL_MS;
|
||||||
|
iwdg_reset();
|
||||||
|
process_events();
|
||||||
|
|
||||||
|
speed = kugoo_s3_get_speed();
|
||||||
|
|
||||||
|
if(gui_is_view(GUI_VIEW_TRIGGER_CALIBRATION)){
|
||||||
|
throttle_trigger = 0;
|
||||||
|
kugoo_s3_tx.brake = 0;
|
||||||
|
} else {
|
||||||
|
throttle_trigger = throttle_value_normalized();
|
||||||
|
kugoo_s3_tx.brake = brake_value_normalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
throttle_locked = kugoo_s3_rx.state & KUGOO_S3_STATE_THROTTLE_LOCKED;
|
||||||
|
if (prev_throttle_lock_state != throttle_locked) {
|
||||||
|
prev_throttle_lock_state = throttle_locked;
|
||||||
|
if (throttle_locked && !storage.anti_throttle_lock) {
|
||||||
|
beep(2000, 10, storage.signals_volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// factory cruise control cancellation
|
||||||
|
// sometimes doesn't work
|
||||||
|
// fuck this
|
||||||
|
if (throttle_locked && storage.anti_throttle_lock && millis() > next_throttle_lock_cancellation) {
|
||||||
|
if(control_state == CS_NORMAL) {
|
||||||
|
next_throttle_lock_cancellation = millis() + ANTI_THROTTLE_LOCK_PERIOD_MS;
|
||||||
|
control_state = CS_FORCE_ZERO_THROTTLE;
|
||||||
|
zero_packets_left = ANTI_THROTTLE_LOCK_ZERO_PACKETS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: reduce complexity
|
||||||
|
if (control_state == CS_THROTTLE_RECOVER) {
|
||||||
|
desired_speed_kmh += THROTTLE_RECOVER_INCREMENT;
|
||||||
|
if (cruise_ctl_status == CRUISE_CTL_DISABLED) {
|
||||||
|
if (desired_speed_kmh >= throttle_value_to_kmh()) {
|
||||||
|
desired_speed_kmh = throttle_value_to_kmh();
|
||||||
|
control_state = CS_NORMAL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (desired_speed_kmh >= cruise_ctl_speed_kmh) {
|
||||||
|
desired_speed_kmh = cruise_ctl_speed_kmh;
|
||||||
|
control_state = CS_NORMAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (cruise_ctl_status == CRUISE_CTL_DISABLED) {
|
||||||
|
if (throttle_trigger > 10) { // throttle trigger pressed
|
||||||
|
if (control_state == CS_FORCE_ZERO_THROTTLE) {
|
||||||
|
// do nothing
|
||||||
|
} else if (storage.soft_start_enabled) {
|
||||||
|
if (desired_speed_kmh < speed / 2) {
|
||||||
|
desired_speed_kmh =
|
||||||
|
speed / 2; // do not start with zero when speed > 0
|
||||||
|
} else if (desired_speed_kmh + storage.soft_start_increment <=
|
||||||
|
throttle_value_to_kmh()) {
|
||||||
|
desired_speed_kmh += storage.soft_start_increment;
|
||||||
|
} else {
|
||||||
|
desired_speed_kmh = throttle_value_to_kmh();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
desired_speed_kmh = throttle_value_to_kmh();
|
||||||
|
}
|
||||||
|
} else { // throttle trigger not pressed
|
||||||
|
desired_speed_kmh = 0;
|
||||||
|
control_output_kmh = 0;
|
||||||
|
}
|
||||||
|
} else if (cruise_ctl_status == CRUISE_CTL_WAITING_RELEASE && throttle_trigger < 50) {
|
||||||
|
desired_speed_kmh = cruise_ctl_speed_kmh;
|
||||||
|
cruise_ctl_status = CRUISE_CTL_ENABLED;
|
||||||
|
} else if (cruise_ctl_status == CRUISE_CTL_ENABLED && throttle_trigger > 50) {
|
||||||
|
desired_speed_kmh = cruise_ctl_speed_kmh;
|
||||||
|
cruise_ctl_status = CRUISE_CTL_DISABLED;
|
||||||
|
beep(2000, 10, storage.signals_volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kugoo_s3_tx.brake > 50) {
|
||||||
|
cruise_ctl_status = CRUISE_CTL_DISABLED;
|
||||||
|
control_output_kmh = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not run throttle lock cancellation while idle
|
||||||
|
if(desired_speed_kmh == 0) {
|
||||||
|
next_throttle_lock_cancellation = millis() + ANTI_THROTTLE_LOCK_PERIOD_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(control_state == CS_FORCE_ZERO_THROTTLE && zero_packets_left > 0){
|
||||||
|
kugoo_s3_tx.throttle = 0;
|
||||||
|
if(--zero_packets_left == 0) {
|
||||||
|
control_state = CS_NORMAL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
kugoo_s3_set_speed_approx(desired_speed_kmh + control_output_kmh);
|
||||||
|
}
|
||||||
|
|
||||||
|
kugoo_s3_tx.features = KUGOO_S3_FEATURE_UNLOCK;
|
||||||
|
if (storage.zero_start_enabled) {
|
||||||
|
kugoo_s3_tx.features |= KUGOO_S3_FEATURE_ZERO_START;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (millis() >= next_packet_send_time) {
|
||||||
|
next_packet_send_time = millis() + PACKET_SEND_INTERVAL_MS;
|
||||||
|
kugoo_s3_packet_send(&kugoo_s3_tx);
|
||||||
|
// printf("%d;%d;%d;%d;%d\r\n", throttle, (int)desired_speed_kmh, kugoo_s3_tx.throttle, (int)kugoo_s3_get_speed(), (int)(kugoo_s3_rx.current/10));
|
||||||
|
}
|
||||||
|
|
||||||
|
gui_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
317
src/persistence.c
Normal file
317
src/persistence.c
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
#include "persistence.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include <libopencm3/stm32/i2c.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define EEPROM_HEADER_SIZE 6
|
||||||
|
static const uint8_t eeprom_valid_header[EEPROM_HEADER_SIZE] = {
|
||||||
|
0x4C, 0x4F, 0x56, 0x45, 0x4C, 0x59};
|
||||||
|
|
||||||
|
struct eeprom_data_t storage;
|
||||||
|
|
||||||
|
static volatile uint32_t current_session_distance_cm = 0;
|
||||||
|
static volatile uint32_t distance_cm_accumulator = 0;
|
||||||
|
|
||||||
|
struct float_config_entry_t cfg_speed_stabilization_Kp = {
|
||||||
|
.ptr = &storage.speed_stabilization_Kp,
|
||||||
|
._min = 0.0f, ._max = 1.0f, ._default = 0.14f,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct float_config_entry_t cfg_current_stabilization_Kp = {
|
||||||
|
.ptr = &storage.current_stabilization_Kp,
|
||||||
|
._min = 0.0f, ._max = 5.0f, ._default = 1.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
struct float_config_entry_t cfg_current_limit = {
|
||||||
|
.ptr = &storage.current_limit,
|
||||||
|
._min = 0.0f, ._max = 100.0f, ._default = 6.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
struct float_config_entry_t cfg_soft_start_increment = {
|
||||||
|
.ptr = &storage.soft_start_increment,
|
||||||
|
._min = 0.1f, ._max = 1.0f, ._default = 0.5f // 0.5 km/h every 50ms
|
||||||
|
};
|
||||||
|
|
||||||
|
struct u16_config_entry_t cfg_throttle_trigger_min_value = {
|
||||||
|
.ptr = &storage.throttle_trigger_min_value,
|
||||||
|
._min = 500, ._max = 4096, ._default = 1200
|
||||||
|
};
|
||||||
|
|
||||||
|
struct u16_config_entry_t cfg_brake_trigger_min_value = {
|
||||||
|
.ptr = &storage.brake_trigger_min_value,
|
||||||
|
._min = 500, ._max = 4096, ._default = 1200
|
||||||
|
};
|
||||||
|
|
||||||
|
struct u16_config_entry_t cfg_throttle_trigger_max_value = {
|
||||||
|
.ptr = &storage.throttle_trigger_max_value,
|
||||||
|
._min = 500, ._max = 4096, ._default = 3050
|
||||||
|
};
|
||||||
|
|
||||||
|
struct u16_config_entry_t cfg_brake_trigger_max_value = {
|
||||||
|
.ptr = &storage.brake_trigger_max_value,
|
||||||
|
._min = 500, ._max = 4096, ._default = 3050
|
||||||
|
};
|
||||||
|
|
||||||
|
struct u16_config_entry_t cfg_wheel_length_mm = {
|
||||||
|
.ptr = &storage.wheel_length_mm,
|
||||||
|
._min = 300, ._max = 4470, ._default = 597
|
||||||
|
};
|
||||||
|
|
||||||
|
struct u16_config_entry_t cfg_magnets_count = {
|
||||||
|
.ptr = &storage.magnets_count,
|
||||||
|
._min = 2, ._max = 1000, ._default = 30
|
||||||
|
};
|
||||||
|
|
||||||
|
struct u16_config_entry_t cfg_keys_volume = {
|
||||||
|
.ptr = &storage.keys_volume,
|
||||||
|
._min = 0, ._max = 100, ._default = 16
|
||||||
|
};
|
||||||
|
|
||||||
|
struct u16_config_entry_t cfg_signals_volume = {
|
||||||
|
.ptr = &storage.signals_volume,
|
||||||
|
._min = 0, ._max = 100, ._default = 255
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void validate_cfg_float(struct float_config_entry_t *entry) {
|
||||||
|
*entry->ptr = clamp_float(*entry->ptr, entry->_min, entry->_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void validate_cfg_u16(struct u16_config_entry_t *entry) {
|
||||||
|
*entry->ptr = clamp_float(*entry->ptr, entry->_min, entry->_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void reset_cfg_float(struct float_config_entry_t *entry) {
|
||||||
|
*entry->ptr = entry->_default;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void reset_cfg_u16(struct u16_config_entry_t *entry) {
|
||||||
|
*entry->ptr = entry->_default;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void eeprom_write(uint8_t addr, const void *ptr, uint8_t count) {
|
||||||
|
// Mostly copy-pasted from i2c_write7_v1,
|
||||||
|
// but data now leading with address byte
|
||||||
|
while ((I2C_SR2(EEPROM_I2C_BUS) & I2C_SR2_BUSY)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_send_start(EEPROM_I2C_BUS);
|
||||||
|
|
||||||
|
// Wait for the end of the start condition, master mode selected, and BUSY
|
||||||
|
// bit set
|
||||||
|
while (!((I2C_SR1(EEPROM_I2C_BUS) & I2C_SR1_SB) &&
|
||||||
|
(I2C_SR2(EEPROM_I2C_BUS) & I2C_SR2_MSL) &&
|
||||||
|
(I2C_SR2(EEPROM_I2C_BUS) & I2C_SR2_BUSY))) {
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_send_7bit_address(EEPROM_I2C_BUS, EEPROM_I2C_ADDRESS, I2C_WRITE);
|
||||||
|
|
||||||
|
// Waiting for address is transferred.
|
||||||
|
while (!(I2C_SR1(EEPROM_I2C_BUS) & I2C_SR1_ADDR)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clearing ADDR condition sequence.
|
||||||
|
(void)I2C_SR2(EEPROM_I2C_BUS);
|
||||||
|
|
||||||
|
i2c_send_data(EEPROM_I2C_BUS, addr);
|
||||||
|
|
||||||
|
while (!(I2C_SR1(EEPROM_I2C_BUS) & (I2C_SR1_BTF))) {
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *byte_ptr = ptr;
|
||||||
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
|
i2c_send_data(EEPROM_I2C_BUS, *byte_ptr);
|
||||||
|
while (!(I2C_SR1(EEPROM_I2C_BUS) & (I2C_SR1_BTF))) {
|
||||||
|
}
|
||||||
|
++byte_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_send_stop(EEPROM_I2C_BUS);
|
||||||
|
msleep(15); // see maximum write time in datasheet
|
||||||
|
}
|
||||||
|
|
||||||
|
void eeprom_set_bytes(uint8_t addr, const void *ptr, uint8_t count) {
|
||||||
|
const uint8_t *bytes = (uint8_t *)ptr;
|
||||||
|
uint8_t page_offset = addr % EEPROM_PAGE_SIZE;
|
||||||
|
uint8_t bytes_to_write;
|
||||||
|
|
||||||
|
while (count > 0) {
|
||||||
|
if (count < EEPROM_PAGE_SIZE - page_offset) {
|
||||||
|
bytes_to_write = count;
|
||||||
|
} else {
|
||||||
|
bytes_to_write = EEPROM_PAGE_SIZE - page_offset;
|
||||||
|
}
|
||||||
|
eeprom_write(addr, bytes, bytes_to_write);
|
||||||
|
|
||||||
|
page_offset = 0;
|
||||||
|
addr += bytes_to_write;
|
||||||
|
bytes += bytes_to_write;
|
||||||
|
count -= bytes_to_write;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void eeprom_set_byte(uint8_t addr, uint8_t data) {
|
||||||
|
uint8_t tmp[] = {addr, data};
|
||||||
|
i2c_transfer7(EEPROM_I2C_BUS, EEPROM_I2C_ADDRESS, tmp, 2, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void eeprom_read_bytes(uint8_t addr, void *ptr, uint8_t count) {
|
||||||
|
uint8_t mem_addr[1] = {addr};
|
||||||
|
i2c_transfer7(EEPROM_I2C_BUS, EEPROM_I2C_ADDRESS, mem_addr, 1, (uint8_t *)ptr,
|
||||||
|
count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void eeprom_read_and_validate(void) {
|
||||||
|
uint8_t eeprom_read_header[EEPROM_HEADER_SIZE];
|
||||||
|
eeprom_read_bytes(0, eeprom_read_header, EEPROM_HEADER_SIZE);
|
||||||
|
if(memcmp(eeprom_read_header, eeprom_valid_header, EEPROM_HEADER_SIZE) != 0) {
|
||||||
|
eeprom_reset_all(true);
|
||||||
|
beep(200, 1000, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
eeprom_read_bytes(EEPROM_DATA_START_ADDRESS, &storage, STORAGE_SIZE);
|
||||||
|
|
||||||
|
if (storage.calibrate_triggers_on_startup){
|
||||||
|
storage.brake_trigger_min_value = brake_adc_value() + TRIGGER_ANTI_JITTER;
|
||||||
|
storage.throttle_trigger_min_value = throttle_adc_value() + TRIGGER_ANTI_JITTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
validate_cfg_float(&cfg_current_limit);
|
||||||
|
validate_cfg_float(&cfg_speed_stabilization_Kp);
|
||||||
|
|
||||||
|
validate_cfg_u16(&cfg_brake_trigger_min_value);
|
||||||
|
validate_cfg_u16(&cfg_throttle_trigger_min_value);
|
||||||
|
validate_cfg_u16(&cfg_brake_trigger_max_value);
|
||||||
|
validate_cfg_u16(&cfg_throttle_trigger_max_value);
|
||||||
|
validate_cfg_u16(&cfg_wheel_length_mm);
|
||||||
|
validate_cfg_u16(&cfg_magnets_count);
|
||||||
|
validate_cfg_u16(&cfg_keys_volume);
|
||||||
|
validate_cfg_u16(&cfg_signals_volume);
|
||||||
|
|
||||||
|
storage.speed_limit_last = clamp_u8(storage.speed_limit_last, 5, 100);
|
||||||
|
|
||||||
|
// this is bad
|
||||||
|
if (storage.brake_trigger_max_value <= storage.brake_trigger_min_value) {
|
||||||
|
storage.brake_trigger_max_value = storage.brake_trigger_min_value + 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storage.throttle_trigger_max_value <= storage.throttle_trigger_min_value) {
|
||||||
|
storage.throttle_trigger_max_value = storage.throttle_trigger_min_value + 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!storage.persist_speed_limit) {
|
||||||
|
storage.speed_limit_last = DEFAULT_SPEED_LIMIT_KMH;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void eeprom_reset_all(uint8_t commit) {
|
||||||
|
storage.contrast = 0xff;
|
||||||
|
storage.soft_start_enabled = 0;
|
||||||
|
storage.zero_start_enabled = 0;
|
||||||
|
storage.current_limit_enabled = 0;
|
||||||
|
storage.speed_stabilization_enabled = 0;
|
||||||
|
storage.total_distance_meters = 0;
|
||||||
|
storage.anti_throttle_lock = 1;
|
||||||
|
storage.mute = 0;
|
||||||
|
storage.calibrate_triggers_on_startup = 1;
|
||||||
|
storage.speed_limit_last = DEFAULT_SPEED_LIMIT_KMH;
|
||||||
|
storage.persist_speed_limit = 0;
|
||||||
|
|
||||||
|
reset_cfg_u16(&cfg_brake_trigger_min_value);
|
||||||
|
reset_cfg_u16(&cfg_throttle_trigger_min_value);
|
||||||
|
reset_cfg_u16(&cfg_brake_trigger_max_value);
|
||||||
|
reset_cfg_u16(&cfg_throttle_trigger_max_value);
|
||||||
|
reset_cfg_u16(&cfg_wheel_length_mm);
|
||||||
|
reset_cfg_u16(&cfg_magnets_count);
|
||||||
|
reset_cfg_u16(&cfg_keys_volume);
|
||||||
|
reset_cfg_u16(&cfg_signals_volume);
|
||||||
|
|
||||||
|
reset_cfg_float(&cfg_speed_stabilization_Kp);
|
||||||
|
reset_cfg_float(&cfg_current_limit);
|
||||||
|
reset_cfg_float(&cfg_current_stabilization_Kp);
|
||||||
|
reset_cfg_float(&cfg_soft_start_increment);
|
||||||
|
|
||||||
|
memset(&storage.last_trips, 0U, sizeof(storage.last_trips));
|
||||||
|
|
||||||
|
if (commit) {
|
||||||
|
eeprom_write_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void eeprom_write_all(void) {
|
||||||
|
eeprom_set_bytes(0, eeprom_valid_header, EEPROM_HEADER_SIZE);
|
||||||
|
eeprom_set_bytes(EEPROM_DATA_START_ADDRESS, &storage, STORAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void eeprom_write_entry(void *variable_addr, uint8_t variable_size) {
|
||||||
|
void *storage_addr = &storage;
|
||||||
|
|
||||||
|
// entry is outside struct
|
||||||
|
if (variable_addr < storage_addr ||
|
||||||
|
variable_addr > storage_addr + STORAGE_SIZE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t eeprom_addr =
|
||||||
|
EEPROM_DATA_START_ADDRESS + (variable_addr - storage_addr);
|
||||||
|
eeprom_set_bytes(eeprom_addr, variable_addr, variable_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void try_persist_distance(void) {
|
||||||
|
if(distance_cm_accumulator > (PERSIST_DISTANCE_EVERY_METERS * 100)) {
|
||||||
|
distance_cm_accumulator -= (PERSIST_DISTANCE_EVERY_METERS * 100);
|
||||||
|
storage.total_distance_meters += PERSIST_DISTANCE_EVERY_METERS;
|
||||||
|
eeprom_persist(&storage.total_distance_meters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void force_persist_distance(void) {
|
||||||
|
storage.total_distance_meters += distance_cm_accumulator / 100;
|
||||||
|
distance_cm_accumulator = 0;
|
||||||
|
eeprom_persist(&storage.total_distance_meters);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void session_add_distance(uint16_t centimeters) {
|
||||||
|
current_session_distance_cm += centimeters;
|
||||||
|
distance_cm_accumulator += centimeters;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t session_distance_cm() {
|
||||||
|
return current_session_distance_cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
void persist_last_trip() {
|
||||||
|
struct last_trip_info_t new_info = {
|
||||||
|
.minutes = millis() / 1000 / 60,
|
||||||
|
.meters = session_distance_cm() / 100,
|
||||||
|
};
|
||||||
|
struct last_trip_info_t *info = storage.last_trips;
|
||||||
|
uint8_t free_index = 0;
|
||||||
|
|
||||||
|
if(new_info.meters == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; free_index < 8; free_index++) {
|
||||||
|
if(info->minutes == 0 && info->meters == 0){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
info++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(free_index > 7) { // shift array and insert to last position
|
||||||
|
info = storage.last_trips;
|
||||||
|
for (uint8_t i = 0; i < 7; i++) {
|
||||||
|
*info = *(info + 1);
|
||||||
|
++info;
|
||||||
|
}
|
||||||
|
storage.last_trips[7] = new_info;
|
||||||
|
} else {
|
||||||
|
storage.last_trips[free_index] = new_info;
|
||||||
|
}
|
||||||
|
eeprom_persist(&storage.last_trips);
|
||||||
|
}
|
88
src/persistence.h
Normal file
88
src/persistence.h
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#ifndef PERSISTENCE_H
|
||||||
|
#define PERSISTENCE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
|
#define EEPROM_I2C_ADDRESS 0x50
|
||||||
|
#define EEPROM_I2C_BUS I2C1
|
||||||
|
#define EEPROM_DATA_START_ADDRESS 0x10
|
||||||
|
#define EEPROM_PAGE_SIZE 16U
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct last_trip_info_t {
|
||||||
|
uint16_t minutes;
|
||||||
|
uint16_t meters;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Struct is automatically mapped to the EEPROM.
|
||||||
|
/// Do not change order of variables without settings reset.
|
||||||
|
struct eeprom_data_t {
|
||||||
|
uint8_t contrast;
|
||||||
|
uint8_t mute;
|
||||||
|
uint32_t total_distance_meters;
|
||||||
|
uint8_t anti_throttle_lock; ///< Factory cruise control cancellation
|
||||||
|
uint16_t keys_volume;
|
||||||
|
uint16_t signals_volume;
|
||||||
|
uint16_t wheel_length_mm;
|
||||||
|
uint16_t magnets_count;
|
||||||
|
uint8_t zero_start_enabled;
|
||||||
|
uint8_t soft_start_enabled;
|
||||||
|
float soft_start_increment;
|
||||||
|
uint8_t current_limit_enabled;
|
||||||
|
float current_limit;
|
||||||
|
float current_stabilization_Kp;
|
||||||
|
uint8_t speed_stabilization_enabled;
|
||||||
|
float speed_stabilization_Kp;
|
||||||
|
uint8_t calibrate_triggers_on_startup;
|
||||||
|
uint16_t brake_trigger_min_value;
|
||||||
|
uint16_t brake_trigger_max_value;
|
||||||
|
uint16_t throttle_trigger_min_value;
|
||||||
|
uint16_t throttle_trigger_max_value;
|
||||||
|
uint8_t speed_limit_last;
|
||||||
|
uint8_t persist_speed_limit;
|
||||||
|
struct last_trip_info_t last_trips[8];
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
struct float_config_entry_t {
|
||||||
|
float* ptr;
|
||||||
|
float _min;
|
||||||
|
float _max;
|
||||||
|
float _default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct u16_config_entry_t {
|
||||||
|
uint16_t* ptr;
|
||||||
|
uint16_t _min;
|
||||||
|
uint16_t _max;
|
||||||
|
uint16_t _default;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct eeprom_data_t storage;
|
||||||
|
#define STORAGE_SIZE sizeof(storage)
|
||||||
|
|
||||||
|
|
||||||
|
// split bytes to pages and write
|
||||||
|
void eeprom_set_bytes(uint8_t addr, const void *ptr, uint8_t count);
|
||||||
|
void eeprom_read_bytes(uint8_t addr, void *ptr, uint8_t count);
|
||||||
|
|
||||||
|
void eeprom_read_and_validate(void);
|
||||||
|
void eeprom_reset_all(uint8_t commit);
|
||||||
|
void eeprom_write_all(void);
|
||||||
|
|
||||||
|
/// Persist variable inside @ref storage to the EEPROM
|
||||||
|
void eeprom_write_entry(void* variable_addr, uint8_t variable_size);
|
||||||
|
|
||||||
|
/// Helper for eeprom_write_entry
|
||||||
|
#define eeprom_persist(variable_addr) (eeprom_write_entry(variable_addr, sizeof(*variable_addr)))
|
||||||
|
|
||||||
|
|
||||||
|
void try_persist_distance(void);
|
||||||
|
void force_persist_distance(void);
|
||||||
|
void session_add_distance(uint16_t centimeters);
|
||||||
|
/// Current session distance in centimeters
|
||||||
|
uint32_t session_distance_cm();
|
||||||
|
void persist_last_trip();
|
||||||
|
|
||||||
|
#endif /* PERSISTENCE_H */
|
334
src/ssd1306.c
Normal file
334
src/ssd1306.c
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
#include "ssd1306.h"
|
||||||
|
#include "font.h"
|
||||||
|
#include <libopencm3/stm32/gpio.h>
|
||||||
|
#include <libopencm3/stm32/i2c.h>
|
||||||
|
#include <libopencm3/stm32/spi.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define SSD1306_FRAMEBUFFER_SIZE (SSD1306_LINES * SSD1306_WIDTH)
|
||||||
|
static uint8_t framebuffer[SSD1306_FRAMEBUFFER_SIZE];
|
||||||
|
static uint8_t *framebuffer_pointer = framebuffer;
|
||||||
|
static const uint8_t *framebuffer_end = framebuffer + SSD1306_FRAMEBUFFER_SIZE;
|
||||||
|
static enum ssd1306_blend_mode_t blend_mode = SSD1306_BLEND_MODE_LIGHTEN;
|
||||||
|
static enum ssd1306_blend_mode_t blend_mode_prev = SSD1306_BLEND_MODE_LIGHTEN;
|
||||||
|
static uint8_t font_scale = 1;
|
||||||
|
|
||||||
|
// If the Co bit is set as logic “0”, the transmission of the following
|
||||||
|
// information will contain data bytes only.
|
||||||
|
#define SSD1306_CONTROL_BYTE_CONTINUATION_OFF (1 << 7)
|
||||||
|
// The D/C# bit determines the next data byte is acted as a command or a data.
|
||||||
|
// If the D/C# bit is set to logic “0”, it defines the following data byte as a
|
||||||
|
// command. If the D/C# bit is set to logic “1”, it defines the following data
|
||||||
|
// byte as a data which will be stored at the GDDRAM.
|
||||||
|
#define SSD1306_CONTROL_BYTE_DC (1 << 6)
|
||||||
|
|
||||||
|
static inline void wait_for_data_transfer_finished(void) {
|
||||||
|
while (!(I2C_SR1(SSD1306_I2C_REGISTER) & (I2C_SR1_BTF))) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void wait_for_address_transfer_finished(void) {
|
||||||
|
while (!(I2C_SR1(SSD1306_I2C_REGISTER) & I2C_SR1_ADDR)) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_transfer_start(void) {
|
||||||
|
i2c_send_start(SSD1306_I2C_REGISTER);
|
||||||
|
|
||||||
|
// wait for i2c ready
|
||||||
|
while (!((I2C_SR1(SSD1306_I2C_REGISTER) & I2C_SR1_SB) &
|
||||||
|
(I2C_SR2(SSD1306_I2C_REGISTER) & (I2C_SR2_MSL | I2C_SR2_BUSY)))) {
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_send_7bit_address(SSD1306_I2C_REGISTER, SSD1306_ADDRESS, I2C_WRITE);
|
||||||
|
wait_for_address_transfer_finished();
|
||||||
|
(void)I2C_SR2(SSD1306_I2C_REGISTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_transfer_end(void) { i2c_send_stop(SSD1306_I2C_REGISTER); }
|
||||||
|
|
||||||
|
void ssd1306_byte(uint8_t b) {
|
||||||
|
i2c_send_data(SSD1306_I2C_REGISTER, b);
|
||||||
|
wait_for_data_transfer_finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_cmd(uint8_t cmd) {
|
||||||
|
ssd1306_byte(0x00 | SSD1306_CONTROL_BYTE_CONTINUATION_OFF); // control byte
|
||||||
|
ssd1306_byte(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_cmd_with_value(uint8_t cmd, uint8_t value) {
|
||||||
|
ssd1306_cmd(cmd);
|
||||||
|
ssd1306_cmd(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_init() {
|
||||||
|
ssd1306_transfer_start();
|
||||||
|
|
||||||
|
ssd1306_cmd(0xAE); // Set display OFF
|
||||||
|
ssd1306_cmd(0xD5); // Set display clock divide ratio or oscillator frequency
|
||||||
|
ssd1306_cmd(0x80); // Display Clock Divide Ratio / OSC Frequency
|
||||||
|
|
||||||
|
ssd1306_cmd_with_value(0xA8, 0x3F); // Multiplex Ratio for 128x64 (64-1)
|
||||||
|
|
||||||
|
ssd1306_cmd_with_value(0xD3, 0x00); // Display Offset
|
||||||
|
|
||||||
|
ssd1306_cmd(0x40); // Set Display Start Line
|
||||||
|
|
||||||
|
ssd1306_cmd_with_value(
|
||||||
|
0x8D, 0x14); // Charge Pump (0x10 External, 0x14 Internal DC/DC)
|
||||||
|
|
||||||
|
// ssd1306_cmd(0x20); // Set Memory Addressing Mode
|
||||||
|
// ssd1306_cmd(0x00); // Horizontal addressing mode
|
||||||
|
|
||||||
|
ssd1306_cmd(0xA1); // Set Segment Re-Map
|
||||||
|
ssd1306_cmd(0xC8); // Set Com Output Scan Direction
|
||||||
|
ssd1306_cmd(0xDA); // Set COM Hardware Configuration
|
||||||
|
ssd1306_cmd(0x12); // COM Hardware Configuration
|
||||||
|
ssd1306_cmd_with_value(0x81, 0x7f); // contrast control
|
||||||
|
ssd1306_cmd_with_value(0xD9, 0xF1); // Set Pre-Charge Period (0x22 External, 0xF1 Internal)
|
||||||
|
ssd1306_cmd_with_value(0xDB, 0x40); // VCOMH Deselect Level
|
||||||
|
ssd1306_cmd(0xA4); // Disable Entire Display On
|
||||||
|
ssd1306_cmd(0xA6); // Normal display, 0xA7 - inverse
|
||||||
|
ssd1306_transfer_end();
|
||||||
|
|
||||||
|
ssd1306_clear();
|
||||||
|
ssd1306_redraw();
|
||||||
|
|
||||||
|
ssd1306_transfer_start();
|
||||||
|
ssd1306_cmd(0xAF); // Set display On
|
||||||
|
ssd1306_transfer_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_contrast(uint8_t val) {
|
||||||
|
ssd1306_transfer_start();
|
||||||
|
ssd1306_cmd_with_value(0x81, val); // contrast control
|
||||||
|
ssd1306_transfer_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_clear(void) { ssd1306_fill(0x00); }
|
||||||
|
|
||||||
|
void ssd1306_fill(uint8_t pattern) {
|
||||||
|
memset(framebuffer, pattern, SSD1306_FRAMEBUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_redraw(void) {
|
||||||
|
for (uint8_t line = 0; line < SSD1306_LINES; line++) {
|
||||||
|
ssd1306_transfer_start();
|
||||||
|
ssd1306_cmd(0xB0 + line); // Set page start address for page addressing mode
|
||||||
|
ssd1306_cmd(0x10); // Set higher column start address for page addressing mode
|
||||||
|
ssd1306_cmd(0x00); // Set lower column start address for page addressing mode
|
||||||
|
|
||||||
|
ssd1306_byte(SSD1306_CONTROL_BYTE_DC); // Control byte
|
||||||
|
|
||||||
|
uint8_t *framebuffer_pos = framebuffer + (line * SSD1306_WIDTH);
|
||||||
|
for (uint8_t col = 0; col < SSD1306_WIDTH; col++) {
|
||||||
|
ssd1306_byte(*(framebuffer_pos++));
|
||||||
|
}
|
||||||
|
|
||||||
|
ssd1306_transfer_end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_blend_mode(enum ssd1306_blend_mode_t mode) { blend_mode = mode; }
|
||||||
|
|
||||||
|
void ssd1306_blend_mode_push() { blend_mode_prev = blend_mode; }
|
||||||
|
|
||||||
|
void ssd1306_blend_mode_pop() { blend_mode = blend_mode_prev; }
|
||||||
|
|
||||||
|
void ssd1306_framebuffer_setpos(uint8_t line, uint8_t column) {
|
||||||
|
uint8_t *new_pos = framebuffer + (line * SSD1306_WIDTH) + column;
|
||||||
|
if (new_pos >= framebuffer && new_pos < framebuffer_end) {
|
||||||
|
framebuffer_pointer = new_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_framebuffer_byte(uint8_t data) {
|
||||||
|
if (framebuffer_pointer >= framebuffer &&
|
||||||
|
framebuffer_pointer < framebuffer_end) { // is pointer within framebuffer
|
||||||
|
switch (blend_mode) {
|
||||||
|
case SSD1306_BLEND_MODE_REPLACE:
|
||||||
|
*framebuffer_pointer = data;
|
||||||
|
break;
|
||||||
|
case SSD1306_BLEND_MODE_LIGHTEN:
|
||||||
|
*framebuffer_pointer |= data;
|
||||||
|
break;
|
||||||
|
case SSD1306_BLEND_MODE_EXCLUSION:
|
||||||
|
*framebuffer_pointer &= ~data;
|
||||||
|
break;
|
||||||
|
case SSD1306_BLEND_MODE_XOR:
|
||||||
|
*framebuffer_pointer ^= data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
++framebuffer_pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_framebuffer_byte_fixedline(uint8_t data) {
|
||||||
|
uint8_t current_line = (framebuffer_pointer - framebuffer) / SSD1306_WIDTH;
|
||||||
|
uint8_t next_line = (framebuffer_pointer + 1U - framebuffer) / SSD1306_WIDTH;
|
||||||
|
|
||||||
|
if (current_line != next_line) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssd1306_framebuffer_byte(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_framebuffer_nextline(uint8_t start_from_zero) {
|
||||||
|
uint8_t line = (framebuffer_pointer - framebuffer) / SSD1306_WIDTH;
|
||||||
|
if (start_from_zero) {
|
||||||
|
ssd1306_framebuffer_setpos(line + 1, 0);
|
||||||
|
} else if (framebuffer_pointer + SSD1306_WIDTH < framebuffer_end) {
|
||||||
|
framebuffer_pointer += SSD1306_WIDTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_font_bytes(const uint8_t *bytes) {
|
||||||
|
uint8_t column = (framebuffer_pointer - framebuffer) % SSD1306_WIDTH;
|
||||||
|
uint8_t *starting_framebuffer_pos = framebuffer_pointer;
|
||||||
|
|
||||||
|
// todo: character wrap
|
||||||
|
|
||||||
|
if (font_scale == 1) { // optimization :D
|
||||||
|
for (uint8_t col = 0; col < FONT_WIDTH; col++) {
|
||||||
|
ssd1306_framebuffer_byte(*bytes);
|
||||||
|
++bytes;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const uint8_t *ptr;
|
||||||
|
for (uint8_t pass = 0; pass < font_scale; pass++) {
|
||||||
|
ptr = bytes;
|
||||||
|
for (uint8_t col = 0; col < FONT_WIDTH; col++) {
|
||||||
|
uint8_t b = 0;
|
||||||
|
uint8_t source_pos = 0;
|
||||||
|
|
||||||
|
// multiply column pixel heights by blocks
|
||||||
|
for (uint8_t i = 0; i < 8; i++) {
|
||||||
|
// What I have made?
|
||||||
|
source_pos = (pass * (8 / font_scale) + i / font_scale);
|
||||||
|
b |= !!(*ptr & (1 << source_pos)) << i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiply column width
|
||||||
|
for (uint8_t i = 0; i < font_scale; i++) {
|
||||||
|
ssd1306_framebuffer_byte(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
++ptr;
|
||||||
|
}
|
||||||
|
ssd1306_framebuffer_nextline(true);
|
||||||
|
framebuffer_pointer += column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
framebuffer_pointer =
|
||||||
|
starting_framebuffer_pos + (FONT_WIDTH + 1) * font_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_font_scale(uint8_t scale) { font_scale = scale; }
|
||||||
|
|
||||||
|
|
||||||
|
void ssd1306_char(char ch) {
|
||||||
|
if (ch == '\n') {
|
||||||
|
ssd1306_framebuffer_nextline(true);
|
||||||
|
} else {
|
||||||
|
ssd1306_mbchar(0x000000ff & ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// auto-generated by font_convert.py
|
||||||
|
void ssd1306_mbchar(uint32_t ch) {
|
||||||
|
uint16_t mapped_idx;
|
||||||
|
|
||||||
|
if (ch >= 0x00000020 && ch <= 0x0000005f) {
|
||||||
|
mapped_idx = 0 + (ch - 0x00000020);
|
||||||
|
} else if (ch == 0x0000d081) {
|
||||||
|
mapped_idx = 64;
|
||||||
|
} else if (ch >= 0x0000d090 && ch <= 0x0000d0af) {
|
||||||
|
mapped_idx = 65 + (ch - 0x0000d090);
|
||||||
|
} else if (ch == 0x00e296aa) {
|
||||||
|
mapped_idx = 97;
|
||||||
|
} else if (ch == 0x00e29c94) {
|
||||||
|
mapped_idx = 98;
|
||||||
|
} else {
|
||||||
|
draw_font_bytes(font_error_symbol);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_font_bytes(font_data + mapped_idx * FONT_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ssd1306_string(const char *str) {
|
||||||
|
uint8_t byte_count;
|
||||||
|
uint32_t char_code;
|
||||||
|
|
||||||
|
while (*str != '\0') {
|
||||||
|
// determine size of sequence
|
||||||
|
if ((*str & 0x80) == 0x00) {
|
||||||
|
byte_count = 1;
|
||||||
|
} else if ((*str & 0xE0) == 0xC0) {
|
||||||
|
byte_count = 2;
|
||||||
|
} else if ((*str & 0xF0) == 0xE0) {
|
||||||
|
byte_count = 3;
|
||||||
|
} else if ((*str & 0xF8) == 0xF0) {
|
||||||
|
byte_count = 4;
|
||||||
|
} else {
|
||||||
|
ssd1306_mbchar(0); // error
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char_code = 0;
|
||||||
|
// read multibyte sequence to the 32-bit int
|
||||||
|
for (uint8_t i = 0; i < byte_count; i++) {
|
||||||
|
if (*str == '\0') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
char_code |= (0x000000FF & *str) << ((byte_count - 1 - i) * 8);
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(byte_count == 1 && char_code == '\n') {
|
||||||
|
ssd1306_framebuffer_nextline(true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssd1306_mbchar(char_code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_horizontal_line(uint8_t x1, uint8_t x2, uint8_t y) {
|
||||||
|
ssd1306_framebuffer_setpos(y / SSD1306_LINES, x1);
|
||||||
|
uint8_t bits = (1 << (y % 8));
|
||||||
|
for (uint16_t x = x1; x < x2 + 1; ++x) {
|
||||||
|
ssd1306_framebuffer_byte(bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_vertical_line(uint8_t y1, uint8_t y2, uint8_t x) {
|
||||||
|
uint8_t pixels_left = y2 - y1;
|
||||||
|
uint8_t line = y1 / SSD1306_LINES;
|
||||||
|
uint8_t tmp_y = y1 % 8;
|
||||||
|
uint8_t bits;
|
||||||
|
uint8_t line_pixels;
|
||||||
|
|
||||||
|
if (y2 < y1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (pixels_left > 0) {
|
||||||
|
line_pixels = (pixels_left < 8 ? pixels_left : 8) - tmp_y;
|
||||||
|
bits = 0xff >> tmp_y;
|
||||||
|
bits &= 0xff << (8 - line_pixels - tmp_y);
|
||||||
|
pixels_left -= line_pixels;
|
||||||
|
tmp_y = 0;
|
||||||
|
ssd1306_framebuffer_setpos(line++, x);
|
||||||
|
ssd1306_framebuffer_byte(bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssd1306_pixel(uint8_t x, uint8_t y) {
|
||||||
|
ssd1306_framebuffer_setpos(y / SSD1306_LINES, x);
|
||||||
|
ssd1306_framebuffer_byte((1 << (y % 8)));
|
||||||
|
}
|
46
src/ssd1306.h
Normal file
46
src/ssd1306.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#ifndef SSD1306_H
|
||||||
|
#define SSD1306_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
enum ssd1306_blend_mode_t {
|
||||||
|
SSD1306_BLEND_MODE_REPLACE, // Replace background and pixels
|
||||||
|
SSD1306_BLEND_MODE_LIGHTEN, // Keep background, switch pixels on
|
||||||
|
SSD1306_BLEND_MODE_EXCLUSION, // Keep background, switch pixels off
|
||||||
|
SSD1306_BLEND_MODE_XOR, // Keep background, toggle pixels
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SSD1306_ADDRESS 0x3c
|
||||||
|
#define SSD1306_WIDTH 128
|
||||||
|
#define SSD1306_LINES 8 // 64 pixels = 8 lines of 8 pixels
|
||||||
|
#define SSD1306_I2C_REGISTER I2C1
|
||||||
|
|
||||||
|
void ssd1306_init(void);
|
||||||
|
void ssd1306_contrast(uint8_t val);
|
||||||
|
void ssd1306_clear(void);
|
||||||
|
void ssd1306_fill(uint8_t pattern);
|
||||||
|
void ssd1306_redraw(void);
|
||||||
|
void ssd1306_blend_mode(enum ssd1306_blend_mode_t mode);
|
||||||
|
void ssd1306_blend_mode_push();
|
||||||
|
void ssd1306_blend_mode_pop();
|
||||||
|
|
||||||
|
/// Origin is top-left corner
|
||||||
|
void ssd1306_framebuffer_setpos(uint8_t line, uint8_t column);
|
||||||
|
void ssd1306_framebuffer_byte(uint8_t data);
|
||||||
|
void ssd1306_framebuffer_byte_fixedline(uint8_t data);
|
||||||
|
void ssd1306_framebuffer_nextline(uint8_t start_from_zero);
|
||||||
|
|
||||||
|
// Only powers of 2 accepted (1, 2, 4, 8)
|
||||||
|
void ssd1306_font_scale(uint8_t scale);
|
||||||
|
void ssd1306_string(const char* str);
|
||||||
|
/// ASCII char
|
||||||
|
void ssd1306_char(char ch);
|
||||||
|
void ssd1306_mbchar(uint32_t ch);
|
||||||
|
|
||||||
|
void ssd1306_horizontal_line(uint8_t x1, uint8_t x2, uint8_t y);
|
||||||
|
void ssd1306_vertical_line(uint8_t y1, uint8_t y2, uint8_t x);
|
||||||
|
|
||||||
|
/// The most innefficient method of drawing
|
||||||
|
void ssd1306_pixel(uint8_t x, uint8_t y);
|
||||||
|
|
||||||
|
#endif /* SSD1306_H */
|
125
src/utils.c
Normal file
125
src/utils.c
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
uint8_t clamp_u8(uint8_t val, uint8_t min_val, uint8_t max_val) {
|
||||||
|
if (val < min_val)
|
||||||
|
return min_val;
|
||||||
|
if (val > max_val)
|
||||||
|
return max_val;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16_t clamp_u16(uint16_t val, uint16_t min_val, uint16_t max_val) {
|
||||||
|
if (val < min_val)
|
||||||
|
return min_val;
|
||||||
|
if (val > max_val)
|
||||||
|
return max_val;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
float clamp_float(float val, float min_val, float max_val) {
|
||||||
|
if (val < min_val)
|
||||||
|
return min_val;
|
||||||
|
if (val > max_val)
|
||||||
|
return max_val;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
float convert_range_float(float val, float old_max, float new_max) {
|
||||||
|
if (val < 0) {
|
||||||
|
return 0;
|
||||||
|
} else if (val > old_max) {
|
||||||
|
return new_max;
|
||||||
|
}
|
||||||
|
return (val * new_max) / old_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t convert_range_u16(uint16_t val, uint16_t old_max, uint16_t new_max) {
|
||||||
|
if (val > old_max) {
|
||||||
|
return new_max;
|
||||||
|
}
|
||||||
|
return (val * new_max) / old_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void incr_u8_loop(uint8_t *val, uint8_t min_val, uint8_t max_val,
|
||||||
|
uint8_t step) {
|
||||||
|
if ((*val + step) <= max_val) {
|
||||||
|
*val += step;
|
||||||
|
} else {
|
||||||
|
*val = min_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void decr_u8_loop(uint8_t *val, uint8_t min_val, uint8_t max_val,
|
||||||
|
uint8_t step) {
|
||||||
|
if ((*val - step) >= min_val) {
|
||||||
|
*val -= step;
|
||||||
|
} else {
|
||||||
|
*val = max_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void incr_u16_loop(uint16_t *val, uint16_t min_val, uint16_t max_val,
|
||||||
|
uint16_t step) {
|
||||||
|
if ((*val + step) <= max_val) {
|
||||||
|
*val += step;
|
||||||
|
} else {
|
||||||
|
*val = min_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decr_u16_loop(uint16_t *val, uint16_t min_val, uint16_t max_val,
|
||||||
|
uint16_t step) {
|
||||||
|
if ((*val - step) >= min_val) {
|
||||||
|
*val -= step;
|
||||||
|
} else {
|
||||||
|
*val = max_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void incr_float_loop(float *val, float min_val, float max_val, float step) {
|
||||||
|
if ((*val + step) <= max_val) {
|
||||||
|
*val += step;
|
||||||
|
} else {
|
||||||
|
*val = min_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decr_float_loop(float *val, float min_val, float max_val, float step) {
|
||||||
|
if ((*val - step) >= min_val) {
|
||||||
|
*val -= step;
|
||||||
|
} else {
|
||||||
|
*val = max_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t digit_count(uint16_t val) {
|
||||||
|
uint8_t count = 0;
|
||||||
|
while (val > 0) {
|
||||||
|
val /= 10;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t pow_u16(uint16_t base, uint16_t exponent) {
|
||||||
|
uint16_t result = 1;
|
||||||
|
for (; exponent > 0; exponent--) {
|
||||||
|
result = result * base;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
float pid_update(struct pid_t *pid, float error, float dt) {
|
||||||
|
pid->P = error;
|
||||||
|
pid->I += error * dt;
|
||||||
|
pid->D = (error - pid->last_error) / dt;
|
||||||
|
pid->last_error = error;
|
||||||
|
return pid->P * pid->Kp + pid->I * pid->Ki + pid->D * pid->Kd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pid_reset(struct pid_t *pid) {
|
||||||
|
pid->P = 0;
|
||||||
|
pid->I = 0;
|
||||||
|
pid->D = 0;
|
||||||
|
pid->last_error = 0;
|
||||||
|
}
|
41
src/utils.h
Normal file
41
src/utils.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#ifndef UTILS_H
|
||||||
|
#define UTILS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct pid_t {
|
||||||
|
float Kp;
|
||||||
|
float Ki;
|
||||||
|
float Kd;
|
||||||
|
|
||||||
|
float P;
|
||||||
|
float I;
|
||||||
|
float D;
|
||||||
|
float last_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct pid_coeff_t {
|
||||||
|
float Kp;
|
||||||
|
float Ki;
|
||||||
|
float Kd;
|
||||||
|
};
|
||||||
|
|
||||||
|
float pid_update(struct pid_t *pid, float error, float dt);
|
||||||
|
void pid_reset(struct pid_t *pid);
|
||||||
|
|
||||||
|
uint8_t clamp_u8(uint8_t val, uint8_t min_val, uint8_t max_val);
|
||||||
|
uint16_t clamp_u16(uint16_t val, uint16_t min_val, uint16_t max_val);
|
||||||
|
float clamp_float(float val, float min_val, float max_val);
|
||||||
|
float convert_range_float(float val, float old_max, float new_max);
|
||||||
|
uint16_t convert_range_u16(uint16_t val, uint16_t old_max, uint16_t new_max);
|
||||||
|
void incr_u8_loop(uint8_t *val, uint8_t min_val, uint8_t max_val, uint8_t step);
|
||||||
|
void decr_u8_loop(uint8_t *val, uint8_t min_val, uint8_t max_val, uint8_t step);
|
||||||
|
void incr_u16_loop(uint16_t *val, uint16_t min_val, uint16_t max_val, uint16_t step);
|
||||||
|
void decr_u16_loop(uint16_t *val, uint16_t min_val, uint16_t max_val, uint16_t step);
|
||||||
|
void incr_float_loop(float *val, float min_val, float max_val, float step);
|
||||||
|
void decr_float_loop(float *val, float min_val, float max_val, float step);
|
||||||
|
uint8_t digit_count(uint16_t val);
|
||||||
|
uint16_t pow_u16(uint16_t base, uint16_t exponent);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* UTILS_H */
|
79
src/views/detailed_view.c
Normal file
79
src/views/detailed_view.c
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#include "detailed_view.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "gui.h"
|
||||||
|
#include "persistence.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "kugoo_s3.h"
|
||||||
|
#include "ssd1306.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
void detailed_view_redraw() {
|
||||||
|
uint16_t current = kugoo_s3_rx.current;
|
||||||
|
uint16_t batt = battery_value();
|
||||||
|
uint32_t distance_m = session_distance_cm() / 100;
|
||||||
|
|
||||||
|
ssd1306_framebuffer_setpos(0, 0);
|
||||||
|
sprintf(sprintf_buf, "0X%lX", reset_reason);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
|
||||||
|
ssd1306_framebuffer_setpos(1, 0);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUG_RESET_REASON
|
||||||
|
if(reset_reason & RCC_CSR_LPWRRSTF) {
|
||||||
|
ssd1306_string("LOW-POWER\n");
|
||||||
|
}
|
||||||
|
if(reset_reason & RCC_CSR_WWDGRSTF) {
|
||||||
|
ssd1306_string("WINDOW WATCHDOG\n");
|
||||||
|
}
|
||||||
|
if(reset_reason & RCC_CSR_IWDGRSTF) {
|
||||||
|
ssd1306_string("INDEPENDENT WATCHDOG\n");
|
||||||
|
}
|
||||||
|
if(reset_reason & RCC_CSR_SFTRSTF) {
|
||||||
|
ssd1306_string("SOFTWARE\n");
|
||||||
|
}
|
||||||
|
if(reset_reason & RCC_CSR_PORRSTF) {
|
||||||
|
ssd1306_string("POWERON/POWERDOWN\n");
|
||||||
|
}
|
||||||
|
if(reset_reason & RCC_CSR_PINRSTF) {
|
||||||
|
ssd1306_string("RESET BTN\n");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ssd1306_font_scale(2);
|
||||||
|
ssd1306_framebuffer_setpos(0, 0);
|
||||||
|
sprintf(sprintf_buf, "%d.%d", batt / 10, batt % 10);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
ssd1306_font_scale(1);
|
||||||
|
ssd1306_framebuffer_nextline(false);
|
||||||
|
ssd1306_string(" В");
|
||||||
|
|
||||||
|
ssd1306_font_scale(2);
|
||||||
|
ssd1306_framebuffer_setpos(2, 0);
|
||||||
|
sprintf(sprintf_buf, "%d.%d", current / 10, current % 10);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
ssd1306_font_scale(1);
|
||||||
|
ssd1306_framebuffer_nextline(false);
|
||||||
|
ssd1306_string(" А");
|
||||||
|
|
||||||
|
ssd1306_font_scale(2);
|
||||||
|
ssd1306_framebuffer_setpos(4, 0);
|
||||||
|
sprintf(sprintf_buf, "%lu", distance_m);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
|
||||||
|
ssd1306_font_scale(1);
|
||||||
|
ssd1306_framebuffer_nextline(false);
|
||||||
|
ssd1306_string(" М");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ssd1306_framebuffer_setpos(6, 0);
|
||||||
|
ssd1306_font_scale(2);
|
||||||
|
sprintf(sprintf_buf, "%lu.%lu", storage.total_distance_meters / 1000,
|
||||||
|
(storage.total_distance_meters / 100) % 10);
|
||||||
|
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
ssd1306_font_scale(1);
|
||||||
|
ssd1306_framebuffer_nextline(false);
|
||||||
|
ssd1306_string(" КМ");
|
||||||
|
}
|
8
src/views/detailed_view.h
Normal file
8
src/views/detailed_view.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef DETAILED_VIEW_H
|
||||||
|
#define DETAILED_VIEW_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void detailed_view_redraw();
|
||||||
|
|
||||||
|
#endif /* DETAILED_VIEW_H */
|
58
src/views/last_trips_view.c
Normal file
58
src/views/last_trips_view.c
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#include "last_trips_view.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "persistence.h"
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "gui.h"
|
||||||
|
#include "ssd1306.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
|
||||||
|
|
||||||
|
void last_trips_view_keyhandler(uint8_t e) {
|
||||||
|
if (e & KB_EVT_TYPE_KEYDOWN) {
|
||||||
|
beep(KEY_BEEP_FREQ, 10, storage.keys_volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e & KB_EVT_TYPE_KEYDOWN && e & KB_EVT_KEY_BEEP) {
|
||||||
|
gui_set_view(GUI_VIEW_SETTINGS);
|
||||||
|
} else if (e & KB_EVT_KEY_SPEED && e & KB_EVT_TYPE_REPEAT) {
|
||||||
|
memset(&storage.last_trips, 0U, sizeof(storage.last_trips));
|
||||||
|
eeprom_persist(&storage.last_trips);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void last_trips_view_redraw() {
|
||||||
|
struct last_trip_info_t *info = storage.last_trips;
|
||||||
|
uint8_t i = 0;
|
||||||
|
|
||||||
|
for (; i < 8; i++) {
|
||||||
|
if(info->minutes == 0 && info->meters == 0){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ssd1306_framebuffer_setpos(i, 0);
|
||||||
|
|
||||||
|
if(info->minutes < 60) {
|
||||||
|
sprintf(sprintf_buf, "%d МИН - ", info->minutes);
|
||||||
|
} else {
|
||||||
|
sprintf(sprintf_buf, "%d.%d Ч", info->minutes / 60, info->meters % 60 / 6);
|
||||||
|
}
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
|
||||||
|
if(info->meters < 1000) {
|
||||||
|
sprintf(sprintf_buf, "%d М", info->meters);
|
||||||
|
} else {
|
||||||
|
sprintf(sprintf_buf, "%d.%d КМ", info->meters / 1000, info->meters % 1000 / 100);
|
||||||
|
}
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
|
||||||
|
++info;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i == 0) {
|
||||||
|
// 6 = font width (5) + interval (1)
|
||||||
|
// 5 - symbols count
|
||||||
|
ssd1306_framebuffer_setpos(3, (millis() / 50) % (SSD1306_WIDTH - 6 * 5));
|
||||||
|
ssd1306_string("ПУСТО");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
src/views/last_trips_view.h
Normal file
9
src/views/last_trips_view.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#ifndef LAST_TRIPS_VIEW_H
|
||||||
|
#define LAST_TRIPS_VIEW_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void last_trips_view_keyhandler(uint8_t e);
|
||||||
|
void last_trips_view_redraw();
|
||||||
|
|
||||||
|
#endif /* LAST_TRIPS_VIEW_H */
|
194
src/views/main_view.c
Normal file
194
src/views/main_view.c
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
#include "main_view.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "gui.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
#include "persistence.h"
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "kugoo_s3.h"
|
||||||
|
#include "ssd1306.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
float speed_limit_prev = 0;
|
||||||
|
uint32_t speed_limit_changed_at;
|
||||||
|
|
||||||
|
void main_view_keyhandler(uint8_t e) {
|
||||||
|
if (e & KB_EVT_TYPE_KEYDOWN) {
|
||||||
|
if (e & KB_EVT_KEY_BEEP) {
|
||||||
|
buzzer_freq_vol(1000, 255);
|
||||||
|
} else if (e & KB_EVT_KEY_SET) {
|
||||||
|
// enable cruise control when throttle trigger pressed
|
||||||
|
if(throttle_value_normalized() > 10) {
|
||||||
|
// cruise_control_speed_kmh = kugoo_s3_get_speed();
|
||||||
|
// todo: maybe use current speed, not throttle trigger
|
||||||
|
cruise_ctl_speed_kmh = throttle_value_to_kmh();
|
||||||
|
cruise_ctl_status = CRUISE_CTL_WAITING_RELEASE;
|
||||||
|
} else {
|
||||||
|
gui_set_view(GUI_VIEW_SETTINGS);
|
||||||
|
gui_redraw_force();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (e & KB_EVT_KEY_LIGHT) {
|
||||||
|
beep_blocking(2000, 10, storage.keys_volume);
|
||||||
|
light_toggle();
|
||||||
|
} else if (e & KB_EVT_KEY_SPEED) {
|
||||||
|
beep_blocking(3000, 10, storage.keys_volume);
|
||||||
|
if(brake_value_normalized() > 10) {
|
||||||
|
storage.current_limit_enabled = !storage.current_limit_enabled;
|
||||||
|
eeprom_persist(&storage.current_limit_enabled);
|
||||||
|
} else {
|
||||||
|
incr_u8_loop(&storage.speed_limit_last, 10, 30, 5);
|
||||||
|
gui_redraw_force();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (e & KB_EVT_TYPE_KEYUP) {
|
||||||
|
if (e & KB_EVT_KEY_BEEP) {
|
||||||
|
buzzer_freq_vol(0, 255);
|
||||||
|
} else if (e & KB_EVT_KEY_POWER) {
|
||||||
|
if (gui_is_view(GUI_VIEW_MAIN)) {
|
||||||
|
gui_set_view(GUI_VIEW_MAIN_DETAILED);
|
||||||
|
} else if (gui_is_view(GUI_VIEW_MAIN_DETAILED)) {
|
||||||
|
gui_set_view(GUI_VIEW_MAIN);
|
||||||
|
}
|
||||||
|
gui_redraw_force();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main_view_redraw() {
|
||||||
|
uint16_t current = kugoo_s3_rx.current;
|
||||||
|
uint32_t ms = millis();
|
||||||
|
uint16_t speed = kugoo_s3_get_speed();
|
||||||
|
uint16_t batt = battery_value();
|
||||||
|
uint16_t batt_clamp =
|
||||||
|
clamp_u16(batt, BATTERY_VOLTS_0_PERCENTS, BATTERY_VOLTS_100_PERCENTS);
|
||||||
|
|
||||||
|
uint16_t batt_pixels = ((batt_clamp - BATTERY_VOLTS_0_PERCENTS) * 83) /
|
||||||
|
(BATTERY_VOLTS_100_PERCENTS - BATTERY_VOLTS_0_PERCENTS);
|
||||||
|
|
||||||
|
uint32_t distance_m = session_distance_cm() / 100;
|
||||||
|
|
||||||
|
|
||||||
|
if(speed_limit_prev != storage.speed_limit_last) {
|
||||||
|
speed_limit_prev = storage.speed_limit_last;
|
||||||
|
speed_limit_changed_at = ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(speed_limit_changed_at - ms < 2000) {
|
||||||
|
ssd1306_framebuffer_setpos(0, 20);
|
||||||
|
ssd1306_font_scale(8);
|
||||||
|
|
||||||
|
sprintf(sprintf_buf, "%u", storage.speed_limit_last);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batt > BATTERY_VOLTS_0_PERCENTS || (ms / 500) % 2 == 0) {
|
||||||
|
// left battery cap
|
||||||
|
ssd1306_framebuffer_setpos(0, 39);
|
||||||
|
ssd1306_framebuffer_byte(0x7F);
|
||||||
|
ssd1306_framebuffer_byte(0x41);
|
||||||
|
|
||||||
|
|
||||||
|
// right battery cap
|
||||||
|
ssd1306_framebuffer_setpos(0, SSD1306_WIDTH - 4);
|
||||||
|
ssd1306_framebuffer_byte(0x41);
|
||||||
|
ssd1306_framebuffer_byte(0x7F);
|
||||||
|
ssd1306_framebuffer_byte(0x1C);
|
||||||
|
|
||||||
|
// battery progressbar
|
||||||
|
ssd1306_framebuffer_setpos(0, 41);
|
||||||
|
for (uint8_t i = 0; i < 83; i++) {
|
||||||
|
if (i < batt_pixels) {
|
||||||
|
ssd1306_framebuffer_byte(0x5D);
|
||||||
|
} else {
|
||||||
|
ssd1306_framebuffer_byte(0x41);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// vertical line
|
||||||
|
for (uint8_t i = 0; i < 8; i++) {
|
||||||
|
ssd1306_framebuffer_setpos(i, 37);
|
||||||
|
ssd1306_framebuffer_byte(0x55);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bottom horizontal line
|
||||||
|
ssd1306_framebuffer_setpos(6, 0);
|
||||||
|
for (uint8_t i = 0; i < 19; i++) {
|
||||||
|
ssd1306_framebuffer_byte(0x80);
|
||||||
|
ssd1306_framebuffer_byte(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current > 0 && millis() / 1000 % 7 > 3) {
|
||||||
|
// current
|
||||||
|
ssd1306_framebuffer_setpos(3, 41);
|
||||||
|
ssd1306_font_scale(4);
|
||||||
|
sprintf(sprintf_buf, "%d", current / 10);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
ssd1306_framebuffer_nextline(false);
|
||||||
|
ssd1306_framebuffer_nextline(false);
|
||||||
|
ssd1306_font_scale(2);
|
||||||
|
sprintf(sprintf_buf, ".%d", current % 10);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
ssd1306_font_scale(1);
|
||||||
|
ssd1306_framebuffer_nextline(false);
|
||||||
|
ssd1306_string(" A");
|
||||||
|
} else {
|
||||||
|
// speeed
|
||||||
|
ssd1306_framebuffer_setpos(0, 39);
|
||||||
|
ssd1306_font_scale(8);
|
||||||
|
if (speed > 99) {
|
||||||
|
ssd1306_string("ЖП");
|
||||||
|
} else {
|
||||||
|
sprintf(sprintf_buf, "%02d", speed);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssd1306_font_scale(1);
|
||||||
|
ssd1306_framebuffer_setpos(7, 3);
|
||||||
|
sprintf(sprintf_buf, "%02lu%c%02lu", (ms / 1000) / 60,
|
||||||
|
(ms / 500) % 2 == 0 ? ':' : ' ', (ms / 1000) % 60);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
|
||||||
|
// current limit indicator
|
||||||
|
if(storage.current_limit_enabled) {
|
||||||
|
ssd1306_font_scale(2);
|
||||||
|
ssd1306_framebuffer_setpos(2, 0);
|
||||||
|
ssd1306_string("LIM");
|
||||||
|
}
|
||||||
|
|
||||||
|
// curent session distance
|
||||||
|
ssd1306_font_scale(2);
|
||||||
|
ssd1306_framebuffer_setpos(4, 0);
|
||||||
|
if(distance_m < 99) {
|
||||||
|
sprintf(sprintf_buf, "%lu", distance_m);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
ssd1306_font_scale(1);
|
||||||
|
ssd1306_framebuffer_nextline(false);
|
||||||
|
ssd1306_string("М");
|
||||||
|
} else {
|
||||||
|
uint32_t km = distance_m / 1000;
|
||||||
|
sprintf(sprintf_buf, "%lu", km);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
|
||||||
|
ssd1306_font_scale(1);
|
||||||
|
ssd1306_framebuffer_nextline(false);
|
||||||
|
if(km < 10) {
|
||||||
|
sprintf(sprintf_buf, ".%luКМ", distance_m % 1000 / 100);
|
||||||
|
} else {
|
||||||
|
sprintf(sprintf_buf, "КМ");
|
||||||
|
}
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: trottle lock indicator, remove
|
||||||
|
if(kugoo_s3_rx.state & KUGOO_S3_STATE_THROTTLE_LOCKED) {
|
||||||
|
ssd1306_framebuffer_setpos(0, 0);
|
||||||
|
for (uint8_t i = 0; i < 24; i++) {
|
||||||
|
ssd1306_framebuffer_byte(0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/views/main_view.h
Normal file
9
src/views/main_view.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#ifndef MAIN_VIEW_H
|
||||||
|
#define MAIN_VIEW_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void main_view_keyhandler(uint8_t e);
|
||||||
|
void main_view_redraw();
|
||||||
|
|
||||||
|
#endif /* MAIN_VIEW_H */
|
308
src/views/settings_view.c
Normal file
308
src/views/settings_view.c
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
#include "settings_view.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "gui.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "kugoo_s3.h"
|
||||||
|
#include "persistence.h"
|
||||||
|
#include "ssd1306.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/// Selected menu item index
|
||||||
|
static uint8_t menu_index = 0;
|
||||||
|
static uint8_t is_editing = 0;
|
||||||
|
static uint8_t editor_digit_index = 0;
|
||||||
|
|
||||||
|
// @see persistence.c
|
||||||
|
extern struct float_config_entry_t cfg_speed_stabilization_Kp;
|
||||||
|
extern struct float_config_entry_t cfg_current_stabilization_Kp;
|
||||||
|
extern struct float_config_entry_t cfg_current_limit;
|
||||||
|
extern struct float_config_entry_t cfg_soft_start_increment;
|
||||||
|
extern struct u16_config_entry_t cfg_wheel_length_mm;
|
||||||
|
extern struct u16_config_entry_t cfg_magnets_count;
|
||||||
|
extern struct u16_config_entry_t cfg_keys_volume;
|
||||||
|
extern struct u16_config_entry_t cfg_signals_volume;
|
||||||
|
|
||||||
|
#ifdef MENU_SEPARATORS
|
||||||
|
#define MENU_SEPARATOR(text) { \
|
||||||
|
.title = (text), \
|
||||||
|
.type = SETTING_TYPE_NONE, \
|
||||||
|
.data = NULL \
|
||||||
|
},
|
||||||
|
#else
|
||||||
|
#define MENU_SEPARATOR(text)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const struct settings_item_t menu_items[] = {
|
||||||
|
{
|
||||||
|
.title = " == ВЕРСИЯ " FIRMWARE_VERSION " ==",
|
||||||
|
.type = SETTING_TYPE_NONE,
|
||||||
|
.data = NULL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "ОТКЛЮЧИТЬ ЗВУКИ",
|
||||||
|
.type = SETTING_TYPE_TOGGLE,
|
||||||
|
.data = &storage.mute
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "ГРОМКОСТЬ КНОПОК",
|
||||||
|
.type = SETTING_TYPE_EDIT_U16,
|
||||||
|
.data = &cfg_keys_volume
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "ГРОМКОСТЬ СИГНАЛОВ",
|
||||||
|
.type = SETTING_TYPE_EDIT_U16,
|
||||||
|
.data = &cfg_signals_volume
|
||||||
|
},
|
||||||
|
MENU_SEPARATOR("")
|
||||||
|
{
|
||||||
|
.title = "АНТИ-ФИКСАЦИЯ УСКОР.",
|
||||||
|
.type = SETTING_TYPE_TOGGLE,
|
||||||
|
.data = &storage.anti_throttle_lock
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "ZERO-START",
|
||||||
|
.type = SETTING_TYPE_TOGGLE,
|
||||||
|
.data = &storage.zero_start_enabled
|
||||||
|
},
|
||||||
|
MENU_SEPARATOR("")
|
||||||
|
{
|
||||||
|
.title = "ПЛАВНОЕ УСКОРЕНИЕ",
|
||||||
|
.type = SETTING_TYPE_TOGGLE,
|
||||||
|
.data = &storage.soft_start_enabled
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "ИНКРЕМ. ПЛАВН. УСКОР.",
|
||||||
|
.type = SETTING_TYPE_EDIT_FLOAT,
|
||||||
|
.data = &cfg_soft_start_increment
|
||||||
|
},
|
||||||
|
MENU_SEPARATOR("")
|
||||||
|
{
|
||||||
|
.title = "ОГРАНИЧЕНИЕ ТОКА",
|
||||||
|
.type = SETTING_TYPE_TOGGLE,
|
||||||
|
.data = &storage.current_limit_enabled
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "ЗНАЧ. ОГРАНИЧЕН. ТОКА",
|
||||||
|
.type = SETTING_TYPE_EDIT_FLOAT,
|
||||||
|
.data = &cfg_current_limit
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "КОЭФФ. СТАБИЛИЗ. ТОКА",
|
||||||
|
.type = SETTING_TYPE_EDIT_FLOAT,
|
||||||
|
.data = &cfg_current_stabilization_Kp
|
||||||
|
},
|
||||||
|
MENU_SEPARATOR("")
|
||||||
|
{
|
||||||
|
.title = "СТАБИЛИЗАЦИЯ СКОР.",
|
||||||
|
.type = SETTING_TYPE_TOGGLE,
|
||||||
|
.data = &storage.speed_stabilization_enabled
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "КОЭФФ. СТАБ. СКОРОСТИ",
|
||||||
|
.type = SETTING_TYPE_EDIT_FLOAT,
|
||||||
|
.data = &cfg_speed_stabilization_Kp
|
||||||
|
},
|
||||||
|
MENU_SEPARATOR("")
|
||||||
|
{
|
||||||
|
.title = "КАЛИБРОВКА РУЧЕК",
|
||||||
|
.type = SETTING_TYPE_VIEW_CHANGE,
|
||||||
|
.data = (void*)GUI_VIEW_TRIGGER_CALIBRATION
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "АВТОКАЛИБРОВКА РУЧЕК",
|
||||||
|
.type = SETTING_TYPE_TOGGLE,
|
||||||
|
.data = &storage.calibrate_triggers_on_startup
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "СОХРАНЯТЬ СКОРОСТЬ",
|
||||||
|
.type = SETTING_TYPE_TOGGLE,
|
||||||
|
.data = &storage.persist_speed_limit
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "ДЛИНА ОКР. КОЛЕСА, ММ",
|
||||||
|
.type = SETTING_TYPE_EDIT_U16,
|
||||||
|
.data = &cfg_wheel_length_mm
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.title = "КОЛИЧЕСТВО МАГНИТОВ",
|
||||||
|
.type = SETTING_TYPE_EDIT_U16,
|
||||||
|
.data = &cfg_magnets_count
|
||||||
|
},
|
||||||
|
MENU_SEPARATOR("")
|
||||||
|
{
|
||||||
|
.title = "ПОСЛЕДНИЕ ПОЕЗДКИ",
|
||||||
|
.type = SETTING_TYPE_VIEW_CHANGE,
|
||||||
|
.data = (void*)GUI_VIEW_LAST_TRIPS
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t menu_items_count = sizeof(menu_items) / sizeof(struct settings_item_t);
|
||||||
|
|
||||||
|
void settings_view_keyhandler(uint8_t e) {
|
||||||
|
const struct settings_item_t *item = menu_items + menu_index;
|
||||||
|
|
||||||
|
if (!(e & KB_EVT_TYPE_KEYDOWN || e & KB_EVT_TYPE_REPEAT)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
beep(KEY_BEEP_FREQ, 10, storage.keys_volume);
|
||||||
|
|
||||||
|
if (is_editing) {
|
||||||
|
if (e & KB_EVT_KEY_BEEP) { // persist data after closing editor
|
||||||
|
is_editing = false;
|
||||||
|
|
||||||
|
if(item->type == SETTING_TYPE_EDIT_FLOAT) {
|
||||||
|
struct float_config_entry_t *entry = (struct float_config_entry_t *) item->data;
|
||||||
|
eeprom_write_entry(entry->ptr, sizeof(float));
|
||||||
|
} else if(item->type == SETTING_TYPE_EDIT_U16) {
|
||||||
|
struct u16_config_entry_t *entry = (struct u16_config_entry_t *) item->data;
|
||||||
|
eeprom_write_entry(entry->ptr, sizeof(uint16_t));
|
||||||
|
}
|
||||||
|
} else if (item->type == SETTING_TYPE_EDIT_U16) {
|
||||||
|
struct u16_config_entry_t *entry = (struct u16_config_entry_t *) item->data;
|
||||||
|
uint8_t digits = digit_count(entry->_max);
|
||||||
|
uint16_t step = pow_u16(10, editor_digit_index);
|
||||||
|
if (e & KB_EVT_KEY_POWER) {
|
||||||
|
incr_u8_loop(&editor_digit_index, 0, digits - 1, 1);
|
||||||
|
} if (e & KB_EVT_KEY_SET) {
|
||||||
|
decr_u16_loop(entry->ptr, entry->_min, entry->_max, step);
|
||||||
|
} else if (e & KB_EVT_KEY_LIGHT) {
|
||||||
|
incr_u16_loop(entry->ptr, entry->_min, entry->_max, step);
|
||||||
|
} else if (e & KB_EVT_KEY_SPEED) {
|
||||||
|
*entry->ptr = entry->_default;
|
||||||
|
}
|
||||||
|
} else if (item->type == SETTING_TYPE_EDIT_FLOAT) {
|
||||||
|
struct float_config_entry_t *entry = (struct float_config_entry_t *) item->data;
|
||||||
|
float step = 0.1f;
|
||||||
|
|
||||||
|
if (editor_digit_index > 3) { // integer part
|
||||||
|
step = pow_u16(10, editor_digit_index - 4);
|
||||||
|
} else if (editor_digit_index == 0) { // 0.1 * 10 ^ -y is too hard
|
||||||
|
step = 0.0001f;
|
||||||
|
} else if (editor_digit_index == 1) {
|
||||||
|
step = 0.001f;
|
||||||
|
} else if (editor_digit_index == 2) {
|
||||||
|
step = 0.01f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e & KB_EVT_KEY_POWER) {
|
||||||
|
incr_u8_loop(&editor_digit_index, 0, 7, 1);
|
||||||
|
} if (e & KB_EVT_KEY_SET) {
|
||||||
|
decr_float_loop(entry->ptr, entry->_min, entry->_max, step);
|
||||||
|
} else if (e & KB_EVT_KEY_LIGHT) {
|
||||||
|
incr_float_loop(entry->ptr, entry->_min, entry->_max, step);
|
||||||
|
} else if (e & KB_EVT_KEY_SPEED) {
|
||||||
|
*entry->ptr = entry->_default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (e & KB_EVT_KEY_SET) {
|
||||||
|
decr_u8_loop(&menu_index, 0, menu_items_count - 1, 1);
|
||||||
|
} else if (e & KB_EVT_KEY_LIGHT) {
|
||||||
|
incr_u8_loop(&menu_index, 0, menu_items_count - 1, 1);
|
||||||
|
} else if (e & KB_EVT_KEY_BEEP) {
|
||||||
|
gui_set_view(GUI_VIEW_MAIN);
|
||||||
|
} else if (e & KB_EVT_KEY_POWER) {
|
||||||
|
if (item->type == SETTING_TYPE_TOGGLE) {
|
||||||
|
uint8_t *val = (uint8_t *)item->data;
|
||||||
|
*val = !*val;
|
||||||
|
eeprom_persist(val);
|
||||||
|
} else if (item->type == SETTING_TYPE_VIEW_CHANGE) {
|
||||||
|
gui_set_view((enum view_id_t)item->data);
|
||||||
|
} else if (item->type == SETTING_TYPE_EDIT_U16 || item->type == SETTING_TYPE_EDIT_FLOAT) {
|
||||||
|
is_editing = true;
|
||||||
|
editor_digit_index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gui_redraw_force();
|
||||||
|
}
|
||||||
|
|
||||||
|
void settings_view_redraw() {
|
||||||
|
const struct settings_item_t *item;
|
||||||
|
uint8_t menu_shift;
|
||||||
|
uint8_t new_index;
|
||||||
|
|
||||||
|
if(is_editing) {
|
||||||
|
item = menu_items + menu_index;
|
||||||
|
ssd1306_framebuffer_setpos(0, 0);
|
||||||
|
ssd1306_string(item->title);
|
||||||
|
|
||||||
|
ssd1306_font_scale(2);
|
||||||
|
ssd1306_framebuffer_setpos(3, 0);
|
||||||
|
|
||||||
|
if(item->type == SETTING_TYPE_EDIT_U16) {
|
||||||
|
struct u16_config_entry_t *params = (struct u16_config_entry_t *) item->data;
|
||||||
|
uint8_t digits = digit_count(params->_max);
|
||||||
|
if(digits < 6) { // suppress compiler warning
|
||||||
|
sprintf(sprintf_buf, "%0*u", digits, *params->ptr);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
}
|
||||||
|
ssd1306_blend_mode_push();
|
||||||
|
ssd1306_blend_mode(SSD1306_BLEND_MODE_XOR);
|
||||||
|
|
||||||
|
ssd1306_framebuffer_setpos(5, 0);
|
||||||
|
for (uint8_t i = 1; i <= digits; i++){
|
||||||
|
if(i == (digits - editor_digit_index)) {
|
||||||
|
ssd1306_char('^');
|
||||||
|
} else {
|
||||||
|
ssd1306_char(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ssd1306_blend_mode_pop();
|
||||||
|
} else if(item->type == SETTING_TYPE_EDIT_FLOAT) {
|
||||||
|
struct float_config_entry_t *params = (struct float_config_entry_t *) item->data;
|
||||||
|
float f = *params->ptr;
|
||||||
|
uint16_t integer_part = f;
|
||||||
|
uint16_t fractional_part = (f - integer_part) * 10000;
|
||||||
|
sprintf(sprintf_buf, "%04d.%04d", integer_part, fractional_part);
|
||||||
|
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
ssd1306_blend_mode_push();
|
||||||
|
ssd1306_blend_mode(SSD1306_BLEND_MODE_XOR);
|
||||||
|
ssd1306_framebuffer_setpos(5, 0);
|
||||||
|
for (uint8_t i = 1; i <= 8; i++) {
|
||||||
|
|
||||||
|
if(i == (8 - editor_digit_index)) {
|
||||||
|
if(i > 4) { // do not point to point
|
||||||
|
ssd1306_char(' ');
|
||||||
|
}
|
||||||
|
ssd1306_char('^');
|
||||||
|
} else {
|
||||||
|
ssd1306_char(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ssd1306_blend_mode_pop();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < SSD1306_LINES; i++) {
|
||||||
|
menu_shift = menu_index / SSD1306_LINES;
|
||||||
|
new_index = (menu_shift * SSD1306_LINES) + i;
|
||||||
|
if (new_index >= menu_items_count) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
item = menu_items + new_index;
|
||||||
|
|
||||||
|
ssd1306_framebuffer_setpos(i, 0);
|
||||||
|
ssd1306_string(item->title);
|
||||||
|
|
||||||
|
if(new_index == menu_index) {
|
||||||
|
ssd1306_framebuffer_setpos(i, 0);
|
||||||
|
ssd1306_blend_mode_push();
|
||||||
|
ssd1306_blend_mode(SSD1306_BLEND_MODE_XOR);
|
||||||
|
for (uint8_t i = 0; i < SSD1306_WIDTH; i++){
|
||||||
|
ssd1306_framebuffer_byte(0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item->type == SETTING_TYPE_TOGGLE) {
|
||||||
|
ssd1306_framebuffer_setpos(i, 122);
|
||||||
|
ssd1306_string(*((uint8_t *)item->data) ? "✔" : "▪");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
src/views/settings_view.h
Normal file
24
src/views/settings_view.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef SETTINGS_VIEW_H
|
||||||
|
#define SETTINGS_VIEW_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
enum settings_item_type_t {
|
||||||
|
SETTING_TYPE_NONE,
|
||||||
|
SETTING_TYPE_VIEW_CHANGE,
|
||||||
|
SETTING_TYPE_TOGGLE,
|
||||||
|
SETTING_TYPE_EDIT_FLOAT,
|
||||||
|
SETTING_TYPE_EDIT_U16,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct settings_item_t {
|
||||||
|
const char *title;
|
||||||
|
enum settings_item_type_t type;
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
void settings_view_keyhandler(uint8_t e);
|
||||||
|
void settings_view_redraw();
|
||||||
|
|
||||||
|
#endif /* SETTINGS_VIEW_H */
|
113
src/views/trigger_calibration_view.c
Normal file
113
src/views/trigger_calibration_view.c
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#include "trigger_calibration_view.h"
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "gui.h"
|
||||||
|
#include "ssd1306.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "hardware.h"
|
||||||
|
#include "persistence.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static enum trigger_calibration_phase_t current_phase = TRIGGER_CAL_PHASE_NONE;
|
||||||
|
|
||||||
|
void trigger_calibration_view_keyhandler(uint8_t e) {
|
||||||
|
if (!(e & KB_EVT_TYPE_KEYDOWN)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
beep(KEY_BEEP_FREQ, 10, storage.keys_volume);
|
||||||
|
|
||||||
|
if (e & KB_EVT_KEY_BEEP) {
|
||||||
|
gui_set_view(GUI_VIEW_SETTINGS);
|
||||||
|
} else if (e & KB_EVT_KEY_POWER) {
|
||||||
|
|
||||||
|
if(current_phase == TRIGGER_CAL_PHASE_NONE) {
|
||||||
|
current_phase = TRIGGER_CAL_PHASE_THROTTLE_MIN;
|
||||||
|
|
||||||
|
} else if(current_phase == TRIGGER_CAL_PHASE_THROTTLE_MIN) {
|
||||||
|
storage.throttle_trigger_min_value = throttle_adc_value() + TRIGGER_ANTI_JITTER;
|
||||||
|
current_phase = TRIGGER_CAL_PHASE_THROTTLE_MAX;
|
||||||
|
|
||||||
|
} else if(current_phase == TRIGGER_CAL_PHASE_THROTTLE_MAX) {
|
||||||
|
storage.throttle_trigger_max_value = throttle_adc_value() - TRIGGER_ANTI_JITTER;
|
||||||
|
current_phase = TRIGGER_CAL_PHASE_BRAKE_MIN;
|
||||||
|
|
||||||
|
} else if(current_phase == TRIGGER_CAL_PHASE_BRAKE_MIN) {
|
||||||
|
storage.brake_trigger_min_value = brake_adc_value() + TRIGGER_ANTI_JITTER;
|
||||||
|
current_phase = TRIGGER_CAL_PHASE_BRAKE_MAX;
|
||||||
|
|
||||||
|
} else if(current_phase == TRIGGER_CAL_PHASE_BRAKE_MAX) {
|
||||||
|
storage.brake_trigger_max_value = brake_adc_value() - TRIGGER_ANTI_JITTER;
|
||||||
|
eeprom_persist(&storage.throttle_trigger_min_value);
|
||||||
|
eeprom_persist(&storage.throttle_trigger_max_value);
|
||||||
|
eeprom_persist(&storage.brake_trigger_min_value);
|
||||||
|
eeprom_persist(&storage.brake_trigger_max_value);
|
||||||
|
current_phase = TRIGGER_CAL_PHASE_FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
gui_redraw_force();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void trigger_calibration_view_redraw() {
|
||||||
|
ssd1306_framebuffer_setpos(0, 0);
|
||||||
|
ssd1306_string("КАЛИБРОВКА РУЧЕК");
|
||||||
|
ssd1306_framebuffer_setpos(2, 0);
|
||||||
|
|
||||||
|
if(current_phase == TRIGGER_CAL_PHASE_NONE) {
|
||||||
|
ssd1306_string("ДЛЯ НАЧАЛА\n"
|
||||||
|
"НАЖМИТЕ [POWER]");
|
||||||
|
} else if(current_phase == TRIGGER_CAL_PHASE_THROTTLE_MIN) {
|
||||||
|
ssd1306_string("ВЫВЕДИТЕ РУЧКУ\n"
|
||||||
|
"[АКСЕЛЕРАТОРА]\n"
|
||||||
|
"В НУЛЕВОЕ ПОЛОЖЕНИЕ\n"
|
||||||
|
"И НАЖМИТЕ [POWER]");
|
||||||
|
} else if(current_phase == TRIGGER_CAL_PHASE_THROTTLE_MAX) {
|
||||||
|
ssd1306_string("ВЫВЕДИТЕ РУЧКУ\n"
|
||||||
|
"[АКСЕЛЕРАТОРА]\n"
|
||||||
|
"В МАКС. ПОЛОЖЕНИЕ\n"
|
||||||
|
"И НАЖМИТЕ [POWER]");
|
||||||
|
} else if(current_phase == TRIGGER_CAL_PHASE_BRAKE_MIN) {
|
||||||
|
ssd1306_string("ВЫВЕДИТЕ РУЧКУ\n"
|
||||||
|
"[ТОРМОЗА]\n"
|
||||||
|
"В НУЛЕВОЕ ПОЛОЖЕНИЕ\n"
|
||||||
|
"И НАЖМИТЕ [POWER]");
|
||||||
|
} else if(current_phase == TRIGGER_CAL_PHASE_BRAKE_MAX) {
|
||||||
|
ssd1306_string("ВЫВЕДИТЕ РУЧКУ\n"
|
||||||
|
"[ТОРМОЗА]\n"
|
||||||
|
"В МАКС. ПОЛОЖЕНИЕ\n"
|
||||||
|
"И НАЖМИТЕ [POWER]");
|
||||||
|
} else if(current_phase == TRIGGER_CAL_PHASE_FINISHED) {
|
||||||
|
ssd1306_string("ПОЗДРАВЛЯЕМ\n"
|
||||||
|
"ВЫ ПОБЕДИЛИ\n"
|
||||||
|
"ЗНАЧЕНИЯ СОХРАНЕНЫ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(current_phase == TRIGGER_CAL_PHASE_THROTTLE_MIN ||
|
||||||
|
current_phase == TRIGGER_CAL_PHASE_THROTTLE_MAX) {
|
||||||
|
ssd1306_framebuffer_setpos(7, 0);
|
||||||
|
sprintf(sprintf_buf, "ЗНАЧЕНИЕ АЦП: %d", throttle_adc_value());
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
} else if(current_phase == TRIGGER_CAL_PHASE_BRAKE_MIN ||
|
||||||
|
current_phase == TRIGGER_CAL_PHASE_BRAKE_MAX) {
|
||||||
|
ssd1306_framebuffer_setpos(7, 0);
|
||||||
|
sprintf(sprintf_buf, "ЗНАЧЕНИЕ АЦП: %d", brake_adc_value());
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
} else if(current_phase == TRIGGER_CAL_PHASE_NONE ||
|
||||||
|
current_phase == TRIGGER_CAL_PHASE_FINISHED) {
|
||||||
|
ssd1306_framebuffer_setpos(6, 0);
|
||||||
|
sprintf(sprintf_buf, "УСКОРЕНИЕ %d = %d%%\n"
|
||||||
|
" ТОРМОЗ %d = %d%%",
|
||||||
|
throttle_adc_value(),
|
||||||
|
throttle_value_normalized() / 10,
|
||||||
|
brake_adc_value(),
|
||||||
|
brake_value_normalized() / 10);
|
||||||
|
ssd1306_string(sprintf_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void trigger_calibration_view_reset() {
|
||||||
|
current_phase = TRIGGER_CAL_PHASE_NONE;
|
||||||
|
}
|
19
src/views/trigger_calibration_view.h
Normal file
19
src/views/trigger_calibration_view.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef TRIGGER_CALIBRATION_VIEW_H
|
||||||
|
#define TRIGGER_CALIBRATION_VIEW_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
enum trigger_calibration_phase_t {
|
||||||
|
TRIGGER_CAL_PHASE_NONE,
|
||||||
|
TRIGGER_CAL_PHASE_THROTTLE_MIN,
|
||||||
|
TRIGGER_CAL_PHASE_THROTTLE_MAX,
|
||||||
|
TRIGGER_CAL_PHASE_BRAKE_MIN,
|
||||||
|
TRIGGER_CAL_PHASE_BRAKE_MAX,
|
||||||
|
TRIGGER_CAL_PHASE_FINISHED
|
||||||
|
};
|
||||||
|
|
||||||
|
void trigger_calibration_view_keyhandler(uint8_t e);
|
||||||
|
void trigger_calibration_view_redraw();
|
||||||
|
void trigger_calibration_view_reset();
|
||||||
|
|
||||||
|
#endif /* TRIGGER_CALIBRATION_VIEW_H */
|
Loading…
x
Reference in New Issue
Block a user