edge lab LIVE! starts Tuesday in Munich. Grab a workshop seat and take home the board you build on. Code FRIEND-HACKSTER saves 20%. () Learn more!
zeijlonsystems
Published © GPL3+

Azande Gball

How hard can you throw a ball In this project, we discover the wonderful world of acceleration.

IntermediateShowcase (no instructions)11,705
Azande Gball

Things used in this project

Hardware components

Arduino MKR1000
Arduino MKR1000
×1
SparkFun Triple Axis Accelerometer Breakout - ADXL345
SparkFun Electronics SparkFun Triple Axis Accelerometer Breakout - ADXL345
×1
Li-Po-Battery 3.7 V 1200 mA
×1
Jumper wires (generic)
Jumper wires (generic)
×1
6" Foam Ball
×1

Story

Read more

Schematics

The Circuit

Code

Gball

Arduino
This is the sketch running on MKR1000. Gball is one of the examples included in Azande Library.
/* Gball
 * In this example we are demonstrating how to use the Azande Library together with the accelerometer ADXL345.
 * The ADXL senses acceleration force in 3 directions X, Y and Z.
 * 
 * Example Specification:
 * https://zeijlonsystems.se/products/azande/specification/ArduinoExampleGball.html
 * 
 * Following functions are demonstrated in this example:
 * - Azande
 * - Connect Wifi to Azande
 * - ADXL345, 3-Axis Digital Accelerometer
 * - Timer Interrupt with help from Timer5 library
 * 
 * Dependencies
 * Following libraries are used and need to be installed in your Arduino IDE:
 * - WiFi101                https://github.com/arduino-libraries/WiFi101
 * - SparkFun_ADXL345       https://github.com/sparkfun/SparkFun_ADXL345_Arduino_Library
 * - Timer5                 https://github.com/michael71/Timer5
 * 
 * Hardware
 * In this example we have used:
 * - one MKR1000
 * - one ADXL345
 * - one Battery
 * 
 * The circuit: 
 *    ADXl345       Arduino Board     
 *    Pin           Pin             Type
 *    SCL           SCL             IC Serial Clock
 *    SDA           SDA             IC Serial Data
 *    Vcc           VCC             Supply
 *    Gnd           GND             Supply
 * 
 * 
 *  Install Azande Studio:
 *  https://zeijlonsystems.se/products/azande/specification/DownloadandInstallAzandeStudio.html
 */


#include <SparkFun_ADXL345.h>
#include <Azande.h>
#include <WiFi101.h>
#include <Timer5.h>
#include <math.h>
#include "arduino_secrets.h" // This file must be updated with your personal credentials.

// Default Calibration Parameters for your ADXL345 ==============
#define DefaultRange      16 // Range settings. Accepted values are 2,4,8 or 16 ==>> +/-2,4,8 or 16g range 
#define DefaultOffsetX    -21
#define DefaultOffsetZ    142
#define DefaultOffsetY    -4
#define DefaultGainMs2X   3.924567
#define DefaultGainMs2Y   3.822630
#define DefaultGainMs2Z   3.160041


// Config: Azande Studio Socket Server ===========================
// See Window/Settings/Socket Server in Azande Studio.
char myPcNetworkName[] = "ZEIJLON-SYSTEMS"; // Host Name
unsigned int azandeStudioServerPortNr = 41414;  // Port number

//#define USE_SERIAL_DEBUG // Comment this row if you don't want the debug texts on 'Serial'

// Config: Wi-Fi Connection ======================================
char ssid[] = SECRET_SSID;  // your network SSID (name)
char pass[] = SECRET_PASS;  // your network password (use for WPA, or use as key for WEP)

// Variables: Azande Studio Socket Server. =======================
WiFiClient azandeSocketClient;

// 'Main Menu' ============================================
// Azande Features
define_enum_command(cmdMainMenu, "Main Menu", MainMenu, 0,\
   define_enum_item(1, "Falling") \
   define_enum_item(2, "Spherical") \
   define_enum_item(3, "Setup") \
   define_enum_item(4, "Sleep") );
define_double_event(Xms2Event, "X", 6, "m/s", -20, 20, "F2" );
define_double_event(Yms2Event, "Y", 6, "m/s", -20, 20, "F2" );
define_double_event(Zms2Event, "Z", 6, "m/s", -20, 20, "F2" );

// Main States
typedef enum
{
  State_MainMenu,
  State_FallTime,
  State_Spherical,
  State_SetupMenu,
  State_Calibrating,
  State_Sleeping
}StateType;
StateType state;

// 'Setup' =================================================
// 'Setup' Azande Features
define_enum_command(cmdSetupMenu, "Setup", SetupMenu, 0,\
   define_enum_item(1, "Calibration: START") \
   define_enum_item(2, "Calibration: END") \
   define_enum_item(3, "Use Default Calibration") \
   define_enum_item(4, "Use Saved Calibration") \
   define_enum_item(5, "BACK to Main Menu"));
define_text_event(textEvent, "Feedback", 0, 64 );

define_int_event(rawXevent, "Raw X", 6, , , ,  );
define_int_event(rawYevent, "Raw Y", 6, , , ,  );
define_int_event(rawZevent, "Raw Z", 6, , , ,  );

define_int_event(CalOffsetXevent, "X Offset", 8, , , ,  );
define_int_event(CalOffsetYevent, "Y Offset", 8, , , ,  );
define_int_event(CalOffsetZevent, "Z Offset", 8, , , ,  );

define_double_event(CalGainXevent, "X Gain", 9, , , , "F4"  );
define_double_event(CalGainYevent, "Y Gain", 9, , , , "F4"  );
define_double_event(CalGainZevent, "Z Gain", 9, , , , "F4"  );

define_enum_command(cmdSetRange, "Set Range", SetRange, 4,\
   define_enum_item(2,  "+/- 2G (20m/s)") \
   define_enum_item(4,  "+/- 4G (40m/s)") \
   define_enum_item(8,  "+/- 8G (80m/s)") \
   define_enum_item(16, "+/- 16G (160m/s)"));

// 'Setup' Text Messages
char * message;
char msgSetupMenu[] = "Setup";
char msgStartCalibration[] = "Calibration Started";
char msgSaveCalibration[] = "Calibration Saved";
char msgUseDefault[] = "Use Default";
char msgUseSaved[] = "Use Saved";
char msgRangeSet2g[] = "Range +/-2G";
char msgRangeSet4g[] = "Range +/-4G";
char msgRangeSet8g[] = "Range +/-8G";
char msgRangeSet16g[] = "Range +/-16G";

// 'Setup' Min and Max while calibrating
int rawMinX;
int rawMaxX;
int rawMinY;
int rawMaxY;
int rawMinZ;
int rawMaxZ;

// 'Setup' Saved values after calibration
int offsetXSaved;
int offsetYSaved;
int offsetZSaved;
double gainMs2XSaved;
double gainMs2YSaved;
double gainMs2ZSaved;


// 'Fall Time' ========================================================
// 'Fall Time' Azande Features
define_enum_command(FallMenuCmd, "Fall Time", FallMenu, 0,\
   define_enum_item(1, "BACK to Main Menu"));
define_int_event(FallTimeEvent, "Fall Time", 4, "ms", , ,  );
define_double_event(FallDistanceEvent, "Fall Distance", 5, "cm", , , "F1" );

// 'Fall Time' Config
#define FALL_UPPER 2.5 // When acceleration is more than FALL_UPPER, the fall have ended.
#define FALL_LOWER 1.5 // When acceleration is less than FALL_LOWER, the fall have started.

// 'Fall Time' Variables
volatile long interruptCounter; // incremented in timer interrupt
volatile long latestFallTime = 0;
volatile double latestFallDistance = 0;
bool isFalling = false;

// 'Spherical' ========================================================
// 'Spherical' Azande Features
define_enum_command(SphericalMenuCmd, "Spherical", SphericalMenu, 0,\
   define_enum_item(1, "BACK to Main Menu"));
define_double_event(PhiEvent, "PHI angle", 14, "", 0, 180, "F1" );
define_double_event(ThetaEvent, "THETA angle", 14, "", -180, 180, "F1" );
define_double_event(RadialEvent, "Radial Acceleration", 14, "m/s", 0, 200, "F1" );

// 'Spherical' Variables
double SphericalPhi = 0;
double SphericalTeta = 0;
double SphericalR = 0;

Azande azande(azandeSocketClient);  // Create Azande object with "Socket Client" as "Stream".
ADXL345 adxl = ADXL345(); // USE FOR I2C COMMUNICATION

// Gravity Constants ====================================
#define OneGms2         9.81
#define OneHalfGms2     9.81 / 2.0

// Conversion Variables =================================
int offsetX = DefaultOffsetX;
int offsetY = DefaultOffsetY;
int offsetZ = DefaultOffsetZ;
double gainMs2X = DefaultGainMs2X;
double gainMs2Y = DefaultGainMs2Y;
double gainMs2Z = DefaultGainMs2Z;

// Read Values ==========================================
double ms2X = 0; // raw X value converted to m/s
double ms2Y = 0; // raw Y value converted to m/s
double ms2Z = 0; // raw Z value converted to m/s
int rawX;
int rawY;
int rawZ;


void setup()
{
  waitForSerialIfAny();

  if (WiFi.status() == WL_NO_SHIELD) 
  {
    debugPrintln("WiFi shield not present");
    while (true); // don't continue:
  }

  tryConnectToWifiNetwork();        // Blocks until connection succeed
  tryConnectToAzandeStudioServer(); // Blocks until connection succeed

  azande.begin();
  gotoMainMenu();
  
  adxl.powerOn();                     // Power on the ADXL345
  adxl.setRangeSetting(DefaultRange);           

  MyTimer5.begin(200);  // 200=for toggle every 5msec
  MyTimer5.attachInterrupt(Timer5_IRQ);
  MyTimer5.start();
}

void loop()
{
  if (state == State_Sleeping)
  {
    delay(500);
    ReadFromSensor();
    SphericalR = sqrt((ms2X * ms2X) + (ms2Y * ms2Y) + (ms2Z * ms2Z));
    if (SphericalR < FALL_UPPER) // If we are Free Falling wakeup
    {
      tryConnectToWifiNetwork();        // Blocks until connection succeed
      tryConnectToAzandeStudioServer(); // Blocks until connection succeed
      gotoMainMenu();
    }
  }
  else if (!azandeSocketClient.connected())
  {
    // if the server is disconnected, try to re-connect.
    debugPrintln("Lost connection with server");
    tryConnectToAzandeStudioServer();
    gotoMainMenu();
  }
  else
  {
    switch (state)
    {
      case State_Spherical:
        ReadFromSensor();
        SphericalR = sqrt((ms2X * ms2X) + (ms2Y * ms2Y) + (ms2Z * ms2Z));
        if (SphericalR != 0.0) // Avoid division with zero
        {
          SphericalPhi = (acos(ms2Z / SphericalR) * 180) / PI;
        }
        else
        {
          SphericalPhi = 0;
        }
        SphericalTeta = (atan2(ms2Y, ms2X) * 180) / PI ;
        azande.send(PhiEvent, SphericalPhi);
        azande.send(ThetaEvent, SphericalTeta);
        azande.send(RadialEvent, SphericalR);
        break;
        
      case State_SetupMenu:
        ReadFromSensor();
        azande.send(rawXevent, rawX);
        azande.send(rawYevent, rawY);
        azande.send(rawZevent, rawZ);
        azande.send(textEvent, message);
        break;

      case State_Calibrating:
        ReadFromSensor();
        azande.send(rawXevent, rawX);
        azande.send(rawYevent, rawY);
        azande.send(rawZevent, rawZ);
        azande.send(textEvent, message);

        if (rawX < rawMinX) rawMinX = rawX;
        if (rawY < rawMinY) rawMinY = rawY;
        if (rawZ < rawMinZ) rawMinZ = rawZ;
        if (rawX > rawMaxX) rawMaxX = rawX;
        if (rawY > rawMaxY) rawMaxY = rawY;
        if (rawZ > rawMaxZ) rawMaxZ = rawZ;
        break;

      case State_FallTime:
        azande.send(RadialEvent, SphericalR);
        azande.send(FallTimeEvent, latestFallTime);
        azande.send(FallDistanceEvent, latestFallDistance);
        break;

      default:
      case State_MainMenu:
        ReadFromSensor();
        azande.send(Xms2Event, ms2X);
        azande.send(Yms2Event, ms2Y);
        azande.send(Zms2Event, ms2Z);
    }

    azande.readStream();
  }

  delay(5);
}


void Timer5_IRQ(void) 
{
  if (state == State_FallTime)
  {
    ReadFromSensor();
    SphericalR = sqrt((ms2X * ms2X) + (ms2Y * ms2Y) + (ms2Z * ms2Z));
    
    if (isFalling)
    {
      interruptCounter++;
      if (SphericalR > FALL_UPPER)
      {
        isFalling = false;
        latestFallTime = interruptCounter * 5;

        // Calculate the distance of the fall. Note that we assume that the ball have velocity=0 when fall starts.
        latestFallDistance = 100.0 * (OneGms2 * ((double)latestFallTime / 1000.0) * ((double)latestFallTime / 1000.0)) / 2.0; 
      }
    }
    else
    {
      if (SphericalR < FALL_LOWER)
      {
        isFalling = true;
        interruptCounter = 0;
      }
    }
  }
}

void ReadFromSensor(void)
{
    adxl.readAccel(&rawX, &rawY, &rawZ);         // Read the accelerometer values and store them in variables declared above x,y,z
  
    // Calculate m/s with help of calibration parameters.
    ms2X = (rawX - offsetX)/gainMs2X;         
    ms2Y = (rawY - offsetY)/gainMs2Y;
    ms2Z = (rawZ - offsetZ)/gainMs2Z;
}

void tryConnectToWifiNetwork()
{
  int status = WL_IDLE_STATUS;

  debugPrint("Attempting to connect to SSID: ");
  debugPrintln(ssid);
  status = WiFi.begin(ssid, pass);
  while (status != WL_CONNECTED) 
  {
    delay(10000); // Wait 10s
    debugPrint("Attempting to connect to SSID: ");
    debugPrintln(ssid);
    status = WiFi.begin(ssid, pass);
  }
  debugPrintln("Connected to wifi");
  printWiFiStatus();
}

void tryConnectToAzandeStudioServer()
{
  debugPrintln("Starting connection to Azande Studio Server...");
  azandeSocketClient.connect(myPcNetworkName, azandeStudioServerPortNr);
  while (!azandeSocketClient.connected())
  {
    debugPrintln("Failed to connect to Azande Studio Server.");
    delay(5000); // wait 5s
    debugPrintln("Retry connection to Azande Studio Server...");
    azandeSocketClient.connect(myPcNetworkName, azandeStudioServerPortNr);
  }

  debugPrint("Connected to server on ");
  debugPrintln(myPcNetworkName);
  delay(100);
}


void waitForSerialIfAny()
{
  #ifdef USE_SERIAL_DEBUG
    // Use "Serial" for debug printing
    Serial.begin(9600);
    while (!Serial); // wait for serial port to connect. Needed for native USB port only
  #endif
}

void printWiFiStatus() 
{
  #ifdef USE_SERIAL_DEBUG
    // print the SSID of the network you're attached to:
    Serial.print("SSID: ");
    Serial.println(WiFi.SSID());
  
    // print your WiFi shield's IP address:
    IPAddress ip = WiFi.localIP();
    Serial.print("My IP Address: ");
    Serial.println(ip);
  
    // print the received signal strength:
    long rssi = WiFi.RSSI();
    Serial.print("signal strength (RSSI):");
    Serial.print(rssi);
    Serial.println(" dBm");
  #endif
}

void debugPrint(char* txt)
{
  #ifdef USE_SERIAL_DEBUG
    Serial.print(txt);
  #endif
}

void debugPrintln(char* txt)
{
  #ifdef USE_SERIAL_DEBUG
    Serial.println(txt);
  #endif
}


void gotoMainMenu(void)
{
  azande.clear();
  azande.add(Xms2Event);
  azande.add(Yms2Event);
  azande.add(Zms2Event);
  azande.add(cmdMainMenu);
  azande.notify();
  state = State_MainMenu;
}

void gotoFallTime(void)
{
  azande.clear();
  azande.add(FallTimeEvent);
  azande.add(FallDistanceEvent);
  azande.add(FallMenuCmd);
  azande.notify();
  state = State_FallTime;
}

void gotoSphericalMenu(void)
{
  azande.clear();
  azande.add(SphericalMenuCmd);
  azande.add(PhiEvent);
  azande.add(ThetaEvent);
  azande.add(RadialEvent);
  azande.notify();
  state = State_Spherical;
}

void gotoSetupMenu(void)
{
  message = msgSetupMenu;
  azande.clear();
  azande.add(textEvent);
  azande.add(cmdSetupMenu);
  azande.add(cmdSetRange);
  azande.add(rawXevent);
  azande.add(rawYevent);
  azande.add(rawZevent);
  azande.add(CalOffsetXevent);
  azande.add(CalOffsetYevent);
  azande.add(CalOffsetZevent);
  azande.add(CalGainXevent);
  azande.add(CalGainYevent);
  azande.add(CalGainZevent);
  azande.notify();
  state = State_SetupMenu;
}


