" На всё есть свой конфиг. "
" Код отдельно, конфиги -отдельно "
« Лучшее программирование — конфигурирование »
В чем проблема?
В программировании микроконтроллеров при каждой новой компиляции адреса глобальных констант всегда оказываются в новых ячейках 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) |
|
ARM Cortex-M: Исполнение кода из RAM памяти |
|
Почему Нам Нужен UART-Shell? |
|
Обзор утилиты TunerPro (или const volatile) |
https://habr.com/ru/articles/965828/?ysclid=mi1j35cxrp269814606 |
Вопросы
--Как размещать переменные по жестким адресам в других компиляторах: IAR, TCC, GHS, Clang и т. п.?
--Как принудительно прописать в 0xFF байты неиспользуемой FLASH памяти?
d_nine
Не лучше выделить пару страниц под простую фс и заливать данные подобного рода уже после прошивки? Таким образом при проверке целостности прошивки у вас будут одинаковые контрольные суммы для каждого экземпляра устройства, что может быть критично в контроле при серийном производстве? И так же можно отдельно контролировать целостность данных в фс, отдельно хранить "слепки" фс для каждого устройства при необходимости восстановления/разбора инцидентов, легче организовать шифрование данных.
aabzel Автор
Да. Это лучше. Называется NVRAM.
NVRAM для микроконтроллеров / Хабр https://share.google/PdklZbkBffAkOMTkp