Как-то раз я начал работать с одним большим проектом для микроконтроллера. Как многие legacy-проекты в embedded, он привязан к старой версии IDE. В моем случае это оказался IAR 7. Эта среда обладает хорошими отладочными средствами, но работать с кодом в ней не очень комфортно: отсутствует много привычных комбинаций клавиш (Ctrl + точка, например), нет темной темы, скромная подсветка синтаксиса.

VS Code с расширениями смотрит на проект из IAR
VS Code с расширениями смотрит на проект из IAR

Со временем я стал все больше пользоваться VS Code для работы с кодом, а для проверки работоспособности переключался в IAR. Тогда я задумался, можно ли перенести основную работу над проектом в VS Code? Достоинств у редактора много: приятный настраиваемый интерфейс, интегрированный Git, расширения на любой вкус и задачу. Главный нюанс состоял в том, что проект должен собираться компилятором IAR, ибо написан он был строго под него.

Коротко о проекте: он огромен, стар (самое раннее упоминание — 2007 год), в нем много всего привязано к компилятору. Проект написан для серии приборов с небольшим количеством модификаций. Программатор J-Link, микроконтроллеры ARM Cortex-M0 и M4.

План

Какие возможности я хотел получить от новой среды разработки? Для начала:

  • сборка прошивки в VS Code;

  • работа с программатором — очистка чипа, запись готового .hex;

  • отображение ошибок компиляции в проекте.

К чему я стремился из плюсов среды:

  • удобство работы с кодом (подсветка синтаксиса, форматирование);

  • автоматизация рутинных задач через tasks;

  • работа с Git;

  • проверка кода анализатором.

Задачи в VS Code

Небольшой ликбез. Задачи в VS Code — это механизм, который позволяет запускать внешние скрипты и программы прямо из редактора. С их помощью можно автоматизировать некоторые действия, например прошивку микроконтроллера, удаление/создание папок и т. п.

Описываются задачи в файле .VS Code/tasks.json и выглядят примерно так:

{
    "label": "Flash: Erase chip",
    "type": "shell",
    "command": "JFlash.exe",
    "args": [
        "-openprj${workspaceFolder}\\_scripts\\J-flash.jflash",
        "-erasechip",
        "-exit"
    ],
    "group": "build",
    "presentation": {
        "reveal": "always",
    }
}

Эта задача запускает SEGGER J-Flash, в нем открывает проект J-flash, стирает чип и закрывает программу. Подробнее про задачи можно почитать в блоге VS Code.

Сборка проекта

Для начала рассмотрим сборку через расширения для VS Code. IAR Build и IAR Debug позволяют перенести работу с прошивкой в VS Code (сборку и отладку), но увы, минимальная поддерживаемая версия IAR — восьмая, так что это не мой вариант.

Чуть более интересный способ — это сборка через IarBuild.exe, поставляемого вместе с IAR. Эта утилита позволяет скомпилировать проект без графической IDE и работает довольно просто. Ее необходимо вызвать, передав ей одну из команд:

  • -make пересобирает, что изменилось;

  • -build — уже полная пересборка проекта;

  • -clean удаляет промежуточные и выходные файлы.

Чтобы запустить сборку проекта, из bat-скрипта вызываем:

"%IAR_DIR%\common\bin\IarBuild.exe" "%REPO_DIR%\name.ewp" -build %CONFIG% -log all

Здесь:

  • IAR_DIR — адрес папки IAR,

  • REPO_DIR — папка с проектом,

  • name.ewp — файл проекта,

  • CONFIG — конфигурация прибора по умолчанию (Release или Debug), а в моем случае еще и конфигурации под каждый вид прибора,

  • log all — вывод сообщений сборки.

Далее в скрипте можно добавить действия с готовой прошивкой — например, прописать заводские настройки или объединить файл с bootloader.

Но вернемся к VS Code. Для компиляции прошивки надо запустить bat-файл, указав необходимую модификацию. Каждой модификации прибора соответствует отдельная конфигурация сборки в IAR. Я создал одноименный список приборов в tasks.json:

"inputs": [
{
    "id": "device",
    "type": "pickString",
    "description": "Выберите прибор для сборки",
    "options": [
        "Device1",
        "Device2",
        "Device3"
    ]
}
]

