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¶
- 10 LED Bar of Shiftbrites
- 9V Battery
- Hall Effect Sensor, ATS177 Manufactures by Diodes Inc.
- 2 Magnets, Neodymium Iron Boron, 10800g
- Mbed
- Lots of Electrical Tape and Zip Ties
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.
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) { }; } } }