В предыдущей статье мы выяснили, что производительность контроллера 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-шными библиотеками, в принципе, всё ясно и при визуальном осмотре. Они довольно красиво написаны.

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