Прошивка встроенных систем: лучшие практики инициализации массивов C из внешних файлов данных и сигнальных волн
Научитесь инициализировать массивы C с помощью внешних текстовых файлов
Это руководство объясняет, как заполнять массивы в программе на C данными, хранящимися в отдельных текстовых файлах. Значения не встраиваются в исходный код, а загружаются на этапе компиляции. Охватываются одномерные и многомерные массивы, показано, как управлять размещением массива в ОЗУ или энергонезависимой памяти, и как выбирать между несколькими файлами данных для инициализации.
Примеры ориентированы на 32-битный микроконтроллер ARM и компилятор GCC. Все фрагменты используют стандартный C и работают с этим компилятором.
Основы инициализации массива
При объявлении массива в C можно указать список значений — инициализаторов — в фигурных скобках. Например:

Если размер в скобках опущен, компилятор выводит его из числа инициализаторов. Если массив объявлен больше списка инициализации, оставшиеся элементы обнуляются. Больше инициализаторов, чем объявленный размер — ошибка компиляции.
Пробелы
Инициализаторы разделяются запятыми; между ними можно свободно добавлять пробелы, табуляции, переводы строк и другие пробельные символы. В C операторы могут занимать несколько строк, что особенно удобно для больших массивов.

Инициализация массива из файла
Препроцессор C может включать произвольные текстовые файлы директивой #include. Эта возможность не ограничена заголовочными или исходными файлами — подходит любой простой текстовый файл. Файл с числами, запятыми и пробелами может использоваться для инициализации массива:

Включаемый файл должен быть чистым текстом — без форматирования, заголовков или других артефактов. Пример, созданный в Блокноте Windows:

После компиляции массив в памяти выглядит так, как показано на скриншоте отладчика. В данном случае массив находится в ОЗУ, что видно по высоким адресам в столбце «Location».

Размещение массива в энергонезависимой памяти и выбор файла данных
В примере выше массив — глобальная переменная без явного размещения. Компилятор предполагает, что она может меняться во время выполнения, и помещает в ОЗУ. Начальные значения хранятся во флеш-памяти, а процедура запуска копирует их в ОЗУ. Если массив действительно константен, квалификатор const говорит компилятору оставить его только во флеше, экономя драгоценное ОЗУ. На скриншоте ниже «Location» указывает на низкие адреса флеша.

Директивы #define и #if позволяют переключать размещение в ОЗУ/флеш или выбирать разные файлы данных. Например, следующий код выбирает место хранения на этапе компиляции:

Конструкция #if — классический пример условного включения. Она определяет, применяется ли квалификатор const на основе флага компиляции.
Аналогично можно условно выбирать файл с исходными данными:

Тестирование на большом массиве
Для проверки техники использовался 10 000-элементный шумовой сигнал в CSV-файле. На графике ниже показана волна, а в объявлении — как данные включаются в программу:


Исходный CSV не содержал запятых после каждого значения. Операция поиска/замены по регулярному выражению (найти \R, заменить на ,\R) добавила запятые ко всем 10 000 записям за один раз.
Результирующий массив виден в отладчике, аккуратно сгруппированный по 100 элементов в строке.

Многомерные массивы
При работе с двух- или трехмерными данными помните: C хранит массивы в порядке «строка за строкой» (row-major). Например, массив uint16_t test[2][3] в памяти выстроен как:
[0,0] [0,1] [0,2] [1,0] [1,1] [1,2]
Доступ к последовательным элементам по последнему индексу быстрее, так как данные смежны. Если нужны два вектора по 1000 отсчетов, объявляйте test[2][1000] для лучшей работы с кэшем.
Инициализатор двумерного массива обычно группируется вложенными скобками:

Однако внешние скобки можно опустить; компилятор заполнит массив слева направо. Выдается предупреждение missing braces around initializer. Если общее число значений совпадает с размером массива, код компилируется.

Чтобы распределить данные по нескольким файлам, просто включайте каждый файл по очереди, оборачивая весь список в пару скобок:

Инициализация массивов в объединениях
union позволяет разным типам разделять одну память. Например, объединение, содержащее одномерный вектор и двумерную матрицу, занимает одно адресное пространство:

При инициализации компилятор заполняет первый член первым. Если порядок обратный, может появиться предупреждение, так как список инициализации не полностью заключен в скобки. Двойные скобки вокруг #include обеспечивают правильную группировку:
Файл данных для объединения выглядит так:

В памяти оба vector и matrix начинаются по одному адресу, как показывает отладчик:

Знаете ли вы другой способ инициализировать многомерный массив из одного числового файла? Делитесь идеями в комментариях.
Бонус: строки
Инициализация строки из внешнего файла требует, чтобы кавычки были внутри самого файла. Прямой #include внутри строкового литерала сломает компилятор. Вместо этого поместите кавычки в файл и включайте его так:

Результирующая строка находится в ОЗУ, как иллюстрирует скриншот.

Приведенные примеры теоретичны и должны работать с любым стандартным компилятором C. Если встретите особенности конкретного компилятора, напишите в комментариях.
Источник предоставлен.
Встроенный
- Облачная революция: как облачные вычисления трансформируют IT, безопасность и карьеры
- STMicroelectronics запускает STM32Cube.AI — внедрение нейросетей в пограничные устройства на STM32
- DATA MODUL запускает ультра-яркие 21.5" и 31.5" open-frame мониторы с EasyTouch для наружного и внутреннего использования
- Microchip представляет 24-бит и 16-бит Delta-Sigma ADC с 153.6 кSPS data rates
- Contrinex 2020: облачные сенсоры и Bluetooth light curtains для безопасности
- Массивы в C++: объявление, инициализация и работа с указателями
- Динамическое выделение массивов в C++: практическое руководство с примерами кода
- Чтение файлов в Java с BufferedReader — Практическое руководство с примерами
- Оживление IoT с IBM и Tech Data — Часть 2
- Превращение IoT в реальность: инсайты Tech Data и IBM — Часть 1