我正在努力从EEPROM获取系统设置并试图避免将它们作为全局变量,并且想知道流行的智慧是什么,以及是否有公认的实践和/或优雅的解决方案。
我通过带有一些错误检查的结构和main.c中的sizeof运算符将系统设置存储在EEPROM中,其行如下:
// 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来设置蜂鸣器模块中的静态卷变量时,事情可能会失去同步。将系统设置为全局变量很容易,但我知道这是不受欢迎的。
有没有更优雅的方式来做这个而不使用全局变量并仍然有一些封装?
我们将非常感激地收到任何建议。
关于避免全局变量的一般建议(以及你为什么需要这样做)在Jack Ganssle的优秀文章“A Pox on Globals”中给出。基本阅读。
一种解决方案就是在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));
}
}
只有在必要或安全时才提交数据的地方 - 例如在受控关闭或明确选择的UI“保存设置”操作中。
更复杂的方法是在更改时设置计时器并在计时器到期时调用提交函数,每个“set”将重新启动计时器,因此提交只会在“安静”时段发生。此方法特别适用于多线程解决方案。