magnetic levitation

I’m working on a project to levitate a magnet using a linear hall effect sensor (SS494B) as feedback, so the closer the magnet gets, the larger the output voltage is. Using the code below I made this video:

the coil is my inductor with the sensor below reading the magnet below that
and my coil driver is set up similarly but instead I’m using a mosfet instead of a bjt and the gate is connected to digital pin 5

The problem I’m having (as you can probably see in the video) is the magnet bounces around and then will settle but the oscillations build back up and I would like to keep the magnet in one place.
What my code does is I have 3 while loops. The first is a standby loop that is waiting until the sensor reaches an arbitrary int of 630 (with no magnet and pwm output at 0 the hall output reads about 530).
Once the sensor passes 630, it will increase the pwm by 15 each iteration until the sensor’s output is 700 and then the program moves to the last loop decreasing the pwm by 15 each iteration until the sensor output drops below 700.

Here’s the code I have so far:

const int hall = A1; //hall=analog pin 1

void setup()
TCCR0B = TCCR0B & 0b11111000 | 0x02; //Change pwm output frequency to 7.8kHz
pinMode(5, OUTPUT); //sets digital pin 5 as Output
analogWrite(5, 30); //start pwm output at low Duty

void loop()
while(analogRead(hall)<630) //standby loop waiting for the presense of a strong magnet
analogWrite(5, 30);

while(analogRead(hall)<700) //increase pwm value until hall reads 700
if (pwmVal>110) break; //exit loop if pwm becomes too high
pwmVal +=15;
analogWrite(5, pwmVal); //updates pwm output

while(analogRead(hall)>700) //decreases pwm value until hall reads 700
if (pwmVal<15) break; //exit loop if pwm becomes too small
analogWrite(5,pwmVal); //updates pwm output

Does anyone know how I can smooth out the oscillations to keep it in one place? I’m not sure if the map() function would help or be a waste of time.

One of my other ideas was I need the sensor to read the levitating magnet’s intensity while ignoring the inductor’s magnetic intensity. My plan was to create an array storing the value of the sensor at each pwm output from 0-255 in set up (using a global array) and apply the following equation:
Mag=analogRead(Hall)-HallValue[pwm]+Hallvalue[0] or the Magnet’s effect on the sensor is equal to the sensor output minus the sensor’s output from the inductor at the current pwm output plus the offset voltage.
Problem is however it slows everything down and it won’t levitate. Any ideas to read the value of the magnet only as it’s levitating?

Thanks for the help and if you need more info let me know.

I've got two thoughts. Firstly, the relationship between your sensor input and the actual range needs to be understood. It might be well-behaved, but it could easily be highly non-linear and you need to know what the distance is in order to control the position.

Secondly, what you have there looks like a proportional controller with an inherent delay. This will tend to be unstable (the smaller the delay, the smaller the instability). What you need is a proportional+integral+differential (PID) control algorithm. Conceptually, the proportional part acts like a spring, as you have now. The differential part acts like a damper. You urgently need this part. The integral part acts to settle the thing at your target position. This is probably less important than the other two components but might make it easier to keep the system stable if you have non-linear sensors and actuators; you might find the system is inherently less stable at some distances and more stable at others simply based on the physics of your levitation and sensing hardware.

Thanks for the help. How would I differentiate or integrate in code without slowing down the output?

I was wondering how you infer the height of the magnet from under a coil that's interacting with a moving magnet. Accelerating up it would read strong? And on the way down?

If you can reflect IR off the side of the magnet then a Wiimote could probably track the position.

If it was flying a plane then a pilot would tell you it's chasing the needle (on the altitude gauge). Same goes if you drive a car by "connecting your foot to the speedometer". Think about how you drive at a steady speed, even if it isn't exactly the speed limit.

"How would I differentiate or integrate in code without slowing down the output?" A little patience goes a long way. The unit's spazzing bad enough as it is.

PID can tame it though coming up with the right numbers can be a bit of work. Maybe you can get a sketch going that adjusts it's own PID values? Looking very quickly at this there does not seem to be any delay or tolerance in your system. Build in a delay after adjusting your output and don't react further unless the input has exceeded a limit from where it was before.

Hi, you need to calculate and take into account the velocity of the object as well as position. As one of the earlier posts hinted at.

I used optical sensing for mine, which is pretty stable.

Here’s the sketch if it helps :slight_smile:

// Project 13 - Anti-gravity
// 15 Dangerous Projects for the Evil Genius

#define coilPin 11
#define irPin 13
#define sensorPin 0

int A = 2;
// Adjust B to improve stability 
int B = 60;
int C = 20;
int D = 1000;

int maxPower = 255; 
long powerCountThreshold = 300000;
int objectPresent = 0;
int monitoring = false;

void setup()
  pinMode(coilPin, OUTPUT);
  pinMode(irPin, OUTPUT);
  pinMode(sensorPin, INPUT);
  Serial.println("m - toggle monitoring");
  Serial.println("B - increase B");
  Serial.println("b - decrease B");

void loop()
  static int count = 0;
  static int oldPosition = 0;
  static int ambient = 0;
  static long powerCount = 0;
  count ++;
  if (count == 1000)
    ambient = readAmbient();
    count = 0;
    objectPresent = (powerCount < powerCountThreshold);
    powerCount = 0;
  int raw = 1024 - analogRead(sensorPin);
  // position from top (0) of sensor region to the bottom (650)
  int position = raw - ambient; 
  // positive value means going downwards, negative going upwards
  int velocity = position - oldPosition; 
  int power = position / A + velocity * B + C;

  powerCount += power;
  oldPosition = position;
  // clip
  if (power > maxPower) power = maxPower;
  if (power < 0) power = 0;

  if (monitoring)
    Serial.print(position);  Serial.print(","); 
  analogWrite(coilPin, power * objectPresent);

int readAmbient()   //todo try speding up delay in micros
  digitalWrite(irPin, LOW);
   // allow time for LED and phototransistor to settle
  int ambient = 1024 - analogRead(sensorPin);
  digitalWrite(irPin, HIGH);
  return ambient;

void checkSerial()
  if (Serial.available())
    char ch =;
    if (ch == 'm')
      monitoring = ! monitoring;
    if (ch == 'B') 
      B += 5;
    if (ch == 'b')
      B -= 5;

As Si says, you need to take account of the velocity as well as the position. Try googling for "PID controller".

These are the key lines of code:

int velocity = position - oldPosition; 
int power = position / A + velocity * B + C;

If your hall effect sensor gives you a position, then you just need to adjust the constants A, B and C.

very cool idea and nice execution by the way.

very cool idea and nice execution by the way.

Thanks Bill.

that's very helpful. my one issue is however that my hall sensor is reading both the inductor and magnet. how can I isolate the two fields? setting an interrupt for when the pwm signal is low but the analogRead takes too long. and my equation i mentioned in the original post slows things down too much.

that's very helpful. my one issue is however that my hall sensor is reading both the inductor and magnet. how can I isolate the two fields?

That is a pretty fundamental flaw and bound to limit the stability achievable. Perhaps if the hall sensor was placed below the magnet rather then above it the greater distance from the sensor to the inductor would not have enough effect to be a concern?


I had that thought about that too but the problem is given the height of the inductor (I'm trying to avoid shortening the arm the inductor is secured to) the hall sensor is too far away from where the magnet needs to be held to levitate.


Did you manage to separate the readings from electromagnet and the normal magnet? if so could please advise me on how to do this? I'm doing the same project.

Appreciate any help.

only update the analgwrite only when it is changed. your control loop is way to fast. you can not use a hall sensor as the the electric field is way bigger . ultrasound is good for this.