Как получить размер моей программы во время выполнения?

(Этот вопрос специфичен для Arduino Due, но, возможно, есть общий ответ на этот вопрос)

Я использую DueFlashStorage для хранения данных приложения во флэш-памяти моего Arduino Due. Это очень хорошо работает. Due имеет два флэш-блока по 256 кб каждый. По умолчанию библиотека записывает данные во второй флэш-блок. Для программы используется первый флэш-блок. Моя программа в настоящее время использует около 160k flash (как говорит мне компилятор). Это оставляет неиспользованными около 96 тысяч флэш-памяти, которые я действительно мог бы использовать для хранения большего количества данных приложений. Мне удалось настроить DueFlashStorage, чтобы разрешить запись в полный диапазон памяти, но как определить, куда безопасно писать? (Очевидно, я не должен перезаписывать какую-либо часть программы)

Я попробовал что-то вроде:

  // this returns the logical address of the first byte in flash (0x80000)
  byte* startAddr = dueFlashStorage.readAddress(0); 
  Serial.println((unsigned int)startAddr, HEX);
  int bytes = 0;
  while (bytes < 512 * 1024) // That's the size of the flash
  {
    int* data = (int*)(startAddr + bytes);
    if (*data == 0xFFFFFFFF) // empty flash is initialized with FF's
     {
       Serial.print("Free block at 0x");
       Serial.println((unsigned int)data, HEX);
     }
     else
     {
       Serial.print("Used block at 0x");
       Serial.println((unsigned int)data, HEX);
     }
    bytes+=512;
  }

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

  • Это медленно. Запуск по всему адресному пространству, чтобы найти конец программы, требует времени (и, возможно, мне не следует просто тестировать первое слово каждого блока).
  • Это невозможно воспроизвести. После того как я записал данные в обнаруженный первый свободный блок, он больше не свободен, поэтому мне нужен другой подход (например, какое-то магическое число) для обнаружения первого блока данных после сброса процессора.

Есть ли лучший способ для этого (например, в идеальном мире я мог бы использовать константу)? Я не хочу жестко кодировать запуск данных, потому что по мере роста программы (а она все еще находится в разработке) Я бы забыл настроить эту константу и booooommm....

, 👍1

Обсуждение

точное местоположение далеко вверху во flash? вы хотите, чтобы данные находились в одном и том же месте, даже если размер скетча изменится или нет?, @Juraj

Я использую некоторое (простое) управление памятью для выделения флэш-памяти для данных. Я заполняю его снизу вверх, как если бы это был файл. Таким образом, адреса, которые я использую, относятся к началу, но начало может перемещаться., @PMF


1 ответ


Лучший ответ:

4

Просто чтобы предупредить вас, я минимально протестировал это, но, похоже, это работает.

В скрипте компоновщика Due есть эти соответствующие строки, содержащие именованный раздел rom.

MEMORY
{
    rom (rx)    : ORIGIN = 0x00080000, LENGTH = 0x00080000 /* Flash, 512K */

Ниже вы увидите:

SECTIONS
{
    .text :
    {
        . = ALIGN(4);
        _sfixed = .;

_sfixed-это символ, который они создали, чтобы отметить текущее местоположение, которое в данном случае является началом раздела rom, 0x00080000. В нижней части скобки , соответствующей следующему тексту, вы увидите:

} > rom

показ этого раздела переходит в ПЗУ, поэтому . в начале раздела .text ссылается на начальный адрес раздела ПЗУ.

Если вы посмотрите рядом с последней вещью , помещенной в ПЗУ, вы увидите:

    . = ALIGN(4);
    _etext = .;

Итак, они установили там еще один символ, представляющий интерес, _etext, адрес которого должен быть байтом ROM, не используемым, или так я думал в первый раз; оказывается, это не так. Я полагаю, что вам не нужен символ start of ROM, так как это статическое значение и в начале, поэтому я проигнорирую это, но если бы вы захотели использовать его, это можно было бы сделать следующим образом для _etext.

Эти символы не имеют никакого типа, но вы можете сделать их так, как вы хотите. Если вы хотите выполнить арифметику на указателе, который приводит, имеет смысл использовать тип char, так что:

extern "C" unsigned char _etext;

Я думал, что это конец истории, мог бы просто взять адрес этого символа, но это еще не все. После обычного кода следует следующий раздел:

    .relocate : AT (_etext)
    {
        . = ALIGN(4);
        _srelocate = .;
        *(.ramfunc .ramfunc.*);
        *(.data .data.*);
        . = ALIGN(4);
        _erelocate = .;
    } > ram

Они помещают код для функции banzai (reset) (и, возможно, других вещей), которая в конечном счете попадает в оперативную память, в это место в ПЗУ. Таким образом, предположительно в стартовом коде это копируется из конца обычного программного кода в какое-то место в оперативной памяти.

Итак, здесь есть еще два интересных символа: _srelocate и _erelocate. Декларации для них:

extern "C" unsigned char _srelocate;
extern "C" unsigned char _erelocate;

Эти символы преобразуются в адреса в оперативной памяти, поэтому их нельзя использовать напрямую, но можно вычесть их адреса, чтобы получить размер изображения в ПЗУ, которое перемещается в оперативную память, что приводит к:

const unsigned char *rom_end = &_etext + (&_erelocate - &_srelocate); 
Serial.print("The end of used ROM is : 0x");
Serial.println((uintptr_t)rom_end, HEX);

Когда я компилирую и запускаю такой простой тест, как этот, и проверяю подключение последовательного монитора, я вижу вывод типа:

The end of used ROM is : 0x82FF8

Если посмотреть на файл .bin, созданный компилятором:

$ wc --bytes  build/arduino.sam.arduino_due_x_dbg/efixed.ino.bin 
12280 build/arduino.sam.arduino_due_x_dbg/efixed.ino.bin

И 0x82FF8 - 0x80000 == 12280.

Когда я шестнадцатеричный дамп флэш-памяти вокруг этого адреса, я могу видеть, как он заполняет сектор флэш-памяти с 0x00 байтами, прежде чем следующий незапрограммированный сектор флэш-памяти покажет 0xFF. Таким образом, вам нужно будет округлить это самостоятельно, и если вы хотите быть несколько безопаснее, возможно, добавить немного маржи.

Я не знаю, насколько распространена эта конфигурация в скрипте компоновщика. Вполне вероятно, что большинство скриптов компоновщика помещают туда символ, если они были немного продуманы. Но я бы не обязательно ожидал, что у него будет такое же название. Возможно, они модифицируют его, чтобы добавить больше материала после этого символа. Но сейчас, для Должного, это, кажется, работает нормально. Хотя это немного грязновато.

,

Вау, спасибо за краткий анализ. Именно то, что я искал. Кажется, это хорошо работает., @PMF

Это в любом случае относится к Arduino (и не похоже, что его набор инструментов будет обновлен в ближайшее время), так что все в порядке. Если я перенесу этот код на другой процессор (например, ESP32), мне все равно придется заменить всю флэш-часть., @PMF

Хорошая сделка. Я *надеялся* сделать методологию достаточно ясной, чтобы будущие читатели могли реагировать на изменения в сценарии компоновщика или применять то же самое мышление для другого Arduino, а не предлагать ответ типа "просто сделай это". С другой стороны, это также может игнорироваться до тепловой смерти, но я подумал, что это интересный вопрос для рассмотрения. =), @timemage

Да, я просто имел в виду тот факт, что https://github.com/arduino/ArduinoCore-sam совершенно мертв. Похоже, никого не волнуют вопросы PR или проблемы., @PMF