Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Tron Disc with .NET Microframework

Harford Hackerspace wanted to make our own Tron Identity Disc using the Netduino, allowing us to quickly load different versions of code to change the disc's functionality.   David Powell, Gary W. Cygiel, Jeremy Ashinghurst, Paul King, Jason McMahon present a simple lightshow in this tutorial, developers can easily extend the code and hardware to produce an interactive game.

To get started, we purchased and retrofitted a Spin Master Tron Identity Disc replica, which kept us from having to create a new physical disk, and so sidestepped the most difficult aspect of the project.

Operation Specifications

We decided that we wanted our version of the Tron Idenity Disc to have the following specifications:

Power

  • Rechargeable lithium-ion battery
  • LED to indicate the Tron Identity Disc is being charged
  • An On/Off switch enabling the battery to disconnect from the Netduino to conserve power while the disc is being stored

Operation

  • Scenario 1 - Power On
    • All LEDs turn on so we can make sure they are all working
  • Scenario 2 – Animations
    • Pressing a momentary button will cycle through LED animations
  • Scenario 3 – Charge Detection
    • Placing the Tron Idenity Disc on or removing the Tron Identity Disc from a charging station will raise an event handler. Placing the disc on the charger will start a pulsating animation. Removing the disc will resume the previous animation.

Hardware

Tron Disc Light Ring

The original DeluxeTronIdentityDisc contained six LED on the outer ring, which left much to be desired. Our modified disc includes 30 LEDs on the outer ring and two LEDs illuminating the inner arc.

To get started, we opened the disc by removing its four Phillips head screws. Next, we removed all electronics including the wires, tilt sensor, LEDs, switch, speaker, and microcontroller board, taking care to not lose the plastic momentary button, since we later used it to switch between our animations. Then, we used a Dremel with a cutoff wheel to remove any unnecessary plastic in order to make room our own electronics. See Figure 1.

Empty_Disc

Figure 1: Removed Plastic with Dremel

As shown in Figure 2, we used superglue to attach the narrow end of the momentary button to the plastic Tron ring. Then, using the Dremel, we cut a small L bracket from a scrap piece of acrylic and attached it with cyanoacrylate. We reinforced the L bracket using hot glue and applied a small amount of cyanoacrylate to the momentary switch and attached it to the L bracket. Consequently, the original plastic momentary button now activates our momentary switch.

While we had the hot glue gun out, we also attached the SPST On/Off switch and the two inner ring LEDs.

mounting_switch

Figure 2: Mounting Momentary Switch

Next, we cut a circle from a piece of cardboard. Using a straight edge, we carefully marked the location of each LED to arrange an equally spaced circular pattern. Then, with a hobby razor knife, we cut notches in the cardboard at a slight angle to hold the LEDs. An additional benefit of the cardboard is it helps keep the anodes and cathodes from shorting out.

clip_image006

Figure 3: LED Nestled in Notched Cardboard

Next, we connected all of the components together per the following schematic diagrams. The MAX7219 IC is capable of controlling 64 LEDs. However, we chose to use only 30 in order to simplify of wiring. The circuit is divided into five segments, each containing six LEDs. In each segment, all the cathodes are tied together and then tied to a single segment pin on the MAX7219 and the anodes are routed back to its digit pins. There is one digit for each LED in the segment and the segments share digit pins. The Netduino controls the logic level of the digit and segment pins using “bit-banged” I2C. All that said, the schematic is the best reference as to how the LEDs were wired.

3

Figure 4: Netduino and MAX7219 Schematic

Charging System

The charging system is comprised of a charging circuit and a detection circuit. A lithium-ion battery inside the disc is charged from an external power source. The charging circuit, which was modified from a design by Scott Henion ofSHDesigns.org, provides a method of safe charging. The detection circuit allows the Netduino to know when it's being charged.

Charging Circuit

1

Figure 5: Charging Station Schematic

