Пишем и отлаживаем приложения для Flipper Zero

в 15:47, , рубрики: flipper, flipper zero, гаджеты, отладка, программирование микроконтроллеров

Недавно я получил свой флиппер и, решив написать первое приложение, столкнулся с проблемой отсутствия информации по отладке программного кода. Есть несколько статей по разработке приложений для флиппера (первое приложение и приложение 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:

Распиновка внутрисхемного программатора ST-Link V2
Распиновка внутрисхемного программатора ST-Link V2

А распиновку флиппера можно посмотреть в документации на официальном сайте.

Распиновка разъема GPIO флиппера
Распиновка разъема GPIO флиппера
./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-карте. Полученный опыт будет полезен мне чтобы отлаживать в будущем более сложные приложения, а так же новичкам, которые только получили устройства и собираются написать свое собственное приложение.

Автор:
danilrom

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js