В предыдущих статьях мы освоили внедрение примеров из NXP-шной среды разработки MCUXpresso в плату Teensy 4.1. Однако у тех примеров, с которыми мне довелось повозиться (а я работал с USB-примерами), есть один существенный (с моей точки зрения, разумеется) недостаток. Все они написаны на языке Си. Ну, то есть, «на чистых Сях», если на жаргоне. А вот оригинальная библиотека от Teensy была написана на С++. И я являюсь сторонником именно этого языка. Не могу жить без честных классов, виртуальных функций и некоторых других средств, которых нет в чистых Сях.



Когда я программирую в среде разработки Keil, чтобы сменить язык, достаточно переключить его в свойствах конкретного исходного файла. В цикле статей про NIOS II я просто переключал язык, изменив расширение файла с *.c на *.cpp. (Со всеми деталями это показано тут ; не как новинка, а как рутина — тут). Увы, в нашем случае так не получится. И количество действий, которое следует совершить, тянет на небольшую статью. Вот её я сейчас и напишу. Итак, давайте разбираться, что там к чему…


В целом, существует замечательная англоязычная статья, которая красиво расписывает все необходимые действия. Вот она From C to C++: Converting Eclipse C Projects — DZone IoT. Как я уже привык, представленного в ней объёма данных для нашего конкретного случая недостаточно. Нам придётся сделать чуть больше. Но зато будет весело!

Раз — Добавляем натуру


Почему у нас ничего не работает после смены расширения файла – USB-шные примеры от NXP не используют «натуру C++». Идём сюда:



И нажимаем Add. В реальной жизни вам потребуется перед этим кое-что сохранить, но я оставлю эту фразу для случая, когда вы зайдёте сюда уже как в справочник. Вы её увидите и всё вспомните. А сейчас, когда вы читаете текст как повествование, просто нажимайте Add и добавляйте натуру С++.



Дальше автор англоязычной статьи велит переименовать startup-код из startup_mimxrt1062.c в startup_mimxrt1062.cpp. Для этого наводимся на файл:



Дальше правая кнопка «мыши» и пункт меню Rename. Это надо, для того чтобы в нашем С++ коде вызывались конструкторы у всех глобально объявленных объектов, что обеспечивается следующими строками:
#if defined (__cplusplus)
    //
    // Call C++ library initialisation
    //
    __libc_init_array();
#endif

Всё! Мы выполнили указания из оригинальной статьи! Пробуем собрать код… Сначала фальстарт:



Тут всё просто. Делаем Clean Project и снова пытаемся собрать код…

Два — Восстанавливаем автоматику для формирования скрипта компоновщика


Нам вываливают вот такую простыню с ошибками:



Видно, что дело происходит на этапе компоновки. Почему он не видит эти функции – я не знаю до сих пор. Некогда разбираться было. Нет, я честно порыскал по Гуглю, узнал, что проблемы возникают у многих, но в совершенно разных случаях. Дальше идут дебаты, ещё дальше – невнятное лечение. Когда мне надоело разбираться, я промотал список в конец, в надежде найти что-то более осмысленное, чтобы не просто сидеть и пялиться в экран. И нашёл более-менее понятные строки.



Константа __base​NCACHE_REGION соответствует скрипту компоновщика, причём автоматически формируемой его части. Ну и прекрасно, идём в настройки… И видим удивительную вещь:



Ещё три дня назад мне бы это ничего не сказало, но я слишком много экспериментировал со скриптом компоновщика, работая над предыдущей статьёй. Я точно помню, что должна стоять галка Manage Linker Script. Ставим её. Опытный глаз говорит, что надо ещё вручную вбить размер кучи и стека, чтобы всё стало, как раньше. Ещё там были Extra Linker Script input sections. Можно и их вбить по образу и подобию того, что было раньше, но сегодняшняя статья заработает и без них. А правок нам предстоит ещё много, не буду прямо сейчас тратить силы.



Ну что, собираем?

Три — Переименовываем файл с функцией main()


Собирается без ошибок. Но и не работает. Я решил переименовать файл virtual_com.c в virtual_com.cpp – именно в этом файле лежит функция main():



Собираем…

Четыре — Копируем настройки из сишной натуры в плюсовую


Получаем ошибки такого вида:



Но ведь раньше-то всё находилось! Идём в настройки каталогов поиска заголовочных файлов, и видим:



А где всё? Смотрим вариант для Сишной культуры:



В общем, все-все-все списки, которые в сишной культуре заполнены – в плюсовой пусты. Ну, разве что в одном сиротливо болтаются три строчки вместо кучи. И наша задача – выделить в сишном списке первую строчку, потом – с шифтом последнюю:



Дальше – Ctrl+C, после чего – Ctrl+V в плюсовом списке. И так – для всех списков, что встретятся… Странно, сегодня встретилось всего два списка. Когда осваивал – казалось, что их много. Ну, тем лучше… Собираем!

Пять — Добавляем extern “C”, где следует


И снова ошибка компоновщика:



Но приятно, что нам сообщают, в каком файле был вызов. Сколько мне крови попила Visual Studio, которая говорила, какой символ не найден, но не говорила, какой файл его хочет!

Очевидно, что перед нами несовместимость именования. Большинство заголовочных файлов примера содержат такие строки:
#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

…

#if defined(__cplusplus)
}
#endif /* __cplusplus */

А в файле usb_device_descriptor.h, где объявлена та проблемная функция USB_DeviceSetSpeed(), разработчики из NXP такое добавить забыли. И вот результат! Так что добавим подобное и в него. Первые три строки – почти в начало, последние – почти в конец. Собираем!!!

Шесть — Добавляем секретный блок


Запускаем – строчки в терминал выводятся:



