Core EEPROM Bit Array utility class

The Spark Core has 100 bytes (or 800 bits) of EEPROM memory. The values in this memory are retained even if the Core loses power. The official doco is here http://docs.spark.io/firmware/#other-functions-eeprom

One hundred bytes is not a lot. But there are ways to pack more data than you might think into that space.

For example my company Erge Solar needs to store as many “Rules” as possible into the 800 bits. Each Rule has a Mode (value 0 to 11), a StartTime (value 0 to 1439) and an EndTime (value 0 to 1439). So I need 4 bits to store a Mode (as 2^4=16 which is greater than 11) and 11 bits to store a StartTime or EndTime (as 2^11=2048 which is greater than 1439). This utility class lets me use the minimum number of bits for each piece of data so I can cram lots of rules into the 100 bytes.

The contents of the “setup” procedure are just test cases showing how to use the class.

// This class facilitates use of the 100 bytes (800 bits) of EEPROM memory on the Spark Core.
class SparkEepromBitArray {
    
  public:
 
 	// The Spark Core has 100 bytes (80 bits) of EEPROM 
	static const int sparkEepromBits = 800;

    // Return a single bit.
    //   'pos' should be in the range 0 to 799
    //   returned value is 0 or 1
    int getBit(int pos) {
        int b8 = EEPROM.read(pos / 8); // Read a byte from the Spark EEPROM
        return (b8 & (1 << (pos % 8))) != 0;
    }
 
    // Set a single bit.
    //   'pos' should be in the range 0 to 799
    //   'b' should be 0 or 1
    void setBit(int pos, int b) {
        int b8 = EEPROM.read(pos / 8); // Read a byte from the Spark EEPROM
        byte posBit = (byte) (1 << (pos % 8));
        if (b) {
            b8 |= posBit;
        } else {
            b8 &= (255 - posBit);
        }
        EEPROM.write(pos / 8, b8 ); // Write a byte to the Spark EEPROM
    }
 
    // Returns the integer value stored in the 'numBits' bits starting at position 'pos'
    //   'pos' should be in the range 0 to 799
    //   'numBits' should be 1 to 800
    //   pos+numBits should be in the range 1 to 800
    int getBits(int pos, int numBits) {
        int answer = 0;
        for( int i = 0; i < numBits; i++)
            answer = answer * 2 + ( getBit( pos + i ) ? 1 : 0 );
        return answer;
    }
 
    // Stores an integer value in the 'numBits' bits starting at position 'pos'
    //   'pos' should be in the range 0 to 799
    //   'numBits' should be 1 to 800
    //   pos+numBits should be in the range 1 to 800
    void setBits(int pos, int numBits, int value ) {
        for( int i = numBits-1; i >= 0; i--)
        {
            setBit( pos + i, ( value % 2 ) == 1 );
            value = value / 2;
        }
    }


	// Erge Solar specific constants 

	// Erge Solar is storing Plans as a collection of Rules. Each Rule has a Mode, StartTime and EndTime
	static const int numBitsForMode = 4;  // A Mode can take values 0 to 11
	static const int numBitsForTime = 11; // A StartTime or EndTime can take values 0 to 1439 minutes (there are 1449 minutes in a day)
	static const int numBitsForRule = numBitsForMode + numBitsForTime * 2; // 26 bytes for Mode, StartTime and EndTime
	static const int maxRules = sparkEepromBits / numBitsForRule; //30 rules
			
	static const int numRulesPerPlan = 12;
	static const int numBitsPerPlan = numRulesPerPlan * numBitsForRule; // 2 plans
	
	
	// Erge Solar specific functions

	// Store a rule
	//  'ruleNum' should be in the range 0 to maxRules-1
	//  'mode' should be in the range 0 to 11
	//  'startTimeMinute' should be in the range 0 to 1439 (=24*60-1)
	//  'stopTimeMinute' should be in the range 0 to 1439 (=24*60-1)
	void setRule( int ruleNum, int mode, int startTimeMinute, int stopTimeMinute )
	{
		setBits( ruleNum * numBitsForRule, numBitsForMode, mode );
		setBits( ruleNum * numBitsForRule + numBitsForMode, numBitsForTime, startTimeMinute );
		setBits( ruleNum * numBitsForRule + numBitsForMode + numBitsForTime, numBitsForTime, stopTimeMinute );
	}
	
