Hack Notes CVA 090409

From Noisebridge
Jump to navigation Jump to search

(Today Just Eric on the go) Hacknotes 090408

Everything Coming Together[edit | edit source]

My compass-vibro-anklet is so close to being done, I can taste it. The armature is really sweet, with 8 motors and three external pouches for the batteries and Arduino. The code is working. With Skory's insight that our Arduino problems are related to using 2 batteries (something wrong with voltage regulation from 7+V), my anklet may in fact be totally working. But some detail on how I got here:

Blister[edit | edit source]

I wore the anklet this morning for an hour, and I think the arduino halted at some point and left just one motor on for an extended period of time. I got a very ugly, very large, but not at all painful blister right under the motor (I have a photo, but you don't want to see it) - which shows that we need to write code to make motors time out after awhile, because I don't think it's unreasonable to expect people to sit at desks and actually have the same motor be the correct motor for hours on end... Also, for Armature #3, I intend to use something like a layer of left to protect the skin from the motors.


This Weeks Work[edit | edit source]

On Tuesday I hacked at noisebridge, and I found a problem with the ribbon crimp, which was effecting the reliability of my vibrators. Basically, the ribbon clamp was not fully closed, showing a small triangular gap on one side - and this meant that motors 5-8 sometimes had poor or no connection, and thus were not working. With that fixed, all 8 vibrators work great.

I also greatly improved the code, eliminate the need for those crazy if(angle>x) and (angle<y) statements in favor of some calculations to do it automatically. This is awesome because now the number of vibrators can be changed easily at any time, and because the code is so much cleaner. Here it is:

/* Skory & Eric
 * Compass Vibro-Anklet
 * We Rule, April 7, 2009
 */


/* Some code from:
 * 2009-03-24, pager motor test, lamont lucas
 */
/*
Some Hitachi HM55B Compass reading code copied from: kiilo kiilo@kiilo.org
License:  http://creativecommons.org/licenses/by-nc-sa/2.5/ch/
 */

/******************************************************************************
 *  i2c_gpio
 *  Keith Neufeld
 *  May 26, 2008
 *
 *  Prototype I2C interface to TI 9535 and 9555 GPIO expanders.
 *
 *  Arduino analog input 5 - I2C SCL
 *  Arduino analog input 4 - I2C SDA
 *
 ******************************************************************************/



// define the pins used to run the shift registers 
int enable_low = 10;  //enable outputs, low = on
int serial_in  = 12; 
int ser_clear_low = 9;  //pulse low to zero out the shift buffer
int RCK  = 7;  //RCK, push the serial buffer to the outputs
int SRCK = 8;  //

#include <math.h>

/*
//// define pins used to operate the digital compass (HM55B)
byte CLK_pin = 10;
byte EN_pin = 8;
byte DIO_pin = 7;

int X_Data = 0;
int Y_Data = 0;
*/
int angle;
int count = 0;
int status;
unsigned long serialTimer = millis();

//// stuff for I2C to HMC6352 compass chip
#include <Wire.h>
//  I2C device address is 0 1 0 0   A2 A1 A0
int compassAddress = 0x42 >> 1;  // From datasheet compass address is 0x42 
                                 // shift the address 1 bit right, the Wire library only needs the 7 
                                 // most significant bits for the address 
int reading = 0; 
int MotorStrength = 230;  // 255 = full power 

void setup() {
  pinMode(enable_low, OUTPUT);  // set shift register pins as outputs
  pinMode(serial_in, OUTPUT);
  pinMode(ser_clear_low, OUTPUT);
  pinMode(RCK, OUTPUT);
  pinMode(SRCK, OUTPUT);
  
  // use some serial for debugging
  Serial.begin(115200);
  Serial.println("Setting up board");
  
  // make sure we start out all off
  digitalWrite(enable_low, HIGH);
  // this should wipe out the serial buffer on the shift register
  digitalWrite(ser_clear_low, LOW);
  delay(100);   //delay in ms
  
  // the TPIC6b595 clocks work on a rising edge, so make sure they're low to start.
  digitalWrite(RCK, LOW);
  digitalWrite(SRCK, LOW);
  
  digitalWrite(ser_clear_low, HIGH);   //we are now clear to write into the serial buffer

  Serial.println("Board is setup");

  /* // setup for HM55B compass chip
  pinMode(EN_pin, OUTPUT);
  pinMode(CLK_pin, OUTPUT);
  pinMode(DIO_pin, INPUT);

  HM55B_Reset();*/

  // setup code for HMC6352
  Wire.begin();                // join i2c bus (address optional for master) 
  pinMode(48, OUTPUT); 
  digitalWrite(48, HIGH); 
}


void loop() {
  // make the compass get a reading
  // step 1: instruct sensor to read echoes 
  Wire.beginTransmission(compassAddress); // transmit to device 
                              // the address specified in the datasheet is 66 (0x42) 
                              // but i2c adressing uses the high 7 bits so it's 33 
  Wire.send('A');             // command sensor to measure angle  
  Wire.endTransmission();     // stop transmitting 
 
  // step 2: wait for readings to happen 
  delay(10);                   // datasheet suggests at least 6000 microseconds 
  
  // step 3: request reading from sensor 
  Wire.requestFrom(compassAddress, 2);    // request 2 bytes from slave device #33 
 
  // step 4: receive reading from sensor 
  if(2 <= Wire.available())    // if two bytes were received 
  { 
    reading = Wire.receive();  // receive high byte (overwrites previous reading) 
    reading = reading << 8;    // shift high byte to be high 8 bits 
    reading += Wire.receive(); // receive low byte as lower 8 bits 
    reading = reading/10; 
    //Serial.println(reading);   // print the reading 
  } 
  //Serial.println("test");
 
  //delay(500);                  // wait for half a second 
  //angle = reading - 180;  // old way, this points SW
  angle = reading - 0;
  //if (angle >180) angle = angle-360; // wrap the top to the bottom
  Serial.print(angle); // print angle
  Serial.println("  ");

  TurnOnMotor(CalcMotor(8, angle));

  // debugging stuff
  //TurnOnMotor(0);

  /*
  count++;
  TurnOnMotor(count);
  Serial.print(count); // print angle
  Serial.println("  ");
  delay(2000);
  if (count >= 9)
  {
    count = 0;
    delay(2000);
  }
  */
  
}



//// FUNCTIONS

// function for communicating to HMC6352
#define HMC6253_GetData (0x41)
#define REGISTER (0x42)

void I2C_transmit(int address, int register_eric, int command) {
  //  Send config register address
  Wire.beginTransmission(address);
  Wire.send(register_eric);

  //  Connect to device and send two bytes
  Wire.send(0xff & command);  //  low byte
  Wire.send(command >> 8);    //  high byte

  Wire.endTransmission();
}

int I2C_receive(int address, int register_eric) {
  int data = 0;

  //  Send input register address
  Wire.beginTransmission(address);
  Wire.send(register_eric);
  Wire.endTransmission();

  //  Connect to device and request two bytes
  Wire.beginTransmission(address+1);
  Wire.requestFrom(address+1, 2);

  if (Wire.available()) {
    data = Wire.receive();
  }
  if (Wire.available()) {
    data |= Wire.receive() << 8;
  }

  Wire.endTransmission();

  return data;
}




void TurnOnMotor(int which){
  // accept which from 1 to 8
  // send message to shift register as appropiate
  digitalWrite(enable_low, HIGH);
  delayMicroseconds(100);  //slow and steady
  Serial.print(which); // print angle
  Serial.println("Motor  ");
  switch(which){
    case 1:
      shiftOut(serial_in, SRCK, LSBFIRST, B00000001); //B00001000);
      break;
    case 2:
      shiftOut(serial_in, SRCK, LSBFIRST, B00000010); //B00000100);
      break;
    case 3:
      shiftOut(serial_in, SRCK, LSBFIRST, B00001000); //B00000010);
      break;
    case 4:
      shiftOut(serial_in, SRCK, LSBFIRST, B00000100); //B00000001);
      break;
    case 5:
      shiftOut(serial_in, SRCK, LSBFIRST, B10000000);
      break;
    case 6:
      shiftOut(serial_in, SRCK, LSBFIRST, B01000000);
      break;
    case 7:  // not used in current armature
      shiftOut(serial_in, SRCK, LSBFIRST, B00100000);
      break;
    case 8:  // not used in current armature
      shiftOut(serial_in, SRCK, LSBFIRST, B00010000);
      break;
    case 9:
      shiftOut(serial_in, SRCK, LSBFIRST, B11111111);
      break;
    case 10:
      shiftOut(serial_in, SRCK, LSBFIRST, B11110000);
      break;
    case 11:
      shiftOut(serial_in, SRCK, LSBFIRST, B00001111);
      break;
    default:
      // turn them all off
      shiftOut(serial_in, SRCK, LSBFIRST, B00000000);
  } 
  //in all cases, pulse RCK to pop that into the outputs
  delayMicroseconds(100);
  digitalWrite(RCK, HIGH);
  delayMicroseconds(100);
  digitalWrite(RCK, LOW);
  analogWrite(enable_low, 255-MotorStrength);
}




int CalcAngle(int howMany, int which)
{  // function which calculates the "switch to next motor" angle
  // given how many motors there are in a circle and which position you want
  // assume which is 1-indexed (i.e. first position is 1, not zero)
  // assume circle is 0-360, we can always offset later...
  
  return (360/howMany*(which-0.5));
}

int CalcMotor(int howMany, int angle)
{  // function to calculate which motor to turn on, given
  // how many motors there are and what the current angle is
  // assumes motor 1 = angle 0
  // assumes angle is from 0-360
  int i;
  for (i = 1; i<howMany;i++)
  {
    if ( (angle > CalcAngle(howMany, i)) & (angle < CalcAngle(howMany, i+1)) )
       return i+1; 
  } 
  // if we're still here, it's the last case, the loop over case, which
  // is actually motor 1 by assumption
  return 1;
}




/*

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

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; 
      }
      else {
        ShiftIn_result = (ShiftIn_result << 1) + 0;
      }
      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 & 1 << 11) == 1 << 11) {
    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;
}
*/

I have commented out the code for Skory's magnetometer, of course - and also note that due to messing up the wiring, I had to make the order of the motors with respect to the shift register bits look pretty strange. All that matters is when you pulse 1-8 in order the motors go in order.

Working!!![edit | edit source]

Thanks to Skory's tip about the battery, and one final discovery, I now have a fully operational battle station, er I mean compass vibro-anklet. Final discovery? You need to power on the device with the compass chip in the proper orientation, i.e. face up and level. If you power up in any other orientation, the device seems to be confused and just outputs random/distorted angles.

Also, it seems to work consistently better in the "upright" orientation rather than upside down, so I'm simply mounting my entire armature upside down to get the compass chip to be right-side up.

I won't be able to wear the CVA this weekend (not willing to risk it at airport security), but I'm very much looking forward to wearing it continuously once I get back.