About
This is my entry to Sparkfun’s contest. More info here: https://www.sparkfun.com/news/2115
Software
I made a proof of concept using Python 2.7.10 and then I ported my algorithm to Arduino.
- 3-77hits.TXT: 73 hits
- 4-81hits.TXT: 80 hits
- 5-93hits.TXT: 83 hits
- 6-79hits.TXT: 77 hits
- MysteryDataSet-1.TXT: 177 hits
- MysteryDataSet-2.TXT: 166 hits
My hits are lower than the real. I think that they might be some lost hits because of accelerometer timeout.
Python
This is a simple Python script that I coded to check the number of hits. With this code my results were:
''' Crowdsourcing Algorithms: BeatBag - A Speed Bag Counter Gerardo Carmona https://makeroboticsprojects.wordpress.com/ 6/30/2016 License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license). This is my attempt to get a better hit counter algorithm using a raw data from an accelerometer. More info https://www.sparkfun.com/news/2115 ''' # Import libraries import math # Variables c = 0 flag = 0 r = 0 xa=0 ya=0 za=0 x=0 y=0 z=0 e=0 # Files, uncomment file that you want to test. File needs to be # on the same folder as this script FILE_NAME = '3-77hits.TXT' # FILE_NAME = '4-81hits.TXT' # FILE_NAME = '5-93hits.TXT' # FILE_NAME = '6-79hits.TXT' # FILE_NAME = 'MysteryDataSet-1.TXT' # FILE_NAME = 'MysteryDataSet-2.TXT' file = open(FILE_NAME, 'r') for line in file: #print line words = line[:-1].split(',') if not words[0][0] == 'A': # Save previous values xa = x ya = y za = z # Get new values x = (float(words[1])) y = (float(words[2])) z = (float(words[3])) dx = x - xa dy = y - ya dz = z - za r = math.sqrt(dx**2+dy**2+dz**2) flag = flag + 1 #print r # Tuning this values could give better results if r > 900 and flag > 100: c = c + 1 flag = 0 else: e = e + 1 print FILE_NAME print c print '----' print "Acc reading errors:", e
Arduino
I don’t have an accelerometer, so I was not able to test my code. Any suggestions/comments/bugs are welcome!
/* Crowdsourcing Algorithms: BeatBag - A Speed Bag Counter Gerardo Carmona https://makeroboticsprojects.wordpress.com/ 6/30/2016 License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license). This is a modified version of Nate's code. More info https://www.sparkfun.com/news/2115 I tried to mantein same code with different algorithm to calculate the number of hits. I didn't tried the code because I don't have an accelerometer. The base code to prove my algorithm is within this repository */ #include <avr/wdt.h> //We need watch dog for this program #include <Wire.h> // Used for I2C #include <SparkFun_MMA8452Q.h> #include <math.h> #define DISPLAY_ADDRESS 0x71 //I2C address of OpenSegment display MMA8452Q accel; int hitCounter = 0; //Keeps track of the number of hits const int resetButton = 6; //Button that resets the display and counter const int LED = 13; //Status LED on D3 long lastPrint; //Used for printing updates every second boolean displayOn; //Used to track if display is turned off or not //Used in the new algorithm float lastMagnitude = 0; float lastFirstPass = 0; float lastSecondPass = 0; float lastThirdPass = 0; long lastHitTime = 0; int secondsCounter = 0; // int prevX; int dx; int prevY; int dy; int prevZ; int dz; float r; //This was found using a spreadsheet to view raw data and filter it const float WEIGHT = 0.9; //This was found using a spreadsheet to view raw data and filter it const int MIN_MAGNITUDE_THRESHOLD = 900; //This is the minimum number of ms between possible hits //We use this to filter out peaks that are too close together const int MIN_TIME_BETWEEN_HITS = 180; //This is the number of miliseconds before we turn off the display long TIME_TO_DISPLAY_OFF = 60L * 1000L * 5L; //5 minutes of no use int DEFAULT_BRIGHTNESS = 50; //50% brightness to avoid burning out segments after 3 years of use unsigned long currentTime; //Used for millis checking void initDisplay(); void setBrightness(int brightness); void resetDisplay(); void printHits(); void clearDisplay(); void setup() { wdt_reset(); //Pet the dog wdt_disable(); //We don't want the watchdog during init pinMode(resetButton, INPUT_PULLUP); pinMode(LED, OUTPUT); //By default .begin() will set I2C SCL to Standard Speed mode of 100kHz Wire.setClock(400000); //Optional - set I2C SCL to High Speed Mode of 400kHz Wire.begin(); //Join the bus as a master Serial.begin(115200); Serial.println("Speed Bag Counter"); initDisplay(); clearDisplay(); Wire.beginTransmission(DISPLAY_ADDRESS); Wire.print("Accl"); //Display an error until accel comes online Wire.endTransmission(); while(!accel.init()) //Test and intialize the MMA8452 ; //Do nothing clearDisplay(); Wire.beginTransmission(DISPLAY_ADDRESS); Wire.print("0000"); Wire.endTransmission(); lastPrint = millis(); lastHitTime = millis(); wdt_enable(WDTO_250MS); //Unleash the beast } void loop() { wdt_reset(); //Pet the dog currentTime = millis(); if ((unsigned long)(currentTime - lastPrint) >= 1000) { if (digitalRead(LED) == LOW) digitalWrite(LED, HIGH); else digitalWrite(LED, LOW); lastPrint = millis(); } //See if we should power down the display due to inactivity if (displayOn == true) { currentTime = millis(); if ((unsigned long)(currentTime - lastHitTime) >= TIME_TO_DISPLAY_OFF) { Serial.println("Power save"); hitCounter = 0; //Reset the count clearDisplay(); //Clear to save power displayOn = false; } } //Still alive? if (accel.available()) { //Save previous readings prevX = accel.x; prevY = accel.y; prevZ = accel.z; //Check the accelerometer accel.read(); //Caluculate the change between last reading and actual reading dx = accel.x - prevX; dy = accel.y - prevY; dz = accel.z - prevZ; //Get the magnutude of the changes. r = (float)sqrt(dx*dx+dy*dy+dz*dz); currentTime = millis(); if (r > MIN_MAGNITUDE_THRESHOLD) { //We have a potential hit! currentTime = millis(); if ((unsigned long)(currentTime - lastHitTime) >= MIN_TIME_BETWEEN_HITS) { hitCounter++; if (displayOn == false) displayOn = true; printHits(); //Updates the display } } } //Check if we need to reset the counter and display if (digitalRead(resetButton) == LOW) { //This breaks the file up so we can see where we hit the reset button Serial.println(); Serial.println(); Serial.println("Reset!"); Serial.println(); Serial.println(); hitCounter = 0; resetDisplay(); //Forces cursor to beginning of display printHits(); //Updates the display while (digitalRead(resetButton) == LOW) wdt_reset(); //Pet the dog while we wait for you to remove finger //Do nothing for 250ms after you press the button, a sort of debounce for (int x = 0 ; x < 25 ; x++) { wdt_reset(); //Pet the dog delay(10); } } } //This function makes sure the display is at 57600 void initDisplay() { resetDisplay(); //Forces cursor to beginning of display printHits(); //Update display with current hit count displayOn = true; setBrightness(DEFAULT_BRIGHTNESS); } //Set brightness of display void setBrightness(int brightness) { Wire.beginTransmission(DISPLAY_ADDRESS); Wire.write(0x7A); // Brightness control command Wire.write(brightness); // Set brightness level: 0% to 100% Wire.endTransmission(); } void resetDisplay() { //Send the reset command to the display - this forces the cursor to return to the beginning of the display Wire.beginTransmission(DISPLAY_ADDRESS); Wire.write('v'); Wire.endTransmission(); if (displayOn == false) { setBrightness(DEFAULT_BRIGHTNESS); //Power up display displayOn = true; lastHitTime = millis(); } } //Push the current hit counter to the display void printHits() { int tempCounter = hitCounter / 2; //Cut in half Wire.beginTransmission(DISPLAY_ADDRESS); Wire.write(0x79); //Move cursor Wire.write(4); //To right most position Wire.write(tempCounter / 1000); //Send the left most digit tempCounter %= 1000; //Now remove the left most digit from the number we want to display Wire.write(tempCounter / 100); tempCounter %= 100; Wire.write(tempCounter / 10); tempCounter %= 10; Wire.write(tempCounter); //Send the right most digit Wire.endTransmission(); //Stop I2C transmission } //Clear display to save power (a screen saver of sorts) void clearDisplay() { Wire.beginTransmission(DISPLAY_ADDRESS); Wire.write(0x79); //Move cursor Wire.write(4); //To right most position Wire.write(' '); Wire.write(' '); Wire.write(' '); Wire.write(' '); Wire.endTransmission(); //Stop I2C transmission }
Get the code here!