The LM317 is an adjustable voltage regulator that may also be used as an adjustable constant-current source. In this case, it's used as a voltage regulator. The 470 ohm resistor forms a voltage divider with the 1k ohm potentiometer and the 2.2k ohm resistor to set the output voltage at 8.4V. Since lithium-ion batteries should be charged using constant current-constant voltage, the transistor and resistor form the current limiting in the circuit. When the charge current reaches a certain threshold, the resistor's voltage drop exceeds the turn-on voltage of the transistor and the transistor starts to conduct. That takes current away from the voltage divider, dropping the voltage until the current is below the threshold. In this way, the current is limited until the voltage limit is reached; then, the current slowly drops off at a constant voltage.

Detection circuit

Tron_Battery_Updated

Figure 6: Charge Detection Schematic

The detection circuit works by applying a voltage to the disc whenever it is plugged in. When the disc is plugged in, 8.4V from the charger run through the 470 ohm resistor, then most of the current runs through the LED while a minimal amount runs through the 10k ohm resistor. The voltage to the Netduino is the voltage across the LED, equaling somewhere around 3V. When the charger is disconnected, however, the 10K ohm resistor pulls the voltage detection line to 0V. SW2 is a disconnect switch which feeds the main power to the Netduino. From there we use the Netduino's 5v power supply.

After all the connections were made we carefully placed all the wires and components inside the disc. We learned the hard way that you must pay extra attention since it's very easy to put a screw through one of your wires.

tucking_components

Creating a driver for the MAX7219:

To create a driver for the MAX7219, we took a little information from the data sheet. First, we created a basic class structure and initialized some static defines for the command reference as well as a few OutputPort's to hold our pin's to bit-bang. We bit-banged the protocol because the SPI implementation of the MAX7219 isn't 100% compliant with the Netduino's SPI Library:

namespace TronDisc
{
    public class max7219
    {
        // Command reference
        public byte max7219_reg_noop           = 0x00;
        public byte max7219_reg_digit0         = 0x01;
        public byte max7219_reg_digit1         = 0x02;
        public byte max7219_reg_digit2         = 0x03;
        public byte max7219_reg_digit3         = 0x04;
        public byte max7219_reg_digit4         = 0x05;
        public byte max7219_reg_digit5         = 0x06;
        public byte max7219_reg_digit6         = 0x07;
        public byte max7219_reg_digit7         = 0x08;
        public byte max7219_reg_decodeMode     = 0x09;
        public byte max7219_reg_intensity      = 0x0a;
        public byte max7219_reg_scanLimit      = 0x0b;
        public byte max7219_reg_shutdown       = 0x0c;
        public byte max7219_reg_displayTest    = 0x0f;

        // Pin ports for spi
        private OutputPort loadPin;
        private OutputPort dataPin;
        private OutputPort clkPin;

        // Constructor, pass pin definitions
        public max7219( OutputPort in_dataPin,
                        OutputPort in_clockPin,
   OutputPort in_loadPin )
        {
            // Assign local port pins to ports passed from constructor
            dataPin = in_dataPin;
            clkPin = in_clockPin;
            loadPin = in_loadPin;
     }
     }
}

Next we added a method to our class to transmit a single byte to the MAX7219:

// Transmits 1 byte over SPI, bitbang method
public void putByte(byte data)
{
    byte i = 8;
    int mask;

    while (i > 0)
    {
        mask = (1 << i - 1);
        clkPin.Write(false);
        if (((int)data & mask) == 0)
            dataPin.Write(false);
        else
            dataPin.Write(true);
        clkPin.Write(true);
        --i;
    }
}

And finally, we added one more method that pulled our load pin low, transmitted the register and column bytes, and pulled load high again to latch the data:

// Sends 1 Command / Data pair to a single driver chip
public void maxSingle(byte reg, byte col)
{
    // LOAD low
    loadPin.Write(false);

    // Transmit Register
    putByte(reg);
    // Transmit Column
    putByte(col);

    // LOAD high latches data sent
    loadPin.Write(true);
}

Complete with a function to send commands to the MAX via the maxSingle, we went back to our constructor and passed some initialization values after our pin assignments. maxSingle is so named because it only addresses one MAX7219, and since they are daisy-chainable, we can control multiple MAX7219s with little modification to our code:

// Initialize MAX7219
// set scan limit
maxSingle(max7219_reg_scanLimit, 0x07);
// using an led matrix mode (not digits)
maxSingle(max7219_reg_decodeMode, 0x00);
// not in shutdown mode
maxSingle(max7219_reg_shutdown, 0x01);
// no display test
maxSingle(max7219_reg_displayTest, 0x00);
// set max intensity  (range 00-0f)
maxSingle(max7219_reg_intensity, 0x0f);

