" На всё есть свой конфиг. "
" Код отдельно, конфиги -отдельно "
« Лучшее программирование — конфигурирование »

В чем проблема?
В программировании микроконтроллеров при каждой новой компиляции адреса глобальных констант всегда оказываются в новых ячейках NOR Flash памяти. Из-за этого приходится каждый раз снова и снова редактировать XDF файл для утилиты TunerPRO.

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

Это особенно полезно если требуется конфигурационные и калибровочные данные выделить в отдельный интервал Flash памяти.

Потом это требование международного стандарта программирования ISO-26262.
Делается это для того, чтобы появилась возможность менять значения в этом интервале утилитой TunerPRO перед прошивкой бинаря.

Фиксированные адреса позволят вам всегда иметь один и тот же xdf файл для утилиты TunerPRO.

Определения

Компилятор - это консольная программа преобразующая исходный код в машинный, который затем может быть выполнен процессором или другой программой (виртуальной машиной). По сути компилятор это и есть программирующая программа. А весь ваш исходный Си-код это просто текстовый комментарий для компилятора.

Компоновщик - это консольная утилита, которая производит компоновку (линковку): принимает на вход один или несколько бинарных файлов с машинным кодом (*.o) плюс текстовый файл со скриптом компоновки(*.ld) и собирает из них финальный исполняемый файл для загрузки в микроконтроллер (*.elf). Собирает образ flash памяти для микропроцессора.

Решение

Чтобы дать понять компилятору и компоновщику, что надо брать в оборот особенную секцию надо внести изменения в двух местах. В настройках для компилятора и в настройках для компоновщика. Настройки для компилятора это не что иное как исходный код (сорцы). В случае с GCC есть спец слово __attribute_ в котором можно указать секцию в которую мы собираемся поместить ту или иную переменную. В коде это выглядит так.


#define CALIB_DATA_SECTION __attribute__((section (".CalibrationDataSection")))

const char CALIB_DATA_SECTION tuner_pro_text[16] = "test_data";
const uint8_t CALIB_DATA_SECTION tuner_pro_u8 = 0x12;
const uint16_t CALIB_DATA_SECTION tuner_pro_u16 = 0x1234;
const uint32_t CALIB_DATA_SECTION tuner_pro_u32 = 0x12345678;
const int8_t CALIB_DATA_SECTION tuner_pro_s8 = -8;
const int16_t CALIB_DATA_SECTION tuner_pro_s16 = -16;
const int32_t CALIB_DATA_SECTION tuner_pro_s32 = -32;
const float CALIB_DATA_SECTION tuner_pro_float = 5.5;
const double CALIB_DATA_SECTION tuner_pro_double = 7.7;

Второе изменение на до сделать в настройках компоновщика. В случае с GCC это LD файлы. Сначала надо определить кусок памяти и назвать его CALIBRATION_DATA. Задать физический адрес и размер. Я программирую STM32F401RE и на всё у меня 512kByte flash памяти. Калибровочные данный я буду размещать в самом конце доступной памяти. Буквально в последнем килобайте NOR flash-a.

/* Specify the memory areas */
MEMORY
{
    RAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 96K
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 511K
    CALIBRATION_DATA (r) : ORIGIN = 0x807FC00, LENGTH = 1K
}

Затем надо определить секцию CalibrationDataSection и прикрепить её к разделу физической памяти.

   .fill_flash :
   {
        . = ALIGN(4);
         _fill_start = .;  
        /* Fill the remaining space in FLASH with 0xFF */
        FILL(0xFF);
        . = ORIGIN(FLASH) + LENGTH(FLASH) - 1; /* Move location counter to end of FLASH */
        BYTE(0xFF); /* Ensure the very last byte is also filled */
        _fill_end = .;        
   } > FLASH
  
  /* placing my named section at given address: */
  .CalibrationDataSection :
  {
      . = ALIGN(4);
      _calib_data_start = .;  
      KEEP(*(.CalibrationDataSection)) /* keep my variable even if not referenced */
      _calib_data_end = .; 
  } > CALIBRATION_DATA 
  

Всё готово. Можно собирать и прошивать. Функция KEEP() не позволять сборщику мусора удалить секцию CalibrationDataSection даже если к ней никто не обращается.

Отладка

Чтобы проверять адреса я накропал вот вспомогательную функцию test_tuner_pro_address.

bool test_tuner_pro_address(void) {
    bool res = true;
    LOG_DEBUG(SYS, "%s():", __FUNCTION__);
    LOG_INFO(SYS, "&tuner_pro_u8:0x%p", &tuner_pro_u8);
    LOG_INFO(SYS, "&tuner_pro_u16:0x%p", &tuner_pro_u16);
    LOG_INFO(SYS, "&tuner_pro_u32:0x%p", &tuner_pro_u32);
    LOG_INFO(SYS, "&tuner_pro_s8:0x%p", &tuner_pro_s8);
    LOG_INFO(SYS, "&tuner_pro_s16:0x%p", &tuner_pro_s16);
    LOG_INFO(SYS, "&tuner_pro_s32:0x%p", &tuner_pro_s32);
    LOG_INFO(SYS, "&tuner_pro_float:0x%p", &tuner_pro_float);
    LOG_INFO(SYS, "&tuner_pro_double:0x%p", &tuner_pro_double);
    LOG_INFO(SYS, "&tuner_pro_text:0x%p", tuner_pro_text);
    return res;
}

Первая сборка проекта показывает, что глобальные переменные в самом деле прыгнули в конец памяти (адреса после 0x807FC00).

Затем я беру и пересобираю прошивку. Вызываю make all. Снова вызываю CLI команду для просмотра физических адресов констант

Как можно заметить, адреса глобальных переменных теперь как будто приклеились в те же значения. Это видно даже в бинарном файле начиная с адреса 0x807FC00.

Успех!

Итог

Удалось научиться размещать глобальные константы по фиксированным адресам в физической памяти ARM микроконтроллера. Это открывает прямую дорогу для варьирования калибровочных или конфигурационных данных утилитой TunerPRO прямо внутри bin файлика без необходимости тотальной пересборки всей прошивки.

Словарь

Акроним

Расшифровка

GCC

GNU Compiler Collection

ISO

International Organization for Standardization

XDF

extensible calibration definition format

Ссылки

Название

URL

Defining Variables at Absolute Addresses with gcc

https://mcuoneclipse.com/2012/11/01/defining-variables-at-absolute-addresses-with-gcc/

Покраска Cтека (Stack Painting)

https://habr.com/ru/articles/936766/

ARM Cortex-M: Исполнение кода из RAM памяти

https://habr.com/ru/articles/935360/

Почему Нам Нужен UART-Shell?

https://habr.com/ru/articles/694408/

Обзор утилиты TunerPro (или const volatile)

https://habr.com/ru/articles/965828/?ysclid=mi1j35cxrp269814606

Вопросы
--Как размещать переменные по жестким адресам в других компиляторах: IAR, TCC, GHS, Clang и т. п.?
--Как принудительно прописать в 0xFF байты неиспользуемой FLASH памяти?

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


  1. d_nine
    16.11.2025 17:54

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


    1. aabzel Автор
      16.11.2025 17:54

      Да. Это лучше. Называется NVRAM.

      NVRAM для микроконтроллеров / Хабр https://share.google/PdklZbkBffAkOMTkp