Hack Notes CVA 090409
(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.