Hack Notes CVA 091230

From Noisebridge
Revision as of 18:12, 30 December 2009 by SpammerHellDontDelete (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

[edit] Reading the Battery Voltage

We want to read the voltage of the battery, so that we can adjust the strength of the motors to compensate for "fresh" vs. "dead" batteries (currently North Paw is too strong with fresh non-rechargeable batteries, and too weak with dead rechargeable ones...)

We can't just use a voltage divider into analog input, because our analog reference voltage moves with battery voltage. Solution: use diodes to get a constant voltage drop, then a resistor to ground, read the junction point. For North Paw, we actually have a very convenient "diode & resistor" - the green power LED circuit. By wiring that junction into arduino A2 (ATMEGA168 pin 25 = ADC2), we can read a value which changes with battery voltage. (note: don't wire into A0 & A1 - those are used by the transistors!) No new hardware required, just a jumper!

We used a power supply with North Paw, having removed the 5V supply pin from the BUB serial board, and we get readings: ~350 when V=4.8V, ~600 when V=3.3. It's not quite linear, but it's close enough that we could approximate it as linear in a motor power scaling algorithm. So this can totally work, we will be able to scale motor power, and potentially even give "low battery" warnings. Note that this will require us to make sure the LEDs are always the same part, or at least have very similar voltage drops... probably worth a mention in any assembly procedure.

[edit] Acrylic Enclosure Mod

Eric hacked up an acrylic enclosure mod using hand tools: sensebridge blog. This was inspiration: can we make a similar enclosure which does not require the screws? Using a laser cutter, the answer is YES, it is possible to make little snaps and holes, and construct a 4-piece enclosure. Rachel is making an instructable about this. If we redesign the PCB to have little notches in each corner, we can hold the board in place easily without screws. The current V1.0 PCB however doesn't adapt itself very well to this idea, because the compass chip projects out the top, and without the notches in the PCB it wouldn't be held very well anyway.


[edit] Belt Version

A few days ago we finally hacked up a real belt version of the North Paw, using 12 motors. Each motor must have a diode in series with it, in order to prevent backwards current flow which results in two motors running when only one is supposed to. (this is a flaw in the design of the "transistor switch" method we used to multiplex the 8 channels from the shift register to support 16 motors).

Discussion:

  • belts are HUGE! you really need like 36 inches or more in length, in order to get around average waist, and compared to the anklet that is just sooo long (3x+). The longest zipper for sale at the sew shop was only 24 inches, so we have kind of a hack, with 6+ inches of zipper-less pocket on either side of the zipper. Also, we just used regular sewing Velcro instead of veltex, which we are very short of now.
  • diodes: as above, diodes must be put in series with each motor, and in the proper orientation as well (the mark on them goes towards NEGATIVE, i.e. if you're soldering it to the power rail, away from it, if you're soldering it to the ground rail, towards it).
  • middle crimp: we used a crimp in the middle of the ribbon cable, so that we could string motors in either direction from the center. This means each ground line gets used on each side, so no double dipping the ground lines, which is good. The middle crimp is then soldered into a ribbon cable with the regular motor jack crimp, which goes into the board. This does make the motor order in the code very confusing, since they count up as you move towards center, then down as you move away on the other side...

Code:

/* Skory & Eric
July 12, 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/
 */


// define the pins used to run the shift register
int enable_low = 3;
int serial_in  = 7;
int ser_clear_low = 5;
int RCK  = 4;
int SRCK = 6;

// define pins for transitors
int transistor_1 = 14;		//14 = A0
int transistor_2 = 15;		//15 = A1

#include <math.h>

//// define pins used to operate the digital compass (HM55B)
byte CLK_pin = 10;
byte EN_pin = 9;
byte DIO_pin = 8;
int X_Data = 0;
int Y_Data = 0;
int angle;
int status;

//timing vars
unsigned long counter = 0;
int prev_motor = 1;
int curr_motor = 1;
int cycles_per_second = 15; //board and compass specific - must measure
int count = 0; //NON-DEBUG CHANGE TO 0
int activity = 100;
int max_activity = 200;

