Общие советы о том, как избегать глобалов (и зачем вам это нужно), приведены в замечательной статье Джека Гансле « Оспа на глобалах ». Основное чтение.
Одним из решений является просто иметь функции доступа в 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));
}
}
, где вы фиксируете данные только тогда, когда это необходимо или безопасно, например, при контролируемом завершении работы или явно выбранной операции «сохранения настроек» пользовательского интерфейса.
Более сложный метод состоит в том, чтобы установить таймер на изменение и вызвать функцию фиксации, вызываемую по истечении таймера, каждый «набор» перезапускает таймер, поэтому фиксация происходит только в «тихие» периоды. Этот метод особенно подходит для многопоточного решения.