Using the driver:

To use the driver, we created a variable to hold the instance of our driver, created the actual OutputPort references to pins, and passed to our instanced MAX7219 driver. The pins we are using are D10, D11, D13:

public static max7219 driver;

public static void Main()
{
    // Define spi pins
    OutputPort loadPin = new OutputPort(Pins.GPIO_PIN_D10, false);
    OutputPort dataPin = new OutputPort(Pins.GPIO_PIN_D11, false);
    OutputPort clkPin = new OutputPort(Pins.GPIO_PIN_D13, false);

    // Instance of driver, passing pin assignments
    driver = new max7219(dataPin, clkPin, loadPin);
}

At this point, we used our driver by calling driver.maxSingle(reg, col) to light either a single LED or a group of LEDs depending on specifications.

Animations

We completed two different types of animations, one based off of an algorithm and one using frame-by-frame animation for more complex patterns.

Our first animation is my personal favorite, the pulsating animation, which uses pulses to raise and lower the brightness:

// Main loop
while (true)
{
   // if direction is 1, we are going to fade down
   if (dir == 1)
      cnt--;
   else // otherwise fade up
      cnt++;

   // if direction is fading down and we are at 0 (the bottom)
   // switch directions to fading up
   if (dir == 1 && cnt == 0)
      dir = 0;
   // and if we are fading up (dir == 0) and we are at the max intensity
   // lets start fading down
   else if (dir == 0 && cnt == 15)
      dir = 1;

    // loop through driver.MAX7219_reg_digit0-7 and make sure all led's
    // are on by passing 0xFF which is all 8 bits on per digit
   for (j = 1; j < 8; j++)
      driver.maxSingle(j, 0xFF);

   // Pass the cnt value to the intensity register
   driver.maxSingle(driver.max7219_reg_intensity, (byte)(cnt & 0x0f));

   // Add a small delay between levels of intensity
   Thread.Sleep(15);
}

Wow, that looks great! Now we can create all kinds of animations using simple algorithms. To create more complex animations, however, a simpler method was needed. The disc is wired up around the ring using five digits of six LED segments. To simplify the math, we made a graphical ring of 30 LEDs, recorded each frame, and stored five characters per frame using six bits per character.

The creator is posted on the web at http://harfordhackerspace.org/ledwiz/tron/ and was created using HTML/Javascript and CSS. We won't get into the details of that code in this article, but feel free to use it to create as many animations as your Netduino will hold!

After creating an animation, click the ‘generate' button to get an output similar to the following:

byte[] animation = new byte[] {
  0x2,0x11,0x4,0x21,0x8,
  0x4,0x22,0x8,0x2,0x11,
  0x8,0x4,0x11,0x4,0x22
};

Here you can see three frames (one line per frame) with five characters using six bits per character. To display the animation, keep track of which digit (0-5) is active as the register and pass the character as the column:

int i;

for (i = 0; i < animation.Length; i++)
{
    driver.maxSingle((byte)((i % 5) + 1), animation[i]);
}

It's a simple animation. To slow it down, add a Thread.sleep(x) (x being the number of milliseconds between each command).

Dealing with multiple frame by frame animations

To create multi-frame animations, make a multi-dimensional array and modify the code to support multiple animations:

byte animation_num = 0;
int i;
byte[][] animations = new byte[][]
{
   new byte[]
   {
      0x2,0x11,0x4,0x21,0x8,
      0x4,0x22,0x8,0x2,0x11,
      0x8,0x4,0x11,0x4,0x22
   },
   new byte[]
   {
      0x1,0x10,0x2,0x8,0x2,
      0x3,0x18,0x6,0x2,0x8,
      0x7,0x3,0x18,0x1,0x8
   }
}
// Main loop
while (true)
{
   for (i = 0; i < animations[animation_num].Length; i++)
   {
      driver.maxSingle((byte)((i % 5) + 1), animations[animation_num][i]);
   }
}

Once it hits the main loop, it will keep playing the first animation (which is 0) until a change in the value of animation_num to 1 (then it will play the second animation).