Вот задача, которая в начале дает выбор модификации, а затем запускает сборку необходимой конфигурации:

{
    "label": "IAR: Build Device",
    "type": "shell",
    "command": "${workspaceFolder}\\build_device.bat",
    "args": [
        "${input:device}"
    ],
    "options": {
        "cwd": "${workspaceFolder}",
        "shell": {
            "executable": "cmd.exe",
            "args": ["/d", "/c"]
        }
    },
    "group": "build",
    "problemMatcher": {
        "owner": "IAR-build",
        "fileLocation": "absolute",
        "source": "IAR",
        "pattern": {
            "regexp": "^(.+)\\((\\d+)\\)\\s*:\\s*(Warning|Error)\\[[^\\]]+\\]:\\s*(.*)$",
            "file": 1,
            "line": 2,
            "severity": 3,
            "message": 4
        }
    }
}

Тут интересен момент с problemMatcher: при компиляции все ошибки передаются в VS Code и будут отображаться в окне problems. Очень удобно. В панели Problems сразу появилась сотня предупреждений. Добавляются предупреждения и ошибки сборки с указанием места, так что можно все найти и поправить.

Раз уж зашла речь об ошибках, то поговорим и про IntelliSense. По умолчанию VS Code использует IntelliSense из расширения C/C++ для анализа кода, и он поначалу выдавал огромное количество ошибок, так как не мог найти те или иные элементы. Так как меня интересуют только ошибки из IAR, я отключил отображение ошибок от IntelliSense, создав файл settings.json с таким содержанием:

{
    "C_Cpp.errorSquiggles": "disabled",
    "C_Cpp.intelliSenseEngine": "default"
}

При этом сам IntelliSense я оставил включенным, поскольку он обеспечивает автодополнение и навигацию по коду. Для корректной работы этих функций необходимо настроить пути к заголовочным файлам. Чтобы не писать их вручную, попросил нейросеть сгенерировать скрипт на Python, который бы создавал файл c_cpp_properties.json со списком путей, импортированных из проекта IAR:

import json
import sys
import xml.etree.ElementTree as ET
from pathlib import Path

IAR_DIR = r"F:\prog\IAR\arm"

project = Path(sys.argv[1]).resolve()
config_name = sys.argv[2]

# Открываем проект и ищем конфигурацию
root = ET.parse(project).getroot()

config = next(
    (c for c in root.findall(".//configuration")
     if c.findtext("name") == config_name),
    None
)

if config is None:
    sys.exit(f"Configuration not found: {config_name}")

# Извлекаем пути заголовочных файлов
includes = []

for option in config.findall(".//option"):
    if option.findtext("name") == "CCIncludePath2":
        for state in option.findall("state"):
            if state.text:
                path = state.text.strip()
                path = path.replace("$PROJ_DIR$", str(project.parent))
                path = path.replace("$TOOLKIT_DIR$", IAR_DIR)
                includes.append(Path(path).resolve().as_posix())

# Формируем настройки VS Code
data = {
    "configurations": [{
        "name": config_name,
        "includePath": list(dict.fromkeys(includes)),
        "intelliSenseMode": "gcc-arm",
        "compilerPath": "",
        "cStandard": "c99",
        "cppStandard": "c++98"
    }],
    "version": 4
}

# Сохраняем файл
output = Path(__file__).resolve().parent.parent / ".vscode" / "c_cpp_properties.json"
output.parent.mkdir(exist_ok=True)
output.write_text(json.dumps(data, indent=4), encoding="utf-8")
print(f"Created: {output}")
print(f"Include paths: {len(includes)}")

Кнопку сбора прошивки и отображение ошибок разобрали, идем дальше.

Прошивка контроллера

Следующий пункт автоматизации — работа с программатором J-Link через задачи. Для J-Flash я создал проект с моим контроллером, всеми выставленными настройками и написал необходимые мне задачи: одна для прошивки, вторая для стирания чипа. 

