Monday, July 27, 2015

Yet Another Arduino Magnetic Levitator

I'd wanted to build a magnetic levitator since high school. I finally got off my butt and got around to putting one together recently. It was totally worth the effort and is thoroughly, as the kids say, the bee's knees. Here's how I put it together.

The general plan is to have an electromagnet whose magnetic field strength varies with the distance the magnetic levitating object, such that the distance is maintained constant. The distance from the levitating object is monitored with a Hall effect sensor. A second Hall effect sensor on the top of the electromagnet detects the magnetic field from the electromagnet and corrects the first sensor's reading to remove the influence of the electromagnet. The current through the coil is constantly adjusted to maintain a proper distance.



First, the electromagnet. I had a steel bar about 20 cm long by 1 cm diameter laying around. A soft iron core would have been better, but not free. I had several small spools of magnet wire, including 22, 26, and 30 AWG wire. The power supply I planned to use was a ATX power supply with 3.3V, 5V, and 12V taps. According to the Wikipedia page, magnet wire can typically handle 2.5-6 A/mm^2, depending on surroundings. Assuming the least conservative ampaciy, this corresponds to:

30 AWG: Current = 6 A/mm^2 * 0.0509 mm^2 = 0.3 A
200 feet at 103 ohm/1000 ft -> about 21 ohms resistance

V = I R = 0.3 * 21 = 6.3 V

26 AWG: Current = 6 A/mm^2 * 0.129 mm^2 = 0.8 A
75 feet at 40.81 ohm/1000 ft -> about 3.6 ohms resistance

V = 0.8 * 3.6 = 2.9 V

22 AWG: Current = 6 A/mm^2 * 0.326 mm^2 = 2.0 A
40 feet at 16.14 ohm/1000 ft -> about 0.65 ohms resistance

V = I R = 1.3 V

Ampere's law says that the strength of the magnetic field is proportional to the number of turns times the current through the wire. Since we want the maximum magnetic field we can get, if we multiply the lengths I had on hand by the max current in each wire, we get:

30 AWG : 0.3 A * 200 ft = 60

26 AWG : 0.8 A * 75 ft = 60

22 AWG : 2.0 A * 40 ft = 80

To get the strongest magnetic field, I should have chosen the 22 AWG wire. However, the length of wire I had has a max voltage of only 1.3 V, which would be rather difficult to control with a power supply with a minimum output of 3.3 volts. I'd need to keep the PWM down to a low level just to limit the current through the wire, leaving less room for control. The 30 AWG and 26 AWG wire should give the same magnetic field strength with voltages much closer to what the power supply puts out, so I arbitrarily chose the 30 AWG wire to use for the magnet.

I wrapped the steel bar with the 200 feet of the 30 AWG wire, which on a 1 cm diameter should have been a bit short of 2000 or so turns.

L = 200 ft 
= 6096 cm 
= # turns * circumference per turn 
= # turns * Pi * diameter

# turns = 6096 cm/ Pi / 1 cm 
= 1940 turns

Since there were several layers of wire on the bar, the actual number of turns is probably somewhat smaller, but should be good enough.

I wrapped the coil by placing the bar in the chuck of a drill, taping the beginning of the wire to the bar, and running the drill, similar to this. I didn't take any particular care to make sure it wrapped smoothly, since Ampere don't care if it looks pretty, as long as it's got the loops.

Loopalicious

Once I had the electromagnet coil wound, I made a wooden frame to mount the electromagnet in. I drilled a hole through a 1x2 and cut a slit through the hole and the wood. I then drilled a hole on the short side and put a machine screw through the hole, and attached a wing nut on the opposite end. This let me tighten down on the coil to hold it in place.

I placed the coil in the frame and attached two linear Hall effect sensors I'd bought from Digikey. These sensors output a 0-5V signal based on the strength of the magnetic field in their vicinity. The Hall sensor on the top of the electromagnet measures the strength of the magnetic field from the electromagnet. The Hall sensor measuring the distance to the floating magnet will also see the magnetic field from the electromagnet. Since we vary the strength of the electromagnet's field to keep the floating magnet steady, we need to compensate for this field to get a good estimate of the actual distance to the floating magnet. This could probably also be accomplished by running the electromagnet through its full current range and measuring the Hall reading at each point, then creating a calibration curve from these readings, but I chose to go the two-sensor route.



Tip - twist wires together with a drill to keep them organized. I first saw it here.


The circuit itself is pretty darn simple. It's just an n-Mosfet to control the coil, a flyback diode, two Hall sensors, and a potentiometer to adjust the distance between the floating magnet and the bottom of the electromagnet.

I'm proud of this soldering job. So fresh and so clean clean! Outkast would be proud, were he a worker in an electronics factory.




I used the Arduino PWM function to control the n-Mosfet to turn the coil on and off. The real heart of the project is the controller to take the Hall reading and output a PWM current to the coil. The controller has to work crazy fast, adjusting the current hundreds of times per second to keep the magnet stable. I ended up using a PID library to manage the calculation. Getting the PID parameters right took more time than the rest of the project combined. The final parameters have the coil power buzzing around like a pissed off yellowjacket, but at least it works!