unsigned long serialTimer = millis();

int max_motor_strength = 255;  // 255 = full power
int min_motor_strength = 120; //point under which motors don't run or are unfeelable
int motor_strength = 210; // holds changing motor strength vals

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);
  
  //set transistors as digital outputs
  pinMode(transistor_1, OUTPUT);
  pinMode(transistor_2, OUTPUT);
  
  // use some serial for debugging
  Serial.begin(57600);
  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 TPIC6 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();

  //set intial motor strength
  analogWrite(enable_low, 255-max_motor_strength);

}


void loop() {
  // make the compass get a reading

  HM55B_StartMeasurementCommand(); // necessary!!
  delay(40); // the data is ready 40ms later
  status = HM55B_ReadCommand();
//  Serial.print(status); // read data and print Status
//  Serial.print(" ");
  X_Data = ShiftIn(11); // Field strength in X
  Y_Data = ShiftIn(11); // and Y direction
  X_Data = X_Data * -1;  // In current rig, chip
  Y_Data = Y_Data * -1;  // is upside-down; compensate
  Serial.print("X: ");
  Serial.print(X_Data); // print X strength
  Serial.print(" Y: ");
  Serial.print(Y_Data); // print Y strength
  Serial.print(" ANG: ");
  digitalWrite(EN_pin, HIGH); // ok deselect chip
  angle = 180 * (atan2(-1 * Y_Data , X_Data) / M_PI); // angle is atan( -y/x) !!!
  angle = angle - 145;  // adjust for belt version, which is center mounted rather than end mounted
  if (angle < 0) angle = (360 + angle); //offset neg angles
  Serial.print(angle); // print angle
  Serial.print(" ");


  //Turn on the appropriate motor while keeping track of time and varying motor strength
  curr_motor = 13 - CalcMotor(12, angle);
  if (curr_motor != prev_motor) { //if we changed angle enough
    TurnOnMotor(curr_motor);      //turn on the new motor
    counter = 0;                  //reset counter
    if (activity < max_activity){
    activity = activity + 1;      //increase activity level up to 200
    motor_strength = (((float)activity / (float)max_activity) * (max_motor_strength - min_motor_strength) + min_motor_strength); //set m_strength proportianately to activity
    }									   // within range of min_ms-max_mas
  } else {						  //if angle hasn't changed
    if (counter < (activity / 10) * cycles_per_second) { //only keep same motor on for
	analogWrite(enable_low, 255-motor_strength);		 //less than cycles * activity level
      TurnOnMotor(curr_motor);
	  while (motor_strength < ((float)activity / (float)max_activity) * (max_motor_strength - min_motor_strength) + min_motor_strength){  	//if m_strength is low (motors off)
		 motor_strength++;						//crescendo the m_strength
	     Serial.print(" MS: ");
		 Serial.println(motor_strength);
		 analogWrite(enable_low, 255-motor_strength);
		 delay(50);
	  }
    } else {									//if counter runs to upper limit
	  while (motor_strength > 50){				//if m_strength is high (motors on)
	     motor_strength--;						//decrescendo the m_strength
	     analogWrite(enable_low, 255-motor_strength); //50 seems like point motor
	     Serial.print(" MS: ");					//stops running
		 Serial.println(motor_strength);
		 delay(50);
	  }
      TurnOnMotor(0);            				//then turn all motors off
    }
    counter++;                   //increment counter
    if (counter > (600 * cycles_per_second) / activity ){
      counter = 0;               //reset counter
      if (activity > 13){        //lower activity level
      activity = activity - 13;  //max val(s) 0-12
      }
    }
  }
  
  analogWrite(enable_low, 255-motor_strength);
  prev_motor = curr_motor;

  Serial.print("ACT: ");
  Serial.print(activity);

  Serial.print(" CNT: ");
  Serial.print(counter);
  
  Serial.print(" MS: ");
  Serial.println(motor_strength);
  

//  Serial.print("counter: ");
//  Serial.print(counter);
//  Serial.print(" activity: ");
//  Serial.println(activity);
  
/*
//Debug wacky motor wiring disorder  
  count++;
  TurnOnMotor(count);
//  Serial.print(count); // print angle
//  Serial.println("  ");
  delay(3000);
  if (count >= 12)
  {
    count = 0;
    delay(3000);
  }
*/
  
}