Но виртуальный COM-порт в менеджере устройств не появляется. То есть чего-то не хватает. Разглядывая настройки, я заметил вот такой сиротливый блок:



Причём блока MCU C Linker нет, скопировать неоткуда! Придётся скопировать из любого другого проекта, в котором плюсовая культура не добавлялась.

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




Собираем, запускаем…

Семь — Нужно больше extern “C”


Та же картинка. Не работает! И ведь возможности отладки нет, потому что на плате отсутствует разъём JTAG! Как найти, кто виноват? Только из прошлого своего опыта я понял, что надо смотреть вектора прерываний внутри cpp-файла. И они нашлись:



Переписываем так:
extern "C"
void USB_OTG1_IRQHandler(void)
{
    USB_DeviceEhciIsrFunction(s_cdcVcom.deviceHandle);
}

extern "C"
void USB_OTG2_IRQHandler(void)
{
    USB_DeviceEhciIsrFunction(s_cdcVcom.deviceHandle);
}

Собираем, прошиваем…



РАБОТАЕТ! С примерами от MCUXPresso – всё. Но на всякий случай, сделаю ещё один исключительно справочный раздел для более серьёзной работы.

Проблемы одновременной работы С++ и сильносвязанных шин


Если вы попробуете применить знания, почерпнутые из предыдущих статей, чтобы разместить полученный код на языке C++ в ОЗУ, доступном через сильносвязанную шину, вас ждёт разочарование. Компилятор отработает без проблем, а вот компоновщик выкатит море ошибок примерно такого вида:



Черновик статьи был написан весной и долго пролежал в столе. Проблема была найдена летом, когда уже стало ясно, что тема не вызывает особого интереса у читателей. Поэтому я не буду рассказывать обо всём в подробностях, а только опишу выводы для тех, кто будет пользоваться полученными знаниями на практике.

Причина бед в том, что часть библиотек поставляется не в виде исходных кодов, а в виде объектных файлов. Поэтому если там применены команды CALL и/или JMP, заменить их ни на что уже нельзя. А область ПЗУ для XIP отстоит от области ОЗУ для сильносвязанной шины инструкций ой как сильно:



Применённым в объектных файлах командам не хватает допустимой длины для перехода…

Первое, что надо сделать – отключить функционал RTTI и обработки исключений. Это пишут на многих форумах. Сам я этим функционалом никогда не пользуюсь, мне его лишиться не страшно. На всякий случай я провёл опрос среди коллег, сильно ли они расстроятся от его потери. Все они в один голос заявили, что при работе с микроконтроллерами, его отключать просто необходимо, потому что он расходует память программ и стек так, как можно позволить себе только в больших системах! В общем, добавляем в опции компилятора следующие:
-fno-exceptions -fno-rtti

В свойствах проекта эти магические заклинания надо вставить сюда:



Ошибок станет меньше, но они не исчезнут вовсе. Гипнотизируя оставшиеся сообщения об ошибках и проводя массу экспериментов, мне удалось выявить, что надо добавить ещё один шаблон для скрипта компоновщика (рядом с теми, которые мы создавали в предыдущей статье). Имя файла: exdata.ldt, его соджержимое:
    /*
     * for exception handling/unwind - some Newlib functions (in common
     * with C++ and STDC++) use this.
     */
    .ARM.extab : ALIGN(${text_align})
    {
        *(.ARM.extab* .gnu.linkonce.armextab.*)
    } > SRAM_ITC AT> ${CODE}

    .ARM.exidx : ALIGN(${text_align})
    {
        __exidx_start = .;
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
        __exidx_end = .;
    } > SRAM_ITC AT> ${CODE}

Заключение


Мы научились перетаскивать реальные примеры от NXP с языка Си на возможность вставки модулей на языке С++. Для этого нам пришлось пройти семь шагов. Многовато! Но вполне терпимо. Я уверен, что можно написать программу, которая выполнит самую неприятную часть шагов за нас, автоматически дорабатывая файл проекта, так как это обычный XML-файл. Но Заказчик не проявил особого интереса к работе с C++, поэтому тема автоматической правки не была включена в задачи по проекту. А потом меня засосали другие работы.

Так или иначе, а теперь мы можем вставлять в наш проект как минимум какие-то наработки из оригинальной библиотеки Teensy 4.1, а как максимум – свои удобные классы, которые наверняка напишем в будущем.

Послесловие


Первые шесть статей для цикла были написаны в апреле-мае, одновременно с освоением новой техники и созданием официальных отчётов. Потом было отвлечение на другие задачи, дальше – небольшой затык с переносом GDB Stub с Ардуиновской идеологии на рельсы MCUXPresso. А потом первая из заготовок была опубликована, и стало ясно, что вообще тема Teensy 4.1 для штатных библиотек NXP мало кому интересна.

Как говаривал мой научный руководитель: «Ненужная работа хуже пьянства», поэтому дальше я просто перестал делать русскоязычные развёрнутые описания. Заказчику было достаточно коротких англоязычных отчётов. Так что тема обрывается из-за отсутствия спроса.

Но в целом, я уже показал, что Teensy 4.1 содержит достаточно мощный контроллер. А малое количество штатных библиотек Teensyduino – не беда. С этой платой вполне можно использовать всю мощь фирменных библиотек от NXP. Как ими пользоваться – в целом ясно из уже описанных примеров. Но ещё раз отмечаю, что у этой платы нет JTAG-разъёма. Соответствующие ножки у контроллера есть, они даже по плате идут для внутренних целей, но на разъём – не выходят. Так что покупать или нет такую плату – каждый решает сам. JTAGа нет, но контроллер о-о-о-о-о-о-очень мощный! И библиотеки от NXP – классные!

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