Как избежать глобализаций в структурах EEPROM для системных настроек? - PullRequest
3 голосов
/ 19 апреля 2019

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

Я получаю системные настройки, хранящиеся в EEPROM через структуры с некоторой проверкой ошибок и оператором sizeof в main.c в соответствии с:

// EEPROM data structures
typedef struct system_tag
{
    uint8_t     buzzer_volume;
    uint8_t     led_brightness;
    uint8_t     data_field_3;
} system_t;

typedef struct counters_tag
{
    uint16_t    counter_1;
    uint16_t    counter_2;
    uint16_t    counter_3;
} counters_t;

typedef struct eeprom_tag
{
    system_t    system_data;
    uint8_t     system_crc;
    counters_t  counters;
    uint8_t     counters_crc;
} eeprom_t;

// Default values
static system_t system_data =
{
    .buzzer_volume = 50,
    .led_brightness = 50,
    .data_field_3 = 30
};

static counters_t counter =
{
    .counter_1 = 0,
    .counter_2 = 0,
    .counter_3 = 0
};

// Get system settings data from the EEPROM
if (EEPROM_check_ok(EEPROM_BASE_ADDRESS, sizeof(system_t)))
{
        eeprom_read_block(&system_data, (uint16_t *) EEPROM_BASE_ADDRESS, sizeof(system_t));
}

if (EEPROM_check_ok((EEPROM_BASE_ADDRESS + offsetof(eeprom_t, counters)), sizeof(counters_t)))
{
        eeprom_read_block(&counter, (uint16_t *) EEPROM_BASE_ADDRESS, sizeof(counters_t));
}

Затем я используюДанные системных настроек на данный момент позволяют устанавливать другие переменные в разных модулях.Например, в другом файле, buzzer.c, у меня есть статическая переменная модуля (чтобы избежать глобальных переменных) с функциями доступа, чтобы попытаться дать некоторую инкапсуляцию:

// Current volume setting of the buzzer
static uint8_t volume = 50;

void BUZZER_volume_set(uint8_t new_volume)
{
    volume = new_volume;
}

uint8_t BUZZER_volume_get(void)
{
    return (volume);
}

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

Есть ли более элегантный способ сделать это без использования глобальных переменных и при этом иметь некоторую инкапсуляцию?

Любые предложения будут с благодарностью приняты.

1 Ответ

3 голосов
/ 19 апреля 2019

Общие советы о том, как избегать глобалов (и зачем вам это нужно), приведены в замечательной статье Джека Гансле « Оспа на глобалах ». Основное чтение.

Одним из решений является просто иметь функции доступа в main.c (или, лучше, отдельный nvdata.c, чтобы защитить его от прямого доступа с помощью что угодно ).

Вместо того, чтобы полагаться на одну функцию инициализации, вызываемую перед любым доступом к данным, я бы предложил «инициализировать при первом использовании» семантическую, таким образом:

const system_t* getSystemData()
{
    static bool initialised = false ;
    if( !initialised )
    {
        eeprom_read_block( &system_data, 
                           (uint16_t*)EEPROM_BASE_ADDRESS, 
                           sizeof(system_t) ) ;
        initialised = true ;
    }

    return &system_data ;
}

void setSystemData( const system_t* new_system_data )
{
    system_data = *new_system_data ;

    eeprom_write_block( &system_data, 
                        (uint16_t*)EEPROM_BASE_ADDRESS, 
                        sizeof(system_t));
}

Тогда в buzzer.c:

uint8_t BUZZER_volume_get(void)
{
    return getSystemData()->buzzer_volume ;
}

void BUZZER_volume_set( uint8_t new_volume )
{
    system_t new_system_data = *getSystemData() ;
    new_system_data.buzzer_volume = new_volume ;
    setSystemData( &new_system_data ) ;
}

Есть некоторые проблемы с этим - например, если ваши структуры большие, обновление одного члена может быть дорогим. Это может быть решено, однако, но не может быть проблемой в вашем приложении.

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

void setSystemData( const system_t* new_system_data )
{
    system_data = *new_system_data ;
    system_data_commit_pending = true ;
}

void commitSystemData()
{
    if( system_data_commit_pending )
    {
        eeprom_write_block( &system_data, 
                            (uint16_t*)EEPROM_BASE_ADDRESS, 
                            sizeof(system_t));
    }
}

, где вы фиксируете данные только тогда, когда это необходимо или безопасно, например, при контролируемом завершении работы или явно выбранной операции «сохранения настроек» пользовательского интерфейса.

Более сложный метод состоит в том, чтобы установить таймер на изменение и вызвать функцию фиксации, вызываемую по истечении таймера, каждый «набор» перезапускает таймер, поэтому фиксация происходит только в «тихие» периоды. Этот метод особенно подходит для многопоточного решения.

...