A method is needed to read the button on the Disc and increment/loop the animation_num value. To do this, use the InterruptPort class.

First, add the InterruptPort code to the Main() function before the Main loop:

// Tron Button to Change Annimations, pulls high
InterruptPort changeBtn = 
   new InterruptPort(
      Pins.GPIO_PIN_D0, 
      false, 
      Port.ResistorMode.Disabled,
      Port.InterruptMode.InterruptEdgeHigh);
changeBtn.OnInterrupt += new NativeEventHandler(changeBtn_OnInterrupt);

Then, add a method called changeBtn_OnInterrupt to the program:

//Interrupt Handler for changeBtn to change annimations
static void changeBtn_OnInterrupt(uint data1, uint data2, DateTime time)
{
    if (++new_animation_num > animation_total)
    {
        new_animation_num = 0;
    }

     // reset intensity to max incase previous animation exits with 
     // intensity less than max
    driver.maxSingle(driver.max7219_reg_intensity, 0x0f);

    // Print debug string
    Debug.Print("Animation Num: ");
    Debug.Print(new_animation_num.ToString());
}

Finally, to get it all working, add a switcher at the top of the loop, change the animation_num values to be public throughout the class, as well as static since the program is static:

public class Program
{
    public static byte new_animation_num = 0;
    public static byte animation_num = 0;
    public static byte animation_total = 2;

    /* more goodness */

    // Main loop
    while (true)
    {
        animation_num = new_animation_num;

        // adding this to the top of our main loop
    }
    /* more goodness */
}

Finishing touches

To make it all work together, we mixed algorithmic animations by filling our animations array, hard-coded a few “if” statements to do algorithmic animations, as well as “else” statements for all the frame-based animations.  We also added another InterruptHandler to both act like a button when the Disc is placed on the charging dock and change the animation to the pulse animation.  Also, we tested the power by turning on all of the LEDs, waiting three seconds, and then proceeding.

About

Harford Hackerspace is a non-profit 501(C)(3) charitable organization set forth to create a place for people to collaborate on hardware and software projects. Members of the space have been meeting weekly since January 2009 to socialize, learn, and work on projects related to science and technology. In 2009 they built a Computer Numerical Controller (CNC) Router capable of precision cutting of wood, plastics, and aluminum. In August 2010 Harford Hackerspace hosted the first ever Netduino class with Secret Labs CTO Chris Walker. Most recently in November 2010 Harford Hackerspace won first place in the Baltimore Hackathon with their RotoFoto Project.

Tags:

Follow the Discussion

  • CuriousCurious

    Can you please put a higher quality image of the LED wiring schematic? It is hard to read.

  • Clint RutkasClint I'm a "developer"

    @Curious in theory, I fixed it by uploading the image to our file server.  It could take a few minutes for it to propagate outward.

  • Clint RutkasClint I'm a "developer"

    @Curious, crap, sorry about that, looks something got fubared when I did an edit.  I'll correct this ASAP.

  • CuriousCurious

    Thank you! Got my netduino mini. Will try it this weekend!

  • sporty982000sporty982000

    wow, what was the cost for all those parts ? did you make or have any extras ? the chip and program ? cost to buy this from you ?

  • Clint RutkasClint I'm a "developer"

    @sporty982000 you'll have to make your own.  I don't even have one yet!  Most of the parts are pretty cheap (under a dollar).  Without calculating everything, I'm betting with tron disc, under 50 dollars.

  • ThorThor

    I've been working on mine since around Christmas. Went through a couple iterations, and I'm just getting to the point where I put mine together. Cost is probably closer to $70 I would guess. The mini costs $30 by itself plus $15-20 for the disc and you already have $50...add on the rest of the parts and it's more like 70-80. Go through a couple iterations (as I did) and you'll spend a bit more. One thing to note...I recommend using 26 gauge wire if you're going the mini route. I started with 22 gauge and it was way too thick. 26 seems just right.

  • CuriousCurious

    I have another question.

    Where are the D31 and D32 LEDs on the disc and what are they for?  

  • ZackZack

    Hey can you post  compiled driver? I trired my hand at it but I can't seem to get it working :/

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.