2013-03-12 3 views
4

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

// EEPROM variable addresses 

#define EE_CRC      0   // EEPROM CRC-16 value 

#define EE_PROCESS_BIAS    1   // FLOAT, -100.00 - 100.00 U 
#define EE_SETPOINT_VALUE   3   // FLOAT, -9999 - 9999.9 
#define EE_SETPOINT_BIAS    5   // CHAR, -100 - 100 U 
#define EE_PID_USED     6   // BYTE, 1 - 3 
#define EE_OUTPUT_ACTION    7   // LIST, DIRE/OBRNU 
#define EE_OUTPUT_TYPE    8   // LIST, GRIJA/MOTOR 

#define EE_PROCESS_BIAS2    9   // FLOAT, -100.00 - 100.00 U 
#define EE_SETPOINT_VALUE2   11   // FLOAT, -9999 - 9999.9 
#define EE_SETPOINT_BIAS2   13   // CHAR, -100 - 100 U 
#define EE_PID_USED2    14   // BYTE, 1 - 3 
#define EE_OUTPUT_ACTION2   15   // LIST, DIRE/OBRNU 
#define EE_OUTPUT_TYPE2    16   // LIST, GRIJA/MOTOR 

#define EE_LINOUT_CALIB_ZERO  17   // FLOAT, -100.0 - 100.0 
#define EE_LINOUT_CALIB_GAIN  19   // FLOAT, -2.0 - 2.0 

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

В других частях кода (т.е. HMI меню, для хранения данных ...) код будет использовать список параметров, соответствующий адреса только что, что-то вроде следующего:

// Parameter identification, NEVER USE 0 (zero) as ID since it's NULL 
// Sequence is not important, but MUST be same as in setparam structure 

#define ID_ENTER_PASSWORD_OPER    1 
#define ID_ENTER_PASSWORD_PROGRAM   2 
#define ID_ENTER_PASSWORD_CONFIG   3 
#define ID_ENTER_PASSWORD_CALIB   4 
#define ID_ENTER_PASSWORD_TEST    5 
#define ID_ENTER_PASSWORD_TREGU   6 

#define ID_PROCESS_BIAS     7 
#define ID_SETPOINT_VALUE     8 
#define ID_SETPOINT_BIAS     9 
#define ID_PID_USED      10 
#define ID_OUTPUT_ACTION     11 
#define ID_OUTPUT_TYPE     12 

#define ID_PROCESS_BIAS2     13 

...       

Затем в коде, используя те параметры, например, в меню пользователя structrues приведены ниже, я построил предметы, используя собственный тип PARAM (структуру):

struct param {      // Parametar decription 
    WORD ParamID;     // Unique parameter ID, never use zero value 
    BYTE ParamType;     // Parametar type 
    char Lower[EDITSIZE];   // Lowest value string 
    char Upper[EDITSIZE];   // Highest value string 
    char Default[EDITSIZE];   // Default value string 
    BYTE ParamAddr;     // Parametar address (in it's media) 
};         

typedef struct param PARAM; 

Теперь список параметров строятся как массив структур:

