Keep track of number of boots (complete demo code)

Hi all,
the idea of counting the boots popped up in this forum recently. I found it interesting, so as an exercise I wrote a quick sketch that does just that. This apparently simple task made me think about some interesting problems, and their solutions, which can have broader applicability (e.g. how to check if the data read from eeprom is not garbage).

So for anyone interested, here's the code.
Just set the serial monitor to 9600, upload and enjoy :slight_smile:

// This sketch counts the number of times the board has booted.

#include <EEPROM.h>

#define SKETCH_NAME "Boot count test"
#define SIGNATURE 0xAE        // arbitrary "magic" number to ensure we detect uninitialized eeprom cells
#define DATA_ADDRESS 0        // this is where we store our data; could be anything between 0 and 1023-sizeof(data)

// uncomment to make the sketch tell you what's going on...
#define _VERBOSE

// data we'll put into eeprom
struct boot_count_t {
    unsigned long value;
    uint8_t signature;
    uint8_t checksum;
};


// sum num_bytes bytes modulo 255 starting from address ptr
uint8_t naive_checksum(const void* ptr, unsigned int num_bytes) {
    uint8_t* p;
    uint8_t result;
    unsigned int i;
    
    result = 0;
    p = (uint8_t*)ptr;
    for (i = 0; i < num_bytes; i++) {
        result += *p;
        p++;
    }
    
    return result;
}


uint8_t struct_checksum(const struct boot_count_t* s) {
    return naive_checksum(s, sizeof(*s) - sizeof(s->checksum));
}


boolean is_struct_good(const struct boot_count_t* s) {
    return (s->signature == SIGNATURE) && (s->checksum == struct_checksum(s));
}


void set_value(struct boot_count_t* s, unsigned long value) {
    s->signature = SIGNATURE;
    s->value = value;
    s->checksum = struct_checksum(s);
}


unsigned long get_value(const struct boot_count_t* s) {
    return s->value;
}


void read_struct(struct boot_count_t* s) {
    unsigned int i;
    uint8_t b;
    uint8_t* p;
    
    p = (uint8_t*)s;
    for (i = 0; i < sizeof(*s); i++) {
        b = EEPROM.read(DATA_ADDRESS + i);
        *p = b;
        p++;
    }
}


void write_struct(const struct boot_count_t* s) {
    unsigned int i;
    uint8_t* p;

    p = (uint8_t*)s;
    for (i = 0; i < sizeof(*s); i++) {
        EEPROM.write(DATA_ADDRESS + i, *p);
        p++;
    }
}


// stores the struct into eeprom, and returns true if the verification succeded
// false means the data was not correctly written or read back from eeprom
boolean write_struct_and_verify(const struct boot_count_t* s) {
    struct boot_count_t aux;
    
    write_struct(s);
    read_struct(&aux);
    return (memcmp(s, &aux, sizeof(aux)) == 0);
}


void eeprom_dump(unsigned int start_addr = 0, unsigned int end_addr = 1023) {
    unsigned int i;
    
    if (start_addr > end_addr) {
        return;
    }
    
    if (end_addr >= 1024) {
        end_addr = 1023;
    }
    
    for (i = start_addr; i <= end_addr; i++) {
        Serial.print(EEPROM.read(i), HEX);
        if (((i+1) % 8 == 0)) {
            Serial.println();
        }
        else {
            Serial.print(" ");
        }
    }
}


void print_struct(const struct boot_count_t* s) {
    Serial.print("value = ");
    Serial.println(s->value);
    Serial.print("signature = ");
    Serial.println(s->signature, HEX);
    Serial.print("checksum = ");
    Serial.println(s->checksum, HEX);
}


struct boot_count_t boot_count;


void setup() {
    boolean b;
  
    Serial.begin(9600);
    Serial.println(SKETCH_NAME);
  
    delay(1000);
  
#ifdef _VERBOSE
    /* verbose mode ;-) */
    Serial.print("Reading struct... ");
    read_struct(&boot_count);
    Serial.println("done.");
   
    Serial.print("Checking data consistency... ");
    if (!is_struct_good(&boot_count)) {
        Serial.println("NOT ok: initializing");
        set_value(&boot_count, 0);
    }
    else {
        Serial.println("ok");
    }
  
    Serial.println("Data printout:");
    print_struct(&boot_count);
  
    Serial.print("Incrementing boot count... ");
    set_value(&boot_count, get_value(&boot_count) + 1);
    Serial.println("done.");

    Serial.println("Data printout:");
    print_struct(&boot_count);

    Serial.print("Writing+verifying... ");
    b = write_struct_and_verify(&boot_count);
    if (b) {
        Serial.println("OK");
    }
    else {
        Serial.println("ERROR: eeprom is probabl corrupt!");
    }
  
    Serial.println("Final memory dump:");
    eeprom_dump(0, 255);
    
#else
    
    // normal operation: just print the number of boots so far, including this one
    read_struct(&boot_count);
    if (!is_struct_good(&boot_count)) {
        set_value(&boot_count, 0);            // initialize if needed
    }
    set_value(&boot_count, get_value(&boot_count) + 1);        // incr. number of boots
    
    Serial.print("Boot count: ");                        // print it
    Serial.println(get_value(&boot_count));
    
    if (!write_struct_and_verify(&boot_count)) {        // save new value and notify in case of errors
        Serial.println("Error writing/verifying: EEPROM is probably corrupt!");
    }

#endif
}


void loop() {
}

Almost forgot: comments and critics are welcome :slight_smile:

Cool.

Just a point, not all Arduino processors have 1023 bytes of EEPROM, should there be a #define for the size?

I expect that'll work, but it seems to be somewhat over-engineered. Perhaps you'd like to take another challenge:

Provide the same basic functionality (count the number of boots) with the minimum code complexity.

Just a point, not all Arduino processors have 1023 bytes of EEPROM, should there be a #define for the size?

Yes. Or better yet, use the predefined symbols to determine where we're going to land on, and act accodingly:

Thanks for making me think (and google) about that!

PeterH:
I expect that'll work, but it seems to be somewhat over-engineered. Perhaps you'd like to take another challenge:

Provide the same basic functionality (count the number of boots) with the minimum code complexity.

As I said, that was an exercise, so I pushed it a bit too far to tackle some problems that came to mind while coding.
In that sketch there are ideas for 2 or 3 libraries.

Your suggestion for the next challenge is... challenging :slight_smile:

Using the processor defines. Blech. Use E2END instead.

Interesting!

http://www.nongnu.org/avr-libc/user-manual/group__avr__io.html

Thanks for mentioning E2END.