I used the serial port to download Hall sensor readings and power output data to tweak the parameters. This chart is how the position of the magnet moves while floating with the final, working, PID parameters. I pulled the magnet out at the very end of this snapshot. The time axis on this chart is in milliseconds, so this entire span is only about a second and a half. Note how quickly the controller has to react to changes in the magnet position - it crosses the setpoint (of 150) about 40 times per second.

Another thing - the duty cycle of the coil while it's floating is only about 60%, meaning that, on average, the coil is only on about 60% of the time. This means that we can actually get away with a higher peak current through the coil than originally premised. Using the full 12V output from the power supply while the magnet is floating causes the coil to get a little warm, but definitely not hot enough to be concerning.

Here it is in action!



Here's the code:
>#include <pid_v1.h>  //PID control library

int Coil = 5; //PWM output pin that controls transistor for coil

double Setpoint, Input, Output; //PID variables

int PotPin = A2;    // select the input pin for the potentiometer

int TimeConst = 1;  //PID calculation frequency in milliseconds

int potValue = 0;  // variable to store the value coming from the pot

//Specify the links and initial tuning parameters for PID controller
double Kp=5, Ki=4, Kd=.069;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

int HALLA = 0; //Pin for reference hall effect sensor on top of coil

int HALLB = 1; //Pin for hall effect sensor on bottom of coil

int StopThreshold = 100; //Threshold below set point at which magnet will be turned off (if the floating magnet falls off or is removed, don't leave the coil just going full blast)

float CALSLOPE; //Slope of calibration curve for hall sensors

float CALINTERCEPT; //intercept of calibration curve for hall sensors

//variables for hall effect sensor calibraction
float CALSLOPEtemp = 0; 
float CALINTERCEPTtemp = 0;
float XA;
float YA;
float XB;
float YB;

float CorrectedVal; //Compensated hall reading

int Power; //Coil PWM variable

void setup(){
   //Reset PWM frequency to the coil pin to 62500/16 = 3906 Hz
  setPwmFrequency(5, 16);
  
  //Set PID sample time to defined value. Default is 200 ms, which is way too slow.
  myPID.SetSampleTime(TimeConst);
  
  Serial.begin(115200); //Need to transmit serial at 115200 bps to keep up  
  
//Calibrate sensors by reading them when the coil is off, then reading them while coil is on. This allows you to compensate for the coil's magnetic field and just identify the magnetic field due to the floating magnet.
 for (int i=0; i < 10; i++){
  digitalWrite(Coil, LOW);
  delay(100);
  XA = analogRead(HALLA);
  YA = analogRead(HALLB);
  
  analogWrite(Coil, 255);
  delay(100);
  XB = analogRead(HALLA);
  YB = analogRead(HALLB);
  
  //Two point slope-intercept form
  CALSLOPE = (YB-YA)/(XB-XA);
  CALINTERCEPT = (YA-CALSLOPE * XA);
  
  CALSLOPEtemp = CALSLOPEtemp + CALSLOPE;
  CALINTERCEPTtemp = CALINTERCEPTtemp + CALINTERCEPT;
 }
 
 //Average slope and intercept
 CALSLOPE = CALSLOPEtemp / 10;
 CALINTERCEPT = CALINTERCEPTtemp/10;
  
  Serial.println(CALSLOPE);
  Serial.println(CALINTERCEPT);
  
  digitalWrite(Coil, LOW);
  
  //arbitrary starting setpoint. We'll reset it as soon as we enter the loop
  Setpoint = 45;
    //turn the PID on
  myPID.SetMode(AUTOMATIC);
}

void loop(){
   //Read potentiometer to get setpoint desired
  potValue = analogRead(PotPin);
  Setpoint = map(potValue, 0, 1023, 0, 255);
  
   //Read hall effect sensors five times in a row, take average corrected reading
  for (int i=0; i < 5; i++){
    CorrectedVal =CorrectedVal + CALSLOPE * analogRead(HALLA) + CALINTERCEPT - analogRead(HALLB);
  }
  CorrectedVal = CorrectedVal / 5;
  
  //Turn off coil if the magnet is nowhere nearby
  if (CorrectedVal < Setpoint - StopThreshold) {
    digitalWrite(Coil, LOW);
  } else {
    //Otherwise, do PID control to keep floating magnet at setpoint
    Input =CorrectedVal;
     myPID.Compute();
     //Take PID output and apply to coil
     Power = Output;
     analogWrite(Coil, Power);
     //Print out timestamp, coil power, corrected hall effect reading, and target set point for hall effect reading.
    Serial.println(String(millis()) + ", "+String(int(Power)) + ", " + String(int(CorrectedVal))+ ", " + String(int(Setpoint)));  
  }
}

void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if(pin == 5 || pin == 6) {
      TCCR0B = TCCR0B & 0b11111000 | mode;
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode;
    }
  } else if(pin == 3 || pin == 11) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x7; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode;
  }

}

Tuesday, July 21, 2015

Foot controlled TIG welding with 80 Amp Harbor Freight welder

