Hack Notes CVA 090324

From Noisebridge
Jump to navigation Jump to search

Compass Vibro-Anklet Hacking Session 2009.03.24[edit]

First two tasks[edit]

  • Build basic circuit with one pager motor and program Arduino.
  • Prototype and test anklet armature

The Circuit[edit]

  • The pager motors when running at over 2V need over the Arduino's maximum 40ma output, so we need to use an external transistor-driven circuit.

Anklethack-0.jpg

  • Attached leads to a pager motor for bread-boarding.

Anklethack-1.jpg

  • Set up circuit with a "safety" diode, not sure why.

Anklethack-2.jpg

  • The transistor seems to be working inverse-proportionally to the pwm voltage from the Arduino. Is that normal? We don't know.

The Code[edit]

  • Important: "On newer Arduino boards (including the Mini and BT) with the ATmega168 chip, this function works on pins 3, 5, 6, 9, 10, and 11. Older USB and serial Arduino boards with an ATmega8 only support analogWrite() on pins 9, 10, and 11."
  • Given that analogWrite() takes a value 0-255 and that compass_angle is a value from -180 to 180. We thought:
    • vibro_strength = (255 - abs(compass_angle) * (255/180))
  • But then it turned out the transistor was working the opposite of expected, and the above code made the vibration strongest towards south! So:
    • vibro_strength = abs(compass_angle) * (255/180)
  • Of course this has East and West not instanteneously distinguishable from the pager motor speed.
/*
Some Htachi HM55B Compass reading code copied from: kiilo kiilo@kiilo.org
License:  http://creativecommons.org/licenses/by-nc-sa/2.5/ch/
*/

#include <LCD4Bit.h> // module for lcd
#include <math.h> //
#include <stdio.h>


//// VARS
byte VIB1_pin = 3;
byte VIB2_pin = 5;
byte CLK_pin = 6;
byte EN_pin = 4;
byte DIO_pin = 11;

int X_Data = 0;
int Y_Data = 0;
int angle;

float vibro_strength;

//Create LCD object with 2 line display
LCD4Bit lcd = LCD4Bit(2);
char string[20];
int status;
unsigned long serialTimer = millis();

//// FUNCTIONS

void ShiftOut(int Value, int BitsCount) {
  for(int i = BitsCount; i >= 0; i--) {
    digitalWrite(CLK_pin, LOW);
    if ((Value >> i) & 1) {
      digitalWrite(DIO_pin, HIGH);
      //Serial.print("1");
    }
    else {
      digitalWrite(DIO_pin, LOW);
      //Serial.print("0");
    }
    digitalWrite(CLK_pin, HIGH);
    delayMicroseconds(1);
  }
//Serial.print(" ");
}

int ShiftIn(int BitsCount) {
  int ShiftIn_result;
    ShiftIn_result = 0;
    pinMode(DIO_pin, INPUT);
    for(int i = BitsCount; i >= 0; i--) {
      digitalWrite(CLK_pin, HIGH);
      delayMicroseconds(1);
      if (digitalRead(DIO_pin) == HIGH) {
        ShiftIn_result = (ShiftIn_result << 1) + 1; 
        //Serial.print("x");
      }
      else {
        ShiftIn_result = (ShiftIn_result << 1) + 0;
        //Serial.print("_");
      }
      digitalWrite(CLK_pin, LOW);
      delayMicroseconds(1);
    }
  //Serial.print(":");

// below is difficult to understand:
// if bit 11 is Set the value is negative
// the representation of negative values you
// have to add B11111000 in the upper Byte of
// the integer.
// see: http://en.wikipedia.org/wiki/Two%27s_complement
  if ((ShiftIn_result >> 11) & 1) {
    ShiftIn_result = (B11111000 << 8) | ShiftIn_result; 
  }


  return ShiftIn_result;
}

void HM55B_Reset() {
  pinMode(DIO_pin, OUTPUT);
  digitalWrite(EN_pin, LOW);
  ShiftOut(B0000, 3);
  digitalWrite(EN_pin, HIGH);
}

void HM55B_StartMeasurementCommand() {
  pinMode(DIO_pin, OUTPUT);
  digitalWrite(EN_pin, LOW);
  ShiftOut(B1000, 3);
  digitalWrite(EN_pin, HIGH);
}

