Maglev

DIY Maglev Project

This research was initiated as Antipodes' (Terra Nova Robotics FIRST Tech Challenge Team 4529) Smart Moves FIRST LEGO League research project.  Six iterations advanced our design from a simple floating car to our 3-phase, 6-solenoid motor.  However, repeatedly pushing buttons to nudge a tethered car down a track was all that we could achieved in one FLL season.  At the 2010-2011 FTC World Championships in St. Louis, Dean Kamen challenged the competitors to create something extraordinary, something that would change the world.  We immediately thought to complete our original vision of a remote controlled hybrid maglev model that would inspire future engineers (other kids) and companies to make maglev advances that would build on our hybrid maglev idea.  See below for our model description, build instructions, Arduino programming code, drive train theory (flash presentation), and original FLL presentation.
 

Acknowledgements

Our project was orginally based on the ideas in William Beaty's 1994 article:  'Linear Motor for Maglev Train'.  Special thanks to the following mentors who helped us with the design of the original controller electronics:

Dale Zimmerman
Eric Benjamin
Byron Davis
 

Model Description

A Do It Yourself (DIY) remote controlled (RC) model maglev with electromagnetic propulsion. The linear motor is built of 6 home-wound 400' x 30 ga, enamel coated, copper wire solenoids in 3 phases mounted to an aluminum undercarriage mounted under a LEGO body containing four 1" x 1" x 3/16" Grade N42 NdFeB magnets for levitation. There are 2 parts to the electronics:  the motor controller and the remote.

The remote consists of an XBee Series 1 mounted to an Arduino Wireless Prototyping Shield, which is in turn mounted to an Arduino UNO R3 and powered by a standard 9V battery.   A Snootlab Encoder knob is also mounted to the Wireless Prototyping Shield for user input.

The motor controller consists of another XBee Series 1 mounted to another Arduino Wirelss Protyping Shield, which is mounted to two Sparkfun Ardumoto Shields, which are mounted to another Arduino UNO R3 and powered by an 11.1V Lithium Polymer (LiPo) battery.  12 bi-directional LEDs mounted on top of the Wireless Protyping Shield monitor the solenoid directions, displayed in a pattern that matches the resulting polarity at each end of each solenoid in the same arrangement as they are arranged under the car.

3 Allegro Micro 1301UA Hall Effect Sensors (one for each phase) are mounted on the inside of the undercarriage between the center solenoids and provide propulsion magnet field measurements to the controller (location information). The pair of levitation tracks are made of 2" x 1" x 1/8" matching polarity magnets epoxy glued to a 1" wide x 1/8" deep groove in a wooden base assembled with solid brass (non-ferrous) screws. The track drive is made from 2 rows of opposing, alternating polarity 1" x 1/4" x 3/16" thick magnets with 1/2" wide x 1/16" thick brass spacers and 3/16" diameter x 1/2" long magnets in a Hallbach-type arrangement (spanning alternating magnets behind the brass spacers). All magnets are Grade N42 NdFeB. The car has conventional lateral confinement (LEGO wheel in center groove) for simplicity and reduced cost without sacrificing nearly frictionless travel (our hybrid idea). The tracks are covered by an acrylic shield that provides a slick surface for the containment wheels and to further confine the track drive magnets for safety (important).
 

Power

The linear motor includes six, 23-ohm solenoids, 2 for each of the 3 phases, ensuring access to motive force at every moment of the motor cycle and consistent startup.  Each solenoid draws 0.43-ohms when connected in parallel pairs to one of the motor bridges of a Sparkfun Ardumoto Shield powered by a 11.2V LiPo (Lithium Polymer) battery pack (Ohms Law:  V=IR or I=V/R or 0.43A=10V/23-ohms).  The 1.2V difference between the maximum battery potential and that delivered to the solenoids is a result of the pack's simultaneous utilization as power for the entire model, including the board logic, LEDs, wireless receiver, etc.

Kjersti Chippindale describes the principles of electromagnetic propulsion utilizing the linear motor on their model maglev project. This was an early prototype built from LEGO Power Functions components and an Arduino Duemilanova.

ml_hes_formulas_small.jpg

This is a screen capture of an Excel file graphing the Hall Effect Sensor readings (field strength) for each phase as they pass by the track's drive magnets.  The bolder colors are the actual sensor readings and also represent full forward unamplified output to coils 1, 2 and 3.  The thinner colors represent the full forward unamplified output to coils 4, 5, and 6, which are wired in reverse.  This file is available for download at the bottom of this page..

Materials Required

maglev_materials_required.jpg

The major components required for the controller and remote.  Note only 1 of the 12 Bi-Color LED's shown.  Also, resistors should be 470Ω not 220Ω as shown.  Rechargeable LiPo Battery pack is one option for primary coil power.  

Track

  • 4' x 18" x 1" thick Medium Density Fiberboard (MDF)

  • 2' x 3/4" thick MDF

  • (20) 2" x 1" x 1/8" rare Grade N42 NdFeB magnets

  • (54) 1" x 1/4" x 1/16" Grade N42 NdFeB magnets

  • (54) 1" x 1/4" 1/8" Grade N42 NdFeB magnets

  • (104) or (156) 3/16" diameter x 1/2" long Grade N42 NdFeB magnets

  • (2) 1-3/8” x 2-1/8” x 21” x 1/8" thick, L-shaped Acrylic sheet

  • (8) #6 x 1-1/4” stainless steel, self-tapping screws (replace with wood dowels for better results)

  • (2) 1” x 1/4" diameter LEGO pneumatic tubing

  • Two-part, two-ton epoxy

  • Carpenter's Wood Glue

Car

  • (2) LEGO 2-Hole Technic Beam

  • (2) LEGO 5-Hole Technic Beam

  • (2) LEGO Technic Connector Peg/Cross Axle (Element ID 4186017)

  • (2) LEGO Technic Connector Peg with Friction (Element ID 4121715)

  • (2) LEGO Technic Pin Towball with Friction (Design ID 6628)

  • (2) LEGO Grey Pulley Wheels (LEGO Education W970018)

  • LEGO 16 x 32 stud plate

  • (5) LEGO 1 x 4 stud plate

  • (2) LEGO 1 x 8 stud plate

  • (4) LEGO 1 x 10 stud plate

  • (4) LEGO 2 x 6 stud plate

  • (4) LEGO 4 x 6 stud plate

  • (2) LEGO 4 x 8 stud plate

  • (2) LEGO 1 x 6 brick

  • (6) LEGO 1 x 12 brick

  • (2) LEGO 1 x 16 brick

  • (2) LEGO 1 x 6 brick

  • Additional LEGO bricks and stud plates per your design

  • (4) 1" x 1" x 3/16" Grade N42 NdFeB magnets

  • 2’ x 1/2" x 3/4" x 1/16” aluminum channel

  • 12” x 3/4" x 1/8” aluminum bar

  • (9) #6 x 1/4" brass screws

  • (9) 3/8” diameter brass washers

  • (9) #6 brass nuts

  • 12” x 1/4” inside diameter brass tubing

  • (6) rolls x 400' x 30 gauge, enamel coated, copper (magnet) wire

  • 1/4" x 3/16" x 8” bass wood

  • 1/4" x 1/16” x 5” bass wood shims

  • (2) 1/4" x 3/4" x 2”, 5-ply marine grade plywood, or similar

  • 6” x 12” x 1/32” thick plastic laminate

  • 1/4" diameter x 1-1/2” hex bolt