I have a cheapo 80 amp Harbor Freight DC welding machine for which I'd purchased a TIG torch and adapted it as I'd seen explained here to a scratch start TIG machine. It worked pretty well for a cost of less  than $500 all said and done, including the argon tank, but it was a bear starting and stopping because I couldn't adjust the amperage on the fly easily. The end of the weld would always be iffy because I had to lift off while at full power, leaving the hot metal without shielding gas and sometimes leaving holes in the welds and sometimes allowing a lot of oxygen contamination.

I saw this video here where a home-made on-off foot pedal made with a car battery knife switch is demonstrated. Seeing that flipped a switch in my head and I though hey - wouldn't it be neat to just put a potentiometer on the hinge of that foot pedal and somehow use that signal to adjust the amperage? This is essentially what commercial welding foot pedal controllers do, but they're generally crazy expensive, at least several hundred dollars. I fiddled with adjusting the amperage dial while welding and it seemed to work and the machine didn't seem to complain. I couldn't find anywhere on the vast reaches of the internet that advised against it, so I figured that I could hook up a servo to the dial and rotate the servo in accordance with the desired amperage, using an Arduino to control the servo and read the pot.

I get the rubegoldbergidity of using a potentiometer to control a servo to control a potentiometer. Alternatively, I could have opened up the welding machine and tapped the amperage control potentiometer, and put a circuit around it that would spoof the potentiometer reading. I was leery of making any permanent modifications to the welding machine wiring, but I may yet go back and do this.

I started by making the foot pedal. I bought a car battery disconnect switch here and mounted it to the foot pedal (itself just a few pieces of scrap wood with a hinge). I mounted a standard 10K potentiometer with a knob on the other side.




On the welding machine, I drilled a hole in the case to mount the servo body, and attached the servo actuator to the welding machine amperage knob with the highly advanced "Rubber Band" technique. The servo actuator has holes in in, through which I placed small nails and tightened them around the knob with the rubber band. The servo can slip on and off of the welding machine very easily like this.



Code wise, it's pretty simple. I used a Makershield I had sitting around with a 5V regulator already wired in to power the servo, and the foot pedal potentiometer is measured with one of the analog pins. When a button on the shield is pressed, it enters a learning mode for ten seconds. During that time, it records the maximum and minimum readings of the potentiometer and maps those to the minimum and maximum servo positions.

The full 10 amp to 80 amp range is a little more than 180 degrees on the welding machine so it's not quite capable of swinging the machine's full range, but it does allow a much better degree of current control than before. Using a 270 degree servo would allow the full range to be used.

How well does it work? See it in action!





Here's the code:
#include <Servo.h>

Servo myservo;

int angle = 0;  //servo angle

int potpin = 0;  //pot connected to Analog Pin 0

int potval;      //potentiometer reading

int btn = 4;  //digital pin 4 is button on MakerShield. This button, when pressed, will reset the high and low range of the potentiometer and re-map those to the servo's working range.

long RangingTime = 10000; //time during which pedal will learn its range

int highrange = 268; //default value for high range of pot

int lowrange = 104; //default value of low range for pot

int LED =5;    //LED is lit when arduino is learning new potentiometer range

int servopin = 6;  //Servo connected to pin 6

long ButtonPressTime;  //For debouncing learning button

void setup()
{

  myservo.attach(servopin); 
 // Serial.begin(9600);
  pinMode(btn, INPUT); 
  pinMode(LED, OUTPUT);

}


void loop()
{
  //button press for setting high and low range of pedal
  
  if (1-debounce(btn)){
    
    digitalWrite(LED, HIGH);
    ButtonPressTime = millis();
    highrange = 0; //reinitialize high and low range
    lowrange = 1024;
    while(millis() < ButtonPressTime + RangingTime){
       
      potval = analogRead(potpin);
      if (potval > highrange){
        
        highrange = potval;
        
      }else if (potval < lowrange){
        lowrange = potval;
      }
    }
    
    digitalWrite(LED, LOW);
    
   // Serial.print("High Range = ");
   // Serial.println(highrange);
    
  //  Serial.print("Low Range = ");
  //  Serial.println(lowrange);
  }

  potval = analogRead(potpin);
  if (potval > highrange){
    potval = highrange;
  }else if (potval < lowrange){
    potval = lowrange;
  }
  
  //Convert the pot reading to a servo angle, turn the servo to that angle. This servo is a 180 degree servo, but I cut it a bit short to keep it from straining at the end.
  angle = map(potval, lowrange, highrange, 0, 170);

  myservo.write(angle);

  //Serial.println(potval);

  //poll every 200 milliseconds. If this time is too short, the servo gets a bit twichy
  delay(200);


}

boolean debounce(int pin){
 boolean state;
boolean previousState;
const int debounceDelay = 10;
previousState = digitalRead(pin);
  for(int counter = 0; counter < debounceDelay; counter++)
  {  
    delay(1);
    state = digitalRead(pin);
    if(state != previousState)
    {
      counter = 0;
      previousState = state;
    }
  }
  return state;
  
}