Data Logging to the Arduino EEPROM
I made the first successful run with the data logger yesterday. I’ve wired up a hall sensor to the wheel of my bike and recorded 60s intervals of my ride into work and back. Ideally I’m looking for increased resolution over a typical bicycle computer; where a low cost CatEye gives you average speed over the course of a total ride, I’d like to see how that average speed changes in increments. I recognize I wouldn’t be able to compete with a device such as a Garmin, but I could definitely improve on the $30 devices you get at bike stores.
The first issue was how to deal with the intermittent pickups of the Hall Sensor. If the sensor fired too rapidly and the loop method wasn’t in the right position you wouldn’t pick up the digital I/O change. The solution, offered by Blalor, was to use the Arduino Interrupt commands. The Interrupt fires whenever a digital I/O signal is received. In this case I receive a 1 every loop. I’d be looking for a 0, so I’d set the Interrupt to look for a FALLING attribute.
The EEPROM was the next issue. 512 bytes is a serious limitation. Some math was required to see if I what wanted to do was even possible. If I were to poll every minute over a ride I’d be able to record 8.5 hours of one byte readings. That’s pretty good, seeing a 200km ride would take approximately 6 hours.
One byte is capable of storing 256 possible values. Is that enough, or would I need more values? If my revolutions were 255 with a wheel circumference of 2110mm I’d be traveling an average speed of 32km/h. Clearly not enough. I’d need to peak at 70km/h, with an average at maybe 30-40km/h.
Is it possible to compress a value of over 256 into a byte? Neil sent along an interesting piece of code on Octet Packed Integers. This code is a little beyond my comprehension so I gave it a pass. Instead it was decided to use a second byte. This allows a max value of 65535 rotations to be recorded — and at a circumference of 2110mm a max speed of 8257km/h. Well over the speed of sound! That should give me ample room to accelerate.
Problem is the second byte reduces my overall recording time to 4.25 hours. I also decided to remove 2 bytes from the array so I could track my counter when the device was turned off. When you start it back up it starts recording at the next address from where you left off. One possible solution to the space issue is to externalize the EEPROM and use the wire.h library to communicate. This would also reduce the issue of each address of the EEPROM only be written (and guaranteed) 100,000 times.
Neil also helped with the splitting of the rotation counter across two bytes. We took the full rotation count and masked it out with a hex value of 0x00FF and stored that as an int low. Then the full rotation count was bit-shifted right ( << 8 ) and stored as int high. When the report is generated after the ride the values are recombined and reported back to the screen.
I also added in three indicator LEDs for the Hall pickup, reset button, and report button. The reset button when held down for three seconds deletes all the data on the EEPROM, anything less than that will generate a report of the current EEPROM memory.
I’ve included the code below, any suggestions on improvements would be much appreciated.
#include #define PIN_SENSOR_HALL 2 //Hall Sensor #define PIN_LED_HALL 13 //Hall indicator LED #define PIN_LED_RESET 6 //Reset indicator LED #define PIN_LED_RESET_COMPLETE 5 //Reset complete indicator #define PIN_BUTTON_RESET 7 //Reset/Report Button #define TIME_INTERVAL 60000 //EEPROM write interval (60s) #define MEM_SIZE 510 //EEPROM memory size (remaining 2 bytes reserved for count) long countReset = -1; long countCurrent = -1; int countEEPROM = 0; //EEPROM Address Count volatile int countRotation = 0; //Rotation Counter void setup() { pinMode(PIN_SENSOR_HALL, INPUT); pinMode(PIN_LED_HALL, OUTPUT); pinMode(PIN_LED_RESET, OUTPUT); pinMode(PIN_LED_RESET_COMPLETE, OUTPUT); pinMode(PIN_BUTTON_RESET, INPUT); Serial.begin(115200); countEEPROM = EEPROM.read(512); //Set counter to last known free space reportEEPROM(); //Report all EEPROM entries } void loop() { digitalWrite(PIN_LED_HALL, LOW); digitalWrite(PIN_LED_RESET_COMPLETE, HIGH); //Write to EEPROM at specified interval if ((millis() % TIME_INTERVAL) == 0) writeEEPROM(); //If Hall Sensor triggered then init the test attachInterrupt(1, incrementRotation, FALLING); resetEEPROM(); } void reportEEPROM() { Serial.println("EEPROM Report: "); Serial.print("["); for (int i = 0; i < (MEM_SIZE - 1); i = (i + 2)) { int h = EEPROM.read(i) < < 8; int l = EEPROM.read(i + 1); Serial.print(h + l); if (i != (MEM_SIZE - 1)) Serial.print(","); } Serial.println("]"); } void incrementRotation() { countRotation++; digitalWrite(PIN_LED_HALL, HIGH); Serial.print("Rotations: "); Serial.println(countRotation); } void writeEEPROM() { byte low = countRotation & 0x00FF; byte high = countRotation >> 8; Serial.print("EEPROM Address: "); Serial.print(countEEPROM); Serial.print(", High: "); Serial.print(high, BIN); Serial.print(", Low: "); Serial.print(low, BIN); Serial.print(", Stored: "); Serial.print(low + (high < < 8), DEC); Serial.println(";"); EEPROM.write(countEEPROM, high); EEPROM.write(countEEPROM + 1, low); if ((countEEPROM + 2) < MEM_SIZE) countEEPROM = countEEPROM + 2; //Increment the EEPROM Address else countEEPROM = 0; //Loop back to start address EEPROM.write(512, countEEPROM); countRotation = 0; //Reset the rotation count } void resetEEPROM() { if (digitalRead(PIN_BUTTON_RESET) == LOW) { digitalWrite(PIN_LED_RESET, LOW); if (countReset == -1) //Start Reset counter only if -1 countReset = millis(); } else { digitalWrite(PIN_LED_RESET, HIGH); if (countReset != -1) { countCurrent = millis(); if ((countCurrent - countReset) > 3000) { Serial.println("Reset"); for (int i = 0; i < MEM_SIZE; i++) { EEPROM.write(i, 0); } digitalWrite(PIN_LED_RESET_COMPLETE, LOW); delay(1000); digitalWrite(PIN_LED_RESET_COMPLETE, HIGH); } reportEEPROM(); countCurrent = countReset = -1; } } }
The post ride report looks something like this (values are in rotations per minute):
EEPROM Report: [-31156,310,332,300,392,306,216,598,228,516,390,282,254,354, 496,198,436,60,254,263,400,382,280,394,362,340,300,306,256, 380,354,297,284,20,72,378,486,450,104,398,522,372,370,133,366, 360,164,336,460,542,488,366,396,384,512,484,300,300,332,346, 344,360,398,226,360,390,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
There appears to be some irrelevant data in the mix (see the array item 0 for example) which I haven’t quite figured out. The numbers don’t quite calculate to the precision I expect from a cycle computer. I have a feeling the Hall count is firing multiple times per revolution — like it has a FALLING state more than once.
Here is a quick JavaScript rendition of the data in a bar chart format. The RPMs are converted using this equation:
(((rpm * wheel circumference) / 1000) / 1000) * 60 = km/h
The next phase is to gather data on multiple days for the same course and comparing the results for analysis. I’m looking at developing an Adobe AIR Project which would allow me to keep an offline/online database. Getting real time data is another big incentive and I’d be looking at the XBee solution for that. Being able to record heart rate data and GPS/Digital Compass are also interesting but the limitations of the EEPROM don’t easily allow for recording of data that complex. So I think hooking up an SDCard is the next most probable option. I’ve already tried a USB adapter but the Arduino does not have USB host capabilities, so that didn’t work. There are options to get the USB host to work, but it involves purchasing another PCB. Finally, I don’t think any of this makes much sense without introducing a mapping API to add context to the data. So there’s still lots more to come!
