Recently, I finished my first Arduino project, a magnet levitation device. It levitates a magnetic object under an electromagnet. What makes it different from similar projects is the position sensor for the feedback loop. I use a single Hall effect sensor placed directly on the bottom of the core of the electromagnet. This creates a problem of separating the electromagnet’s field from the levitating magnet’s field. Fortunately, this can be done in software by the microcontroller ATmega168 that is the heart of Arduino Duemilanove, the board that I use to control the levitation. This article gives a detailed description of the project together with source code at https://github.com/rekka/levitation, as I promised.
WARNING: This is an advanced project that requires quite a substantial amount of tweaking and understanding of the software internals to make it work. Please consider this post just a description of what I did. Furthermore, it is more than 3 years old and therefore hopelessly outdated; the software most likely needs nontrivial changes.
This is my first attempt to describe something that I don’t have much experience with. If you find an error, if there is something that is not clear or if there is something I could improve, please, leave a comment.
Setup
The general setup is quite straightforward. The main part is an electromagnet consisting of a coil on an iron core. The current through my coil from a push-type solenoid is 300mA at 12V. At the bottom of the electromagnet, there is a Hall effect sensor directly on the iron core, positioned in such a way that the sensor detection axis is aligned with the core axis (figure on the right).
Components
- Arduino Duemilanove
- Switched power supply 12V
- Linear Hall effect sensor Honeywell SS19
- Norton operational amplifier MC3401P
- NPN transistor MPSA06
- Rectifier 1N4001
- Electromagnet (I used a coil from a 12V push-type solenoid)
- Resistors 2x 1k, 5k6, 47k, 68k, 330k, 4x 1M
- Capacitors 2x 1μ
Since I don’t have much experience, I tried to keep the circuit as simple as possible. The project consists of two quite independent parts.
The first part is the coil driver. I used a small transistor to turn the coil on or off, added a reverse-biased diode across the coil for protection of the transistor against fly-back currents, and put a capacitor across the supply for noise reduction. The base of the transistor is connected to a Arduino digital output through a 1k resistor, with an extra LED to indicate the pin state.
The second part is a bit more involved. I used linear Hall effect sensor SS19 from Honeywell. It is a tiny black box that has 3 pins, 2 of which are connected to GND and +5V, the 3rd one is the output. The sensor translates the perpendicular (to the two largest faces of the sensor) component of a magnetic field into voltage on the output. In my case, it was 2.15V with no field and 3.0 V with the maximal field (coil on and magnet nearby). Thus I attached 2 Norton operational amplifiers (out of 4 in MC3401). The first stage subtracts ~1.5V while the second amplifies it by a factor of ~3. That gives a signal in the range 1.8 — 4.5V, in the working range of the amplifier. This amplified signal then connects to an Arduino analog input pin. Note that I used a Norton op amp. It amplifies current difference, unlike the usual op amp that amplifies voltage difference. That’s why the wiring is slightly different. I also had to add a load resistor 5k6 on the sensor output to make it work properly.
A bit of theory
The magnetic field needed to keep a magnet from falling changes with the distance from the electromagnet. In our case, the position of the magnet is monitored by the Hall effect sensor. The closer the magnet is to the sensor the higher voltage is on the output. And the closer the magnet is the smaller field is required to keep it from falling. The situation is shown in the following figure:
The purple straight line represents the magnetic field required to keep a magnet from falling with respect to the reading on the sensor. In reality, it is nonlinear curve but that’s not important for us. We need to produce a field that will keep a magnet at a specific position. For that we will modulate the coil’s power so that the field produced is given by the blue line in the previous plot. The two extreme values are the maximal field of the coil and the field when the coil is off. If we are successful, the magnet should be stable at the intersection of the two curves in the middle of the plot. If the magnet gets too close to the iron core the shear attractive force of these two is enough to attract it. That is represented by the rightmost intersection. We need to prevent this situation.
That sounds quite easy, doesn’t it? But there is a small complication. The reading on the sensor includes the magnetic field of the coil. In fact, because the sensor is right on the core this component will probably be bigger than the component we are interested in, the field of the magnet itself. Fortunately for us, we know what signal we use to drive the coil. However, if we plot the driving signal and the magnetic field of the coil, we get the following plot:
This behavior is caused by the large inductance of the coil. When the transistor is open, the there is +12V on the coil. But the change in current induced magnetic field that resist this change and it takes more time for the full current to flow. When the transistor is closed, the current continues to flow through the diode in parallel. The resistance of the coil and the diode disipates the energy and the current decreases to zero. In my case, it takes around 5ms for the coil to energize or deenergize. That is way too long for us to ignore. But we can model this behavior. A coil can be approximated as a resistance R and inductance L in series. The circuit can be simplified like this:
The differential equation for the current I through the coil then reads
where V is the voltage on the coil. In our case, it is +12V when the driving signal is 1 and -0.6V (the diode voltage) when the signal is 0. The magnetic field is proportional to the current. For simplicity, we rewrite the equation for dimensionless field power y and applied voltage x, x being -0.05 = -0.6/12 or 1 and y being in the range 0 — 1. That yields the equation
λ is a parameter depending on the coil. But this is a equation for a low-pass filter. This continuous form can be discretized, see Low-pass filter on wikipedia. Assuming constant timesteps, Δt = 1, we get the final formula
The parameter α depends on the coil and on the timestep. It can be found using
where T is the number of timesteps it take for the coil to energize from 0 to 0.632 of its maximal field. This has to be found by some experimentation. Generally, α is different for the energization and deenergization phase since the diode gives rise to extra resistance in the circuit.
Control algorithm
Now with some theoretical background, we can proceed to describe the algorithm that controls the output signal. First, we have to find the following parameters (field shall denote the reading on the sensor):
- sample frequency = how often to sample field and adjust the output signal, I use 10kHz
- baseline = field with no magnetic fields
- coilMag = the maximal strength of the coil, i.e. the difference of field with the coil on and baseline
- alphaInc = the constant α from above for energization of the coil
- alphaDec = the constant α from above for deenergization
We have to keep track of the following variables:
- signal = last signal ouput, 0 or 1
- filter = value in the range 0 to 1, this is the field of the coil in the last step, the result of the discretized formula.
With these parameters, we perform the following steps with the sample frequency:
- Update the filter
- If ouput == 1, use filter += alphaInc(1 – filter),
- else filter -= alphaDec(filter + 0.05).
- Read the sensor input, field. Based on this value, find the value of the permanent magnet’s field alone, mag = field – coilMag * filter – baseline.
- Using the value mag that represents the distance of the magnet from the sensor, estimate the required coil power required to keep the magnet from falling, power. This is estimated based on the simple model shown in Figure 1.
- Produce new signal.
- If power > filter, set signal = 1 (need to energize)
- else set signal = 0 (need to deenergize)
Implementation
I should make some remarks on my software implementation:
Constant timestep
To achieve a constant sampling and output rate, some constant time-base is necessary. The standard millis() method doesn’t allow for the desired precision. One option is to use the build-in timers, like millis() is using, and change their resolution. But the simplest way is to use the A/D converter itself for timing purposes. The time it takes for ADC to perform a reading is constant and can be changed by changing the ADC prescaler settings. It is given by the formula
clock = 16,000,000 prescaler = 2, 4, 8, 16, 32, 64 or 128 conversionCycles = 13 * clock / prescaler;
The default prescaler value is 128, which gives the maximal conversion rate to be ~~9.6kHz. I’m using 64 which leads to 19230.8Hz sampling rate. The ADC clock value, clock/prescaler, affects the ADC accuracy. ATMEL recommends the range 50-200kHz for maximal accuracy, with rates up to 1MHz if less accuracy is needed. My choice gives 250kHz ADC clock, that’s quite close to the recommendation.
Now for the synchronization. When a conversion is finished, an interrupt flag in the ADC registers is set. Waiting for this flag provides the necessary synchronization. To ensure that the next conversion is started as soon as one is finished, the ADC is configured to work in a free running mode with an auto-trigger enabled. The functions analogSetup and analogStart configure the ADC and start the first conversion while analogNext waits for a conversion to finish and thus provides the synchronization for the program.
Multiplication code
The coil simulation is done by fixed-point integer math. That means that values of filter, alphaDec and alphaInc in the range 0 — 1 are multiplied by 2^16 = 65536. When a multiplication such as coilMag * filter is performed, the actual code is
result = coilMag * filter >> 16;
Since AVG GCC is adding extra unnecessary instructions for multibyte multiplications like this, I implemented my own assembler routines. This is not really required for this simple code, but I also experiment with additional digital signal processing to achieve greater stability of the magnet, and this processing is quite costly in terms of multiplications. Also, my own multiplication routine allows me to perform rounding of the result of >> 16 and that adds extra precision to the computation.
Computer interface
The Arduino performs all computations but the code is written in such a way that it is necessary for it to be connected to the computer. The levitation routine is initiated when Arduino receives 32 bytes from the computer. These 32 bytes must contain 16 integers and are written in the array ap. The significance of the individual fields is
ap[0] = alphaInc * 2^16 ap[2] = alphaDec * 2^16 ap[3] = 0.05 * 2^16 ap[4] = lowestMag ap[5] = powerDecay ap[15] = counter
alphaInc and alphaDec are the constants from above, 0.05 = 0.6/12 is the diode voltage. lowestMag and powerDecay are two parameters of the linear function used to compute the power of the coil magnetic field using the formula
power = 255 + (ap[4] - (mag)) * ap[5];
The levitation routine is stopped when a byte 0 is received by Arduino.
Finding the constants
Since the constants alphaInc, alphaDec and diode voltage seem fixed, I enter them by hand. To find their value, I simply guess, it is not that hard. To control the Arduino code and to monitor the performance, I use a simple Mathematica program. It looks like this:
The plots allow me to see the sensor reading, the signal output and the computed field of the permanent magnet (the three lines from top to bottom). To find a value alphaDec, for example, I guess some value and use the counter setting (ap[15]) to be 10. This causes the signal to be a simple square wave. Then I take a look at the computed magnet’s field. With no permanent magnet around, it should be constant and the plots should look like this:
If I guess the value too low or too high, I see some bumps like:
Code
Finally, here is the code:
https://github.com/rekka/levitation
To make it work with the 1Mbaud communication, you will also have to download check my optimized Serial library,
wiring_serial.c (the library is too outdated now. Fortunately, the official Arduino libraries have been updated and the issue has been fixed, it seems.)
Final thoughts
With the current implementation, the project faces a serious problem, instability. It works well for levitating a magnet for short time, minutes at most. After a while, oscillations develop and a magnet eventually falls. The reason for this is the lack of energy dissipation, or damping. The magnet under an electromagnet can be approximated by the equation
where y is the distance from an equilibrium. In words, the higher the magnet is, the closer it is to the electromagnet’s core and the stronger it is attracted. The lower it is, the further from the core, the less attraction there is. This system is unstable, the solutions of the equation diverge. By applying power to the electromagnet, we are trying to modify the equation to have the form
where c is some positive constant. Now this equation is stable, it is a harmonic oscillator. If there is a oscillation, it will be preserved, but it won’t grow. The problem appears when there is a delay between reading of the sensor and producing new output. And this will be there in every real system. This translates into appearance of a new term in the equation
Again, a is a positive constant. Since –a indicates how much oscillations are damped, this term will increase the osciallations. The question is how to fight this term. I experimented with a couple ideas that I had, like diferentiation or integration of the signal, or using some other more involved filters. So far, I wasn’t able to produce a consistent oscillation damping. But research continues.
Amazing tutorial!!!
I never thought to do this with the arduino but I think I’m going to give it a try if I can find an electromagnet. well done!
Thank you so much for creating this tutorial and providing so much detailed information and code! I am going to try to duplicate your project and add a light to the magnet that is levitated to create a little floating bulb for an art class.
Do you think that adding a button cell battery and LED circuit to the floating magnet will affect the Hall sensor or electromagnet? Or, will the magnets interfere with the LED?
Thanks again!
Hey Hannah,
I don’t think the LED or battery will change anything, you might just need a stronger magnet. You might also want to check out the floating bulb project at http://bea.st/sight/lightbulb/
Norbert
thanks for the tutorial
i want to learn more about levitation magnet and i want to ask;
1. why u use the norton op-amp (i meant, why u amplify the current? not the voltage? is it okay if i use 358 opamp ??
2. how to separate the reading sensor of the floating magnet field and the field of the electromagnet, please give me more explanation.
sorry for my stupid urge, and my bad bad english.
thanks before
Hey Freddi,
1. norton op-amp was the only amplifier I had, that’s why =)
2. most of the post is about separating the two fields, can you be more specific?
Best, Norbert
The reading becomes unstable once you start changing PWM since the coil acts as an inductor. That creates a delay between the PWM intensity and the field produced by the coil. You need software to compensate for this, or a secondary hall sensor on the other side of the core.
Sory for waiting my reply.
ok, i’m doing what u have done. I’m using ATmega8 as a pwm controller.
with 11,059200Hz oscliator. my sensor is hall effect 3503 from Allegro. the output of the sensor is linear when the PWM is constant. but if the PWM change, the output of my sensor became smaller and not stabil. any suggestion about that? ( i put my sensor right in the bottom of my electromagnet like u do in your project).
btw, how many hall effect sensor u used in your project? just one in the bottom o your electromagnet or there is another one in the top of the electromagnet?
i decided to use ugn 3503 coz i can’t find the same sensor as u have.
thanx,
Hi freddi, sorry, I had a really busy week =) I used only 1 hall effect sensor, see the 1st figure. Any linear hall effect sensor should be fine. But when you use only one sensor you have to separate the fields of the coil and of the magnet. The explanation of this is the rest of my post, starting with “A bit of theory”. And it is quite involved. I chose this approach because I’m much better at coding than at electronics. You can try different approaches, for example using 2 hall sensors, one on each side of the coil core, see for instance http://bea.st/sight/levitation/
hello???
ok, last question,
should we use a constant current for the power of the electromagnet??
and what kind of power scheme do you use for your electromagnet???
do you use PWM technique to control your electromagnet?
thank u very much and sory for wasting your time with my question. I’m just a beginner for this kind of stuff.
All the best
Hi freddi,
you need to use a PWM, of course. But not the Arduino PWM, analogWrite(). You need to generate the wave manually by turning on or off a digital pin based on the sensor reading. I described the algorithm in the section “Control Algorithm”. You can check out my code in the “Code” section. This is definitely not a project for beginners (even though I’m a total beginner too =)) You have to hack the Arduino libraries to get a fast enough performance, if you are using Arduino of course.
Norbert
thanks for ur help, i really appreciate it.
Interesting project and more interesting than that you’re using your math books to support your setup!
I wonder where I can buy a solenoid which is similar to yours or how I can make an electromagnet which shows similar properties of yours.
Ozhan
Fantastic project, I’m aiming to try something similar also (but using optical sensor vs. hall).
With the instability problem you’re experiencing, you might try the simple solution of creating some kind of dial that allows you to adjust the sampling rate while your program is running. Some adjustment to your sampling rate might automatically solve the issue (sometimes slowing it down rather than increasing it can help fix things). Best of luck in any case, and great to see such a detailed description of the project.
Hello dudes :), this is a great forum! if any of yous are thinking of making a website for whatever purpose, please visit us: SEO Company UK, also is it acceptable to donate to the owner of this website? good stuff!
Hi freddi, sorry, I had a really busy week =) I used only 1 hall effect sensor, see the 1st figure. Any linear hall effect sensor should be fine. But when you use only one sensor you have to separate the fields of the coil and of the magnet. The explanation of this is the rest of my post, starting with “A bit of theory”. And it is quite involved. I chose this approach because I’m much better at coding than at electronics. You can try different approaches, for example using 2 hall sensors, one on each side of the coil core, see for instance
http://www.magnets-fasts.com/custom.html
Hi Mekonik,
Thanks for this detailed tutorial of a great project!
If I understand correctly, in the loop, the variable “filter” keeps track of the field of the coil at the current time. Based on this value, if the controller needs to generate a field of a certain strength, the controller can decide whether there is a need to energize the coil (if the field of the coil is currently lower than the desired value) or if it is OK to leave the coil deenergized (if the field of the coil is currently greater than the desired value)
But since “filter” is updated indirectly (calculated via your discretized differential equation), shouldn’t there be some small difference between the calculated field and the actual existing field, which gets worse over time? Was this a problem you encountered while designing the device? Also, how did you manage to keep this difference to a minimum?
Thanks a lot!
Matt
Hi.
I now trying to build something like this, but for now i dont have good results. (
I have handmade magnet – it is 2,5 Om and works on 7,2 V with 1,9 A current. So I use hi-power motor driver on MOSFETs. I am not very good in arduino and electronics and don’t understand everything in your article.
I try to use more simple algoritm. I made computations with coil and build a function – like a*x+b from PWM value to magnetic field. So I can substract field made by coil. After, if sensor is more then some value i substract from PWM, if less, than I add to PWM.
But my algoritm is not working. (( magnet fly up too fast. ((
As I understand, your algoritm just turn magnet off and on?
I will continue my experiments, when I get better power supply. Now it works from 6 AA NiCd and power of magnet is changing very fast.
Thankyou for your report. It is very low information about this.
Maybe you’ve addressed this before (sorry for wasting your time if you have), but is it possible to replace the hall sensor setup with a simple reed switch using digitalRead on one of the arduino’s digital pins?
>With the current implementation, the project faces a serious problem, instability. It works well for levitating a magnet for short time, minutes at most.
I thought it you might be interested.
In all of professional technical devices that I’ve seen use two magnets are located opposite each other. Always.
Too long since I did this, not touched control engineering since late 90s, but there lies the answer. My old text book is “Feedback Control of Dynamic Systems – Third Edition” by Gene F Franklin and J David Powell. There’s also a book on digital control which is a little different because of the quantisation, so has different analysis. Same authors = “Digital Control of Dynamic Systems”.
“Feedback Control of Dynamic Systems” talks about lead and lag compensation. “Lead compensation” is like differential control – subtracting a multiple of the measured speed of the magnet to stop it overshooting – but adds an extra term (an extra pole in the transfer function – eek! maths!). It tries to get round problems with differential controllers, notably being prone to high frequency noise and differentiation error. You’re introducing high frequency noise in your digital process.
There is also information about systems where the sensor and actuator are co-located. I don’t know from what I’ve read whether the problems discussed are a general case (and don’t see why they should be) but it sounds like your system. The system discussed is a disk drive head mechanism.
I think this is the area to start exploring, but it is quite mathematical. Techniques have improved since the 90s so you may find an easier way of working it out. You’re on the right start with the differential equation of your system. You then transform this into a transfer function and work on it. I don’t remember studying the technique I’ve been reading about this evening (“root locus”), but instead some of the other techniques in later chapters of the book (“Bode” and “Nyquist” plots). Given a Bode plot of your system you can work out the transfer function of the control system that’s needed to control it. Then it’s a case of realising that system.
Hey Mekonik,
Nice work. Having built two different successful magnetic levitator projects in the past, I can tell you are close to finish line. While many complex approaches are available to analyze stability and avoid oscillations, the simple idea of adding electronic damping is easy and good.
How? You already mention the possibility of differentiating the signal. Yes, I would go with that. The differentiated signal will be noisy. You will probably want to start by differentiating the unfiltered data (filtered data is not the most up-to-date information). You might be motivated to learn about band-limiting the signal. But save band limiting refinements as a polishing step. You just want a reasonable velocity estimate, even if it is a little noisy to start. You need it so you can electronically fight any unwanted velocity up or down.
As soon as you have a reasonable velocity estimate (noisy or not), multiply this “Derivative” term by some gain and add it to your existing control signal. Be sure to flip the sign of this term such that you will attenuate (rather than amplify) existing motion. This is to be added to your existing control term. You will then have Proportional+Derivative (PD) control, instead of only Proportional (P) control.
Numerically estimating the derivative can be simple: velocity_estimate = (current_position – previous_position) / sampling_time.
You can add a simple filter that can help the derivative not be so noisy later. You probably have one or two in mind already. But be careful. Filters introduce delays, and delays can be de-stabilizing.
Good luck. There are lots of ways to add a little electronic damping. Hope you find great satisfaction in finding your own method. I would be happy to offer other suggestions if you are interested.
Tim