我正在努力从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));
}
然后在蜂鸣器中:
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 "保存设置"操作上。
更复杂的方法是在更改时设置一个计时器,并在计时器到期时调用 commit 函数,每个"set"都会重新启动计时器,因此提交只会在"安静"期间发生。 此方法特别适用于多线程解决方案。