{   // прошивка
    "label": "Flash: artifacts",
    "type": "shell",
    "command": "JFlash.exe",
    "args": [
        "-openprj${workspaceFolder}\\_scripts\\J-flash.jflash",
        "-open${workspaceFolder}\\programm.hex",
        "-auto",
        "-exit"
    ],
    "group": "build",
    "problemMatcher": []
},
{   // стирание
    "label": "Flash: Erase chip",
    "type": "shell",
    "command": "JFlash.exe",
    "args": [
        "-openprj${workspaceFolder}\\_scripts\\J-flash.jflash",
        "-erasechip",
        "-exit"
    ],
    "group": "build",
    "presentation": {
        "reveal": "always",
    }
}

Все задачи пишут результат своего выполнения в терминал. Можно отдельно прописать, что если задача выполнена успешно, то терминал автоматически закрывается, но мне это показалось неудобным. Отвлечешься и неясно, а запускал ли ты задачу вообще.

Отладка

Внимательный читатель наверняка уже сидит с вопросом: а как же отладка проекта? Ведь написание и сборка проекта — это далеко не всё, проект надо еще и дебажить! Тут, увы, результатами похвастаться не получится: последний камень преткновения перенести не удалось. Расширение для отладки не подошло из-за разницы в версиях IAR. Единственное, чем я пользуюсь, это отладка через UART, а вот за полноценной отладкой приходится идти обратно в IAR.

Полезные расширения

План «минимум» закрыл, собрать прошивку и перепрошить контроллер могу, не выходя из VS Code. Теперь рассмотрим расширения, которые мне помогают в работе:

Code Spell Checker — проверяет правильность написания слов. Бывало, что после запушенного на сервер изменения вы находили опечатку в своем комментарии? С этим расширением таких ситуаций больше не будет. Одна беда: постоянно ругается на опечатки, которые внесли другие программисты в проект. 

Подсвечивает место ошибки, по нажатию «Ctrl + .» показывает варианты замены, по повторному нажатию исправляет на первое значение.

Также есть отдельное окошко, в котором показаны все ошибки в проверенных файлах. Имеется вариант приложения для русского языка.

Trailing Spaces — показывает лишние пробелы, например, в конце строки. Пишем чище.

VS Code-icons — меняет иконки папок и файлов в Explorer, становится визуально проще ориентироваться в проекте.

Serial monitor — позволяет работать с COM-портом в отдельном окошке, я использую его для отладки по UART.

Doxygen Documentation Generator — расширение для генерации шаблона описания функции. Создает ту самую шапку:

/********************************************************************************
 * @brief тестирует исправность АЦП
 *
 * @param num_channel - номер канала
 * @return uint16_t значение на канале
 ********************************************************************************/
uint16_t ADC_test (uint8_t num_channel)
{

Преимущества использования VS Code

Теперь рассмотрю коротко, что я получил, начав работать с кодом в VS Code:

  • Настраиваемую подсветку синтаксиса и темную тему. Честно говоря, даже одного этого было бы достаточно для того, чтобы начать в этом разбираться.

  • Форматирование кода.

  • Удобный поиск по всему проекту.

  • Структура проекта соответствует структуре папок. В IAR расположение файлов в дереве проекта может не иметь ничего общего с реальным расположением.

  • Удобно открывать среду: зашел в папку проекта, нажал правой кнопкой мыши — «открыть с помощью code» — и всё, проект открыт.

  • Можно посмотреть почти любой файл в IDE, разве что с офисными документами не работает.

  • В любом участке проекта я вижу, кто именно и как давно написал тот или иной код. Особенно полезно в работе с командой.

  • Подключил анализатор кода — cppcheck. После анализа он выдал тысячу предупреждений. Конечно, это не все места с ошибками, но рассмотреть стоит. Полезно для поиска потенциальных ошибок.

Выводы

Стал ли я все время работать в VS Code, забыв, как выглядит ярлык IAR? Увы, нет. Бо́льшая часть отладки проекта все так же происходит в IAR. Но вся работа по написанию кода и работа с Git теперь перенесена в приятную среду. Сейчас я не перепроверяю орфографию комментариев, стало заметно легче ориентироваться в коде, а часть задач решается через отладку по UART прямо в VS Code. Считаю, что по большей части моя изначальная задача выполнена.

Далее я попытаюсь прикрутить к проекту хоть какое-нибудь автоматическое тестирование, чтобы меньше работать ?

Ссылки

Комментарии (0)