Sparkfun Contest – BeatBag

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!