//// FUNCTIONS

void TurnOnMotor(int which){
  // accept which from 1 to 12
  // send message to shift register and transistors as appropiate
  delayMicroseconds(100);  //slow and steady
  Serial.print("Motor  ");
  Serial.println(which); // print angle
  switch(which){
    case 1:
      digitalWrite(transistor_2, LOW);
      digitalWrite(transistor_1, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B01000000);
      Serial.println("Trans 1");
      Serial.println("Shift 01000000");
      break;
    case 2:
      digitalWrite(transistor_2, LOW);
      digitalWrite(transistor_1, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B10000000);
      Serial.println("Trans 1");
      Serial.println("Shift 10000000");
      break;
    case 3:
      digitalWrite(transistor_2, LOW);
      digitalWrite(transistor_1, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B00010000);
      Serial.println("Trans 1");
      Serial.println("Shift 00010000");
      break;
    case 4:
      digitalWrite(transistor_2, LOW);
      digitalWrite(transistor_1, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B00100000);
      Serial.println("Trans 1");
      Serial.println("Shift 00100000");
      break;
    case 5:
      digitalWrite(transistor_2, LOW);
      digitalWrite(transistor_1, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B00000100);
      Serial.println("Trans 1");
      Serial.println("Shift 00000100");
      break;
    case 6:
      digitalWrite(transistor_2, LOW);
      digitalWrite(transistor_1, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B00001000);
      Serial.println("Trans 1");
      Serial.println("Shift 00001000");
      break;
    case 12:
      digitalWrite(transistor_1, LOW);
      digitalWrite(transistor_2, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B01000000);
      Serial.println("Trans 2");
      Serial.println("Shift 01000000");
      break;
    case 11:
      digitalWrite(transistor_1, LOW);
      digitalWrite(transistor_2, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B10000000);
      Serial.println("Trans 2");
      Serial.println("Shift 10000000");
      break;
    case 10:
      digitalWrite(transistor_1, LOW);
      digitalWrite(transistor_2, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B00010000);
      Serial.println("Trans 2");
      Serial.println("Shift 00010000");
      break;
    case 9:
      digitalWrite(transistor_1, LOW);
      digitalWrite(transistor_2, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B00100000);
      Serial.println("Trans 2");
      Serial.println("Shift 00100000");
      break;
    case 8:
      digitalWrite(transistor_1, LOW);
      digitalWrite(transistor_2, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B00000100);
      Serial.println("Trans 2");
      Serial.println("Shift 00000100");
      break;
    case 7:
      digitalWrite(transistor_1, LOW);
      digitalWrite(transistor_2, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B00001000);
      Serial.println("Trans 2");
      Serial.println("Shift 00001000");
      break;
    case 15:
      digitalWrite(transistor_1, HIGH);
      digitalWrite(transistor_2, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, B11111111);
      Serial.println("Trans 1&2");
      Serial.println("Shift 11111111");
      break;
    case 0:
      digitalWrite(transistor_1, LOW);
      digitalWrite(transistor_2, LOW);
      shiftOut(serial_in, SRCK, LSBFIRST, 0);
      Serial.println("Trans 0");
      Serial.println("Shift 00000000");
      break;
    default:
      // turn them all off
      digitalWrite(transistor_1, HIGH);
      digitalWrite(transistor_2, HIGH);
      shiftOut(serial_in, SRCK, LSBFIRST, 0);
      Serial.println("Trans 0");
      Serial.println("Shift 00000000");
  } 

  //in all cases, pulse RCK to pop that into the outputs
  delayMicroseconds(100);
  digitalWrite(RCK, HIGH);
  delayMicroseconds(100);
  digitalWrite(RCK, LOW);
}




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;
}




//HM55B Functions

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;
}
Personal tools