Controller

  • Arduino Uno R3 (MakerShed # MKSP11, Sparkfun # DEV-11021)

  • (2) Sparkfun ArduMoto Shields, Retail Version (Sparkfun # RTL-09896 )

  • Arduino Wireless Protoshield (Maker Shed # MKSP13)

  • Option 1:  XBee Series 01 802.15.4 Wireless Module (Maker Shed # MKAD14)

  • Option 2:  XBee Series 02 802.15.4 Wireless Module (Maker Shed # MKDG02) (Requires more advanced configuration)

  • (1) Breakaway Headers - Straight (Sparkfun # PRT-00116 )

  • (1) 6-Pin extra tall stackable header (Sparkfun # PRT-09280)

  • (1) 2-Pin Screw Terminal, 5mm Pitch (Sparkfun # PRT-08432)

  • (1) 3-Pin Screw Terminal, 5mm Pitch (Sparkfun # PRT-08433)

  • (3) 3-Pin Breakaway Header Sockets - Straight 1.27mm pitch x 4.6mm high (Harwin M52-5000345 available from Mouser)

  • (3) Allegro Micro 1301UA Hall Effect Sensors (Digikey has these, but in min. quantities of 3,000)

  • (12) 470Ω 1/4 Watt Resistor (RadioShack #) (optional - for coil monitoring)

  • (12) Bi-Color (Red/Green) LEDs (Fry's 625632) (optional - for coil monitoring)

  • 12V (8 x AA Battery Pack) or...

  • 11.2V Turnigy 2200mAh 3S 25C LiPo Rechargeable Battery Pack (Hobby King #T2200.3S.25/14975) with...

  • Battery Connector (Female Mini Tamiya <-> Male XT60) (Hobby King #605B-601A/30666) 

  • Jumpers of various lengths

  • 10’ x Cat-5E Cable (mostly pulled apart for color-coded AWG-24-Guage Wire inside)

Remote

  • Arduino Uno R3 (MakerShed # MKSP11, Sparkfun # DEV-11021)

  • Arduino Wireless Protoshield (Maker Shed # MKSP13)

  • Option 1:  XBee Series 01 802.15.4 Wireless Module (Maker Shed # MKAD14)

  • Option 2:  XBee Series 02 802.15.4 Wireless Module (Maker Shed # MKDG02) (Requires more advanced configuration)

  • SnootLab Encoder

  • 9V Battery Holder with ...

  • 9V Battery (logic)

  • Jumpers of various lengths

  • Clear Polycarbonate (TAP Plastics # )

Tools

  • Radial Arm Saw or Table Saw

  • Hole Saw

  • Power Drill

  • Hacksaw

  • File

  • Router

  • Countersink Bit

  • Clamps

  • Vice

  • Soldering Iron

  • Solder

Build Instructions: Car

  1. With a pipe cutter, cut six pieces of 1/4" (inside diameter) brass tube such that their lengths match the width of the aluminum channel.  Overcut at first.  The cut will force the tube to a slightly smaller diameter at the ends. We will refer to these at the SOLENOID TUBES.

  2. Insert a hole punch into the ends of the SOLENOID TUBES and strike them lightly with a hammer to force the diameters be identical for the entire length of each tube.

  3. Rub the ends of the SOLENOID TUBES on a metal file or emery paper so that each end of each tube is smooth and the same length as the aluminum channel is wide.  Set the tubes aside.

  4. Cut two pieces of 1/2" x 3/4" (inside dimensions) x 1/16” (thickness) aluminum channel, each 10” long.  One we will refer to as the SOLENOID CHANNEL and the other the WIRING CHANNEL.  Set the WIRING CHANNEL aside for now.

  5. Remove a 1” long x 1/4" deep rectangular cutout from the ends of all four legs of the SOLENOID CHANNEL.  We used a hack saw and metal file for this.

  6. CAREFULLY MARK six, 1/4" deep x 9/32” long, rectangular slots, on one leg of the SOLENOID CHANNEL.  The holes need to be 1-1/4” on center (the START of one hole to the START of the next hole).  This spacing is 1-2/3 times the spacing of the DRIVE MAGNETS magnets, which is critical for smooth driving.

  7. CAREFULLY CUT the edge of each slot using a hack saw, cutting through both legs.  Ensure that the hack saw blade is perpendicular to the channel so that the slots will align.

  8. Cut the other edge of each slot, but leave a little extra aluminum that can be filed off.

  9. File down the inside of the slots to the 1/4” depth.

  10. CAREFULLY file off the most recently cut edge of the slots until the SOLENOID TUBES fit snuggly and can be pushed down to the bottom of the 1/4” slot.  Test frequently so that the holes do not get too wide and the tubes will not be held in place by friction alone. 

  11. The tubes and slots will all be slightly different dimensions, so mark the tubes and slots to save time matching them up later.  Set the SOLENOID CHANNEL aside.

  12. Cut the aluminum bar into three equal pieces approximately 4” long.  These will be referred to as the ALUMINUM BEAMS.

  13. Drill three 9/64” diameter holes in each ALUMINUM BEAM:  one in the center of the beam, and the other two approximately 1/4" from the ends.

  14. Countersink the two outside holes in each ALUMINUM BEAM.

  15. Cut out three, 3/4" x 1/8” deep slots from the WIRING CHANNEL, cutting through both legs (flanges).  One channel will be centered on the WIRING CHANNEL, and the other two will be centered 1” from each end.  File these slots down until the ALUMNINUM BEAMS sit flush with the end of the legs and do not wobble.

  16. Drill a 9/64” diameter hole in the bottom (web) of the WIRING CHANNEL, centered on the three cutouts in the legs.

  17. Countersink each of the three holes on the side opposite the channel legs (flanges).

  18. Clamp the WIRING CHANNEL to the SOLENOID CHANNEL with the legs facing away from each other and their lengths aligned.

  19. Drill a pair of holes through both webs of the channels, centered between solenoid cutouts 1 and 2; one near each leg (flange).  Repeat with another pair between solenoid cutouts 5 and 6.

  20. Countersink the four holes from the SOLENOID CHANNEL side.  Remove the clamps.

  21. Place #6-32 x 1/4" flat head, brass screws into each of the three holes in the WIRING CHANNEL with the head in the countersink.  Make sure the countersink is deep enough that the head does not protrude from the channel web.  Do not add nuts yet.

  22. Put the SOLENOID and WIRING CHANNELS together again.

  23. Place #6-32 x 1/4" flat head, brass screws into each of the four countersunk holes in the SOLENOID CHANNEL and fasten with a brass nut on the WIRING CHANNEL side.

  24. Drill another pair of 9/64” diameter holes through the CHANNEL webs between solenoid cutouts 2 and 3 and another pair between solenoid cutouts 4 and 5.  Drill a single countersunk hole between cutouts 3 and 4.

  25. Countersink both sides of the five holes from the previous step.  These holes will be pathways for magnet wire to pass between the SOLENOID and WIRING CHANNELS.  The countersinking will ease the edges of the holes and minimize the chance of wires breaking or enamel coating wearing through. 

  26. Measure one of the 5-hole LEGO Technic Beams from solid side to solid side.  Subtract this from the inside width of the channel and divide by two.  Cut four pieces of 1/4" x 3/16” bass wood (and 1/4" x 1/16” bass wood shims) to approximately the height and length of the Technic Beam and the width just calculated.

  27. Epoxy glue the Technic Beam to the web of the SOLENOID CHANNEL with the holes facing the web.  Epoxy glue the bass wood to snuggly fill in each side adjacent to the Technic Beam.  The Technic Beam should now be securely fastened down the center of the channel at one end.  Repeat at the other end.

  28. Insert a LEGO Connector Peg/Cross Axle into the 2nd hole from the end of the Technic Beam.

  29. Insert a LEGO Pulley Wheel onto the Axle.

  30. Insert a LEGO Connector Peg with Friction into the 4th hole from the end of the Technic Beam.

  31. Insert a LEGO 2-Hole Technic Beam onto the Connector Peg so that it covers holes 4 and 5 on the beam below.

  32. Insert a LEGO Technic Pin Towball with Friction (or similar) into the hole closest to the middle of the car of the 2-Hole Technic Beam with the ball facing.  This ball helps protect the solenoid windings from accidentally scraping anything below.

  33. Repeat adding the LEGO pieces to the other end of the channel.

  34. Drill twelve, 9/32” diameter holes in the plastic laminate with enough space around each hole to cut out a concentric 3/4” diameter circle.  Test to make sure the SOLENOID TUBES can be fit inside the holes snuggly if possible.

  35. Draw twelve 9/32” diameter circles concentric to the holes.

  36. Cut the circles with a jig saw.  (We used a hole saw at first and spent tons of time trying to dislodge the circle from the saw.  The jig saw was much less time in the end.)

  37. Fit the circles over the ends of the SOLENOID TUBES and fit them inside the SOLENOID CHANNEL.  Press the circles out so they are flat against the inside of the channel.

  38. Add a circular bead of super glue where the inside of the plastic laminate circles meet the SOLENOID TUBES.

  39. Remove the six SOLENOID TUBES with circles attached.  These are now SOLENOID BOBBINS.

  40. Place a 1-1/2” long x 1/4" diameter hex bolt through one of the tubes.

  41. Thread a 1/4” diameter nut onto the bolt and tighten it down so the SOLENOID BOBBIN is securely fastened.

  42. Insert the exposed, threaded end of the bolt into the chuck of a variable speed drill, and tighten the chuck.

  43. By hand, wind one of the 400’ x 30 gauge enameled magnet wire around the SOLENOID TUBE at least a dozen times starting at one end keeping the wire tightly packed to the adjacent winding.  Leave the free end of the wire about 10” from the SOLENOID TUBE.

  44. Apply a small amount of super glue to the wire on the SOLENOID TUBE.

  45. Slowly start the drill in the same direction that the hand winding has begun.  It is important to keep the windings tightly packed against each other for the wire to flow smoothly onto the bobbin and maximize the packing of the wire.  We would occasionally flow a bead of superglue up and down the winding.  If the winding gets loose or wraps back on the previous winding, stop the drill and unwind by putting it in reverse and then go forward again.  When the winding gets to the end of the bobbin, work it backwards without building up a thick winding at the end.  This is not easy, but will eventually determine how well the winding will pack toward the end.  Wind until there is around 10” left at the other end of the wire.

  46. Rub about 1/4" of enamel off of the end of the two ends of wire with sandpaper.  Test the resistance of the wire.  It should be approximately 11 ohms.  We can’t remember if in the final version we spliced together two spools of wires or used just one.  So, if the resistance is about 5 or 6 ohms, rub off the end of another spool and splice it onto the first one with solder.  We bent the wire ends around a pair of needle nose pliers, placed them adjacent to each other, and soldered right on the needle nose pliers so the splice fits smoothly into the future windings.  Then wind the remaining spool by hand until there is enough friction to wind the remainder with the drill.

  47. Add a line of superglue perpendicular to all of the final windings to create the final SOLENOID.

  48. Repeat for the other five SOLENOIDS.

  49. Push the SOLENOIDS into the six SOLENOID cutouts, ensuring that the windings are all in the same orientation (VERY IMPORTANT).

  50. Feed the outer wire end (not the one that leads to the inside windings) from each SOLENOID into the closest of the four open holes through the channels.  If the holes were not countersunk before, countersink them now so that the wires do not rub against the relatively sharp corner of the holes.

  51. wound

  52. wire from the

Build Instructions: Track

  1. Fabricate two 1/8” thick, L-shaped, clear, acrylic covers for the track.  We had TAP plastics make these for us at a reasonable price in about 2 days.  Finished dimensions should be 1-3/8” x 2-1/8” x the length of your track (ours is 21” to fit in carry-on baggage).  Set this piece aside for later.

  2. Using a table saw, radial arm saw, or similar saw, rip two 1/8" wide x 3/16" deep grooves the length of the 3/4" MDF, exactly 1" apart from each other (1” clear space between), equidistant from the center.

  3. Flip the board over and mark it “BOTTOM of MDF BASE”.  Set it aside.

  4. Using a fence, rout a 1" wide x 1/8" deep groove the length of the 1" thick MDF, 1/2” away from one edge.  This edge will become the OUTSIDE edge of the MDF RAIL, and the grooved side will become the TOP of the MDF RAIL.  (We used a wide piece of MDF clamped to the board for the fence.)

  5. Remove the fence and rip cut the MDF RAIL to be 1-3/4" wide. (We started with a wide enough piece to easily balance the router and clamp the fence.)

  6. Mark the cut face “INSIDE” at two locations, neither of which is the middle of the board.

  7. Cut the MDF RAIL into two pieces the length of your track (ours is 21”).

  8. Flip the rails over and mark each of them “BOTTOM of MDF RAIL”.  (These are the wider sides that do not have grooves in them.)

  9. Draw a pencil line on top of the MDF BASE, parallel to one of the top grooves, 3/8" offset from the outside of the groove.  This will mark the alignment of one of the MDF RAILs to the MDF BASE.  Repeat this for the other side of the track.

  10. Clamp one of the MDF RAIL BOTTOMS to the MDF BASE TOP along the line marked in the previous step such that the ends align.  Ensure that the recess for the magnets in the MDF RAIL is further from the center of the track (1/2" curb, not 1/4" curb).  See the photo in the slide show above.

  11. Drill a __” diameter pilot hole __” deep into the BOTTOM of the MDF BASE, located __” from the end of the length of the boards, and centered on the rail.

  12. Remove the clamps and drill a __” diameter shank hole through the entire thickness of the MDF BASE.

  13. Screw a __ x __” drywall screw into the base to temporarily secure the two pieces together.

  14. Clamp the other end of the pieces together such that the outside edges align.

  15. Drill another pilot hole at the other end of the boards.

  16. Drill another pilot hole mid-distance between the first two pilot holes.

  17. Drill two more pilot holes between the other holes such that there are 5 total holes, including the one that has a screw in it.

  18. Unscrew the screw.

  19. Unclamp the other end of the board.

  20. Drill shank holes through the remaining four holes in the MDF BASE.

  21. Countersink the holes so that the drywall screw heads will not protrude from the bottom.

  22. Screw the drywall screws into the five holes to test that the screw tips do not protrude into the groove of the RAILS.  If they do, the magnets will neither lie flat nor bend.  So, we recommend filing the screw tips down or getting a slightly shorter screw of the same type.

  23. Unscrew the screws and set the MDF BASE aside.

  24. Squirt a bead of carpenters wood glue between the holes of the MDF RAIL (connect the dots).

  25. Press the MDF BASE and MDF RAIL together once again.  If excessive glue squeezes out the side, separate the pieces and wipe off some glue.

  26. Screw drywall screws into the 3 middle holes to fasten the MDF BASE to the MDF RAIL.  These are temporary and should be removed when the glue is completely dry and the track is complete.

  27. Screw flat head stainless steel screws into the holes at each end of the MDF RAIL.  These are permanent screws.

  28. Wipe away any excess glue that has squeezed out.

  29. Repeat the drilling, gluing, and screwing steps for the other MDF RAIL.

  30. Cut two 1/4" x 1” x 1/8” strips of wood such as maple.

  31. Apply a small amount of wood glue to the ends and the largest face of each wood strip.

  32. Insert one wood strip into the very end of one of the MDF RAIL grooves.  It should fit snugly in the groove and will prevent the first LEVITATION MAGNET from shooting out the end of the track.

  33. Insert the other wood strip into the end of the OTHER MDF RAIL, NOT THE OTHER END OF THE SAME RAIL.

  34. Mix and apply a small amount of two-part, two-ton epoxy on the first 3/4" of one of the MDF RAIL grooves, starting from the wood inserts.  Cover the side of the wood insert and the sides of the groove for the same 3/4" distance from the insert with a very thin amount of the epoxy also.

  35. Insert a 2" x 1" x 1/8", Grade N42, NdFeB magnet with the NORTH side facing up, into the epoxied portion of the groove.  Slide the magnet so that it is flush with the wood insert.  The magnet should fit snugly.

  36. Clamp the first magnet down to the MDF RAIL.

  37. Remove any excess epoxy.

  38. Apply epoxy along the exposed, thin edge of the first LEVITATION MAGNET and the bottom and sides of the groove for the next 3/4".

  39. Insert the next LEVITATION MAGNET into the groove with NORTH side facing up.  The end of this magnet will strongly repel the end of the first LEVITATION MAGNET if both magnets have been correctly oriented with their NORTH sides facing up.  Clamp the second magnet down to the rail with just a slight amount of movement remaining.  In this position, it should be relatively easy to force the second magnet against the first and clamp the second magnet tightly to the MDF RAIL.  This is a much easier task with two people:  one to force the magnets together, and one to clamp.

  40. Remove any excess epoxy, and repeat the epoxying and clamping with as many mangets as you have clamps.  IT IS VERY IMPORTANT TO REMOVE EXCESS EPOXY FROM THE GROOVE AFTER THE LAST MAGNET IS INSTALLED.  IF ANY EXCESS EPOXY REMAINS, THE NEXT MAGNET WILL NOT FIT SNUGLY TO THE LAST DUE TO A VERY HARD BEAD OF EPOXY.

  41. Wait for the epoxy to set for the duration recommended on the epoxy packaging.  Do not shortcut this duration.  Once the clamps are removed there are tremendous repelling forces between adjacent magnets, and if they are not adequately restrained by the epoxy one of the magnets will fly off the track at a startling and dangerous velocity.  This happened to us before we used grooves in the track.  The magnet would have flown through our window (or one of us) if it had not hit the wall instead.  Also note that if the epoxy mix is too rich with resin, the setting time will take longer than the recommended time on the packaging.  So, carefully mix the recommended proportions of epoxy resin and hardener.

  42. Remove the clamps once you are certain that the epoxy has attained its maximum strength.

  43. Repeat the epoxying, magnet inserting, and clamping process until the end of the MDF rail has been reached.  There should be approximately a 1/4” space at the end of the groove.

  44. Measure and cut another wood insert to fill the remaining portion of the groove.

  45. Glue the wood insert in place with carpenter’s wood glue as soon as the last magnet is clamped.

  46. Repeat the LEVITATION MAGNET installation with the other MDF RAIL, except this time the magnets should be oriented with SOUTH facing up.  By using the opposite polarity for each rail, the car can be attracted to the rail when inserted backwards, a very handy orientation for traveling with your model track and train.  Also, the opposing orientations will create a zero field along the centerline of the track, which might be significant for abating unwanted currents in the train electronics.

  47. Place the short leg of one of the L-shaped, PLASTIC COVERS into one of the rip cuts in the MDF BASE.  The COVER should lie flat on top of the adjacent MDF RAIL because the short leg does not bottom out in the rip cut.  It is merely held snuggly from side to side.  If not, the short leg is too long, and should be trimmed.

  48. Drill a __” diameter, __” deep pilot hole through both the PLASTIC COVER and into the MDF RAIL, inboard of the LEVITATION MAGNETS and __” from the end of the rail.

  49. Remove the cover and drill a __” shank hole through the pilot hole of the PLASTIC COVER ONLY.

  50. Countersink the top of the shank hole so that a #__ self-tapping, stainless steel, flat head screw will be flush with the top of the PLASTIC COVER.

  51. Insert the PLASTIC COVER into the MDF BASE rip cut again, and fasten the COVER to the RAIL.  BE VERY CAREFUL TO NOT OVERTIGHTEN SCREWS THROUGH THE PLASTIC COVER.  IT WILL CRACK.

  52. Now that the PLASTIC COVER is firmly positioned relative to the MDF RAIL, drill and fasten another stainless steel screw in an identical fashion at the other end of the COVER and RAIL.

  53. Drill a __” diameter shank hole all the way through the PLASTIC COVER, MDF RAIL, and MDF BASE, midway between the two stainless steel screws.

  54. Drill two more identical holes midway between the new hole and the stainless screws.

  55. Countersink the PLASTIC COVER for each of the three new holes.

  56. Drill a __” diameter hole only 1/4” deep into the BOTTOM of the MDF BASE centered on the three holes.  Ideally, these would be counterbores, but we didn’t have that tool available.

  57. Insert a __” #8 flathead nylon screw into each of the three holes from the PLASTIC COVER side, and fasten with #8 nylon nuts from the bottom.  The nuts should fit into the counterbores.  Add nylon washers below the nuts if desired.

  58. Repeat the drilling, countersinking, counterboring and screwing for the other PLASTIC COVER and MDF RAIL.

  59. Remove all the screws attaching the PLASTIC COVERS to the MDF RAILS.  (The COVERS will be reattached after the DRIVE MAGNETS are in place)

  60. Mix the 2-part epoxy again, and smear a small amount of the epoxy on a 1/16” x 1/2” x 1” BRASS SPACER and place it firmly against the inside leg of the PLASTIC COVER approximately 1/8” from the end of the PLASTIC COVER.  The short and narrow edge of the BRASS SPACER should be pressed against the bottom of the longer PLASTIC COVER leg. 

  61. Smear a small amount of epoxy on one of the largest sides of a 1/16” x 1/4” x 1”, Grade N42, NdFeB magnet, and place it firmly against the inside leg of the PLASTIC COVER with a long edge against the BRASS SPACER.  This will be the first DRIVE MAGNET.  It is good if a small amount of epoxy is on the top and sides of the magnets and spacers for extra adhesion.

  62. Repeat attaching BRASS SPACERS and DRIVE MAGNETS to the PLASTIC COVER, but alternate the polarity of the DRIVE MAGNETS, N/S/N/S/…, until the end of the PLASTIC COVER has been reached.  A BRASS SPACER should be the final object glued to the PLASTIC COVER.

  63. Drill a __” diameter hole through the middle of the two BRASS SPACERS at the ends of the track, approximately 1/4” down from the top of the spacer.  Continue the holes through the short leg of the PLASTIC COVER the spacers are glued to.  These holes will hold the TRAIN BUMPERS that will be installed later.

  64. Place 1/8” x 1/4” x 1”, Grade N42, NdFeB magnets onto the DRIVE MAGNETS, North face to South face.  These magnets will strongly attract each other, and align themselves perfectly, so do NOT glue them.

  65. Place two or three rows of cylindrical, ______, Grade N42, NdFeB magnets between each drive magnet and its adjacent drive magnet, North end to South face, and visa versa.  Again, no glue is necessary.  These magnets will align themselves, attracting themselves to the drive magnets, and separating themselves evenly from each other through repulsion.  These magnets will complete the approximation of a Hallbach Array, and hence we refer to them as HALLBACH MAGNETS.

  66. Repeat attaching DRIVE MAGNETS and BRASS SPACERS to the other PLASTIC COVER, except be careful to start with the opposite polarities facing each other for each magnet that is directly across the DRIVE TRENCH.  Also be careful that the spacing does not creep.  The magnets and spacers should be directly across from each other for the entire length of the track.

  67. Repeat all of the other steps above so that both PLASTIC COVERS are identically configured (except for the magnet polarity opposition).

  68. Insert a 1-5/8” #6? Oval head, Brass screw through each end of one of the new holes of one of the plastic covers.  (2x)

  69. Cut two pieces of LEGO pneumatic tubing so that they are both 1” long, and insert them over the exposed threads of the brass screws.

  70. Insert the other PLASTIC COVER over the exposed ends of the brass screws and insert both PLASTIC COVERS into the MDF BASE rip cut.  The PLASTIC COVERS should mirror each other and be lined up with the holes drilled previously.

  71. Reattach the two stainless steel screws and three nylon screws for each PLASTIC COVER.


Build Instructions: Motor Controller - Lower ArduMoto Shield

  1. Insert the 6-Pin and 8-Pin Stackable Headers (included in the retail version of the shield) into the top of the Ardumoto Shield as shown.

  2. Turn the board upside down and solder ONLY 1 pin of each header to the bottom of the board.  Reheat each of the solder joints and wiggle the header into a vertical position, flush with the board.

  3. Solder the remainder of the header pins to the bottom of the board.

  4. Insert the 3-Pin 5mm Pitch Screw Terminals and 2-Pin 5mm Pitch Screw Terminals to the top of the board as shown (3-Pin terminal is closest to the Analog In pins).  The 3-Pin terminals will receive the output voltage from the 3 Hall-Effect sensors.  The 2-Pin terminals will connect a common 5V supply and ground to each Hall-Effect Sensor in parallel.

  5. Solder ONLY one of the pins of each terminal to the bottom of the board.

  6. Reheat the solder joints and wiggle the terminals flush to the board.  Then solder the remaining terminal pins to the bottom of the board.

  7. Insert 3 jumpers into the top of the board as shown.  These jumpers will return signal voltages from the Hall Effect sensors.

  8. Bend one end of the jumpers down to the 3-Pin Screw Terminal pins on the bottom of the board as shown, and solder.

  9. Bend the other end of the jumpers down to the Analog Input A0, A1, and A2 stackable header pins on the bottom of the board as shown, and solder.

  10. Insert 2 jumpers into the top of the board as shown.  These jumpers will provide a common Ground and 5V supply to each of the Hall Effect sensors.

  11. Bend one end of the jumpers down to the 2-Pin Screw Terminal pins on the bottom of the board as shown, and solder.  Solder the other end of the jumpers to the bottom of the board.  Then solder these jumper ends to the 5V and GND stackable header pins with clipped resistor leads, as shown

  12. Pull the middle pins out of a 6-Pin Stackable Header.

  13. Cut the Stackable Header in half to create a 2-Pin Stackable Header.

  14. Solder the 2-Pin Stackable Header to the board as shown.  This will provide main coil power from the lower shield to the upper shield.

  15. Solder a 2-Pin 5mm Pitch Screw Terminal to the motor power input holes as shown.

  16. Solder 4 short jumpers to the top of the board in front of digital pins D6, D7, D9 and D10 as shown.

  17. Bend one end of the jumpers to the stackable header pins D6, D7, D9 and D10 under the board and solder.

  18. Snip off the other end of the short jumpers.

  19. Solder 4 jumpers to the bottom of the board that will connect motor ouput A1 to D10, A2 to D9, B3 to D6, and B4 to D7.

  20. Bend one end of each of the four jumpers to the short jumpers installed in Step 16.

  21. Snip off the Stackable Header Pins D6, D7, D9 and D10 below the board as shown.  This will prevent motor power from reaching the Arduino Uno board.

  22. Slide the sides of two 2-Pin 5mm Pitch Screw Terminals together.

  23. Continue to slide them until they are aligned flush.

  24. Solder the combined 4-Pin Screw Terminal to the top of the board in motor output holes A1, A2, B3 and B4 as shown.

Build Instructions: Motor Controller - Upper ArduMoto Shield

  1. Insert the 6-Pin and 8-Pin Stackable Headers (included in the retail version of the shield) into the top of the Ardumoto Shield as shown.

  2. Turn the board upside down and solder ONLY 1 pin of each header to the bottom of the board.  Reheat each of the solder joints and wiggle the header into a vertical position, flush with the board.

  3. Solder the remainder of the header pins to the bottom of the board.

  4. Align a pair of pliers to cover 2 of the breakable header pins.

  5. Break off the 2 pins so they remain connected.

  6. Insert the shorter end of the header pins into the bottom of the board in the VIN holes as shown.  Solder ONLY 1 pin of the header to the top of the board.  Reheat the solder joint and wiggle the header into a vertical position, flush with the board.

  7. Solder the other pin to the top of the board.

  8. Solder a jumper into the bottom of the board from the A1 hole to the hole across from the digital Pin 3 hole, leaving 2 holes between the jumper and the header. Solder a jumper into the bottom of the board from the A2 hole to the hole across from the digital Pin 5 hole, leaving 2 holes between the jumper and the header.  Solder the shortest jumper into the top of the board in the holes between the long jumpers and the header.  Bend the ends of the short jumpers under the bottom of the board to the adjacent header pins (D3 and D5) and solder.  Clip off the other end of the short jumpers.

  9. Clip off the ends of the long jumpers that are soldered into holes A1 and A2.  Bend the other ends of the long jumpers onto the short jumpers and solder.

  10. Clip off the bottom of the stackable header pin in digital Pin 2 below the solder joint to the short jumper.

  11. Clip off the bottom of the stackable header pin in digital Pin 4 below the solder joint to the short jumper.  This step and the preceding step keeps power to the LEDs from being sent to the Arduino Board as well.

  12. Solder a 2-Pin 5mm Pitch Screw Terminal to the motor power output holes as shown.

  13. The completed, top side of the board should look like the photo.

  14. Clip off the bottom of the stackable header pins in digital Pins 3 and 12.  This keeps control power for motor C from traveling down to the Lower Ardumoto board and invadvertently controlling motor A.  Control power for motor C is actually traveling up Pins 5 and 8 and will jumper across to Pins 3 and 12 on the Wirelss Proto Shield described in the next part of the Controller build.


Build Instructions: Motor Controller - Wireless Proto Shield

Note:  Most of the work on this shield is for the optional coil monitoring.  You can monitor the coils, though hidden from most spectators, using the built-in LED's on the ArduMoto Shields.  If you want to skip the coil monitoring on the top of the Wireless Proto Shield, skip everything except the last step.

  1. Insert a bi-color LED into the Wireless Proto Shield in the hole locations shown.

  2. Make sure that the LONG LED lead is closest to the XBee socket, and solder it to the bottom of the board.

  3. Clip off the LONG lead below the solder joint.

  4. Solder another bi-color LED into the hole locations shown.  There should be 4 open holes between each lead of the LED's.  The LONG lead should be located AWAY from the XBee socket.  Once again snip off the LONG lead only.

  5. Insert a 470 ohm resistor into the top of the board in the holes shown. (The photo shows the wrong resistor, this was corrected later.) Prop up the resistor so there is a small gap between the resistor body and the board.

  6. Solder ONLY the end of the resistor closest to the first LED.  Bend the soldered lead down and solder to the clipped lead of the first LED.

  7. Clip off the end of the lead you just bent down and soldered.

  8. From the top of the board, pull up the other end of the resistor to allow space to solder a jumper to the bottom of the board.

  9. Insert a yellow jumper into the bottom of the board as shown.  Allow a small gap below the jumper so the insulation doesn't melt off and make an inadvertent connection to the LED or resistor joint below.

  10. Solder the ends of the jumper on the top side of the board, and snip both ends off.

  11. Bend the resistor back down and solder to the board.

  12. Bend the remaining lead of the resistor down and solder to the yellow jumper as shown.  Clip off the extra lead beyond the solder joint.

  13. Bend the remaining lead of the second LED down and solder to the resistor.  Clip off the extra lead beyond the solder joint.

  14. Insert a second 470 ohm resistor adjacent to the first (The photo shows the wrong resistor, but this will magically transform to the correct resistor later).  Leave the same gap below the resistor body as you did in Step 5.

  15. Solder ONLY the resistor lead that is CLOSEST to the Analog Input pins and AWAY from the Digital pins.  Bend the soldered resistor lead down and solder to the adjacent LED lead.  Clip off the extra lead beyond the solder joint.

  16. Pull up the other end of the resistor as you did in Step 8.

  17. Insert an orange jumper into the bottom of the board as shown.  Allow a small gap below the jumper so the insulation doesn't melt off and make an inadvertent connection to the LED or resistor joint below.

  18. Solder the end of the jumper closest to the Analog pins.  Pull up the other end of the jumper as shown.

  19. Clip off the extra lead of the jumper that is below the resistor.

  20. Push the second resistor back down level.

  21. Solder the remaining lead of the resistor to the board.  Bend the lead down and solder to the orange jumper as shown.

  22. Snip off the extra lead past the last solder joint.

  23. Bend down the final LED lead and solder to the resistor lead as shown.  Snip off the extra lead material past the solder joint.

  24. Solder the remaining end of the orange jumper to the board and snip off the extra.

  25. Install temporary jumpers and test the first two LED's.  When connected as shown, one LED should light up red and the other green.

  26. Insert the next four LED's.  We messed this up and had to fix it later, so be careful with this.  The two LED's directly adjacent to the first LED should have their LONG leads located CLOSEST to the XBee socket.  The two LED's directly adjacent to the second LED should have their LONG leads located AWAY from the XBee socket.  Clip off the LONG leads of these next four LEDs.  Then insert the next six LED's.  The three LED's in line with the first LED should REVERSE their orientation so the long lead is now located AWAY from the XBee socket.  The three LED's in line with the second LED should REVERSE their orientation so the long lead is now located CLOSEST to the XBee socket.

  27. Snip the SHORT lead of these next six LED's.  If done properly, the snipped end of the first line of LED's will all be located CLOSEST to the XBee socket, and the snipped end of the second line of LED's will all be located AWAY from the XBee socket.

  28. Bend all the remaining LED leads away from the middle of the board to make room to work.

  29. Install the resistors and jumpers for the first six LED's as shown, repeating steps 5 through 24.

  30. Install the resistors and jumpers for the fourth pair of LED's as you did with the first 3 pairs, except that the yellow jumper will instead extend to the whole adjacent to the yellow jumper for the first LED.  See photo.

  31. Install the resistors and jumpers for the remaining LED's, extending one end of the jumpers back near the first 3 pairs of LED's as shown.

  32. If you can get your battery to touch the ends of the jumpers for LED pairs 1 and 4, you should see 4 LED's light up.  The fourth pair should be colored opposite from the first pair.  (We were missing 2 LED's that were still being shipped, hence the photo only shows 10 LED's.)

  33. Insert one end of additional jumpers into the bottom of the board in the holes adjacent to the jumpers mentioned in the preceding step.  Insert the other end of the jumpers into the holes marked D2, D4, D6, D7, D9 and D10.  Solder both ends and snip off the extra.

  34. Solder a red jumper into the top of the board from hole D5 to D3.  Solder a yellow jumper into the top of the board from hole D8 to D12.  Snip off the extra from the underside of the board.

  35. A view of Step 34 from the top.

  36. We finally received our last 2 LED's to complete our board.

  37. Here's the final view from the bottom 

  

Testing:  Motor Controller

It's important to test your boards build before proceding. 

  1. Download the sketch Maglev_Wireless_LEDs" (attached to this page) to your Arduino Uno R3.

  2. Detach the USB cable.

  3. Insert the assembled Lower Ardumoto Shield into the Arduino Uno R3.

  4. Insert the assembled Upper Ardumoto Shield into Lower Ardumoto Shield.

  5. Insert the assembled Wireless ProtoShield into the Upper Ardumoto Shield.

  6. Connect a 9V battery to your Arduino Uno R3. It should behave like the video above.


Build Instructions:  Motor Controller - Final Assembly

  1. Insert the XBee into the Wireless Proto Shield.

  2. Cut back the plastic casing on the Tamiya end of the battery connector cable (Female Mini Tamiya <-> Male XT60) so that the pins inside can be connected securely to an Ardumoto Shield's VIN Screwposts.

  3. Insert the battery connector into the Lower Ardumoto Shield's VIN screwposts and secure it with the screws.

  4. Insert the Lower Ardumoto Shield into the Arduino Uno R3.

  5. Insert the Hall Effect Sensor output wires into the 3-pin screwposts connected to the Lower Ardumoto Shield's Analog Input pins as shown.  Label the pins A, B and C.  Insert the Hall Effect Sensors' common ground and +5V wires into the 2-pin screwposts connected to the corresponding pins of the Lower Ardumoto Shield.  Label them as well.

  6. Insert the phase A and B solenoid wires into the Lower Ardumoto Shield's 2-pin screwposts for phases A and B.  Label them as shown.

  7. Insert the Upper Ardumoto Shield into the Lower Ardumoto Shield.  Insert the phase C solenoid wires into the Upper Ardumoto Shield's 2-pin screwpost for phases C.  Label as shown.

  8. Insert the Wireless Protoshield (with XBee) into the Upper Ardumoto Shield.

  9. Connect the battery pack WHEN YOU'RE READY TO TEST.  DO NOT LEAVE PLUGGED IN WHEN NOT IN USE.  NOTE: we had to remove the Arduino UNO R3 every time we uploaded a new sketch program.  Otherwise we received upload errors.

Testing the Hall Effect Sensors

It's important to test your Hall Effect Sensors before trying to drive your motor.  Download the "Maglev_HES_Test" sketch to your Controller Arduino UNO R3 board (disconnecting other boards before downloading).  You can use the serial monitor on your computer to see your sensor readings, and you can see the results of the Hall Effect Sensor readings by pushing the car down the track.  This test outputs the polarity and amplitudes of the Hall Effect Sensor readings to the corresponding motor phases of the Ardumoto boards, which in turn illuminates the bi-directional LEDs on the wireless prototyping shield.

 

Testing the Linear Motor

It's important to test your motor before trying to control it with the remote.  If the motor isn't working properly troubleshooting motor control will be time-wasting.  Download the "Maglev_Motor_Test" sketch to your Controller Arduino UNO R3 board (disconnecting other boards before downloading).  This is essentially the same program as the Hall Effect Sensor test because the LEDs and motors are wired in parallel.  The only difference is that PWM is not used because the Ardumoto Boards don't appear to be able to handle the kickback of raw coils.  PWM Values of 0 or 100% are fed to the motors when the Hall Effect Sensors read anything above 50% of maximum.


Build Instructions:  Remote Controller - Encoder Knob (Optional Build) 

Note:  This encoder knob (throttle speed) can be purchased from Snootlab pre-assembled for another euro/dollar or two, making these steps unnecessary.

  1. Remove the parts from the Snootlab box.  We were not given any instructions for the build, but it was trivial.

  2. Solder the three resistors into the holes on the top of the board as shown.

  3. Insert the encoder into the top of the board and solder the pins to the bottom of the board.

  4. Solder the breakaway header pins to the bottom of the board as shown.  That's it...

Build Instructions:  Remote Controller - Wireless Proto Shield

  1. Insert a 6-pin stackable header into the shield area of the Wireless Proto Shield Board as shown.  This location is important for the screw holes to line up between the boards and the encoder.  Solder ONLY one pin from the bottom of the board.  Reheat the solder (frees up a hand) and wiggle the header to a vertical orientation.  We did this with the Encoder inserted to make sure we could align the screw holes.

  2. Insert jumpers to connect both end holes of the header to the grounded holes.  The labeled holes on the protoshield are connected to the corresponding header holes, so no wired connection between them is necessary.

  3. Solder both ends of the jumpers to the bottom of the board as shown.  Bend down one end of each jumper to the first and sixth header pins and solder.

  4. Cut off the bottom of the header pins just below the jumpered solder joints.  Cut off the extra length of jumpers in the ground holes.

  5. Insert one end of a jumper into the Digital Pin 9 hole and the other end into the hole adjacent to the second header pin, as shown.

  6. Solder the ends of the jumper to the bottom of the board.  Bend down one end of the jumper to the second header pin and solder.  Cut off the bottom of the header pin just below the jumpered solder joint. Cut off the extra lenth of jumper in the Digital Pin 9 hole.

  7. Insert one end of the shortest jumper to the hole adjacent to the third header pin.  Insert the other end of the jumper so the jumper is perpendicular to the header, as shown.

  8. Solder the ends of the jumper to the bottom of the board.  Bend down one end of the jumper to the third header pin and solder.  Cut off the bottom of the header pin just below the jumpered solder joint.  Don't cut off the other end of the jumper.

  9. Insert one end of a jumper into the Digital Pin 8 hole and the other end into the hole adjacent to the jumper installed in the preceding step, as shown.

  10. Solder the ends of the jumper to the bottom of the board.  Bend down one end of the shorter jumper to this jumper and solder.  Cut off the extra lengths of jumpers below the solder joints.

  11. Insert one end of the next shortest jumper to the hole adjacent to the fifth header pin (skipping a pin).  Insert the other end of the jumper so the jumper is perpendicular to the header, as shown.

  12. Solder the ends of the jumper to the bottom of the board.  Bend down one end of the jumper to the fifth header pin and solder.  Cut off the bottom of the header pin just below the jumpered solder joint.  Don't cut the other end of the jumper.

  13. Insert one end of a jumper into the Digital Pin 7 hole and the other end into the hole adjacent to the jumper installed in the preceding step, as shown.

  14. Solder the ends of the jumper to the bottom of the board.  Bend down one end of the shorter jumper to this jumper and solder.  Cut off the extra lengths of jumpers below the solder joints

  15. Insert one end of a jumper into the 5V hole and the other end into the hole adjacent to the fourth header pin as shown.

  16. Solder the ends of the jumper to the bottom of the board.  Bend down one end of the jumper to the fourth header pin and solder.  Cut off the extra lengths of the jumper and header pin below the solder joints.

  17. Insert the completed Wireless Proto Shield into the Arduino Uno R3.

  18. Insert the encoder board into the Wireless Proto Shield.

  19. Insert the XBee into the Wireless Proto Shield.

Build Instructions:  Remote Controller - Final Assembly

  1. Size and cut 1/8" thick, clear polycarbonate sheet and mark at bend lines to form a U-shape.  Heat the bend line using a plastic bender like ones sold by TAP Plastics.  TAP will also bend it for you at a reasonable cost.  Lay scrap pieces to the sides of your material so that heat does not escape near the edges and prevent you from bending the plastic.

  2. When you can see a thick deformed line along the full width of the piece, simultaneously raise up an end and push down toward the joint until the bend is 90-degrees.

  3. Bend the other end of the polycarbonate to complete the U-shape.

  4. Place the remote assembly and a 9-volt battery box into the polycabonate base to mark locations for screw holes.

  5. Drill and countersink four 1/8" holes for the Arduino board, and drill and countersink two 9/64" holes for the 9-volt battery pack.

  6. Cut a piece of 1/8" thick polycarbonate to fit snugly between the polycarbonate base legs.  Mark and drill six 1/8" holes for the Arduino board and encoder.  Mark and drill a 9/32" hole for the encoder shaft.

  7. Insert the encoder in the 9/32" hole and thread on the encoder shaft nut.

  8. Insert three 4-40 pan head screws into the top of the polycarbonate holes for the encoder.  Add vinyl spacers and a nut as shown.  Use a drop of thread locker to keep the nut from unscrewing.

  9. Insert a 4-40 flat head screw into the bottom of the polycarbonate base as shown.  Add vinyl spacers and a nut to fasten the Arduino and Wireless Prototyping Shield as shown.  Use a drop of thread locker to keep the nut from unscrewing.

  10. Insert a longer 4-40 flat head screw into the bottom of the polycarbonate base as shown.  Add vinyl spacers and a nut to fasten the Arduino, Wireless Prototyping Shield, and Snootlab Encoder as shown.  Use a drop of thread locker to keep the nut from unscrewing.

  11. Insert two longer 4-40 flat head screw into the bottom of the polycarbonate base as shown.  Add vinyl spacers and a nut to fasten the Arduino and Wireless Prototyping Shield as shown.  Use a drop of thread locker to keep the nut from unscrewing.

  12. Drill two 9/64" holes into the base of a 9V battery pack to match the holes drilled into the side of the polycarbonate base.

  13. Insert two 3/8-inch long, 6-32 flat head screws into the countersunk holes on the outside of the polycabonate side.  Fasten the battery pack base to the screws with two 5/16" keps or nylock nuts.

  14. Insert a 9-volt battery into the battery pack and slide onto the battery pack base.

  15. When the battery pack is completely slid onto the base the switch should be easily accessible.

  16. Plug the battery pack cable into the Arduino power socket.

  17. Snap the encoder knob onto the shaft. 



Testing the Remote

Test your remote build with the Arduino sketch "Maglev_Encoder_Test" attached to this page.  When the knob is turned, the Remote's onboard LED attached to pin 13 of the UNO should blink the absolute number of times corresponding to the throttle power (does not distinguish between forward and reverse).  Note there is a delay before starting so as not to append it to the preceding blink.  Pushing the knob will reset the throttle back to zero.


Testing the Remote with the Controller

When you have your Controller Boards together, you can test the remote and controller together.  This will prove that your controller is receiving the remote signals and capable of driving the motors as indicated by the monitoring LEDs.

Download and run the sketch "Maglev_Remote" to your remote's UNO R3.  Download and run the sketch "Maglev_Cuttlefish_Test" to your controller's UNO R3.  (Make sure you revmove all the shields from the UNOs before you download anything to them.)  When the knob is turned, the monitoring LED's will speed up or slow down their intervals depending on the knob direction.  The knob sends integers from -5 to +5.  At zero (braking) the LED's will be off.  Negative throttle positions (reverse) reverse the direction of the sequence for the purposes of this test.  See video above.  Pushing the knob will reset the throttle back to zero.


Testing Driving with the Remote and Braking

When you have your remote and car completely assembled, you can test your project by downloading "Maglev_Driving_Power_Control".  As seen in the video above, this will allow you to control direction and powered driving with the remote.  It will also allow you to brake the car by pushing the encoder knob on the remote.  This puts the car into reverse until the car is stopped.  This program also determines the direction the car is moving and counts the drive magnets the sensors pass.  Together these capability allow you to automatically brake the car before it reaches the end of the track as you can see in the video.  As speed sensing and control is refined, the braking distance from the end of the track can be refined as well.


Arduino 3-Phase Linear Motor Controller Circuit Diagram

Sorry, currently cannot find this file.

Arduino Remote Control Sketch Program (file attached to this web page)

/*
  Team Antipodes' code for interpreting Snootlab Encoder knob input:
  January 21, 2013
  April 14, 2013 (revised)(as demonstrated at 2013 Bay Area Maker Faire)
  ----------------------------------------------------------------------------------------------
  Hardware (models that were available January 2013):
    Arduino Uno R3
    Official Arduino Wirless Proto Shield
    Digi International XBee Series 1
    Snootlab Encoder Board
  ----------------------------------------------------------------------------------------------
  The Encoder is wired as follows:
    GND (ground) is wired to GND (ground) on the ProtoShield
    CHA is wired to digital pin 2 on the ProtoShield
    CHB is wired to digital pin 3 on the ProtoShield
    SWI is wired to digital pin 4 on the ProtoShield
    Vcc (logic power) is wired to 5V (5 volts) on the ProtoShield
    GND (ground) is wired to GND (ground) on the ProtoShield (encoder has 2 ground connections)
  ----------------------------------------------------------------------------------------------
  Ideally, our encoder seems to want to provide the following output to the digital pins when turning LEFT:
    A=1 B=1 (this is the state when no one is touching the knob)
    A=1 B=0 (knob starts turning LEFT)
    A=0 B=0 (knob halfway to click)
    A=0 B=1 (knob continuing to be turned LEFT)
    A=1 B=1 (knob clicked and not moving)
  Ideally, our encoder seems to want to provide the following output to the digital pins when turning RIGHT:
    A=1 B=1 (this is the state when no one is touching the knob)
    A=0 B=1 (knob starts turning RIGHT)
    A=0 B=0 (knob halfway to click)
    A=1 B=0 (knob continuing to be turned RIGHT)
    A=1 B=1 (knob clicked and not moving)
  Many of these conditions will repeat, depending on how fast the knob is being turned and how long it takes
  to loop through your code and poll the pins.  Occasionally, one or more of these states
  will be skipped, or the wrong condition will creep in.  The code below attempts to mitigate that as much
  as possible.  A previous attempt was made to weigh the preponderance of the evidence of all the codes
  during a click, but it slowed down the code to the point that too little evidence was being collected.
  -----------------------------------------------------------------------------------------------
  The reason for the 8 readings is to ensure that the knob has stopped turning for a sufficient
  duration and is in the clicked position.  Fewer reads would interpret occasional errant A=1 B=1 readings
  as clicked/stopped instead of between clicks, and therefore generate multiple speed increments or decements
  from a single turn/click.
  -----------------------------------------------------------------------------------------------
  Our push button (used for immediate motor braking) seems to provide very reliable output to a digital pin:
    SWI = 0 (button is not pushed)
    SWI = 1 (button is pushed)
  -----------------------------------------------------------------------------------------------
  variable throttleMax (set below) is the maximum positive and negative integer sent wirelessly to the motor
  controller.  This corresponds to the number of encoder knob clicks you want the user to turn to go from 
  stopped to full speed, whether in forward or reverse.
  ------------------------------------------------------------------------------------------------
*/
const int throttleMax = 5;
int throttle = 0;
int throttleLast = 0;
bool brake = false;
int brakeNumber = 100;

//  -----------------------------------------------------------------------------------------------
//  The onboard LED (connected to pin13 by the manufacturer) will blink the throttle setting, though not
//  distinguish between forward and reverse.  Consequently, it will indicate whether throttle settings are
//  being transmitted wirelessly to the motor controller for troubleshooting purposes.  The variable
//  blinkInterval adjusts the rate of blinking.  The variable pauseInterval adjusts the pause betwee sets
//  of blinks.
// ------------------------------------------------------------------------------------------------

const int blinkInterval = 200;
const int pauseInterval = 1000;
unsigned long blinkPrev = 0;
unsigned long pausePrev = 0;
int blinkNum = 0;
const int aPin = 9;
const int bPin = 8;
const int swPin = 7;
const int ledPin = 13;
int aVal = 1;
int bVal = 1;
int aRecent = 1;
int bRecent = 1;
int aOld = 1;
int bOld = 1;
int aOldest = 1;
int bOldest = 1;

void setup(){
  //start serial connection
  Serial.begin(9600);
  pinMode(aPin, INPUT);
  pinMode(bPin, INPUT); 
  pinMode(swPin, INPUT);
  pinMode(ledPin, OUTPUT);
}

int getEightValues(){
  aOldest = aOld;
  bOldest = bOld;
  aOld = aRecent;
  bOld = bRecent;
  aRecent = aVal;
  bRecent = bVal;
  aVal = digitalRead(aPin);
  bVal = digitalRead(bPin);
  return aOldest + bOldest + aOld + bOld + aRecent + bRecent + aVal + bVal;
}

bool readSwitch(){
  brake = !digitalRead(swPin);
  return brake;
}

void onLED(bool choiceOn) {
  if (choiceOn) digitalWrite(ledPin, HIGH);
  else digitalWrite(ledPin, LOW);
}

void blinkReset(){
    blinkNum = throttleMax + 1;
    pausePrev = millis();
}

void blinkThrottle(){
  int pauseTest = millis() - pausePrev;
  if (blinkNum > abs(throttle)) {
    onLED(false);
    if (pauseTest > pauseInterval){
      blinkPrev = millis();
      blinkNum = 1;
    }
  }
  else {
    int blinkTest = millis() - blinkPrev;
    if (blinkTest < blinkInterval) onLED(true);
    else {
      onLED(false);
      if (blinkTest > blinkInterval * 2) {
        blinkPrev = millis();
        blinkNum++;
      }
      if (blinkNum > abs(throttle)) pausePrev = millis();
    }
  }
}

void loop(){
  int aFirst = 0;
  int bFirst = 0;
  int aLast = 0;
  int bLast = 0;
  brake = false;
  throttleLast = throttle; 
  while(getEightValues() == 8 && !readSwitch()) blinkThrottle();
  onLED(false);
  if (brake) throttle = 0;
  else {
    aFirst = aVal;
    bFirst = bVal;
    do{
      if (aVal + bVal == 1) {
        aLast = aVal;
        bLast = bVal;
      }
    } while(getEightValues() != 8 && !readSwitch());
    if (brake) throttle = 0;
    else {
      if(aFirst > bFirst && aLast < bLast) throttle --;
      if(aFirst < bFirst && aLast > bLast) throttle ++;
      if (throttle > throttleMax) throttle = throttleMax;
      if (throttle < throttleMax * -1) throttle = throttleMax * -1;
    }
  }
  if (brake) {
    Serial.write(brakeNumber);        // sends the brake integer wirelessly
  }
  else if (throttle != throttleLast){ // When there's a change to the throttle...
     Serial.write(throttle);          // send the throttle integer wirelessly
     blinkReset();
  }
}

Arduino Motor Controller Sketch Program (file attached to this web page)

/* maglevDrive.ino

Written by Team Antipodes (Violet Replicon)
Terra Nova High School FIRST Tech Challenge team #4529
www.TheOneRobot.com
July 15, 2011
May 20, 2013
------------------------------------------------------------------------
This is the Arduino Sketch program for a 3-phase linear motor controller for Antipodes' remote controlled (RC) maglev model as exhibited at the 2013 Bay Area Maker Faire.

The program receives wireless throttle values from Antipodes' remote control.  The remote hardware consists of an Arduino Uno R3, an Official Arduino Wireless Shield, an XBee Series 1, and a Snootlab Encoder.  The Arduino Sketch program for the remote controller is provided serparately.  The motor conroller hardware consists of another Arduino Uno R3, 2 Sparkfun Ardumoto Motor Driver Shields, another Official Arduino Wireless Shield, and another XBee Series 1.

The linear motor consists of 6 solenoids with their axes mounted parallel to the surface of the track and perpendicular to the direction of travel.  Each coil generates a magnetic field at both ends of an air core (could be adapted to an iron core) that repel and attract 2 sets of permanent magnets arranged along the sides of a channel in the track.  The permanent magnets are arranged with opposing poles facing each other across the channel, and alternate orientations along the track's length.  Continuous motion is achieved by switching the polarity and strength of the coils.  The 6 coils are divided into 3 separate pairs (phases) that are wired in parallel, but with opposing windings.  The only reason for altetnating the direction of the windings has to do with our chosen coil spacing of 1-2/3 magnet spacings.  This fact has no effect on the programming and should be ignored for an initial understanding of our device.  Instead, consider only the first coil in each phase.

Coil control for each phase is calculated using the voltage (0 to +5v) returned from one of three dedicated, linear, bi-directinal Hall Effect Sensors.  The sensor's quiescent voltage (the voltage midway between min and max that corresponds to zero field) is subtracted from the raw voltage reading so that polarity corresponds to sign, and absolute value corresponds to field strength. The polarity and magnitude are then fed to the two input pins for each motor driver, which then direct the 12v power (up to nearly 1 amp based on our 13ohm coil pairs) to the coils in the desired direction using H-Bridges.  Because the coils lag the sensor by one quarter cycle, the coils are off when adjacent to a magnet (and the sensor is between 2 magnets), and the coils are fully on when between two magnets (and the sensor is adjacent to a magnet).  As the train moves, the coils switch polarity as they pass alternating polarity track magnets, providing continuous forward acceleration without any additional calculation except unit conversion.

Braking is achieved by driving in reverse until movement direction flips.  Reverse driving is achieved by simply multiplying the sensor readings by -1.  Position is calculated by counting magnets, and automatic braking is initiated when the vehicle nears the ends of the track.  PID speed control is roughly achieved by applying more coil power or coasting, not braking, to save battery power and for clarity of spectator understanding of the basic propulsion principle (observing the monitoring LEDs reflect the forward wafeform vs. random driving and braking).  Our track looks smooth to the eye, but is actually filled with relatively massive magnetic potholes.  Consequently, slower throttle settings result in full power being applied to exit the potholes and long coasting times on descent. (Note:  this program does not sustain D for longer than 1 program loop per half magnet cycle, and this should eventually be fixed). 

We failed to calculate real-time (updated every loop) speed reliably.  Instead we calculate it intermittently, but very reliably, six times per magnet cycle (each time each sensor value crosses zero) because the distance between magnets is very consistent.  Calculating actual speed (distance / time) is unnecessary since the distances are always the same.  Consequently, we used the time interval in lieu of speed for speed control.  This simplifies the program, but is a bit unintuitive since larger intervals correspond to slower speeds. Movement direction is critically important for accurately tracking the magnet count.  Movement direction is determined by multiplying the sign of the largest absolute HES value by the sign of the differential (current value - last value) of the smallest absolute HES value.  Many other algorithms were tried and failed to produce correct results throughout the entire cycle at this program's data aquisition rate.

The motor controller's XBee receives serial input from the remote's XBee as integer values (after conversion from Bytes to Integers) from -5 to 5, corresponding to full speed reverse to full speed forward, as well as a braking integer value of 100.

Experienced programmers will want to type-cast some of the numeric values instead of using more memory intensive data types.

Note:  turning off the LEDs is equivalent to turning off the coils since they are wired in parallel.  LED testing is performed before full 12V power is applied to the coils, and that is why many of the functions have names like "ledOn" and "allLedsOff".
*/


//  Global variables
//  ------------------------------------------------------------------------------------------
//  Note: array variables are used to store values for each of the 3 phases.  Phase A = 0, B=1, C=2.
//  If you are unfamiliar with array variables this is a simple application and an easy one to learn on.  For example,
//  "long hesVal[3] = {};" is the variable declaration for storing three Hall Effect Sensor readings of type LONG (nevermind for
//  the moment that values will not exceed the INT range), and sets all three initial values to zero (by default).  This could be
//  equivalently written as "long hesVal[3] = {0, 0, 0};".  Refering to the values is easy:  hesVal[0] stores the Hall Effect Sensor
//  value for the first phase (which we label 'A' on our physical model), hesVal[1] stores the second sensor value (phase B), and
//  hesVal[2] stores the third sensor value (phase C).  This simplifies the program in certain places in particular.  For example:
//
//    digitalWrite(dirPin[phase], ledDir);
//
//  without using arrays might need to be written as:
//
//    switch(phase) {
//      case 0:
//        digitalWrite(dirPin0), ledDir);
//        break;
//      case 1:
//        digitalWrite(dirPin1), ledDir);
//        break;
//      case 2:
//        digitalWrite(dirPin2), ledDir);
//    }
//
//  as well as having to declare the three variables dirPin0, dirPin1, and dirPin2, instead of the single array variable dirPin[3].

const int powerPin[3] = {3, 11, 5};  // digital pin 3 controls PWM for phase A, pin 11 for B, and pin 5 (rerouted back to 3 on the upper board) for C
const int dirPin[3] = {12, 13, 8};   // digital pin 12 controls DIR for phase A, pin 13 for B, and pin 8 (rerouted back to 12 on the upper board) for C
const int hesPin[3] = {2, 1, 0};     // analog input pins for Hall Effect Sensor outputs (numbered backward only for ease of wiring)
const int hesOffset[3] = {11, 5, 2}; // calibration offsets that zero the sensors away from magnetic fields
long hesVal[3] = {};                 // Hall Effect Sensor values
long hesLast[3] = {};                // previous Hall Effect Sensor values
int hesLastMagnet[3] = {};           // the HES value the last time a magnet was counted
const long minOnHesVal = 100;        // the minimum threshold for switching on coils
long onHesVal = minOnHesVal;         // the threshold for switching on coils.  This is varied to achieve speed control
long throttle = 0;                   // the speed desired by the user as transmitted by the remote's XBee
long throttleMax = 5;                // the maximum throttle setting (needs to be the same number in this code and in the remote's encoder code)
boolean braked = false;              // tracks whether braking has completed
boolean braking = false;             // tracks whether braking has been initiated
const int brakingDuration = 1000;    // backup measure for switching off braking.  Time since braking initiated (in milliseconds)
int currentMagnet[3] = {};           // the current magnet count
int lastCountMovement[3] = {};       // the direction the vehicle was traveling the last time a magnet was counted
boolean inTrack = false;             // whether or not the vehicle is in the track.  Used to turn off the coils.
int incomingByte;                    // a variable to read incoming serial data into
int incomingInt = 0;                 // initializes the integer translation of the incoming Byte
int maxPhase;                        // the phase with the highest absolute HES value
int minPhase;                        // the phase with the lowest absolute HES value
int phaseRecent;                     // the phase that most recently passed a magnet
long currentTime = 0;                // the current time in milliseconds since reboot
long brakingStartTime = 0;           // the time in milliseconds since braking was initiated
long lastCountTime[3] = {};          // the time in milliseconds since a magnet was counted
float movement = 0;                  // travel direction:  positive numbers are forward, negative backward.  Magnitude is irrelevent.
float brakingStartMovement = 0;      // the movement at the time braking was initiated.  Braking stops when this x movement < 0
const int highBreakMagnet = 8;       // the magnet count past which braking should be initiated when moving forward
const int lowBreakMagnet = 4;        // the magnet count past which braking should be initiated when moving backward
const long slowInterval = 200;       // the slowest desired speed (largest time interval).  Speed target for throttle = 1
const long fastInterval = 40;        // the fastest desired speed (smallest time interval).  Speed target for throttle = 5
long targetInterval;                 // the calculated target interval used in PID speed control
long timeInterval[3] = {2 * slowInterval, 2 * slowInterval, 2 * slowInterval};
                // Interval is the time (milliseconds) between magnets.  It is used in lieu of speed since the distances
               // between magnets are fixed.  However, lower intervals correspond to faster speeds.  Here it's being
               // initialized to a large number so the program understands it is stopped at startup.
long maxHesVal = 0;                  // greatest absolute HES value, but retains sign
long minHesVal = 0;                  // least absolute HES value, but retains sign
const int midHesVal = slowInterval + ((512 - minOnHesVal) / 2);  // uses initial onHesVal
const float kP = 1;                  // Proportional constant used in PID speed control
const float kI = 0.2;                // Integral constant used in PID speed control
const float kD = 1;                  // Differential constant used in PID speed control
const float dampI = 0.66;            // Integral dampening used in PID speed control
long error = 0;                      // the difference between the current and target intervals (speed) used in PID speed control
long integral = 0;                   // the Integral (cummulative sum of errors)


void setup(){
  pinMode(powerPin[0], OUTPUT);      // set to OUTPUT the Arduino pins that will control the motor driver PWM
  pinMode(powerPin[1], OUTPUT);
  pinMode(powerPin[2], OUTPUT);
  pinMode(dirPin[0], OUTPUT);        // set to OUTPUT the Arduino pins that will control the motor driver direction
  pinMode(dirPin[1], OUTPUT);
  pinMode(dirPin[2], OUTPUT);
  Serial.begin(9600);                // initialize serial communication
  for(int i = 0; i < 3; i++){                                  // for each of the 3 phases...
    hesVal[i] = analogRead(hesPin[i]) - (512 + hesOffset[i]);  // get the initial Hall Effect Sensor readings
  }
  for(int i = 0; i < 3; i++){                                  // for each of the 3 phases...
    hesLastMagnet[i] = hesVal[i];                              // tell the program the last time a magnet was counted was at startup
  }                                                            // in order to get valid initial intervals, etc.
}


int byteToInt(byte inByte){
// -----------------------------------------------------------------------------------------------
//  function that converts bytes received by the XBee to integers
// -----------------------------------------------------------------------------------------------
  int inInt;
  if (inByte > 127){
    inInt = inByte - 256;
  }
  else {
    inInt = inByte;
  }
  return inInt;
}


int getThrottle(){
// -----------------------------------------------------------------------------------------------
//  function that reads throttle values received by the XBee
//  Note:  the braking function sets incomingInt to 100 to use subsequent code as-is
// -----------------------------------------------------------------------------------------------
  if (Serial.available() > 0) {
    incomingByte = Serial.read();
    incomingInt = byteToInt(incomingByte);  
  }
  return incomingInt;        
}


void getHesValues(){
// -----------------------------------------------------------------------------------------------
// function that reads the voltage output from our linear, bi-directional Hall Effect Sensors.  Voltages range from approximately
// 0 (full South) to +5V (full North) with +2.5V = no field (quiescent).  AnalogRead converts these voltages to values from 0 to
// 1024 with 512 = no field (quiescent).  Subtracting 512 (quiescent) converts these values to a range of -512 to +512 with 0 = 
// no field (quiescent).  Consequently, the sign of the values can be used for determining magnetic field polarity, and the
// magnitude for determining field strength.  Checking for an absolute maximum reading over 100 will determine whether or not the
// car is in the track.  The absolute maximum and minimum readings are also stored 
// ----------------------------------------------------------------------------------------------
for(int i = 0; i < 3; i++){                // for each of the 3 phases...
    hesLast[i] = hesVal[i];                  // set the current HES values to the previous HES values before... 
    hesVal[i] = analogRead(hesPin[i]) - (512 + hesOffset[i]);    // updating current HES values with new HES sensor readings
  } 
  if (abs(hesVal[0]) > abs(hesVal[1])) {     // if the phase A sensor is closer to a magnet than the phase B sensor...
    maxPhase = 0;                            // temporarily assume that A is the max phase...
    maxHesVal = hesVal[0];                   // and update the maximum HES value...
    minPhase = 1;                            // and temporarily assume that B is the min phase...
    minHesVal = hesVal[1];                   // and update the minimum HES value...
  }
  else {                                     // otherwise...
    maxPhase = 1;                            // temporarily assume that B is the max phase...
    maxHesVal = hesVal[1];                   // and update the maximum HES value...
    minPhase = 0;                            // and temporarily assume that A is the min phase...
    minHesVal = hesVal[0];                   // and update the minimum HES value...
  }
  if (abs(hesVal[2]) > abs(maxHesVal)){      // if the phase C sensor is closer to a magnet than either A or B...
    maxPhase = 2;             // remember that C is the max phase (leaving the min phase whichever was set above)...
    maxHesVal = hesVal[2];   // and update the max HES value (leaving min HES value whichever was set above)...
  }
  else if (abs(hesVal[2]) < abs(minHesVal)){ // otherwise if the phase C sensor is further from a magnet than either A or B...
    minPhase = 2;                            // remember that C is the min phase (leaving the max phase whichever was set above)...
    minHesVal = hesVal[2];                   // and update the min HES value (leaving max HES value whichever was set above)...
  }
  if (abs(maxHesVal) > 100) {                // if there are any sensors near a magnet, remember that the vehicle is in the track 
    inTrack = true;
  }
  else {                                     // otherwise remember that the vehicle is not in the track...
    inTrack = false;
    for(int i = 0; i < 3; i++){              // and for each of the 3 phases...
      hesVal[i] = 0;                         // assign 0 to hesVal
    } 
  }
}


void getMovement(){
// -----------------------------------------------------------------------------------------------
//  function that determines which direction the vehicle is moving and assigns this to the global
//  variable "movement".  Movement has type FLOAT because it is later multiplied to produce a
//  FLOAT result (this could be recast instead).  Positive values correspond to forward movement.
//  Negative values correspond to backward movement.  Movement direction is critically important
//  for accurately tracking the magnet count.  Movement direction is determined by multiplying
//  the sign of the largest absolute HES value by the sign of the differential (current value - 
//  last value), which we call "distance" of the smallest absolute HES value.  Many other algorithms
//  were tried and failed to produce correct results throughout the entire magnet cycle at this
//  program's data aquisition rate.
// -----------------------------------------------------------------------------------------------
  long distance[3] = {};                            // variable to store: HES value - previous HES value
  for(int i = 0; i < 3; i++){                       // for each of the 3 phases...
    distance[i] = hesVal[i] - hesLast[i];           // set distance to current HES value - previous HES value
  } 
  switch(maxPhase){                                 
    case 0:                                         // when max HES is A:
      if (minPhase == 1){                           // and min HES is B... 
        if (maxHesVal * distance[1] > 0) movement = 1;  // if HES A x distance B is positive, movement is forward...
        else movement = -1;                         // otherwise movement is backward
      }
      else if (maxHesVal * distance[2] > 0) movement = 1; // otherwise min HES is C, and if HES A x distance C is positive, movement is forward...
      else movement = -1;                           // otherwise movement is backward
      break;
    case 1:                                         // when max HES is B:
      if (minPhase == 0){                           // and min HES is A... 
        if (maxHesVal * distance[0] < 0) movement = 1;  // if HES B x distance A is negative, movement is forward...
        else movement = -1;                         // otherwise movement is backward
      }
      else if (maxHesVal * distance[2] > 0) movement = 1; // otherwise min HES is C, and if HES B x distance C is positive, movement is forward...
      else movement = -1;                           // otherwise movement is backward
      break;
    case 2:                                         // when max HES is C:
      if (minPhase == 0){                           // and min HES is A... 
        if (maxHesVal * distance[0] < 0) movement = 1;  // if HES C x distance A is negative, movement is forward...
        else movement = -1;                         // otherwise movement is backward
      }
      else if (maxHesVal * distance[1] < 0) movement = 1; // otherwise min HES is B, and if HES C x distance B is positive, movement is forward...
      else movement = -1;                           // otherwise movement is backward
  }   
}


void countMagnets(int phase){
// -----------------------------------------------------------------------------------------------
//  function to keep track of the position of each Hall Effect Sensor on the track, and consequently, the vehicle.
//  Note:  magnets are said to have been crossed actually when the next midpoint between magnets have been crossed.
//  There is a danger in inadvertently miscounting magnets when the vehicle changes direction.  The algorithm below accounts for this.
// -----------------------------------------------------------------------------------------------
  long currentTime = millis();                             // temporarily stores the current time for use in this function only
  long checkInterval = currentTime - lastCountTime[phase]; // store how long it has been since this phase's sensor passed a magnet midpoint
  if (checkInterval > timeInterval[phase]) timeInterval[phase] = checkInterval;  // because timeInterval is at least this big. Important for stopped condition.  
  if (hesVal[phase] * hesLastMagnet[phase] < 0) {          // if the sensor has crossed the midpoint between magnets...
    if (lastCountMovement[phase] * movement > 0) {         // and if the vehicle is moving in the same direction as when it last crossed a magnet midpoint...
      hesLastMagnet[phase] = hesVal[phase];                // then update the HES value when the last magnet midpoint was crossed...
      phaseRecent = phase;                                 // and remember that this phase was the one that most recently crossed a magnet...
      timeInterval[phase] = checkInterval;                 // and update this phase's timeInterval (speed)...
      lastCountTime[phase] = currentTime;                  // and remember the current time so future intervals can be calculated...
      if (movement > 0) currentMagnet[phase]++;            // so, add a magnet if moving forward...
      else currentMagnet[phase]--;                         // otherwise movement is backward, so subtract a magnet
    }
    lastCountMovement[phase] = movement;                   // otherwise vehicle has reversed direction.  Update the
time, but don't adjust the magnet count
  } 
  if (timeInterval[phase] > slowInterval * 2) timeInterval[phase] = slowInterval * 2;  // don't allow massive intervals to skew Integrals for a long time...
}                                                          // otherwise, and typically, do nothing


void getSpeed(){
// -----------------------------------------------------------------------------------------------
//  function that sets the HES value at which coils will be turned on, effectively adjusting power (a VERY slow PWM)
//  and speed with PID control.
// -----------------------------------------------------------------------------------------------
  int powerP;                             // Proportional speed control (correction proportional to current error)
  int powerI;                             // Integral speed control (correction based on accumulated errors)
  int powerD;                             // Differential speed control (correction based on projected future error)
  int throttleUsed = throttle;            // make a local copy of the throttle value to preserve the actual throttle input
  if (abs(throttleUsed) > throttleMax) throttleUsed = throttleMax;  // eliminates big numbers due to braking throttle of 100
  if (abs(throttleUsed) == throttleMax) onHesVal = minOnHesVal;     // if max throttle, forget speed control to demonstrate max acceleration...
  else {                                  // otherwise throttle is between extremes and speed should be controlled...
    targetInterval = (((slowInterval - fastInterval) * (throttleMax - abs(throttleUsed))) / (throttleMax - 1)) + fastInterval; // target speed
    long lastError = error;               // save current error as previous error before updating current error
    error = timeInterval[phaseRecent] - targetInterval;  // compare the speed of the most recent phase to the desired speed and use it as the error
    if (error <= 0) integral = 0;         // reset Integral when desired speed has been achieved (not using Integral to slow down)
    else integral = (dampI * integral) + error;  // integral calculation (I).  Recent errors are more heavily weighted through dampening.
    powerP = kP * error;                  // proportional calculation (P)
    powerI = kI * (integral + error);     // integral calculation {I}
    powerD = kD * (error - lastError);    // differential calculation {D}
    onHesVal = midHesVal - (powerP + powerI + powerD);  // sets the threshold above which coils are turned on
    if (onHesVal < minOnHesVal) onHesVal = minOnHesVal; // reset the treshold if below the minimum threshold
  }
}


int getPower(int rawValue){
// -----------------------------------------------------------------------------------------------
//  function that returns either 255 or 0 for PWM output to the Sparkfun Ardumoto motor driver boards.
//  These values turn the coil either full on or full off.  Presumably, true PWM could be fed to the motor
//  driver with the proper capacitor connected across the coil.
// -----------------------------------------------------------------------------------------------
  int power;                                  // local variable to be returned from the function
  if (abs(rawValue) > onHesVal) power = 255;  // HES value exceeds the threshold calculated in getSpeed, turn full on
  else power = 0;                             // otherwise turn full off
  return power;
}


int getDir(int rawValue){
// -----------------------------------------------------------------------------------------------
//  function that returns a 0 or 1, as specified to control the direction of the current through the motor outputs.
//  If the product of the HES value and throttle are negative, direction is set to reverse, otherwise direction is set to forward.
// -----------------------------------------------------------------------------------------------
  if (rawValue * throttle < 0) return 0;      // if product of HES value and throttle are negative, reverse...
  else return 1;                              // otherwise forward
}


void ledOn(int phase, int ledDir, int ledPower){
// -----------------------------------------------------------------------------------------------
//  function that writes direction and power to the digital output pins for input to the motor driver shields.
// -----------------------------------------------------------------------------------------------
  digitalWrite(dirPin[phase], ledDir);          // motor direction, 1 = forward, 0 = reverse
  analogWrite(powerPin[phase], ledPower);       // motor power, 255 = full on, 0 = full off    
}


void drive(){
// -----------------------------------------------------------------------------------------------
//  function that writes direction and power to the digital output pins for input to the motor driver shields.
// -----------------------------------------------------------------------------------------------
 for(int i = 0; i < 3; i++){                           // for each of the 3 phases...
   ledOn(i, getDir(hesVal[i]), getPower(hesVal[i]));   // send power to the coils based on HES values, throttle and PID speed control
 } 
}


void brake(){  
// -----------------------------------------------------------------------------------------------
//  function that is the opposite of DRIVE.  Direction is the opposite of current movement.
// -----------------------------------------------------------------------------------------------
 for(int i = 0; i < 3; i++){                                         // for each of the 3 phases...
  ledOn(i, getDir(hesVal[i] * movement * -1), getPower(hesVal[i]));  // same as drive, except in the opposite direction
 } 
}


void allLedsOff(){
// -----------------------------------------------------------------------------------------------
//  function that turns off all the coils.  Direction is irrelevent.
// -----------------------------------------------------------------------------------------------
 for(int i = 0; i < 3; i++){
  analogWrite(powerPin[i], 0);   
 } 
}


void loop(){
// -----------------------------------------------------------------------------------------------
//  the MAIN iterative function that turns off all the coils.
// -----------------------------------------------------------------------------------------------
 getHesValues();                        // read the Hall Effect Sensors
 getMovement();                         // determine which way the vehicle is moving
 for(int i = 0; i < 3; i++){            // for each of the 3 phases...
   countMagnets(i);                     // check the magnets
 } 
 if (inTrack == false){                 // if the vehicle is not in the track...
   allLedsOff();                        // turn off all the coils...
 }
 else{                                  // otherwise...
   throttle = getThrottle();            // get the throttle input from the XBee
   getSpeed();                          // calculate the PID speed values
   if (throttle == 100){                // if braking has been requested by the user or the magnet count...
     if (braking == true){              // and if braking has already been initiated...
       if (movement != brakingStartMovement || millis() - brakingStartTime > brakingDuration) {  // if car has reversed or braking has taken too long, stop.
         braking = false;               // no longer braking...
         braked = true;                 // because vehicle has braked
         allLedsOff();                  // turn off the coils
       }
       else {                           // otherwise...
         brake();                       // continue braking
       }
     }
     else if (braked == true) {         // braking is false and braked is true, so has already braked. Do nothing.
     }
     else {                             // otherwise braking is false and braked is false, so first time braking...
       braking = true;                  // and set braking to true...
       brakingStartTime = millis();     // and remember the time when braking began...
       brakingStartMovement = movement; // and remember the direction the vehicle was traveling when braking was initiated...
       brake();                         // and brake the vehicle
     }
   }
   else if ((currentMagnet[1] > highBreakMagnet && throttle >= 0) || (currentMagnet[1] < lowBreakMagnet && throttle <= 0)) incomingInt = 100; // brake if near the track ends
   else {                               // otherwise user desires driving and not at the end of the track...
     braked = false;                    // so, no longer stopped...
     braking = false;                   // and no longer braking...
     drive();                           // so, drive
   }
 }
}


Smart Moves FIRST LEGO League Project Description

Our project mission was to find the most efficient yet practical form of ground transportation. We evaluated the efficiencies of different technologies and found Maglev to be the winner due to its lack of rolling friction. That left us with a question, "If maglevs are so efficient, why don't we see them everywhere?" The answer led us to our hybrid maglev proposal, which we had peer reviewed by MIT and CalTech experts. Realistically though, we believe implementation has a better chance through the cumulative contributions of many minds. So we are working with LEGO to create a maglev set to inspire future maglev engineers.

CLICK HERE FOR FLL PRESENTATION

P1060085.JPG

On January 13, 2010, Antipodes pitched their maglev idea to LEGO Education in Billund, Denmark, with the intent of helping LEGO develop a kit and series of components that would give basic and advanced engineering kids (and adults) tools to explore the phenomena of maglev. The videoconference lasted 45 minutes, but the discussions are confidential. Many of the following attendees were also responsible for creating Mindstorms:

Gerhard Bjerrum-Anderson Product / Project Manager
Søren Thomsen Educational Concept Developer
Henrik V. Hougaard Director - Project Management
Torben Jessen Educational Concept Developer
Peter Thesbjerg Robotics Platform Manager
Carina Ottesen Project Manager FLL/LMC
Harv Stanic Robotics Software Producer


File Downloads

maglev_hes_formulas.xls
maglevDrive_2013_05_21.ino
snootlabEncoder_2013_04_14.ino
Cuttlefish.ino
SnootlabEncoderTest.ino