int HM55B_ReadCommand() {
  int result = 0;
  pinMode(DIO_pin, OUTPUT);
  digitalWrite(EN_pin, LOW);
  ShiftOut(B1100, 3);
  result = ShiftIn(3);
  return result;
}


void setup() {
  Serial.begin(115200);
  pinMode(EN_pin, OUTPUT);
  pinMode(CLK_pin, OUTPUT);
  pinMode(DIO_pin, INPUT);

  lcd.init();
  lcd.commandWrite(0x0E); //curson on, display on, blink off

  HM55B_Reset();
}

void loop() {
  if (millis() - serialTimer > 50 ) {

  //read compass and print data to lcd and serial out
  lcd.cursorTo(2,0);
  HM55B_StartMeasurementCommand(); // necessary!!
  //delay(40); // the data is 40ms later ready
  status = HM55B_ReadCommand();
  Serial.print(status); // read data and print Status
  sprintf(string, "%2d", status);
  lcd.printIn(string);
  Serial.print(" ");
  lcd.printIn(" ");
  X_Data = ShiftIn(11); // Field strength in X
  Y_Data = ShiftIn(11); // and Y direction
  Serial.print(X_Data); // print X strength
  sprintf(string, "%3d", X_Data);
  lcd.printIn(string);
  Serial.print(" ");
  lcd.printIn(" ");
  Serial.print(Y_Data); // print Y strength
  sprintf(string, "%3d", Y_Data);
  lcd.printIn(string);
  Serial.print(" ");
  lcd.printIn(" ");
  digitalWrite(EN_pin, HIGH); // ok deselect chip
  angle = 180 * (atan2(-Y_Data , X_Data) / M_PI); // angle is atan( -y/x) !!!
  Serial.print(angle); // print angle
  sprintf(string, "%4d", angle);
  lcd.printIn(string);
  Serial.println(" ");
  lcd.printIn(" ");
  
  //control motors
  vibro_strength = abs(angle) * (215.0/180.0);
  pinMode(VIB1_pin, OUTPUT);
  analogWrite(VIB1_pin, int(vibro_strength));
  lcd.cursorTo(1,0);
  lcd.printIn("V1=");
  sprintf(string, "%3d", int(vibro_strength));
  lcd.printIn(string);
  
  vibro_strength = 215 - (abs(angle) * (215.0/180.0));
  pinMode(VIB2_pin, OUTPUT);
  analogWrite(VIB2_pin, int(vibro_strength));
  lcd.printIn(" V2=");
  sprintf(string, "%3d", int(vibro_strength));
  lcd.printIn(string);
  }
}

Basic Armature[edit]

  • We decided that we will just use a long strip of 2"-wide sew-on velcro as the components can just be sandwiched in between the opposite sides of the velcro, which can then be offset to allow for stick to itself after wrapping around the ankle.

Anklethack-4.jpg

  • We decided to only cut half the overlapping bit to still give somewhere to sandwich electronics on the section of the band that sticks to itself after wrapping.

Anklethack-5.jpg

Tubing for pager motor[edit]

  • To be able to encase the motor in fabric we need some kind of hard shell so the weighted shaft can spin freely. Two out of several ball point pen casings found in the space appear to be a decent fit.

Anklethack-3.jpg

Testing[edit]

