====== Хранение параметров в энергонезависимой памяти ======
===== Требования к библиотеке =====
Большинство разработчиков сталкиваются с задачей хранения настроек или параметров прибора в энергонезависимой памяти. После пятой или десятой подобной задачи (в зависимости от количества лени в теле разработчика) в голову приходит гениальная идея - изобрести велосипед удобный инструмент, который сократит количество рутины и одним нажатием на кнопку "шедевр" сформирует некий исходный код, заточенный под текущий проект.
В этой статье рассматривается один из таких, как мне кажется, удобных инструментов. Это набор функций на языке Си и файл 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)'' | Сброс параметра на значение по умолчанию по указателю |