Available Languages?:

Хранение параметров в энергонезависимой памяти

Требования к библиотеке

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

В этой статье рассматривается один из таких, как мне кажется, удобных инструментов. Это набор функций на языке Си и файл Excel, который по нажатию той самой кнопки формирует дополнительные .c и .h файлы, подключаемые к проекту.

Какие требования предъявлялись к инструменту в ходе разработки?

  • параметр - это переменная одного из стандартных типов языка Си или пользовательская структура
  • параметр имеет рабочую копию в ОЗУ. Это позволяет быстро обращаться к параметру, использовать его в выражениях, а не загружать каждый раз по необходимости. Конечно, при каждом изменении параметра придется вызывать функцию сохранения и следить за конкурентным доступом - но это редкая ситуация изменения настроек
  • параметр имеет значение по умолчанию - переменную того же типа, что и рабочая копия, только расположенную в секции const (программной памяти). Таким образом возможен сброс параметра при детектировании ошибки записи в NVRAM, а так же реализация функции "сброс на заводские настройки"
  • должна быть возможность определять значение по умолчанию в виде #define - для контроллеров с небольшим объемом набортной flash
  • автоматическая инициализация параметров при первом включении
  • возможность добавления параметров, например, при обновлении прошивки. При этом старые параметры не должны сбрасываться
  • всеядность типов NVRAM - работа как с Flash, так и с EEPROM
  • повышенная надежность - защита параметра контрольной суммой (с выбором размерности), дублирование, троирование
  • ну и как обычно - небольшой объем кода, минимальное использование ОЗУ, быстрое выполнение


А что внутри?

Ядром инструмента является специально подготовленный файл электронных таблиц Excel, в который разработчик в удобной табличной форме вносит список существующих в его устройстве настроек или параметров - всего того, что требует сохранения при выключении питания устройства. С помощью штатных формул Excel, пакета 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, а затем преобразовывать таблицу в исходный код и заголовочные файлы. Конечно таблица должна быть специальным образом подготовлена, содержать необходимые формулы и макросы экспорта. Для этого проекта был сделан шаблон таблицы, который можно использовать в реальных проектах.

Таблица Excel

Каждая строка таблицы (выделено на рисунке) формирует дескриптор параметра. Разберем назначение столбцов:

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 <utils\tparam.h>	// подключение библиотеки
#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 <csp\types.h>
 
 
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) Сброс параметра на значение по умолчанию по указателю
 
articles/common/nvparam_library.txt · Последние изменения: 27.09.2010 16:05 От admin
 
Creative Commons License Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki