In a previous post I have documented how to write to and read from EEPROM in the PIC 18F2550.
For the new revision of CapToolKit I wanted to store all user-modifiable configuration variables in EEPROM. The user can change these settings over a plain-text serial interface. Typing "S↵" should store everything to EEPROM while "R↵" should restore all variables to the values stored in the EEPROM.
Here's what I've done (many variables removed for brevity). Im sure there are many similar or better solutions for this task – if you happen to know one please let me know.
// config.c // (c) 2008 Raphael Wimmer // licensed under GPL v2 or later // do not include "configuration.h" #include "eeprom.h" #include <stdio.h> #ifndef STORAGE #define STORAGE 24 // keep this also in sync with config.h! // of course we could use all 256 bytes of EEPROM. // but we also have to keep the whole array in RAM #endif // STORAGE union { struct { unsigned int device_id; volatile unsigned char use_leds; volatile unsigned char bm_led_state; volatile unsigned char bm_led_blink; volatile unsigned int drift_offset[8]; }; // SUM: 21 bytes struct { unsigned char eeprom_data[STORAGE]; }; } cfg; void save_cfg(){ unsigned int i; printf("Saving config to EEPROM:rn"); for (i=0; i < STORAGE; i++){ printf("."); ee_write_byte(i, &cfg.eeprom_data[i]); } printf(" donern"); } void restore_cfg(){ unsigned int i; printf("Restoring config from EEPROM:rn"); for (i=0; i < STORAGE; i++){ printf("."); ee_read_byte(i, &cfg.eeprom_data[i]); } printf(" donern"); }
And the header file:
// config.h // (c) 2008 Raphael Wimmer // licensed under GPL v2 or later #ifndef _CONFIG_H #define _CONFIG_H #ifndef STORAGE #define STORAGE 24 // keep this also in sync with config.c! // of course we could use all 256 bytes of EEPROM. // but we also have to keep the whole array in RAM #endif // STORAGE extern union { struct { unsigned int device_id; volatile unsigned char use_leds; volatile unsigned char bm_led_state; volatile unsigned char bm_led_blink; volatile unsigned int drift_offset[8]; }; struct { unsigned char eeprom_data[STORAGE]; }; } cfg; void save_cfg(); void restore_cfg(); #endif // _CONFIG_H
As you can see all configuration variables are stored in a struct cfg. Thanks to union this struct occupies the same space in memory as the struct eeprom_data. Instead of writing every variable to a specified EEPROM location I write the whole eeprom_data array. Restoring the variables is done the same way.
Be aware that the union definition in config.c and config.h is different:
The keyword extern in the header file tells the compiler that this union will be available from some other object file (i.e. config.o) when linking.
Advantages:
[*] no need to assign EEPROM positions manually
[*] no danger of assigning the same EEPROM byte twice.
[*] easily extensible
[*] storing/restoring works either completely or not at all – this avoids stupid bugs.
Disadvantages:
[*] array size should be adapted to your needs. you usually don't need 256 bytes of config data – and you usually don't want to spend 25% of your RAM for it.
[*] config.c and config.h have to be synchronized each time you add a variable
[*] no simple way to store/restore single variables
One caveat: C does not guarantee that the members of a struct in memory are in the same order as in your code. You can not expect that the first byte the struct occupies is also (part of) the first variable in your struct.
AFAIK, SDCC does order the variables in memory in the order you wrote them. But don't expect it or any other C compiler to behave this way.
For my example this caveat does not apply. I do not care in which order the variables are in memory. All this code requires is that the variables stay at the same place in memory all the time. Lacking a MMU the PIC does not have any other choice.
If you want to define EEPROM contents in your hex-file:
typedef unsigned char eeprom; __code eeprom __at 0x2100 __EEPROM[] = { 0xAA, 0x1E, 0x00, 0x01, 0x0A, 0x64, 0x1E, 0x01 };
Thanks to arkadi for this tip!