I would like to see the API defined well enough, and in such a way, that any conceivable (within limits, of course) back-end library could be used without any changes to the sketch, except minor changes to a user EEPROM specific header file included with the sketch.
The contents of this header file may differ depending on the back-end used. This header file would have a specific standard name, such as localEEPROM.h. It would contain information needed to locate or create the sketch’s reserved area in EEPROM. In order to have different library names for each back-end, so they could co-exist under the Arduino libraries folder, a #define, which I’ll call EEPROM_LIBRARY_HEADER, giving the name of the library header file, would be included. The localEEPROM.h file would be based on a standard template for a given back-end and be changed as little as necessary by the sketch developer or user or possibly an external management program, to uniquely identify the sketch per the back-end requirements.
localEEPROM.h would also include a #define for the number of EEPROM bytes required by the sketch, which I’ll call SKETCH_EEPROM_SIZE. This define would be available for use by the sketch. It’s included in the header, instead of the sketch, to allow the possibility of an external management program to easily use it.
A single class will be used and will have the same name for all back-ends. I’ll call it ArduboyEeprom.
The API will require a function to allocate or locate the sketch’s reserved space. I’ll call it open(). The parameter types passed to this function could vary between libraries. For instance, @Dreamer3 has proposed an 8 byte character string which is searched for, and a length, whereas I’d use a fixed EEPROM location, with a 16 bit user ID and an 8 bit sketch ID for verification. I would possibly also require a length but only for additional verification. To avoid having to alter the function arguments within the sketch when changing libraries, the function call would be defined by a #define in the header file. I’ll call this define EEPROM_OPEN.
An example for localEEPROM.h for a back-end library called eepromMLXXXp that uses my proposed technique, in which the user, or an offline management program, must manually set the start of each sketch’s EEPROM area such that they don’t overlap:
// Number of EEPROM bytes required by this sketch
#define SKETCH_EEPROM_SIZE 25
// This is the start address for the block of EEPROM space used by this sketch.
// This must be set so that areas used by all desired sketches don't overlap.
#define MLXXXP_EEPROM_START 0x0042
// The combination of user ID and sketch ID must be unique amongst all sketches using EEPROM.
// Change these as necessary.
#define ARDUBOY_USER_ID 1234
#define USER_SKETCH_ID 1
// ========== DO NOT CHANGE ANYTHING BEYOND THIS POINT ==========
#define EEPROM_LIBRARY_HEADER eepromMLXXXp.h
#define EEPROM_OPEN open(MLXXXP_EEPROM_START, ARDUBOY_USER_ID, USER_SKETCH_ID, SKETCH_EEPROM_SIZE)
The sketch, using any EEPROM library, would include:
#include "localEEPROM.h"
#include <EEPROM_LIBRARY_HEADER>
ArduboyEeprom savedData;
void setup() {
if (savedData.EEPROM_OPEN != 0) {
// (handle error conditions here)
}
Note that I propose that the open() function should return a status code as follows:
- 0 to indicate that the area had been previously allocated and was OK.
- 1 to indicate that a new area was successfully allocated but may need to be set to initial values by the sketch. A #define, such as EEPROM_ALLOCATED, should be added to the library header file for this, so the sketch doesn’t use a hard coded value, in case we want to change it in the future.
- Negative numbers indicate an error, such as not enough space available. We’ll have to decide what possible error conditions could occur and create a #define for each of them.
If I’ve got things above correct, then only the localEEPROM.h file would need to be changed in all sketch folders to use a different library. The template for this file would be included with the library. You wouldn’t have to touch the .ino or other files.
For instance, localEEPROM.h for a manager similar to what @Dreamer3 proposed would look something like:
// Number of EEPROM bytes required by this sketch
#define SKETCH_EEPROM_SIZE 25
// Name of EEPROM block. Maximum 8 characters not including quotes.
// Must be different for all sketches using EEPROM.
#define DREAMER3_EEPROM_NAME "MyGame01"
// ========== DO NOT CHANGE ANYTHING BEYOND THIS POINT ==========
#define EEPROM_LIBRARY_HEADER eepromDreamer3.h
#define EEPROM_OPEN open(DREAMER3_EEPROM_NAME, EEPROM_SIZE)
As already discussed, there should be read byte, write byte and probably read block and write block functions. Except for read byte, I would have them all return a good or bad status. This could be used by back-ends that were capable of bounds checking. It would be nice to have the read byte function return a status as well but I think this complicates it too much. If a sketch wanted checking for a byte read, it could use a block read with a length of 1. I’ve used unsigned int for the address (actually an offset) for better portability.
uint8_t read(unsigned int address); //< read a single byte
boolean write(unsigned int address, uint8_t data); //< write a single byte
boolean read(unsigned int address, uint8_t *buffer, size_t size);
boolean write(unsigned int address, uint8_t *buffer, size_t size);
We need to discuss whether a function is required to free up allocated space. It’s kind of a waste to include code to free up EEPROM in the sketch that uses it. Depending on the specific back-end implementation, it may be possible to have the library include its own specific sketch which would be temporarily loaded to free up EEPROM, using a menu system or some other means.
Upon further thought, I don’t think I’d include a verify function. If the saved data gets corrupted, in most cases I think it would just show up as weird high scores or strange game play. And, as I’ve previously mentioned, a properly written sketch should include a means for the user to reset saved data. The Arduboy is just a simple gaming system with not much more than disappointment at stake if data is lost.
Finally, although I’ve used EEPROM in many ways in this proposal, we may want to use more generic terms to refer to non-volatile storage. It’s possible that, on future hardware, data could be stored somewhere else, such as on an SD card, but still be able to use this API with a different back-end. In such a case, having the term EEPROM as part of defines, etc., could be confusing. Even the title of this thread could be broadened to “Shared non-volatile storage management across multiple apps”.