Большинство разработчиков сталкиваются с задачей хранения настроек или параметров прибора в энергонезависимой памяти. После пятой или десятой подобной задачи (в зависимости от количества лени в теле разработчика) в голову приходит гениальная идея - изобрести велосипед удобный инструмент, который сократит количество рутины и одним нажатием на кнопку "шедевр" сформирует некий исходный код, заточенный под текущий проект.
В этой статье рассматривается один из таких, как мне кажется, удобных инструментов. Это набор функций на языке Си и файл Excel, который по нажатию той самой кнопки формирует дополнительные .c и .h файлы, подключаемые к проекту.
Какие требования предъявлялись к инструменту в ходе разработки?
const
(программной памяти). Таким образом возможен сброс параметра при детектировании ошибки записи в NVRAM, а так же реализация функции "сброс на заводские настройки"#define
- для контроллеров с небольшим объемом набортной flash
Ядром инструмента является специально подготовленный файл электронных таблиц 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;
Дескрипторы параметров располагаются в программной памяти в виде массива, таким образом каждый параметр имеет уникальный индекс - положение в массиве дескрипторов. Массив дескрипторов выглядит следующим образом:
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, а затем преобразовывать таблицу в исходный код и заголовочные файлы. Конечно таблица должна быть специальным образом подготовлена, содержать необходимые формулы и макросы экспорта. Для этого проекта был сделан шаблон таблицы, который можно использовать в реальных проектах.
Каждая строка таблицы (выделено на рисунке) формирует дескриптор параметра. Разберем назначение столбцов:
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
.
Для таблицы, приведенной на скриншоте файлы будут содержать следующее
/* Таблица параметров */ #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. Все константы рассчитываются автоматически и не нуждаются в правке.
/* Список параметров, объявления значений параметров по умолчанию */ #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
- об этом ниже.
#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.
Для работы с параметрами используется всего несколько функций:
Функция | Описание |
---|---|
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) | Сброс параметра на значение по умолчанию по указателю |