Оптимизируем производительность Teensy 4.1 при разработке в NXP MCUXpresso

от автора

В предыдущей статье мы выяснили, что производительность контроллера MIMXRT1062, применённого на плате Teensy 4.1 можно существенно поднять, перераспределив внутреннюю память по сильносвязанным шинам. Для этого мы воспользовались механизмом FlexRAM. В библиотеках от Teensyduino всё уже сделано за нас, но в данном цикле статей я рассказываю, как вести разработку в среде MCUXpresso от NXP. Мы произвели необходимые доработки проекта, и вот уже данные ложатся в достаточно потолстевший банк памяти DTCM:

То же самое текстом

Memory region         Used Size  Region Size  %age Used      BOARD_FLASH:       32400 B         8 MB      0.39%         SRAM_DTC:       22420 B       256 KB      8.55%         SRAM_ITC:          0 GB       256 KB      0.00%          SRAM_OC:          0 GB       512 KB      0.00% 

Но банк ITCM, где должен быть код, пуст. Сегодня мы научимся настраивать среду разработки для переноса большей части кода в него.

Предыдущие статьи цикла:

  1. Запускаем программу созданную в NXP MCUXpresso на плате Teensy 4.1
  2. Teensy 4.1 через MCUXpresso. Часть 2. Осваиваем GPIO и UART
  3. Настраиваем сильносвязанные шины контроллера на плате Teensy 4.1

Зачем это надо

Для тех, кто начал читать не с первой статьи цикла, а с этой, бегло поясню, чего мы добиваемся. В наше время достаточно большую популярность набирает технология XIP – eXecute In Place. Код хранится в SPI-флэшке (пусть это даже QSPI-флэшка, всё равно она последовательная). И он подгружается по мере необходимость в кэш размером 32 килобайта. Размер страницы, если честно, я в описании на контроллер не нашёл, но размер всего кэша – однозначно 32 килобайта.

Какие весёлые вещи ждут нас при кэш-промахах, я писал в этой статье . Процессор летит-летит на частоте 600 МГц, вдруг БАХ! Начинает неспешно грузить код из флэшки, работающей на частоте десятки мегагерц (ну точно не более, чем 104 МГц), причём не байтами, а по полбайта за такт… Но во второй части той статьи я показал, что есть такая замечательная штука, как сильносвязанная шина, посетовав на то, что такое невозможно в известных мне микроконтроллерах. Но теперь мне известен тот, в котором всё есть!

Но XIP — это ещё не всё! Кроме безумно медленной шины SPI, есть ещё и системная шина, которая хоть и работает быстро, но даёт некоторую латентность. Подробнее про эту проблему я писал в статье про DMA. В рассматриваемом же микроконтроллере мы можем размещать код и данные в памяти, доступной через сильносвязанную шину, не имеющую такой латентности!

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

Как мы будем работать

Итак, вот наши настройки карты памяти, которые мы сделали в прошлый раз:

Нам надо поместить код в секцию SRAM_ITC, имеющую псевдоним RAM2. Давайте осмотримся в поисках настроек, позволяющих размещать всё в памяти. В свойствах проекта идём в настройки компоновщика C/C++ Build->Settings, там – MCU Linker->Manage Linker Script. В целом, секции .data и .bss и так размещены в SRAM_DTC (выделено зелёным). Я, на всякий случай, ещё поместил глобальные переменные туда же.

С данными разобрались. А как указать целевую секцию для кода? Есть желание поставить галочку Plain load Image и выбрать сегмент SRAM_ITC. Даже всё ляжет, куда следует… Это видно при осмотре получившегося ассемблерного файла. Только лечь-то оно ляжет, а работать не будет. Потому что эта галочка для другого контроллера, у которого две флэшки. Там ничего копировать не нужно. Код прошьётся и в ту, и в другую. А нам надо, чтобы загрузчик сначала скопировал код в ОЗУ и только потом передал ему управление. Что делать?

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

Простой, но хлопотный способ

Давайте сначала быстренько проверим первый. Он описан в разделе 17.3.5 документа. Вот хочу я разместить некую функцию в секции RAM2. Для этого в исходный код я добавляю строку:

#include <cr_section_macros.h> 

После чего перед теми функциями, которые хочу разместить в ОЗУ, подключённом к сильносвязанной шине инструкций, ставлю префикс __RAMFUNC(RAM2). Например:

То же самое текстом.

__RAMFUNC(RAM2) usb_status_t USB_DeviceCdcVcomCallback(class_handle_t handle, uint32_t event, void *param) 

Всё! Эти функции будут скомпонованы для исполнения в сильно связанном ОЗУ, и startup код обеспечит их предварительный перенос туда. Правда, сколько будет функций, столько раз я должен добавить этот префикс. Но я же предупреждал, что способ хлопотный!

Универсальный способ, требующий подготовки проекта

Правильное решение – правка скрипта компоновщика (он же Linker Script, он же ld-файл). Но с одной стороны, этот файл достаточно сложен (с грустью я вспоминаю Кейловский вариант), а с другой – он автоматически пересоздаётся при каждой сборке проекта. Нет, можно поставить флажок, чтобы Эклипса перестала это делать, но это же неправильно! Если он формируется каждый раз, значит, это было придумано неспроста. К счастью, в описании MCUXpresso есть решение и на этот случай. Смотрим раздел 17.15.1. Оказывается, этот скрипт формируется на основе шаблонов (файлов с расширением *.ldt). Вон их сколько!

Все они живут в каталоге со средой разработки. Править мы их не будем. Но если заглянем внутрь, то найдём, что структура у них иерархическая. Одни шаблоны включают в себя другие:

    .text : ALIGN(${text_align})     { <#include "extrasections_text.ldt"> <#include "main_text.ldt" > <#include "extrasections_rodata.ldt"> <#include "freertos_debugconfig.ldt"> <#include "main_rodata.ldt" > 

Если включаемые файлы присутствуют в каталоге проекта, будут включены они. При отсутствии — будут взяты варианты по умолчанию. Поэтому мы можем дорабатывать часть шаблонов. Кое-что на этот счёт описано в документе на MCUXpresso. Там даже есть описание, какие файлы и как следует доработать.

Правда, если действовать строго по документу, ничего не получится. Во-первых, он почему-то не отвечает на вопрос, где должны располагаться эти шаблоны. Пришлось порыскать по просторам сети в поисках более точного примера. А во-вторых, получившийся код не запускается. Поэтому я оставил в ПЗУ чуть больше функций которые производят инициализацию. Итого, у меня получилось следующее:

Создаём папку linkscripts:

В ней создаём три файла (когда вы освоите технологию, этот каталог можно будет просто копировать из проекта в проект хоть тем же FAR-ом, но сейчас создаём всё из среды разработки):

Первый — c именем main_text.ldt и содержимым:

*startup_*.o (.text.*)  *system_*.o (.text.*)   *(.text.main)   *(.text.__main)  *fsl_cache.o (.text.*)  *fsl_clock.o (.text.*)  *fsl_clock.o (.text.*)  

Этот файл задаёт источники кода, который должен остаться в ПЗУ. Именно с ним пришлось творчески поработать… Он существенно отличается от описанного в документе.

Второй – с именем main_rodata.ldt и содержимым:

*startup_*.o (.rodata .rodata.* .constdata .constdata.*) *system_*.o (.rodata .rodata.* .constdata .constdata.*) . = ALIGN(${text_align}); 

Третий файл забавный. Дело в том, что, с точки зрения типов секций, код должен попасть в секцию данных. Но таких секций много. Как из всех секций данных выбрать именно RAM2? Оказывается, для этого в шаблоне есть специальный макрос. Итого, получаем файл с именем data.ldt и содержимым:

<#if memory.alias=="RAM2">   *(.text*)   *(.rodata .rodata.* .constdata .constdata.*)   . = ALIGN(${text_align});   </#if>   *(.data.$${memory.alias}*)   *(.data.$${memory.name}*) 

Собираем проект, видим, что теперь секция SRAM_ITC имеет существенный размер:

То же самое текстом.

Memory region         Used Size  Region Size  %age Used      BOARD_FLASH:       46692 B         8 MB      0.56%         SRAM_DTC:       21640 B       512 KB     16.51%         SRAM_ITC:       30984 B       512 KB     23.64%          SRAM_OC:          0 GB       768 KB      0.00%      BOARD_SDRAM:          0 GB        30 MB      0.00%    NCACHE_REGION:          0 GB         2 MB      0.00% Finished building target: evkmimxrt1060_dev_cdc_vcom_bm.axf 

Если посмотреть на map-файл или сгенерить asm-файл, то будет видно, что основной код действительно переехал в ОЗУ, но при старте он туда переносится из ПЗУ силами стартового кода. Задача выполнена!

Заключение

Мы освоили процесс разработки кода для платы Teensy 4.1 в среде MCUXpresso с использованием фирменных библиотек от NXP. Теперь можно приступать к их осмотру.

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

Однако иногда всё-таки хочется поставить точку останова и осмотреть переменные, не вписывая в код отладочных выводов через UART. При работе с Teensy я воспользовался проектом с GitHub, который хоть как-то позволяет это сделать. Если мне удастся малой кровью перенести его в эту среду, то в следующий раз я расскажу о том, как это делается. Правда, решение очень страшное. Но всяк лучше, чем никакого. В общем, я буду пробовать. Но из опыта скажу, что с NXP-шными библиотеками, в принципе, всё ясно и при визуальном осмотре. Они довольно красиво написаны.


ссылка на оригинал статьи https://habr.com/ru/articles/574878/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *