Saturday, 16 March 2013

Dive Compass - Part # 6 - Experiment with Mask

Once mounted, you can truely understand how the software is working and how you as a diver might perceive the signals...

Some observations:

LEDS are blurred so close up to your face but 10mm seperation is enough to determine which one is which... just.

Might have to change the resistors on the LEDS, in low light, they may be too bright... I've put them on analog output pins so I can vary the brightness under software...but its fixed at the moment..

They need to be forward of the mask frame so as to be both seen.

Some colours are more visible that others, red and blue  is good, green less so and the white ( all three colours) can look like red/green on at the same time..

The green level indicator on the LED_2 is a bit of a distraction so I might go back to a single LED.. or use it purely for the waypoint indicator..







Just thinking, I'm going to keep with this format for the moment until I can prove the in-water use of it but saw on another website ( rebreather world) the use of a small OLED display which intrigues me.... maybe mount an OLED display on the mask below the field of view and have the compass/battery at the side of the head...because the tilting of the head to check compass heading is a key feature...




Dive Compass - Part #5 - Program

Arduino Sketch below...

Notes:
Debug print statement are still active.. very useful for debugging.. but take out when happy with code.

Code starts here, COPY from after the "//" to the end..

//**************************************************************************************************************************************
//
// Arduino Compass HUD - Use RGB LED(s) to communicate bearing to diver and other info ( Tilt/Waypoint direction)
//
// Author : johnohuk@gmail.com
//
//
// History
//
// 3_0 Feedback from first headmounted prototype,
//     2 RGB LED Version
//
//     Two Hall Effect sensors to increment and decrement current waypoint.
//
// Reference the I2C Library
#include <Wire.h>
// Reference the HMC5883L Compass Library
#include <HMC5883L.h>
// Reference the ADXL345 Accelerometer Library
#include <ADXL345.h>

// Store our compass as a variable.
HMC5883L compass;
// Store our accelerometer as a variable.
ADXL345 accel;

int areConnected = 0; // Store our connection status here.

// Main NSEW Bearing LED

#define LED1_RED 3
#define LED1_BLUE 6
#define LED1_GREEN 5

// Secondary Tilt/Waypoint LED

#define LED2_RED 10
#define LED2_BLUE 9
#define LED2_GREEN 11

// Inputs for controlling current waypoint

#define HF_1 2    // Hall effect sensor #1 on Pin2
#define HF_2 12   // Hall effect sensor #2 on Pin12

#define delayTime 20
#define BoardLed 13    //  digitalWrite(BoardLed , HIGH);// HIGH/LOW only

int tilt_too_muchn;      // Global flag to indicate the tilt is too much to give a valid bearing

int WayPoints[13] = {-1,56,130,180,93,45,300,55,66,130,200,230,66};
int Current_WayPoint = 1;
int WayPointMax = 12;        // Size of the waypoint array

int hallState1;             // the current reading from the input pin
int lasthallState1 = LOW;   // the previous reading from the input pin

int hallState2;             // 2nd Hall effect sensor
int lasthallState2 = LOW;

int hallcount1 = 0;        // Current Halleffect sensor (A3144)senses two changes incoming/outgoing so need /2
int hallcount2 = 0;

void setup()
{
  pinMode(LED1_GREEN, OUTPUT);    //LED #1 Primary Compass showing N,S,E and West
  pinMode(LED1_BLUE, OUTPUT);
  pinMode(LED1_RED, OUTPUT);

  pinMode(LED2_GREEN, OUTPUT);  // LED #2 Level indicator and Waypoint indicator
  pinMode(LED2_BLUE, OUTPUT);
  pinMode(LED2_RED, OUTPUT);

  pinMode(HF_1, INPUT );        // Inputs to control Waypoint selection, increment and decrement pointer into Array
  pinMode(HF_2, INPUT );

  Serial.begin(9600);       // Initialize the serial port.
  Wire.begin();             // Start the I2C interface.

  compass = HMC5883L();     // Construct a new HMC5883 compass.
  accel = ADXL345();        // Construct a new ADXL345 accelerometer.

  compass.EnsureConnected();
  accel.EnsureConnected();

  if(compass.IsConnected && accel.IsConnected)
  {
    areConnected = true;
    Serial.println("Connected to HMC5883L and ADXL345.");
  }
  else
  {
    areConnected = false;
 
    if(compass.IsConnected == 0)
      Serial.println("Could not connect to HMC5883L.");
    if(accel.IsConnected == 0)
      Serial.println("Could not connect to ADXL345.");
 
    digitalWrite(BoardLed , HIGH);    //If connection fault then onboard green LED will stay on.
  }
  if(areConnected)
  {
    compass.SetScale(1.3); // Set the scale of the compass.
    compass.SetMeasurementMode(Measurement_Continuous); // Set the measurement mode to Continuous

    accel.SetRange(2, true); // Set the range of the accelerometer to a maximum of 2G.
    accel.EnableMeasurements(); // Tell the accelerometer to start taking measurements.
  }

  // Test the three colours in both LED(s), ramp up each one...

  for( int i = 0 ; i < 5 ; i += 1 ){
 
        analogWrite( LED1_RED, i );
        analogWrite( LED1_BLUE, 0 );
        analogWrite( LED1_GREEN, 0 );

        analogWrite( LED2_RED, i );
        analogWrite( LED2_BLUE, 0 );
        analogWrite( LED2_GREEN, 0 );

        delay( 200 );
     
      Serial.println(i);
   
    }

    digitalWrite(BoardLed , LOW); // Toggle the LED on the board itself to indicate progress..

    for( int i = 0 ; i < 5 ; i += 1 ){
 
        analogWrite( LED1_RED, 0 );
        analogWrite( LED1_BLUE, i );
        analogWrite( LED1_GREEN, 0 );
     
        analogWrite( LED2_RED, 0 );
        analogWrite( LED2_BLUE, i );
        analogWrite( LED2_GREEN, 0 );
        delay( 200 );
     
      Serial.println(i);
   
    }

    digitalWrite(BoardLed , HIGH);

    for( int i = 0 ; i < 5 ; i += 1 ){
 
        analogWrite( LED1_RED, 0 );
        analogWrite( LED1_BLUE, 0 );
        analogWrite( LED1_GREEN, i );

        analogWrite( LED2_RED, 0 );
        analogWrite( LED2_BLUE, 0 );
        analogWrite( LED2_GREEN, i );

        delay( 200 );
     
      Serial.println(i);
   
    }

    digitalWrite(BoardLed , LOW);
}

void loop()
{
  int MaxVal = 5;     //Full on LED=255, 5 = low, dont want LED to be distracting...
  int headingTCn=0;

  if(areConnected)
  {
    MagnetometerScaled magnetometerReadings = compass.ReadScaledAxis();
    AccelerometerScaled accelerometerReadings = accel.ReadScaledAxis();
 
    float headingNTC = CalculateHeadingNotTiltCompensated(magnetometerReadings);
    float headingTC = CalculateHeadingTiltCompensated(magnetometerReadings, accelerometerReadings);
       
    // Update RGB (LED1)

    headingTCn = int ( RadiansToDegrees(headingNTC)); //changed!!
    //Serial.println(headingTCn);

    if (tilt_too_muchn == 1 )
    {
        Serial.println("Tilted too far");
        analogWrite( LED1_RED, 0 );
        analogWrite( LED1_BLUE, 0 );
        analogWrite( LED1_GREEN, 0 );

        analogWrite( LED2_GREEN, 0 );    // Clear 2nd LED2 which indicates headset is level... so all u need is to keep that orientation and twist head to get a bearing..

    }
    else if ( headingTCn>355 || headingTCn <05 )
    {
        Serial.println("North");

        analogWrite( LED1_RED, 0 );
        analogWrite( LED1_BLUE, 0 );
        analogWrite( LED1_GREEN, MaxVal  );
     }
    else if ( headingTCn> 84 && headingTCn <95 )
    {
        Serial.println("EAST");
   
        analogWrite( LED1_RED, 0 );
        analogWrite( LED1_BLUE, MaxVal );
        analogWrite( LED1_GREEN, 0  );
     }
    else if ( headingTCn> 174 && headingTCn <185 )    
    {
        Serial.println("SOUTH");

        analogWrite( LED1_RED, MaxVal );
        analogWrite( LED1_BLUE, MaxVal );
        analogWrite( LED1_GREEN, MaxVal  );
    }
    else if ( headingTCn> 264 && headingTCn <275 )    
    {
        Serial.println("WEST");

        analogWrite( LED1_RED, MaxVal );
        analogWrite( LED1_BLUE, 0 );
        analogWrite( LED1_GREEN, 0  );
    }
    else
    {

      // For any heading outside the above parameters
   
        analogWrite( LED1_RED, 0 );
        analogWrite( LED1_BLUE, 0 );
        analogWrite( LED1_GREEN, 0  );
    }  
 
    //Waypoint functionallity & Level indicator - communicated through LED2 Blue..
 
    // Sense Inputs
 
    hallState1 = digitalRead(HF_1);
    hallState2 = digitalRead(HF_2);
 
    Serial.print("debug - Hallstate 1 & 2, Waypoint Bearing and Index : ");
    Serial.print(hallState1);  
    Serial.print(" ");
    Serial.print(hallState2);
    Serial.print(" ");
    Serial.print(WayPoints[Current_WayPoint]);
    Serial.print(" ");
    Serial.print(Current_WayPoint);
    Serial.println("");
 
    if (hallState1 != lasthallState1)
    {
      hallcount1 = hallcount1 +1;
      lasthallState1 = hallState1;
    }

    if (hallState2 != lasthallState2)
    {
      hallcount2 = hallcount2 +1;
      lasthallState2 = hallState2;
    }
     
    if ( hallcount1 == 2 )
    {
        if (Current_WayPoint<WayPointMax)
        {
          Current_WayPoint = Current_WayPoint + 1 ;
        }

        signal_led( Current_WayPoint);  // Flash LED_2 Red the count

        hallcount1 = 0;
    }
    if ( hallcount2 == 2 )
    {
        if (Current_WayPoint>1)
        {
          Current_WayPoint = Current_WayPoint - 1 ;
        }

        signal_led( Current_WayPoint);  // Flash LED_2 Red the count

        hallcount2 = 0;
    }  

    // Level indicator & Waypoint display

    if (tilt_too_muchn == 0 )
    {
      if ( headingTCn> (WayPoints[Current_WayPoint] -5 )&& headingTCn< (WayPoints[Current_WayPoint] +5 ) )
      {
          analogWrite( LED2_GREEN, 0 );  
          analogWrite( LED2_BLUE, MaxVal );
          Serial.println("On Waypoint Bearing");  
      }
      else
      {
          analogWrite( LED2_BLUE, 0 );    
          analogWrite( LED2_GREEN, MaxVal );    // Set 2nd LED2 to green to indicate headset is level... so all u need is to keep that orientation and twist head to get a bearing..
          Serial.println("Level but NO Waypoint Bearing");
      }
    }
  }
}
void signal_led ( int pulse_count )
{

     // Pulse LED_2 Red using the pulse_count to indicate to user which waypoint is currently active
   
     analogWrite( LED2_BLUE, 0 );
     analogWrite( LED2_GREEN, 0 );
     analogWrite( LED2_RED, 0 );
   
     delay( 2000 );
   
     for( int i = 1 ; i <= pulse_count ; i += 1 )
     {

       analogWrite( LED2_RED, 1 );

       delay( 1000 );

       analogWrite( LED2_RED, 0 );

       delay( 1000 );    
    }
 
    analogWrite( LED2_RED, 0 );
 
    delay( 2000 );
}

float CalculateHeadingTiltCompensated(MagnetometerScaled mag, AccelerometerScaled acc)
{
  // We are swapping the accelerometers axis as they are opposite to the compass the way we have them mounted.
  // We are swapping the signs axis as they are opposite.
  // Configure this for your setup.

  float accX = -acc.YAxis;
  float accY = -acc.XAxis;


 // float accY = -acc.YAxis;
 // float accX = -acc.XAxis;

  float rollRadians = asin(accY);
  float pitchRadians = asin(accX);

  // We cannot correct for tilt over 40 degrees with this algorthem, if the board is tilted as such, return 0.

//  if(rollRadians > 0.78 || rollRadians < -0.78 || pitchRadians > 0.78 || pitchRadians < -0.78)    //44 degress
  if(rollRadians > 0.175 || rollRadians < -0.175 || pitchRadians > 0.175 || pitchRadians < -0.175)  //10 degrees
  {
    tilt_too_muchn = 1;
    return 0;
  }
  else
  {
    tilt_too_muchn = 0;
  }

  // Some of these are used twice, so rather than computing them twice in the algorithem we precompute them before hand.
  float cosRoll = cos(rollRadians);
  float sinRoll = sin(rollRadians);
  float cosPitch = cos(pitchRadians);
  float sinPitch = sin(pitchRadians);

  // The tilt compensation algorithem.
  float Xh = mag.XAxis * cosPitch + mag.ZAxis * sinPitch;
  float Yh = mag.XAxis * sinRoll * sinPitch + mag.YAxis * cosRoll - mag.ZAxis * sinRoll * cosPitch;

  float heading = atan2(Yh, Xh);
 
  return heading;
}

float CalculateHeadingNotTiltCompensated(MagnetometerScaled mag)
{
   // Calculate heading when the magnetometer is level, then correct for signs of axis.
   float heading = atan2(mag.YAxis, mag.XAxis);
   return heading;
}

float RadiansToDegrees(float rads)
{
  // Correct for when signs are reversed.
  if(rads < 0)
    rads += 2*PI;
   
  // Check for wrap due to addition of declination.
  if(rads > 2*PI)
    rads -= 2*PI;
 
  // Convert radians to degrees for readability.
  float heading = rads * 180/PI;
     
  return heading;
}

void Output(float headingNTC, float headingTC)
{
  Serial.print("Heading (Not Compensated): ");
  Serial.print(RadiansToDegrees(headingNTC));
  Serial.print("\tHeading (Tilt Compensated): ");
  Serial.println(RadiansToDegrees(headingTC));
}

Dive Compass - Part #4 - Circuit

Latest circuit..... Created in TinyCad.

NB Link on ProMini board has been broken to make it run its regulation/output at 3.3v, same on the FTDI usb board..


Dive Compass - Part #3 - New board...

After the first prototype I added the two hall effects sensors as a way of controlling the current active way points and a second RGB LED for showing extra information...

So it started out as this... You really have to prototype on a breadboard, its so much easier...



Then moving to Vero board...

Break in copper strips made down the middle...


Completed board..


Both RGB LEDs are on the underside because I was running out of space topside..


You can see them a bit better from this view..


And here.... you can also one of the hall effect devices, they are place at the edge of the board to sense the magnetic field outside the housing. The second one is at the other end of the board to make them more easily distinguished.




Back in the tube, with battery and mounted on a mask. This prototype needs to go quite forward for the diver to be able to see the LEDs. Its quite fiddly trying to mount a cylinder on a flimsy bit of rubber mask strap...But will do for now...





HUD Compass - Part #2 - Parts list

Purchased from ebay 

Pro Mini Enhancement (3.3V/5V adjustable)/16MHz MEGA168 (Arduino-compatible) US $9.30

To program it : -

FTDI Basic 6 PIN 3.3/5V For Arduino(Free USB Cable), US $6.90

Triple Axis Accelerometer ADXL345

HMC5883 Module Three-Axis Digital Compass

A3144 A3144E OH3144E Hall Effect Sensor, two of.

5mm RGB LED Diffused Arduino (Red Green Blue in one LED) Shared Cathode, two of.

Resistors :- Four 220 Ohms, two 8.8k and two 11K Ohms.

Test bed was :-

Arduino Uno

ARDUINO UNO Proto Shield + Breadboard

And for further testing ... you really need a larger breadboard ... its so much easier..


830 Points Tie-points Solderless Breadboard Circuit Bread Board


The Test waterproof container is like my dive lights but specifically:-

21mm solid Rubber Stopper (rubber bung) for each end.

25mm x 2mm CLEAR Acrylic Perspex Plastic TUBE Pipe 1M.








HUD Compass - Part #1 - Concept

HUD - Compass  Project

The idea for this came when I got fed up of getting lost in zero vis river dives and never being able to see my compass or rather it had to be 1cm from my face to see it... There had to be an easier way!

I’ve played with PIC chips and the arduino which is definitely easier to prototype with quickly.. so this project evolved..

So, what is it trying to be, a HUD compass with waypoint indication, some design ideas I wanted to keep to:

Attach to any mask configuration.
Not obtrusive, on a dark dive i don’t want lights /displays flashing at me..
Parts easily sourced and cheap!
Simple programming language... I’m sick of setting up linkers and compilers every 6 months and i just don’t have the time to keep in with C/C++ etc..
Accessible to anyone.. Simple circuits and international parts..

Evolution

It started out life on an Arduino Uno and its evolved into using a Pro Mini board ( battery operated)...

It used a single RGB LED to show North=Green, West=Red etc... But its moved on from that to a two RGB LED solution and two hall effect sensors for the input controls...

Current function is : Main RGB led show different colours for North, South, East and West. Second led indicates unit is level and hence will show a valid heading. It also server a second purpose to change colour to blue when your point in the direction of your waypoint.. Waypoints are hard-coded at the moment.. in a list, i'll add a bluetooth function later so I can programme from the phone..

Two hall effect sensors allow you to move onto the next waypoint or move back to the last one.

Early prototype with an Ardiono Uno, breadboard, compass and accelerometer boards..



Experimenting against a real compass.. ( Sunnto SK7)


Moving platforms to a Pro Mini so I can get the form factor down....

Program with this... 




Then going to something i can waterproof and get in the water with...


Components wired together to fit inside a arcylic tube.


And fitted into the tube with battery... this will connect  to the mask strap..




After proving i could drive it by battery and wear it... it was  back to the breadboard to refine the software design based on user experience... Adding a second LED and user controls ( with magnet and hall effect sensor).





Next rebuild it again in miniature form.... and try in the pool again..






Sunday, 10 March 2013

Underwater Laser

Could it work?

My cheap £5 laser fits in a acrylic tube no worries... just need to switch it somehow... next project...


These also look interesting..

http://img.dxcdn.com/productimages/sku_26891_1.jpg


Friday, 8 March 2013

Dive light - Part #8 - Upgraded Power Pack

Following advice from Candlepower forums.

Upgraded to better batteries and double the number... to 4 batteries..

XTAR 18650 2600mAh 3.7V Protected Sanyo Li-ion cell inside Rechargeable Battery (2-pack)

From Amazon £11.99 for two.

Longer Tube, 4 batteries in a 2S2P confguration and positive take off from the centre maintaining the 8.4v but at ~5200mAh. Burn time achieved = 3hrs approx...

Slight issue with the new tube I ordered, it was supposed to the same size as the one I ordered before but there appears to be a variance on the diameter so the original rubber bungs were too small.

So, new test tube is made up of ( off ebay) :-

21mm solid Rubber Stopper (rubber bung)
21mm 1 Hole Rubber Stopper (rubber bung)
25mm x 2mm CLEAR Acrylic Perspex Plastic TUBE Pipe 1M

The holes in the rubber bung had to be drilled out a bit to accept the cable..Cable thread through the hole with silicon sealant smeared over the cable/bung and left to dry.

Mounted between the wing and cylinders:




The old one and new one..


Wired up to the torch head.