void MainMenu(long item)
{
  switch (item)
  {
    case 1: // "1. Fall Time"
      gotoFallTime();
      break;
    
    case 2: // "2. Spherical"
      gotoSphericalMenu();
      break;
    
    case 3: // "3. Setup"
      gotoSetupMenu();
      break;
    
    case 4: // "4. Sleep"
    state = State_Sleeping;
    WiFi.end(); // Disconnect and turn off WiFi.
    delay(5000); // Wait 5s to make sure Azande Studio removed the connection.
    break;
  }
}

void SetupMenu(long item)
{
  switch (item)
  {
    case 1: // "1. Start Calibration"
      state = State_Calibrating;
      message = msgStartCalibration;
      rawMinX = rawX;
      rawMaxX = rawX;
      rawMinY = rawY;
      rawMaxY = rawY;
      rawMinZ = rawZ;
      rawMaxZ = rawZ;
      break;
    
    case 2: // "2. Save Calibration"
      state = State_SetupMenu;
      message = msgSaveCalibration;

      // Calculate and save new calibration parameters
      offsetXSaved = (rawMinX + rawMaxX) / 2;
      offsetYSaved = (rawMinY + rawMaxY) / 2;
      offsetZSaved = (rawMinZ + rawMaxZ) / 2;
      gainMs2XSaved = (double)(rawMaxX - rawMinX) / OneHalfGms2;
      gainMs2YSaved = (double)(rawMaxY - rawMinY) / OneHalfGms2;
      gainMs2ZSaved = (double)(rawMaxZ - rawMinZ) / OneHalfGms2;

      // Use new parameters
      offsetX = offsetXSaved;
      offsetY = offsetYSaved;
      offsetZ = offsetZSaved;
      gainMs2X = gainMs2XSaved;
      gainMs2Y = gainMs2YSaved;
      gainMs2Z = gainMs2ZSaved;

      azande.send(CalOffsetXevent, offsetX);
      azande.send(CalOffsetYevent, offsetY);
      azande.send(CalOffsetZevent, offsetZ);
      azande.send(CalGainXevent, gainMs2X);
      azande.send(CalGainYevent, gainMs2Y);
      azande.send(CalGainZevent, gainMs2Z);
      break;
    
    case 3: // "3. Use Default Calibration"
      message = msgUseDefault;
      adxl.setRangeSetting(DefaultRange);  
      offsetX = DefaultOffsetX;
      offsetY = DefaultOffsetY;
      offsetZ = DefaultOffsetZ;
      gainMs2X = DefaultGainMs2X;
      gainMs2Y = DefaultGainMs2Y;
      gainMs2Z = DefaultGainMs2Z;
      break;
    
    case 4: // "4. Use Saved Calibration"
      message = msgUseSaved;
      // Use new parameters
      offsetX = offsetXSaved;
      offsetY = offsetYSaved;
      offsetZ = offsetZSaved;
      gainMs2X = gainMs2XSaved;
      gainMs2Y = gainMs2YSaved;
      gainMs2Z = gainMs2ZSaved;
      break;
    
    case 5: // "5. BACK to Main Menup"
      gotoMainMenu();
      break;
  }
}

void SphericalMenu(long item)
{
  switch (item)
  {
    case 1: // "BACK to Main Menu"
      gotoMainMenu();
      break;
  }
}

void FallMenu(long item)
{
  switch (item)
  {
    case 1: // "BACK to Main Menu"
      gotoMainMenu();
      break;
  }
}


void SetRange(long range)
{
  switch (range)
  {
    case 2:
      adxl.setRangeSetting(2);  
      message = msgRangeSet2g;
      break;
      
    case 4:
      adxl.setRangeSetting(4);  
      message = msgRangeSet4g;
      break;
      
    case 8:
      adxl.setRangeSetting(8);  
      message = msgRangeSet8g;
      break;

    default:
    case 16:
      adxl.setRangeSetting(16);  
      message = msgRangeSet16g;
      break;
  }
}

arduino_secrets.h

C/C++
This file is included by Gball.ino and holds your Wi-Fi network credentials.
#define SECRET_SSID "name"
#define SECRET_PASS "password"

Credits

zeijlonsystems
1 project • 2 followers

Comments