	// Get a rule's mode
	//  'ruleNum' should be in the range 0 to maxRules-1
	int getRuleMode( int ruleNum )
	{
		return getBits( ruleNum * numBitsForRule, numBitsForMode );
	}
	
	// Get a rule's start time
	//  'startTimeMinute' should be in the range 0 to 1439 (=24*60-1)
	int getRuleStartTime( int ruleNum )
	{
		return getBits( ruleNum * numBitsForRule + numBitsForMode, numBitsForTime );
	}
	
	// Get a rule's stop time
	//  'stopTimeMinute' should be in the range 0 to 1439 (=24*60-1)	
	int getRuleStopTime( int ruleNum )
	{
		return getBits( ruleNum * numBitsForRule + numBitsForMode + numBitsForTime, numBitsForTime );
	}
	
};


void setup() {

    SparkEepromBitArray object;

	
	Serial.println("Test single bit set and get");
	object.setBit(0,true);
	object.setBit(1,true);
	object.setBit(2,false);
	object.setBit(3,true);
	object.setBit(3,false);
	
	boolean i0 = object.getBit(0);	
	boolean i1 = object.getBit(1);	
	boolean i2 = object.getBit(2);	
	boolean i3 = object.getBit(3);	
	
    Serial.println("i0: Expected true. Got " + i0);
    Serial.println("i1: Expected true. Got " + i1);
    Serial.println("i2: Expected false. Got " + i2);
    Serial.println("i3: Expected false. Got " + i3);
    
    
	Serial.println("Test multiple bit set and get");
    
	object.setBits(4, 13, 1234);	
	int i1234 = object.getBits(4,13);	

	object.setBits(17, 5, 7);	
	int i7 = object.getBits(17,5);	

	object.setBits(33,11, 1041);	
	int i1041 = object.getBits(33,11);	

	object.setBits(563,19, 84321);	
	int i84321= object.getBits(563,19);	
	
    Serial.println("i1234: Expected 1234. Got " + i1234);
    Serial.println("i7: Expected 7. Got " + i7);
    Serial.println("i1041: Expected 1041. Got " + i1041);
    Serial.println("i84321: Expected 84321. Got " + i84321);
    
    
	Serial.println("Test Erge Solar rule set and get");

    object.setRule( 1, 6, 0, 600 );
    int rule1_mode = object.getRuleMode( 1 );
	int rule1_startTime = object.getRuleStartTime( 1 );
	int rule1_stopTime = object.getRuleStopTime( 1 );
	
    object.setRule( 2, 7, 999, 1000 );
    object.setRule( 2, 1, 333, 444 );
    object.setRule( 2, 2, 1041, 1111 );
    object.setRule( 2, 5, 120, 240 );
    int rule2_mode = object.getRuleMode( 2 );
	int rule2_startTime = object.getRuleStartTime( 2 );
	int rule2_stopTime = object.getRuleStopTime( 2 );

    object.setRule( 3, 4, 180, 500 );
    int rule3_mode = object.getRuleMode( 3 );
	int rule3_startTime = object.getRuleStartTime( 3 );
	int rule3_stopTime = object.getRuleStopTime( 3 );

    object.setRule( 29, 3, 1400, 1439 );
    int rule29_mode = object.getRuleMode( 29 );
	int rule29_startTime = object.getRuleStartTime( 29 );
	int rule29_stopTime = object.getRuleStopTime( 29 );

    Serial.println("rule1_mode: Expected 6. Got " + rule1_mode);
    Serial.println("rule1_startTime: Expected 0. Got " + rule1_startTime);
    Serial.println("rule1_stopTime: Expected 600. Got " + rule1_stopTime);	
    
    Serial.println("rule2_mode: Expected 5. Got " + rule2_mode);
    Serial.println("rule2_startTime: Expected 120. Got " + rule2_startTime);
    Serial.println("rule2_stopTime: Expected 240. Got " + rule2_stopTime);	    
	
	Serial.println("rule3_mode: Expected 4. Got " + rule3_mode);
    Serial.println("rule3_startTime: Expected 180. Got " + rule3_startTime);
    Serial.println("rule3_stopTime: Expected 500. Got " + rule3_stopTime);	
	
	Serial.println("rule29_mode: Expected 3. Got " + rule29_mode);
    Serial.println("rule29_startTime: Expected 1400. Got " + rule29_startTime);
    Serial.println("rule29_stopTime: Expected 1439. Got " + rule29_stopTime);	

}

void loop() {

}

I couldn’t find another post that duplicates this approach. So I thought people might find the class useful. Enjoy