PARAM code setparam[] = { 
    {NULL, NULL, NULL, NULL, NULL, NULL},     // ID 0 doesn't exist 

    {ID_ENTER_PASSWORD_OPER, T_PASS, "0", "9999", "0", NULL}, 
    {ID_ENTER_PASSWORD_PROGRAM, T_PASS, "0", "9999", "0", NULL}, 
    {ID_ENTER_PASSWORD_CONFIG, T_PASS, "0", "9999", "0", NULL}, 
    {ID_ENTER_PASSWORD_CALIB, T_PASS, "0", "9999", "0", NULL}, 
    {ID_ENTER_PASSWORD_TEST, T_PASS, "0", "9999", "0", NULL}, 
    {ID_ENTER_PASSWORD_TREGU, T_PASS, "0", "9999", "0", NULL}, 

    {ID_PROCESS_BIAS, T_FLOAT, "-100.0", "100.0", "0", EE_PROCESS_BIAS}, 
    {ID_SETPOINT_VALUE, T_FLOAT, "-999", "9999", "0.0", EE_SETPOINT_VALUE}, 
    {ID_SETPOINT_BIAS, T_CHAR, "-100", "100", "0", EE_SETPOINT_BIAS}, 
    {ID_PID_USED, T_BYTE, "1", "3", "1", EE_PID_USED}, 
    {ID_OUTPUT_ACTION, T_LIST, "0", "1", "dIrE", EE_OUTPUT_ACTION}, 
    {ID_OUTPUT_TYPE, T_LIST, "0", "1", "GrIJA", EE_OUTPUT_TYPE}, 

    {ID_PROCESS_BIAS2, T_FLOAT, "-100.0", "100.0", "0", EE_PROCESS_BIAS2}, 

...

В сущности, каждый параметр имеет уникальный идентификатор и этот идентификатор должен совпадать с жестко запрограммированный адрес EEPROM. Поскольку параметры не были фиксированы по размеру, я не мог использовать сам ID параметра как EEPROM (или другой носитель). Организация EEPROM в приведенном выше примере была 16-битным словом, но это не имеет значения, в принципе (больше места тратятся на голец, так что я предпочел бы 8-разрядную организацию в будущем так или иначе)

вопроса:

Есть ли более элегантный способ сделать это? Некоторые хеш-таблицы, хорошо известный шаблон, стандартное решение для подобных проблем? Сейчас EEPROMS намного больше по размеру, и я бы не прочь использовать фиксированный размер параметра (теряя 32 бита для логического параметра) в обмен на более элегантное решение. Похоже, что с фиксированными параметрами размера я мог бы использовать ID параметра в качестве адреса. Есть ли очевидный недостаток в этом методе, который я не вижу?

Теперь я использую распределенные HW (HMI, I/O и главный контроллер), и я хотел бы использовать структуру, в которой все устройства знают об этой структуре параметров, так что, например, удаленный ввод-вывод знает, как масштабировать входные значения, и HMI знает, как отображать и форматировать данные, все они основаны только на идентификаторе параметра. Другими словами, мне нужно одно место, где будут определены все параметры.

Я сделал свое исследование Google, очень мало можно было найти для небольших устройств, не содержащих некоторых баз данных. Я даже думал о каком-то определении XML, которое создало бы некоторый C-код для моих структур данных, но, возможно, было какое-то изящное решение, более подходящее для небольших устройств (до 512 K Flash, 32 K RAM)?

ответ

1

Я не уверен, действительно ли это лучше, чем у вас, но вот идея. Для упрощения обслуживания рассмотрите вопрос об инкапсуляции знаний о адресах EEPROM в объект «eeprom». Прямо сейчас у вас есть объект параметра, и каждый экземпляр знает, где его данные хранятся в физической EEPROM. Возможно, было бы легче поддерживать, если объект параметра не знал EEPROM. И вместо этого отдельный объект eeprom отвечал за взаимодействие между физическими EEPROM и экземплярами объекта параметров.

Кроме того, рассмотрите возможность добавления номера версии для данных EEPROM к данным, сохраненным в EEPROM. Если обновление прошивки устройства и формат данных EEPROM изменяется, то этот номер версии позволяет новой прошивке распознавать и преобразовывать старую версию данных EEPROM.

+0

Спасибо, это хорошее предложение, которое устраняет зависимость от конкретной организации хранения. Я использую FreeRTOS, поэтому то, что вы называете объектом «eeprom», было бы в моем случае некоторой задачей, получающей сообщения с параметром ID и командой (сохранение, получение, проверка, резервное копирование ...) и обращение с хранилищем в EEPROM (ы) или любых других носителях. Я мог бы иметь все структуры параметров с постоянными данными (по умолчанию, min, max ...), хранящиеся в этом неизменяемом объекте, и я должен убедиться, что любая задача, требующая специфики внутренней организации данных, должна спросить этот объект (или задачу) параметра, который он должен знать. –

+0

И да, наличие версии для идентифицированных данных является обязательным, иначе всевозможные плохие вещи могут произойти, если какая-то старая EEPROM была использована на новой плате или во время обновления FW на том же устройстве. Старая версия просто переписывала данные со значениями по умолчанию из поля «по умолчанию» для каждого параметра, безопасным, но требующим много времени, когда мы хотели сохранить конфигурацию клиента. Устройство было основано на EPROM, поэтому во время операции не было обновлено прошивка во время работы, что сегодня считается стандартным. –

2

Если вас не беспокоит совместимость между изменениями или процессорами, вы можете просто скопировать структуру между ОЗУ и EEPROM и только получить доступ к отдельным членам копии ОЗУ.

Вы также можете относительно легко создать инструмент, который бы скомпилировал список определений из структуры и известных правил упаковки вашего компилятора, если вы хотите сделать явный доступ к отдельным членам непосредственно в EEPROM.

+0

Спасибо, это был именно то, что делал мой основной код. Я никогда не использовал параметры непосредственно из EEPROM, кроме случаев, когда что-то было изменено. Начало основного контура управления (каждые 100 мс) проверит CRC блока RAM, где параметры были скопированы из EEPROM, и если произошли изменения по сравнению с предыдущим CRC, весь блок параметров будет обновлен. –

+0

Второе предложение также стоит исследовать. Мне неинтересно зависеть от особенностей компилятора, но я мог получить доступ к носителям (EEPROM, NVRAM, SD Card) непосредственно только в рамках задачи RTOS (назовем ее taskDeviceConfig), ответственной за нелетучее хранилище. Когда получено сообщение с запросом на получение определенного идентификатора параметра, эта задача (и только эта задача) будет выполнять любую магию, необходимую для извлечения единственного значения и отправки его в очередь получателя. –

+1

Как правило, все взаимодействующие компоненты, работающие в системе, будут иметь общую базовую идею структурированной упаковки, поскольку взаимозаменяемые структуры являются общими для многих функций службы ОС. Поэтому я не думаю, что вам нужно ограничить себя одним аксессуаром по этой причине. Также вы можете с умеренным вниманием вычислять смещение элемента в структуру во время выполнения с помощью арифметики указателя. –

2

Вот что я сделал бы.

Я бы создал typedef структуры с переменными, которые вы хотите иметь в EEPROM.

Используя ваш пример, это будет выглядеть примерно так:

typedef struct eeprom_st 
{ 
    float process_biass; 
    float setpoint_value; 
    char setpoint_bias; 
    .... 
} eeprom_st_t; 

чем я хотел бы создать смещение определить, чтобы отметить, где структура должна быть сохранена в EEPROM.

И я хотел бы добавить указатель к этому типу, чтобы использовать его в качестве фиктивного объекта:

#define EEPROM_OFFSET 0 
eeprom_st_t *dummy; 

чем я хотел бы использовать offsetof, чтобы получить смещение конкретной переменной, мне нужно так:

eeprom_write(my_setpoint_bias, EEPROM_OFFSET + offsetof(eeprom_st_t,setpoint_bias), 
sizeoff(dummy->setpoint_bias)); 

Чтобы сделать его более изящным, я бы превратил рулетку eeprom в макрос.