====== Хранение параметров в энергонезависимой памяти ====== ===== Требования к библиотеке ===== Большинство разработчиков сталкиваются с задачей хранения настроек или параметров прибора в энергонезависимой памяти. После пятой или десятой подобной задачи (в зависимости от количества лени в теле разработчика) в голову приходит гениальная идея - изобрести велосипед удобный инструмент, который сократит количество рутины и одним нажатием на кнопку "шедевр" сформирует некий исходный код, заточенный под текущий проект. В этой статье рассматривается один из таких, как мне кажется, удобных инструментов. Это набор функций на языке Си и файл Excel, который по нажатию той самой кнопки формирует дополнительные .c и .h файлы, подключаемые к проекту. Какие требования предъявлялись к инструменту в ходе разработки? * параметр - это переменная одного из стандартных типов языка Си или пользовательская структура * параметр имеет **рабочую копию** в ОЗУ. Это позволяет быстро обращаться к параметру, использовать его в выражениях, а не загружать каждый раз по необходимости. Конечно, при каждом изменении параметра придется вызывать функцию сохранения и следить за конкурентным доступом - но это редкая ситуация изменения настроек * параметр имеет **значение по умолчанию** - переменную того же типа, что и рабочая копия, только расположенную в секции ''const'' (программной памяти). Таким образом возможен сброс параметра при детектировании ошибки записи в NVRAM, а так же реализация функции "сброс на заводские настройки" * должна быть возможность определять значение по умолчанию в виде ''#define'' - для контроллеров с небольшим объемом набортной flash * автоматическая инициализация параметров при первом включении * возможность добавления параметров, например, при обновлении прошивки. При этом старые параметры не должны сбрасываться * всеядность типов NVRAM - работа как с Flash, так и с EEPROM * повышенная надежность - защита параметра контрольной суммой (с выбором размерности), дублирование, троирование * ну и как обычно - небольшой объем кода, минимальное использование ОЗУ, быстрое выполнение \\ ===== А что внутри? ===== Ядром инструмента является специально подготовленный файл электронных таблиц Excel, в который разработчик в удобной табличной форме вносит список существующих в его устройстве настроек или параметров - всего того, что требует сохранения при выключении питания устройства. С помощью штатных формул Excel, пакета [[http://office.microsoft.com/en-us/excel-help/load-the-analysis-toolpak-HP001127724.aspx|Analysis ToolPack VBA]] и написанной на VBA функции из этой таблицы формируется несколько файлов, которые подключаются к проекту и полностью описывают структуру параметров устройства. Два дополнительных файла (модуль на Си ''tparam.c'' и заголовочный файл ''tparam.h'') обеспечивают интерфейс пользовательского приложения к структуре параметров. Каждый параметр описывается следующим дескриптором: typedef struct _TPARAM_DESC { TPARAM_ADDR addr; /* смещение параметра относительно базового адреса в NVRAM */ U08 attr; /* атрибуты параметра */ U08 size; /* размер параметра в байтах (без учета контрольной суммы) */ void *val; /* указатель на рабочую копию параметра в ОЗУ */ void const *def; /* указатель на значение параметра по умолчанию */ } TPARAM_DESC; Размер параметра объявлен типом U08. Таким образом, если в качестве параметра используется объявленная пользователем структура, ее размер не должен превышать 255 байт Дескрипторы параметров располагаются в программной памяти в виде массива, таким образом каждый параметр имеет уникальный индекс - положение в массиве дескрипторов. Массив дескрипторов выглядит следующим образом: const TPARAM_DESC param_desc_table[] = { /* ( 0) */ { 0x0, TPARAM_SAFETY_LEVEL_2, 4, &test_param_1, &test_param_1_def}, // Тестовый параметр 1 /* ( 1) */ { 0xA, TPARAM_SAFETY_LEVEL_1, 4, &test_param_2, &test_param_2_def}, // Тестовый параметр 2 /* ( 2) */ { 0xF, TPARAM_SAFETY_LEVEL_1, 4, &test_param_3, &test_param_3_def}, // Тестовый параметр 3 /* ( 3) */ { 0x14, TPARAM_SAFETY_LEVEL_1, 1, &test_param_4, &test_param_4_def}, // Тестовый параметр 4 /* ( 4) */ { 0x16, TPARAM_SAFETY_LEVEL_1, 4, &test_param_5, &test_param_5_def}, // Тестовый параметр 5 /* ( 5) */ { 0x1B, TPARAM_SAFETY_LEVEL_1, 4, &test_param_6, &test_param_6_def}, // Тестовый параметр 6 }; В NVRAM параметры хранятся так же в виде массива без пропусков. Это позволяет добавлять новые параметры в конец массива, например, при обновлении прошивки устройства. Старые параметры при этом не затрагиваются. "Точкой входа" является таблица параметров (не путать с массивом дескрипторов), которая описывается следующей структурой: typedef struct _TPARAM_TBL { U32 addr; /* базовый адрес таблицы параметров в энергонезависимой памяти */ TPARAM_ADDR size; /* размер таблицы параметров в энергонезависимой памяти в байтах */ U16_FAST pnum; /* количество параметров в таблице */ TPARAM_DESC const *dt; /* указатель на массив дескрипторов */ } TPARAM_TBL; Тип ''TPARAM_ADDR'' определяет разработчик. Размерности этого типа должно хватить для адресации всех параметров. Понятно, что чем меньше разрядность, тем меньше параметров можно обслуживать. Использование типа U16_FAST для переменной числа параметров в таблице определяет максимальное число параметров как 65535 - более чем достаточно. Перменная ''dt'' должна указывать на массив дескрипторов (например, ''param_desc_table''). Таблица параметров может выглядеть следующим образом: const TPARAM_TBL param_table = { 0x0, 32, 6, param_desc_table }; ===== Автоматизация ===== Все написанное выше - очевидное, но неудобное решение. Поддержка такой структуры вручную - нудное занятие, результатом которого будет множество трудноуловимых ошибок. Как же свести рутину к минимуму и избавится от опечаток и багов? Один из вариантов - использовать наиболее уместный способ ввода табличных данных - программу Excel, а затем преобразовывать таблицу в исходный код и заголовочные файлы. Конечно таблица должна быть специальным образом подготовлена, содержать необходимые формулы и макросы экспорта. Для этого проекта был сделан шаблон таблицы, который можно использовать в реальных проектах. {{:articles:common:tparam_fig1.png|Таблица Excel}} Каждая строка таблицы (выделено на рисунке) формирует дескриптор параметра. Разберем назначение столбцов: {| class = "fpl" |- | ''ID'' | Индекс параметра в таблице дескрипторов. Начинается с 0, максимальное значение 65535. Отображается автоматически и только в том случае, если введено имя параметра ''Name'' |- | ''Name'' | Имя параметра. Именно с таким именем будет объявлена рабочая копия параметра в ОЗУ |- | ''Safety'' | Уровень надежности. Может принимать одно из четырех значений (выпадающий список). Если Safety = **Simple** - в NVRAM сохраняется только значение параметра без контрольной суммы. Если Safety = **1 + CRC**, то вместе со значением параметра сохраняется так же контрольная сумма. Если Safety = **2 + CRC**, то параметр дублируется в NVRAM вместе с контрольной суммой, что позволяет восстановить значение параметра при потере одной из копий. Safety = **3 + CRC** - пока не реализовано. |- | ''Type'' | Тип параметра, выбирается из выпадающего списка: ''U08'', ''S08'', ''U16'', ''S16'', ''U32'', ''S32'', ''U64'', ''S64'', ''float'', ''double'', ''struct''. Последний вариант позволяет назначить параметру пользовательский тип. |- | ''User Type'' | Выбор одного из пользовательских типов (если Type = ''struct''). Пользовательские типы вводятся на втором листе файла (лист так и называется "Пользовательские типы"). Выпадающий список |- | ''User Size'' | В это поле автоматически подставляется размер пользовательского типа. Подробнее об использовании пользовательских типов ниже. |- | ''Size'' | Размер параметра в байтах. Рассчитывается автоматически в зависимости от типа параметра |- | ''Total Size'' | Размер параметра в байтах с учетом дублирования и контрольных сумм. Рассчитывается автоматически в зависимости от типа параметра и уровня надежности |- | ''Addr'' | Смещение параметра в энергонезависимой памяти относительно базового адреса. Рассчитывается автоматически в зависимости от общего размера параметра |- | ''Default'' | Значение параметра по умолчанию. В этом поле можно инициализировать даже структуры. А можно игнорировать это поле, о том что произойдет в этом случае - ниже. |- | ''Description'' | Описание параметра. Добавляется в генерируемые файлы как комментарий. |} Над самой таблицей можно увидеть пять полей ввода. Четыре из них выделены зеленым цветом - в них вводятся необходимые настройки. Пятое красное поле отображает общий объем базы параметров в энергонезависимой памяти с учетом всех контрольных сумм и дублирований. Ячейка **C1** (Start Address (HEX)) служит для ввода базового адреса параметров в энергонезависимой памяти. Этот адрес нужен для передачи абсолютного адреса в функции низкоуровневого чтения/записи. В ячейку **С2** (Table Index) вводится индекс, который добавляется к названию генерируемых файлов и имен переменных. Этот параметр можно использовать, если в приборе используется несколько таблиц параметров, об этом ниже. В ячейку **E1** (CRC Size) вводится размер контрольной суммы (в байтах). Ячейка **E2** (Def in flash?) служит для определения стратегии размещения значений по умолчанию. Нажатие на кнопку **Export** запускает макрос, формирующий в рабочей папке три файла: * ''param_[indx]_tbl.c'' * ''param_[indx]_list.c'' * ''param_[indx]_list.h'' ''[indx]'' это значение которое вводится в ячейку **C2**. Оно может быть как буквенным, так и числовым. Например, если в ячейке **С2** введено "01", то будут формироваться файлы ''param_01_tbl.c'' и т.д. Если ячейка **C2** пустая, поле ''[indx]'' не вставляется: ''param_tbl.c''. Если файлы уже существуют, их содержимое будет утеряно. Поэтому не рекомендуется эти файлы изменять и добавлять туда свою информацию. Для этого предусмотрен отдельный прием, который будет описан ниже. Для таблицы, приведенной на скриншоте файлы будут содержать следующее ==== param_tbl.c ==== /* Таблица параметров */ #include "param_list.h" // список параметров const TPARAM_DESC param_desc_table[] = { /* ( 0) */ { 0x0, TPARAM_SAFETY_LEVEL_2, 4, &test_param_1, &test_param_1_def}, // Тестовый параметр 1 /* ( 1) */ { 0xA, TPARAM_SAFETY_LEVEL_1, 4, &test_param_2, &test_param_2_def}, // Тестовый параметр 2 /* ( 2) */ { 0xF, TPARAM_SAFETY_LEVEL_1, 20, &test_param_3, &test_param_3_def}, // Тестовый параметр 3 /* ( 3) */ { 0x24, TPARAM_SAFETY_LEVEL_1, 1, &test_param_4, &test_param_4_def}, // Тестовый параметр 4 /* ( 4) */ { 0x26, TPARAM_SAFETY_LEVEL_1, 4, &test_param_5, &test_param_5_def}, // Тестовый параметр 5 /* ( 5) */ { 0x2B, TPARAM_SAFETY_LEVEL_1, 4, &test_param_6, &test_param_6_def}, // Тестовый параметр 6 }; const TPARAM_TBL param_table = { 0x0, // абсолютный адрес таблицы в NVRAM (U32) 48, // размер таблицы в NVRAM в байтах 6, // количество параметров в таблице param_desc_table }; Первым делом включается заголовочный файл ''param_[indx]_list.h''. Далее объявляется массив дескрипторов param_[indx]_desc_table. Для удобства каждая строка обозначается комментарием в виде индекса в массиве и описанием параметра из столбца Description таблицы Excel. После массива дескрипторов объявляется таблица параметров с именем param_[indx]_table. Все константы рассчитываются автоматически и не нуждаются в правке. ==== param_list.c ==== /* Список параметров, объявления значений параметров по умолчанию */ #include "param_list.h" // Тестовый параметр 1 S32 test_param_1; const S32 test_param_1_def = 0x10101010; // Тестовый параметр 2 U32 test_param_2; const U32 test_param_2_def = 0x20202020; // Тестовый параметр 3 USER_PARAM_TYPE test_param_3; const USER_PARAM_TYPE test_param_3_def = {1, 2, 3, 1.2, 5.65}; // Тестовый параметр 4 U08 test_param_4; const U08 test_param_4_def = TEST_PARAM_4_DEF_INIT; // Тестовый параметр 5 float test_param_5; const float test_param_5_def = 1.5568; // Тестовый параметр 6 U32 test_param_6; const U32 test_param_6_def = 0x12345678; В этом файле объявляются рабочие копии параметров и значения параметров по умолчанию. Рабочая копия - это глобальная переменная с именем, которое вводится в столбце Name таблицы Excel. Значение по умолчанию объявляется с квалификатором const - для большинства контроллеров это значение означает использование внутренней Flash памяти для хранения. Имя значения по умолчанию соответствует имени параметра с суффиксом ''_def''. Т.е. если имя параметра названо как ''my_param'', то для него будет сгенерировано значение по умолчанию с именем ''my_param_def''. Значения по умолчанию инициализируются текстом, который вводится в столбец Default таблицы Excel. Таким образом можно инициализировать даже структуры. Будьте внимательны! При иницилизации переменных типа ''float'' и ''double'' используйте в качестве разделителя точку, а не запятую. Значения введенные в столбец Default не проверяются на синтаксическое соответствие! Если в таблице Excel поле Defaul оставить пустым, то вместо его содержимого будет подставлена литеральная константа. Ее имя будет соответствовать имени рабочей копии в верхнем регистре плюс суффикс ''_DEF_INIT''. Таким образом, если название параметра ''my_param'', то значению по умолчанию будет присвоено имя ''my_param_def'', а литеральной константе-инициализатору - ''MY_PARAM_DEF_INIT''. Определить эту константу можно в специальном файле ''param_types.h'' - об этом ниже. ==== param_list.h ==== #ifndef _PARAM_LIST_H #define _PARAM_LIST_H #include // подключение библиотеки #include "param_types.h" // пользовательские типы параметров extern const TPARAM_TBL param_table; // таблица параметров // (0) - Тестовый параметр 1 extern S32 test_param_1; extern const S32 test_param_1_def; #define TEST_PARAM_1_INDX 0 // (1) - Тестовый параметр 2 extern U32 test_param_2; extern const U32 test_param_2_def; #define TEST_PARAM_2_INDX 1 // (2) - Тестовый параметр 3 extern USER_PARAM_TYPE test_param_3; extern const USER_PARAM_TYPE test_param_3_def; #define TEST_PARAM_3_INDX 2 // (3) - Тестовый параметр 4 extern U08 test_param_4; extern const U08 test_param_4_def; #define TEST_PARAM_4_INDX 3 // (4) - Тестовый параметр 5 extern float test_param_5; extern const float test_param_5_def; #define TEST_PARAM_5_INDX 4 // (5) - Тестовый параметр 6 extern U32 test_param_6; extern const U32 test_param_6_def; #define TEST_PARAM_6_INDX 5 #define PARAM_IMBUF_SIZE 21 // размер буфера для загрузки-сохранения параметров STATIC_ASSERT(PARAM_IMBUF_SIZE == TPARAM_BUF_SIZE); /* Проверка на правильность установки настройки в файле poram_conf.h */ #endif //_PARAM_LIST_H Этот файл включается в любой программный модуль, который будет использовать какую-либо рабочую копию параметра, а так же API для обслуживания параметров. Вначале включается заголовочный файл ''tparam.h'' в котором описан интерфейс библиотеки параметров. Затем заголовочный файл ''param_types.h'', создаваемый пользователем. Этот файл должен быть один на весь проект, даже если используются несколько таблиц параметров (несколько файлов Excel). В файле ''param_types.h'' определяются пользовательские типы параметров, а так же в этом файле я рекомендую определять значения параметров по умолчанию в виде #define. Файл ''param_types.h'' может выглядеть следующим образом: #ifndef _PARAM_TYPES_H #define _PARAM_TYPES_H #include typedef struct { U08 mem1; U16 mem2; U32 mem3; float mem4; double mem5; } USER_PARAM_TYPE; #define TEST_PARAM_4_DEF_INIT 0x40 #endif //_PARAM_TYPES_H После включения необходимых заголовочных файлов объявляются внешние переменные рабочих копий и значений по умолчанию. Так же для каждого параметра генерируется значение индекса в массиве дескрипторов. Имя литеральной константы соответствует имени параметра в верхнем регистре плюс суффикс ''_INDX''. В конце определяется константа **''PARAM_[indx]_IMBUF_SIZE''**. Значение этой константы соответствует размеру внутреннего массива в ОЗУ (в байтах), который используется для загрузки и сохранения параметров в NVRAM. Если вы используете в проекте несколько таблиц (фалов Excel), то для каждой таблицы будет рассчитано свое значение размера массива ===== Flash vs. EEPROM ===== Для работы с параметрами используется всего несколько функций: ^ Функция ^ Описание ^ | \ \ ''TPARAM_ERR tparam_init(TPARAM_TBL const *tbl)'' | Инициализация всех рабочих копий | | \ \ ''TPARAM_ERR tparam_load(TPARAM_TBL const *tbl, U16_FAST i)'' | Загрузка параметра в рабочую копию по индексу | | \ \ ''TPARAM_ERR tparam_load_p(TPARAM_TBL const *tbl, void *val)'' | Загрузка параметра в рабочую копию по указателю | | \ \ ''TPARAM_ERR tparam_save(TPARAM_TBL const *tbl, U16_FAST i)'' | Сохранение рабочей копии в NVRAM по индексу | | \ \ ''TPARAM_ERR tparam_save_p(TPARAM_TBL const *tbl, void *val)'' | Сохранение рабочей копии в NVRAM по указателю | | \ \ ''TPARAM_ERR tparam_reset(TPARAM_TBL const *tbl, U16_FAST i)'' | Сброс параметра на значение по умолчанию по индексу | | \ \ ''TPARAM_ERR tparam_reset_p(TPARAM_TBL const *tbl, void *val)'' | Сброс параметра на значение по умолчанию по указателю |