Shiftbrite LED Rotation Display

Introduction

  • As the bike wheel rotates, a string of Shiftbrite LEDs display an image on a polar coordinate system
  • Hall Effect sensor is used to find the time for 1 full rotation of the bicycle wheel
  • Timing determined by dividing the total time of 1 full rotation into the number of segments that we want to display.

Materials Used

http://i.imgur.com/jyK7x.png http://i.imgur.com/bkM3v.png

How it Works

Part 1: Converting a Bitmap Image to the Rotational Display Array

  • Utilizing the facts that the bitmap image is a square and our wheel is a circle, the simplest solution for displaying the image on the wheel was to transcribe a circle in the image and display the image within the transcribed circle.
  • Given that the column of Shiftbrites contained 10 LEDs, a 21 x 21 bitmap image was ideal since it provided a center point with 10 pixels above and below that center point.
  • One way to think about the column of LEDs is to think about each light independently.
  • Each LED is a fixed radius from the center, and we want to rotate that light in a circle.
  • If each light is a fixed radius away from the center, then each light starts at some point that has the vector coordinates (0,-y) where y is pixels away from the center point. For example the light closest to the origin point starts at the coordinate (0,-1).
  • Using a rotation matrix (http://en.wikipedia.org/wiki/Rotation_matrix) the following equations can be used to rotate this vector by the angle, theta:
    • new_x = old_x * cos (theta) - old_y* sin (theta) ++x_offset + rounding factor.
    • new_y = old_x * sin (theta) + old_y* cos (theta) + y_offset + rounding factor.
  • Now that the hard work is done, for loops can be used to divide the wheel into however many segments you want (in our case 36) and for how many LEDs you have (in our case 10)
  • Once we have the new coordinates for each LED at each angle of rotation we can go back into original image and get the color pallet for each pixel.
  • Things to be weary of:
    • C# trig functions take radian values not degree values.
    • The origin in the math is the center point. The origin in the image is the upper left corner. Therefore an offset of 10 in the x-direction and 10 in the y-direction are needed.
    • The easiest way to round a float to an int is to add 0.5 to the float and cast it as an int since casting does a front end estimation of the float.
    • In normal Cartesian coordinates the positive y direction is up. In our image the positive y direction is down. Another way to think about this is that the upper left point has the coordinates (0, 0) and the lower right point as the coordinates (20, 20).

Part 2: Understanding the Hall Effect Sensor

  • The Hall Effect Sensor is a "latch type" sensor that outputs "high" when subjected to a "N" Magnetic Field and outputs a "low" when subjected to a "S" magnetic field. Thus, we used two magnets to "set" and "reset" the device. The distance was within a half an inch from from the magnets.
  • Within the Mbed, an interrupt function was created to activate whenever the Hall Effect Sensor was set to low. Within this function, the Mbed recorded the time for the full rotation and then reset the timer as well as image indices.

Part 3: Shiftbrite SPI Chain

  • The Shiftbrites are connected together serially; thus, we send 10 SPI commands one after another to light up a current segment.
  • The segment time is calculated by dividing the total rotation time (found from the hall effect sensor) by the number of segments needed to light up. We played with the number of segments we wanted to use for lighting up the full wheel and found that if we used too few segments, the resolution of our image would be very low, while using too many would delay the SPI commands sent to the Shiftbrites.

http://i.imgur.com/mAf2U.png

Video Demos

IMPORTANT NOTE ABOUT THE UPLOADED VIDEO DEMO

Please note that the video that was uploaded is not a good indication to how the project came out, due to frames being clipped by YouTube and Windows Movie Maker. In reality, the entire wheel is lit up. Thanks for watching!

Code

MBED Software

Import programShiftbright_Hall

Mbed Program for Gatech 4180 Shiftbrite LED Rotation Display

/* ----- 4180 Final Design Project -----
	Bicycle Wheel LED Image Display
*/
#include "mbed.h"

InterruptIn hall(p28);								// This interupt is used to poll the Hall Effect IC
DigitalOut latch(p8);		
DigitalOut enable(p9);
Timer timer;										// This timer is used to time the wheel's rotation

int LEDRowNumber = 10;								// This is equal to the number of divisions the user has setup for his or her image
int cycle_time = 0;									// Global Variable for the full rotation
int rowIndex = 0;									// Index for shifting through the Image array
bool top_wheel = false;								// Variable Debug LED Circuit
int color = 0;										// Variable for shifting through colors
int j;												// For Loop index for LED debug circuit
float adj_cycle_time = 0;							// Variable used to hold "cycle_time / LEDRowNumber"

//Cycles through different colors on RGB LED
SPI spi(p5, p6, p7);

//Use SPI hardware to write color values to LED driver chip
void RGB_LED(int red, int green, int blue) {
    unsigned int low_color=0;
    unsigned int high_color=0;
    high_color=(blue<<4)|((red&0x3C0)>>6);
    low_color=(((red&0x3F)<<10)|(green));
    spi.write(high_color);
    spi.write(low_color);
    latch=1;
    latch=0;
}

//Interupt Routine used to detect when the magnet passes the Hall Effect Sensor
void intRoutine() {
    cycle_time = timer.read_ms();					//This is only called then the Hall Effect IC returns a "0"
    adj_cycle_time = cycle_time / LEDRowNumber;		//Calculates the time between sending each LED Row within the color Arrays
    timer.reset();									//Resets the Timer
    rowIndex = 0;									//Resets the Row to be sent so the image always stays in the relatively same place
    color=0;										//Resets the color Index for the LED sample wheel so the image always stays in the same place
}

int main() {
    hall.mode(PullUp);								//Initializes the PullUp Resistor for the Hall Effect Sensor
    hall.fall(&intRoutine);							//Initializes the Interrupt Routine for the Hall Effect Sensor (on falling edge)
    
	spi.format(16,0);								//Initializes the Shiftbright LED Chain
    spi.frequency(500000);
	enable=0;
    latch=0;
    wait(2);

	timer.start();									//Starts the Timer

/*//----- Color Initialization for Debug Program -----    
    int red=0;
    int green=0;
    int blue=0;
    */
    
/* //----- Spiral Pattern -----
    int red[10][10]={50,0,0,0,0,0,0,0,0,0
                     ,0,50,0,0,0,0,0,0,0,0
                     ,0,0,50,0,0,0,0,0,0,0
                     ,0,0,0,50,0,0,0,0,0,0
                     ,0,0,0,0,50,0,0,0,0,0
                     ,0,0,0,0,0,50,0,0,0,0
                     ,0,0,0,0,0,0,50,0,0,0
                     ,0,0,0,0,0,0,0,50,0,0
                     ,0,0,0,0,0,0,0,0,50,0
                     ,0,0,0,0,0,0,0,0,0,50};
    int green[10][10]={50,0,0,0,0,0,0,0,0,0
                     ,0,50,0,0,0,0,0,0,0,0
                     ,0,0,50,0,0,0,0,0,0,0
                     ,0,0,0,50,0,0,0,0,0,0
                     ,0,0,0,0,50,0,0,0,0,0
                     ,0,0,0,0,0,50,0,0,0,0
                     ,0,0,0,0,0,0,50,0,0,0
                     ,0,0,0,0,0,0,0,50,0,0
                     ,0,0,0,0,0,0,0,0,50,0
                     ,0,0,0,0,0,0,0,0,0,50};
    int blue[10][10]={50,0,0,0,0,0,0,0,0,0
                     ,0,50,0,0,0,0,0,0,0,0
                     ,0,0,50,0,0,0,0,0,0,0
                     ,0,0,0,50,0,0,0,0,0,0
                     ,0,0,0,0,50,0,0,0,0,0
                     ,0,0,0,0,0,50,0,0,0,0
                     ,0,0,0,0,0,0,50,0,0,0
                     ,0,0,0,0,0,0,0,50,0,0
                     ,0,0,0,0,0,0,0,0,50,0
                     ,0,0,0,0,0,0,0,0,0,50};
*/

//----- Pac Man -----

    int red[10][10]={50,50,50,50,50,50,50,50,50,50
                     ,50,50,50,50,50,50,50,50,50,50
                     ,50,50,50,50,50,50,50,50,50,50
                     ,50,50,50,50,50,50,50,50,50,50
                     ,50,50,50,50,50,50,0,0,50,50
                     ,50,50,50,50,50,50,50,50,50,50
                     ,0,0,0,0,0,0,0,0,0,0
                     ,0,0,0,0,0,0,0,0,0,0
                     ,50,50,50,50,50,50,50,50,50,50
                     ,50,50,50,50,50,50,50,50,50,50};
                     
    int green[10][10]={50,50,50,50,50,50,50,50,50,50
                     ,50,50,50,50,50,50,50,50,50,50
                     ,50,50,50,50,50,50,50,50,50,50
                     ,50,50,50,50,50,50,50,50,50,50
                     ,50,50,50,50,50,50,0,0,50,50
                     ,50,50,50,50,50,50,50,50,50,50
                     ,0,0,0,0,0,0,0,0,0,0
                     ,0,0,0,0,0,0,0,0,0,0
                     ,50,50,50,50,50,50,50,50,50,50
                     ,50,50,50,50,50,50,50,50,50,50};
                     
    int blue[10][10]={0,0,0,0,0,0,0,0,0,0
                     ,0,0,0,0,0,0,0,0,0,0
                     ,0,0,0,0,0,0,0,0,0,0
                     ,0,0,0,0,0,0,0,0,0,0
                     ,0,0,0,0,0,0,0,0,0,0
                     ,0,0,0,0,0,0,0,0,0,0
                     ,0,0,0,0,0,0,0,0,0,0
                     ,0,0,0,0,0,0,0,0,0,0
                     ,0,0,0,0,0,0,0,0,0,0
                     ,0,0,0,0,0,0,0,0,0,0};
                     
                     
/*// -----Blue Triangle: From Bitmap Image Program -----
    int red[36][10]={0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,42,42,42,42,42,
                    0,0,0,0,0,42,42,42,42,42,
                    0,0,0,0,0,0,0,42,42,42,
                    0,0,0,0,0,0,0,0,0,42,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,42,
                    0,0,0,0,0,0,0,0,0,42,
                    0,0,0,0,0,0,0,0,42,42,
                    0,0,0,0,0,0,0,0,42,42,
                    0,0,0,0,0,0,0,0,42,42,
                    0,0,0,0,0,0,0,0,0,42,
                    0,0,0,0,0,0,0,0,0,42,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,42,
                    0,0,0,0,0,0,0,42,42,42,
                    0,0,0,0,0,42,42,42,42,42,
                    0,0,0,0,0,42,42,42,42,42,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0}; 
    
int green[36][10]={0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,0,0,0,0,0,0,
                    0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,0,42,42,42,42,42,
                    0,0,0,0,0,0,0,42,42,42,
                    0,0,0,0,0,0,0,0,42,42,
                    0,0,0,0,0,0,0,42,42,42,
                    0,0,0,0,0,42,42,42,42,42,
                    0,0,0,0,0,42,42,42,42,42,
                    0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,42,42,42,42,42,42,
                    0,0,0,0,42,42,42,42,42,42};
    
    int blue[36][10]={42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,42,0,0,0,0,0,
                    42,42,42,42,42,0,0,0,0,0,
                    42,42,42,42,42,42,42,0,0,0,
                    42,42,42,42,42,42,42,42,42,0,
                    42,42,42,42,42,42,42,42,42,42,
                    42,42,42,42,42,42,42,42,42,0,
                    42,42,42,42,42,42,42,42,42,0,
                    42,42,42,42,42,42,42,42,0,0,
                    42,42,42,42,42,42,42,42,0,0,
                    42,42,42,42,42,42,42,42,0,0,
                    42,42,42,42,42,42,42,42,42,0,
                    42,42,42,42,42,42,42,42,42,0,
                    42,42,42,42,42,42,42,42,42,42,
                    42,42,42,42,42,42,42,42,42,0,
                    42,42,42,42,42,42,42,0,0,0,
                    42,42,42,42,42,0,0,0,0,0,
                    42,42,42,42,42,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,42,0,0,0,0,0,
                    42,42,42,42,42,42,42,0,0,0,
                    42,42,42,42,42,42,42,42,0,0,
                    42,42,42,42,42,42,42,0,0,0,
                    42,42,42,42,42,0,0,0,0,0,
                    42,42,42,42,42,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0,
                    42,42,42,42,0,0,0,0,0,0};
*/
    while(1) {
        //led1 = hall;		// Used for Debugging the Hall Effect IC
        //led2 = !hall;		// Used for Debugging the Hall Effect IC

		//----- Code for Cycling through a 2D Picture Array -----
        wait_ms(adj_cycle_time);
        for(int i=9;i>=0;i--){  //If we update without any waiting, the LEDs change colors super fast, and you won't notice the shifting LEDs
            RGB_LED( red[rowIndex][i], green[rowIndex][i], blue[rowIndex][i]);
        }
        rowIndex++;
        if(rowIndex>=LEDRowNumber){
            rowIndex = 0;
        }
        
        
       /* //----- Debug Code: Rainbow Pattern ------ (Pre 2D Color Array)
	   wait_ms(adj_cycle_time);
       top_wheel = true;
       if (top_wheel && (color == 0)) {
            // Red, Color 0
            //printf("RED RED RED\r\n");
            red = 50;
            blue = 0;
            green = 0;
            for(j = 0; j < 10; j++)
                RGB_LED(red, green, blue);
            top_wheel = false;
            color++;
        }
        if (top_wheel && (color == 1)) {
            //printf("ORANGE ORANGE ORANGE\r\n");
            // Orange, Color 1
            red = 50;
            blue = 0;
            green = 25;
            for(j = 0; j < 10; j++)
                RGB_LED(red, green, blue);
            top_wheel = false;
            color++;
        }
        if (top_wheel && (color == 2)) {
            //printf("YELLOW YELLOW YELLOW\r\n");
            // Yellow, Color 2
            red = 50;
            blue = 0;
            green = 50;
            for(j = 0; j < 10; j++)
                RGB_LED(red, green, blue);
            top_wheel = false;
            color++;
        }
       if (top_wheel && (color == 3)) {
            //printf("GREEN GREEN GREEN\r\n");
            // Green, Color 3
            red = 0;
            blue = 0;
            green = 50;
            for(j = 0; j < 10; j++)
                RGB_LED(red, green, blue);
            top_wheel = false;
            color++;
        }
        if (top_wheel && (color == 4)) {
            //printf("CYAN CYAN CYAN\r\n");
            // Cyan, Color 4
            red = 0;
            blue = 50;
            green = 50;
            for(j = 0; j < 10; j++)
                RGB_LED(red, green, blue);
            top_wheel = false;
            color++;
        }
        if (top_wheel && (color == 5)) {   
            //printf("BLUE BLUE BLUE\r\n"); 
            // Blue, Color 5
            red = 0;
            blue = 50;
            green = 0;
            for(j = 0; j < 10; j++)
                RGB_LED(red, green, blue);
            top_wheel = false;
            color++;
        }
        if (top_wheel && (color == 6)) {
            //printf("PURPLE PURPLE PURPLE\r\n");
            //Purple, Color 6
            red = 50;
            blue = 50;
            green = 0;
            for(j = 0; j < 10; j++)
                RGB_LED(red, green, blue);
            top_wheel = false;
            color = 0;
        }
        */
    }
}

C# Bitmap to LED Array Program

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace BitmapToLEDRotationImage
{
    class Program
    {
        static void Main(string[] args)
        {
            //File location for the Bitmap Image you want to convert
            System.Drawing.Bitmap image = (Bitmap)Bitmap.FromFile("C:\\Users\\Kris\\Dropbox\\Projects\\EasyBMPProject\\TestProject1\\TestProgram1_Csharp\\testImage2.bmp");
            
            //File Location for the Output Arrays for the New Arrays
            System.IO.StreamWriter file = new System.IO.StreamWriter("C:\\Users\\Kris\\Dropbox\\Projects\\EasyBMPProject\\TestProject1\\TestProgram1_Csharp\\test.txt");
            
            //Initializing Arrays for Color Components of the Bitmap Images
            int[,] redOld = new int[21,21];
            int[,] grnOld = new int[21,21];
            int[,] bluOld = new int[21,21];

            //Initializing Arrays for Color Components of the Converted Bitmap Images
            int[,] redNew = new int[36, 10];
            int[,] grnNew = new int[36, 10];
            int[,] bluNew = new int[36, 10];

            //Embedded For Loop for Creating Color Arrays for the Original Bitmap Image
            for (int i = 0; i < 21; i++)
            {
                for (int j = 0; j < 21; j++)
                {
                    Color pixelColor = image.GetPixel(i, j);
                    int g = pixelColor.G / 6;
                    grnOld[i, j] = g;
                    int b = pixelColor.B / 6;
                    bluOld[i, j] = b;
                    int r = pixelColor.R / 6;
                    redOld[i, j] = r;
                    int a = pixelColor.A / 6;
                    string text = String.Format("Index ({4},{5}) has these ARGB values: Alpha:{0}, " +
                        "red:{1}, green: {2}, blue {3}\r\n", new object[] { a, r, g, b, i, j });
                    System.Console.Write(text);
                }
            }    
            
            //Embedded For Loop for Finding the New Color Arrays for the LED Bicycle Wheel
            int theta;          // Variable for Angle in Degrees
            double newTheta;    // Variable for the Converted Angle in Radians
            float oldX = 0;     // Starting "X" Coordinate
            double newX;        // Variable for Converting Old "X" Point to new point after degree rotation
            double newY;        // Variable for Converting Old "Y" Point to new point after degree rotation
            int rowIndex = 0;   // Variable for keeping the Rows Managable

            for (theta = 0; theta > -360; theta -= 10)      // Cycles through the degrees from 0 to 360
            {
                newTheta = (double)(theta * 3.14) / 180;    // Converts Values from degrees to Radians
                for (int oldY = 1; oldY < 11; oldY++)       // Cycles through points 1 through 10 on the Y axis
                {
                    newX = 10.5 + oldX * Math.Cos(newTheta) - oldY * Math.Sin(newTheta);        // Value for Finding the "X" index for the color "theta" degrees away from the other point
                    newY = 10.5 + oldX * Math.Sin(newTheta) + oldY * Math.Cos(newTheta);        // Value for Finding the "Y" index for the color "theta" degrees away from the other point
                    redNew[rowIndex, oldY - 1] = redOld[(int)newY, (int)newX];                  // Uses "newX" and "newY" to find the color for the new index for the red array
                    grnNew[rowIndex, oldY - 1] = grnOld[(int)newY, (int)newX];                  // Uses "newX" and "newY" to find the color for the new index for the red array
                    bluNew[rowIndex, oldY - 1] = bluOld[(int)newY, (int)newX];                  // Uses "newX" and "newY" to find the color for the new index for the red array
                }
                rowIndex++;
            }

            //Embedded For Loops for Writing the Color Arrays to a Text File
            string redMessage = "RED,";
            file.Write(redMessage);
            file.Write("\r\n");
            for (int i = 0; i < 36; i++)
            {
                for (int j = 0; j < 10; j++)
                {
                    string text = String.Format("{0},", new object[] { redNew[i, j] });
                    file.Write(text);
                }
                file.Write("\r\n");
            }
            string bluMessage = "BLUE,";
            file.Write(bluMessage);
            file.Write("\r\n");
            for (int i = 0; i < 36; i++)
            {
                for (int j = 0; j < 10; j++)
                {
                    string text = String.Format("{0},", new object[] { bluNew[i, j] });
                    file.Write(text);
                }
                file.Write("\r\n");
            }
            string grnMessage = "Green,";
            file.Write(grnMessage);
            file.Write("\r\n");
            for (int i = 0; i < 36; i++)
            {
                for (int j = 0; j < 10; j++)
                {
                    string text = String.Format("{0},", new object[] { grnNew[i, j] });
                    file.Write(text);
                }
                file.Write("\r\n");
            }
            file.Close();

            while (true) { };
        }
    }
}

All wikipages