Round 1[edit]

  • With the variation in motor speed running the gammut from North=100% and South=0%, and _only one motor_ installed in the anklet, we found the following sensitivities for these ranges:
    • 0% not felt; likely because the compass reading constantly varies and is never at absolutely 180 degrees for more than an instant.
    • 1%-60% pretty noticeable differentiation in speeds (but of course we're paying attention! Long-term sub-concious sensitivity to these variations totally unknown.)
    • 61%-90% very little discernable differention in speeds. (Next step should be to change the code to have North run at somewhere around 60%, this will also reduce power consumption.)
    • 91%-100% differences in this range were again noticeable.

Anklethack-6.jpg


Round 2[edit]

  • We added a second pager motor and had them ranging from 0% to about 70% of the max motor speed, with one vibrating fully at North and one vibrating fully at South.
  • It was very hard to distinguish which pager motor was vibrating, two theories for this:
    1. The ankle is not sensitive enough
    2. The velcro strap is too rigid and transmits too much vibration throughout the material
  • Further testing quickly revealed that:
    1. The behavior of the pager motors are not equal and as a pair are not acting as expected.
    2. Whoah something is crazy wrong with our circuits!
    3. Hooray! Turns out the little bin labeled "2N2222" transistors actually has 2N2222's in the front, but other, almost visually identical transistors in the back. We had one of each. / "Oh look, there are actually two labels!" / "BLAST!"
  • Dang. Now the two pager motors are clearly differentiable.
    • The Moral: Always debug your circuits and learn to read labels.
  • Now that the right transistors are driving both motors we see:
    1. The ankle is sensitive and perfectly capable of differentiating which vibrator is on
    2. So, the velcro strap is not transmiting so much vibration as to be confusing.
    3. But, when the motors are both on and running somewhere in the middle of the range of speeds (as in when pointing Eastwardish or Westwardish) it's practically impossible to tell which one is running faster.
    4. Having one vibrator on the shin and one and the back tendon is less sensitive than positioning both on the left and right of the ankle (I think the muscle jiggles more).

Anklethack-7.jpg

Round 3[edit]

  • Now we messed with phasing the vibration in pulses, rather than varying the strength. We are starting with running both motors at the same speed together.

Pulse width table:

Direction Pulse
S !----------
W !-----XXXXX
N !XXXXXXXXXX
E !XXXXX-----

Key: "!" is the initial 100ms pulse, "-" is 200ms off, and "X" is 200ms on. Each interval lasts 2100ms followed (I think) by another 100ms gap.

  • This seems fairly intuitive on the ankle, and easier to tell direction than the strength modulation of Round 2. It also works almost as well with _just one motor_ going.
  • As in Round 2, sensitivity is better with motors on sides, rather than on shin and back tendon

Anklethack-8.jpg

The New Code[edit]

/*
Some Hitachi HM55B Compass reading code copied from: kiilo kiilo@kiilo.org
License:  http://creativecommons.org/licenses/by-nc-sa/2.5/ch/
*/

#include <LCD4Bit.h> // module for lcd
#include <math.h> //
#include <stdio.h>


//// VARS
byte VIB1_pin = 3;
byte VIB2_pin = 5;
byte CLK_pin = 6;
byte EN_pin = 4;
byte DIO_pin = 11;

int X_Data = 0;
int Y_Data = 0;
int angle;

float vibro_strength;
float pulse_length;

//Create LCD object with 2 line display
LCD4Bit lcd = LCD4Bit(2);
char string[20];
int status;
unsigned long serialTimer = millis();

//// FUNCTIONS

void ShiftOut(int Value, int BitsCount) {
  for(int i = BitsCount; i >= 0; i--) {
    digitalWrite(CLK_pin, LOW);
    if ((Value >> i) & 1) {
      digitalWrite(DIO_pin, HIGH);
      //Serial.print("1");
    }
    else {
      digitalWrite(DIO_pin, LOW);
      //Serial.print("0");
    }
    digitalWrite(CLK_pin, HIGH);
    delayMicroseconds(1);
  }
//Serial.print(" ");
}

int ShiftIn(int BitsCount) {
  int ShiftIn_result;
    ShiftIn_result = 0;
    pinMode(DIO_pin, INPUT);
    for(int i = BitsCount; i >= 0; i--) {
      digitalWrite(CLK_pin, HIGH);
      delayMicroseconds(1);
      if (digitalRead(DIO_pin) == HIGH) {
        ShiftIn_result = (ShiftIn_result << 1) + 1; 
        //Serial.print("x");
      }
      else {
        ShiftIn_result = (ShiftIn_result << 1) + 0;
        //Serial.print("_");
      }
      digitalWrite(CLK_pin, LOW);
      delayMicroseconds(1);
    }
  //Serial.print(":");

// below is difficult to understand:
// if bit 11 is Set the value is negative
// the representation of negative values you
// have to add B11111000 in the upper Byte of
// the integer.
// see: http://en.wikipedia.org/wiki/Two%27s_complement
  if ((ShiftIn_result >> 11) & 1) {
    ShiftIn_result = (B11111000 << 8) | ShiftIn_result; 
  }


  return ShiftIn_result;
}

void HM55B_Reset() {
  pinMode(DIO_pin, OUTPUT);
  digitalWrite(EN_pin, LOW);
  ShiftOut(B0000, 3);
  digitalWrite(EN_pin, HIGH);
}

void HM55B_StartMeasurementCommand() {
  pinMode(DIO_pin, OUTPUT);
  digitalWrite(EN_pin, LOW);
  ShiftOut(B1000, 3);
  digitalWrite(EN_pin, HIGH);
}

int HM55B_ReadCommand() {
  int result = 0;
  pinMode(DIO_pin, OUTPUT);
  digitalWrite(EN_pin, LOW);
  ShiftOut(B1100, 3);
  result = ShiftIn(3);
  return result;
}


void setup() {
  Serial.begin(115200);
  pinMode(EN_pin, OUTPUT);
  pinMode(CLK_pin, OUTPUT);
  pinMode(DIO_pin, INPUT);

  lcd.init();
  lcd.commandWrite(0x0E); //curson on, display on, blink off

  HM55B_Reset();
}

void loop() {
  if (millis() - serialTimer > 2200 ) {

  //read compass and print data to lcd and serial out
  lcd.cursorTo(2,0);
  HM55B_StartMeasurementCommand(); // necessary!!
  //delay(40); // the data is 40ms later ready
  status = HM55B_ReadCommand();
  Serial.print(status); // read data and print Status
  sprintf(string, "%2d", status);
  lcd.printIn(string);
  Serial.print(" ");
  lcd.printIn(" ");
  X_Data = ShiftIn(11); // Field strength in X
  Y_Data = ShiftIn(11); // and Y direction
  Serial.print(X_Data); // print X strength
  sprintf(string, "%3d", X_Data);
  lcd.printIn(string);
  Serial.print(" ");
  lcd.printIn(" ");
  Serial.print(Y_Data); // print Y strength
  sprintf(string, "%3d", Y_Data);
  lcd.printIn(string);
  Serial.print(" ");
  lcd.printIn(" ");
  digitalWrite(EN_pin, HIGH); // ok deselect chip
  angle = 180 * (atan2(-Y_Data , X_Data) / M_PI); // angle is atan( -y/x) !!!
  Serial.print(angle); // print angle
  sprintf(string, "%4d", angle);
  lcd.printIn(string);
  Serial.println(" ");
  lcd.printIn(" ");
  
  // control motors with new pulse scheme
  // always pulse at start for 100ms
  // then, pulse for variable time depending on angle
  pulse_length = (angle*2000.0/180.0);
  lcd.cursorTo(1,0);
  lcd.printIn(" PuLn=");
  sprintf(string, "%4d    ", int(pulse_length));
  lcd.printIn(string);
  pinMode(VIB1_pin, OUTPUT);
  pinMode(VIB2_pin, OUTPUT);
  analogWrite(VIB1_pin, 255);
  analogWrite(VIB2_pin, 255);
  delay(100);
  if (pulse_length > 0)
  {
    analogWrite(VIB1_pin, 215);
    analogWrite(VIB2_pin, 215);
    delay(2000-int(pulse_length));
    analogWrite(VIB1_pin, 0);
    analogWrite(VIB2_pin, 0);
    delay(pulse_length);  // not actually necessary...
  }
  else
  {
    analogWrite(VIB1_pin, 0);
    analogWrite(VIB2_pin, 0);
    delay(-int(pulse_length));
    analogWrite(VIB1_pin, 215);
    analogWrite(VIB2_pin, 215);
    delay(2000+ int(pulse_length));
    analogWrite(VIB1_pin, 0);
    analogWrite(VIB2_pin, 0);
  }
  }
}

Plans for Next Hacking Session[edit]

  • Next meeting 10am on Thursday
  • We plan to get more motors going - at least 4; this will require moving/removal of LCD screen (which has been bloody useful for debugging!) in order to access more PWM pins on the arduino.

Anklethack-9.jpg

Stuff that would have been nice to have[edit]

  • "Third hand" for holding stuff while soldering.
  • Our RBBBs already! That's why we ordered them today.