Недавно я получил свой флиппер и, решив написать первое приложение, столкнулся с проблемой отсутствия информации по отладке программного кода. Есть несколько статей по разработке приложений для флиппера (первое приложение и приложение HewViewer). Однако, когда я приступил к разработке и отладке приложения, опираясь на информацию из указанных статей, то столкнулся с трудностями, которые я опишу далее и укажу способы их решения.

Установка VS Code
Для начала работы с флиппером нужно скачать и установить Git и Visual Studio Code, так как для VS Code есть интеграция в официальном репозитории. После установки необходимо клонировать репозиторий в папку на диске.

После копирования среда предложит справа внизу установить дополнительные расширения для форматирования и отладки кода. Разработчики рекомендуют согласиться с этим предложением.

После клонирования репозитория необходимо выполнить в терминале две команды для настройки среды разработки. Чтобы открыть терминал необходимо нажать (Ctrl + `)(русская буква ё). Далее по очереди вписываем команды:
./fbt vscode_dist
./fbt firmware_cdb
После настройки среды необходимо добавить в файл конфигурации VS Code путь подключения библиотек чтобы их видел InteliSense в файлах приложений. Путь к файлу конфигурации ./.vscode/c_cpp_properties.jsonUPD: Как подсказал @hedgerв комментариях: "Не надо так делать, IntelliSense работает с compile_commands.json, который генерится при сборке полной прошивки. Он оттуда возьмёт все необходимые пути до инклюдов (и не добавит лишние/приватные, как это делает приведённый пример). "
Добавление includePath для InteliSense
к
Файл конфигурации до изменений
{ "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-карте. Полученный опыт будет полезен мне, чтобы отлаживать в будущем более сложные приложения, а также новичкам, которые только получили устройства и собираются написать свое собственное приложение.
