Недавно я получил свой флиппер и, решив написать первое приложение, столкнулся с проблемой отсутствия информации по отладке программного кода. Есть несколько статей по разработке приложений для флиппера (первое приложение и приложение HewViewer). Однако, когда я приступил к разработке и отладке приложения, опираясь на информацию из указанных статей, то столкнулся с трудностями, которые я опишу далее и укажу способы их решения.
Установка VS Code
Для начала работы с флиппером нужно скачать и установить Git и Visual Studio Code, так как для VS Code есть интеграция в официальном репозитории. После установки необходимо клонировать репозиторий в папку на диске.
После копирования среда предложит справа внизу установить дополнительные расширения для форматирования и отладки кода. Разработчики рекомендуют согласиться с этим предложением.
После клонирования репозитория необходимо выполнить в терминале две команды для настройки среды разработки. Чтобы открыть терминал необходимо нажать (Ctrl + `)(русская буква ё). Далее по очереди вписываем команды:
./fbt vscode_dist
./fbt firmware_cdb
После настройки среды необходимо добавить в файл конфигурации VS Code путь подключения библиотек чтобы их видел InteliSense в файлах приложений. Путь к файлу конфигурации ./.vscode/c_cpp_properties.json
Добавление includePath для InteliSense
"includePath": [
"${workspaceFolder}/**"
]
Файл конфигурации до изменений
{
"configurations": [
{
"name": "Win32",
"compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe",
"intelliSenseMode": "gcc-arm",
"compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
"configurationProvider": "ms-vscode.cpptools",
"cStandard": "gnu17",
"cppStandard": "c++17"
},
{
"name": "Linux",
"compilerPath": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gcc",
"intelliSenseMode": "gcc-arm",
"compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
"configurationProvider": "ms-vscode.cpptools",
"cStandard": "gnu17",
"cppStandard": "c++17"
},
{
"name": "Mac",
"compilerPath": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gcc",
"intelliSenseMode": "gcc-arm",
"compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
"configurationProvider": "ms-vscode.cpptools",
"cStandard": "gnu17",
"cppStandard": "c++17"
}
],
"version": 4
}
Файл конфигурации после изменений
{
"configurations": [
{
"name": "Win32",
"compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe",
"intelliSenseMode": "gcc-arm",
"compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
"configurationProvider": "ms-vscode.cpptools",
"cStandard": "gnu17",
"cppStandard": "c++17",
"includePath": [
"${workspaceFolder}/**"
]
},
{
"name": "Linux",
"compilerPath": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gcc",
"intelliSenseMode": "gcc-arm",
"compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
"configurationProvider": "ms-vscode.cpptools",
"cStandard": "gnu17",
"cppStandard": "c++17"
},
{
"name": "Mac",
"compilerPath": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gcc",
"intelliSenseMode": "gcc-arm",
"compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
"configurationProvider": "ms-vscode.cpptools",
"cStandard": "gnu17",
"cppStandard": "c++17"
}
],
"version": 4
}
Не забываем сохранить файл и переходим к еще одной конфигурации, которая показалась мне удобной. Расширение С/С++ переносит код с одной строчки на несколько, если длина выражения превышает 99 символов. Мне удобно чтобы код переносился при длине выражения более 150 символов, поэтому изменяем значение переменной ColumnLimit на 59 строке в файле .clang-format
Значение до изменения
ColumnLimit: 99
Значение после изменения
ColumnLimit: 150
Сохраняем конфигурацию и переходим к сборке прошивки.
Сборка Debug - версии прошивки
Поочередно вводим команды в терминал. Сначала нам нужно собрать debug версию прошивки:
./fbt
После сборки флиппер необходимо прошить и это можно сделать несколькими способами. Я рассмотрю два способа прошивки. Первый - это подключить флиппер к компьютеру через USB:
./fbt FORCE=1 flash_usb
Второй способ - это прошивка через внутрисхемный программатор, я использую ST-Link V2:
Распиновку флиппера можно посмотреть в документации на официальном сайте.
./fbt FORCE=1 flash
Сейчас во флиппере чистая прошивка, подготовленная для разработки приложений. Напишем простое приложение по примеру. Для этого необходимо создать папку для приложения, добавить иконку и манифест к приложению.
Код приложения
#include <furi.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include <input/input.h>
#include <stdlib.h>
typedef struct {
Gui* gui; //GUI Struct Pointer
ViewPort* view_port; //ViewPort Struct Pointer
} HelloWorldApp; //App Struct
static void render_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx); //UNUSED App context
canvas_clear(canvas); //Clear Screen
canvas_set_color(canvas, ColorBlack); //Set Font Color
canvas_set_font(canvas, FontKeyboard); //Set Font Type
canvas_draw_str(canvas, 0, 12, "Hello, World!"); //Draw String
}
static HelloWorldApp* hello_world_app_alloc() {
HelloWorldApp* app = malloc(sizeof(HelloWorldApp)); //Allocate memory for App
app->view_port = view_port_alloc(); //Allocate ViewPort
view_port_draw_callback_set(app->view_port, render_callback, app); //ViewPort Render Callback Init
app->gui = furi_record_open(RECORD_GUI); //Open GUI
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); //Add ViewPort to GUI
return app; //Return Allocated App Struct
}
static void hello_world_app_free(HelloWorldApp* app) {
gui_remove_view_port(app->gui, app->view_port); //Remove ViewPort from GUI
furi_record_close(RECORD_GUI); //Close GUI
view_port_free(app->view_port); //Clear ViewPort
free(app); //Clear App memory and sources
}
int32_t hello_world_app(void* p) {
UNUSED(p); //UNUSED pointer
HelloWorldApp* HelloWorld = hello_world_app_alloc(); //Allocate memory and sources for application
//Main application cycle
for(int i = 0; i < 70000000; i++) {
//Delay
}
//Main application cycle
hello_world_app_free(HelloWorld); //Deallocate memory and sources for application
return 0; //Stop application
}
Манифест приложения
App(
appid="hello_world",
name="Hello World",
apptype=FlipperAppType.EXTERNAL,
entry_point="hello_world_app",
cdefines=["APP_HELLO_WORLD"],
requires=[
"gui",
"dialogs",
],
stack_size=1 * 1024,
order=100,
fap_icon="icons/hex_10px.png",
fap_category="Misc",
fap_icon_assets="icons",
)
Описание манифеста приложения и его параметров можно найти в репозитории, однако изменение параметра order у меня не вызвало никаких видимых изменений в порядке отображения приложений в меню выбора, возможно, я неправильно понимаю его влияние, надеюсь разработчики устройства подскажут в комментариях.
Иконку для приложения я взял из приложения @QtRoS HexViewer, она лежит в репозитории и ее необходимо поместить в папку icons.
Сборка и запуск приложения
Далее собираем и запускаем приложение на устройстве:
./fbt launch_app APPSRC=./applications_user/hello_world
Если на флиппере открыто какое-либо приложение, кроме главного экрана и меню, то получим ошибку:
[ERROR] Unexpected response: Can't start, Applications application is running
Здесь, например, открыто приложение(Applications) для открытия пользовательских приложений)).
Отладка приложения
Далее попробуем отладить написанное приложение, для этого нам понадобится: внутрисхемный программатор ST-Link V2, а также расставить точки останова в нескольких местах. Пользовательские приложения .fap хранятся на SD-карте и не могут быть исполнены там, поэтому они сначала загружаются в оперативную память RAM и оттуда исполняются. Из этого следует, что мы не знаем, где в памяти окажется приложение, его адрес мы узнаем только после загрузки приложения в память. Итак, начнем отладку: сначала открываем файл загрузчика приложений ./applications/main/fap_loader/fap_loader_app.c на строке 107 ставим точку останова (F9):
FuriThread* thread = flipper_application_spawn(loader->app, NULL);
Открываем наше приложение и ставим точку останова (F9) в том месте, где нам нужно его отладить. Например, я поставлю на строчку 21, где начинается аллокация памяти для приложения:
HelloWorldApp* app = malloc(sizeof(HelloWorldApp)); //Allocate memory for App
Далее собираем и запускаем приложение, а затем собираем и заливаем прошивку:
./fbt launch_app APPSRC=./applications_user/hello_world
./fbt
Прошивка через USB:
./fbt FORCE=1 flash_usb
Прошивка через ST-Link:
./fbt FORCE=1 flash
Далее подключаем отладчик по схеме, которая находится выше, и переходим во вкладку отладки (Ctrl + Shift + D). Выбираем устройство отладки (в моем случае ST-Link) и запускаем отладку (F5). Даем флипперу запуститься с того места, где мы его остановили (F5). Запускаем свое приложение из меню флиппера Applications->Misc->Hello World. Срабатывает точка останова перед загрузкой приложения, выполняем один шаг без захода в функцию (F10), чтобы загрузить приложение.
Далее нужно в окне отладки слева внизу выключить и включить галочкой точку останова, чтобы отладчик нашел место в памяти, куда загрузилась программа:
Видно, что отладчик нашел программу в памяти и готов прерваться в следующей точке. Нажимаем (F10) и попадаем в нашу программу на точку останова, которую мы ставили на строку 21. Слева в меню видим переменные, а также регистры процессора. Отлаживать программу можно несколькими способами:
выполнить шаг без захода в функцию (F10)
выполнить шаг с заходом в функцию (F11)
выполнить код из текущей отлаживаемой функции, чтобы выйти из нее (Shift + F11)
продолжить выполнение кода до следующей точки останова (F5)
закончить отладку и отключиться (Shift + F5)
После отладки приложения убираем точки останова из загрузчика приложений fap_loader_app.c и нашего приложения. Собираем приложение и прошивку как релиз и прошиваем:
./fbt launch_app APPSRC=./applications_user/hello_world
./fbt COMPACT=1 DEBUG=0
Прошивка через USB:
./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb
Прошивка через ST-Link:
./fbt COMPACT=1 DEBUG=0 FORCE=1 flash прошиваем через ST-Link
После загрузки прошивки проверяем работоспособность приложения, на этом отладка заканчивается.
Заключение
В итоге получилось понять механизм отладки пользовательских приложений, которые хранятся на SD-карте. Полученный опыт будет полезен мне, чтобы отлаживать в будущем более сложные приложения, а также новичкам, которые только получили устройства и собираются написать свое собственное приложение.