Introduction
We set out to make an easy to interact with, highly custimizable POV display
In deciding on a project we looked for a challenge that would have a good mix of hardware and software problems. We ended up primarily concentrating on looking at unusual display technologies and decided that a persistence of vision(POV) display would be a good balance. A POV display is a display created by rotating an array of LEDs rapidly. Due to the fact that human eyes can only render so many images per second, the fast spinning LEDS seem like a solid display.
While many POV projects have been designed before, they tend to be very static. Often they only display one image or animation and usually have no interactivity. We set out to make a very modular multipurpose design. Our original goal was to make two different LED arrays, a 2D version with RGB LEDs and a 3D version that uses monochrome LEDs. While we only had time to make the 2D display, our design makes it easy for the user to swap the LED array for a different one. Additionally, our design is fully customizable. Images can be uploaded and managed from a computer connected wirelessly, as can the firmware.
High Level Design
We found the idea for the persistence of vision device (also known as a propeller clock) here.
To improve upon the device we found, we wanted a way for the propeller clock to communicate with a computer and decided to use the serial protocol for this. At the same time, we decided to make a friendly GUI with JAVA so that any user can intuitively do the updates. The serial interface that we ended up using is the XBee. This device was extremely helpful since it allows us to have wireless serial communication. At the same time it is also extremely easy to use. They work right out of the box and only needs to be plugged into the laptop and our device.
In order to make our multipurpose propeller clock, we decided to make two main components. The main circuit board will do all the calculations necessary to figure out what LEDs to turn on while the secondary board will only have LEDs. This would make it extremely simple to switch between the 2D vs 3D propeller clock that we envisioned. The main circuit board is also unique since it can either read data from the external EEPROMs (rom control) or be controlled directly (direct control). This gives us a lot of versatility. Rom control makes animations simple to do while the direct control allows us to display a clock, dynamically generated messages (Gaming capability is also being looked at). More details on how this all works can be found under Hardware Design.
Hardware Design
Power
Power was a major issue in our design. Since we need to power a motor and also 32 RGB LEDs (96 individual LEDs), the current being drawn is tremendous. As a result, we decided to use two power supplies to power the motor and the LEDs. Another reason why we did this is because two separate power sources get rid of the noise that might have occurred if the motor power supply was used to power the LEDS as well.
The total current needed by the microcontroller unit, the EEPROMs, and the LED drivers is about 450mA from the 5V regulator. The XBee that we use for the wireless communication takes about 50mA from a 3.3 linear regulator. The LEDS take about 173mA from a 9V power supply. This means that we need to draw about 673mA in order for the PCB boards to work. This number was actually even higher in our originally calculations since the datasheet on the LEDs said that they each need about 10mA (2mA ended working just as well), or 960mA for all 96 to turn on. That would have meant the total current being drawn would have been around 1500mA. This makes battery power not feasible. Duracell batteries have a 0.83amp-hour rating at 1A current draw, so the batteries would have died way too quickly. As a result, we decided to use a brushing system to supply the power which is explained in more details under brushing.
Essentially to power the PCB boards, a power adapter is used to pass 9V (1.1A rating) to the spinning board. The 9V goes straight to the LEDs and the 5V regulator. The regulator goes through a PI filter and then to a 3.3V regulator and the rest of the 5V devices. The PI filter ensures that slight current variations caused by the noisy brushing are removed. As a result, we never had any brownouts except for when the brushing fails (explained in more detail below). The motor has its own independent 5V (2.5A rating) power supply so there was no problem there.
Costs
Budgeting was also an issue since we are only allowed to use $70 in addition to what we have lying around. Our design requires many connections between multiple devices, so it was impossible to do by hand to get the small form factor we needed. At the same time, custom made PCB boards are expensive. On top of that, since we wanted a modular design, we needed two PCB boards. To minimize cost, we decided to get a custom made main board and etch the LED PCB board by ourselves. More details can be found under PCB board
Motor System
A working motor system is critical for our success. For one, it must spin fast enough so that we can get at least 10 frames a second. Lower rotation speed will make images unrecognizable. Extremely good images require 15 – 30 frames per second, but since we have a spinning device, the higher rotation speed can be dangerous. As a result, we usually tried to use the lowest speed possible.
Brushing
As you can see from the picture below, the motor assembly design is relatively straight forward. However, perfecting the brushing took a long time and it was main cause of our headaches. For awhile, we weren’t even able to test our code since the brushing was constantly losing contact or shorting
The brushing for the ground was relatively easy to make. We just took some metal bristles from a barbeque grill cleaner and used that to contact the motor shaft. This causes the connector(custom made screw) to be grounded. Also, since the connector between the PCB board and the motor is metal, the connector’s head always end up touching the grounded bristles as well.
If you want to read about the different designs we had to go through to get the power brushing to work, click HERE.
However, the brushing with power was a different story and we had to go through a total of 6 different revisions for it. The first one we did was just a proof of concept and we made a regular piece of wire stick straight up. It kept contact so we knew that the brushing idea would work. After that, in order to guarantee that we would have power at all times, we decided to use three of those wires at the same time. This did not work at all since the friction was too high and the motor wouldn’t spin. After that we tried to use the barbeque grill cleaner wire as we did for the ground brushing. Unfortunately, it was too hard to make them fit in the limited vertical clearance so we gave up on that idea. After that, we tried to use a special type of wire called the Wet Noodle(wire with hundreds of thin strands of metal) to be our brushing. The Wet Noodle’s hundred’s of wires almost guaranteed contact with the underside of the PCB board. In addition, the wires are relatively soft due to their thinness so friction was low. This turned out to work well enough to do some basic tests with the PCB board, but not well enough for the final version. After a few minutes of rotation the PCB board, the wires would lose their springiness so contact was lost. To resolve all these issues, we decided to combine all the good aspects of all of our different designs. In addition, we used part of a pen spring as the base so that we wouldn’t lose that springiness. We took a solid piece of wire and soldered it onto the spring and wrapped wet noodle wire at the contact point. The only flaw in this design was that the solder wasn’t strong enough so the wire kept breaking away from the spring.
In our final version, the power brushing was made entirely out of a single spring that we took from a pen and some wet noodle. We used a lot of hot glue so that relatively little spring would be exposed so that it wouldn’t get knocked over too easily. At the same time, we wrapped some Wet Noodle wire around the end of the spring that we straightened. This results in extremely good contact(more surface area due to the straight part) and low friction. To reduce friction even further, we put some motor oil on the wet noodle tip.
In addition to all that, we also need to insulate the power brushing from ground. The connector is grounded, but due to a lack of space the power brushing is only 1 mm away. We resolved this by wrapping the sides of the connector with electrical tape and filling in the gaps at the base of the tape with wood glue (Thick, non instant glue is best). In addition, to prevent the power brushing from going out of its boundaries, we boxed it in with some wire.
Connector
We machined our connector(screw) in the Cornell machine shop. Since our motor has a flat edge on the motor shaft, it gives the worm screw on the side of the connector a lot of grip. We made the connector longer so that we could secure it to the PCB board with a nut. A picture of this is shown below. When making this connector, we had to take into account that our modular design doesn’t give the connector much clearance. If the connector was too high, it would hit the PCB board right above it (A LED will always be at the center of the board, or right above the connector).
Vibrations
We had some major vibration issues in the beginning. At times, it felt as if the PCB board would fly off due to the imbalance of the spin. We fixed this in various ways. We increased the weight of the base so that it wouldn’t move around as much. At the same time, we put the base on top of foam pads to dampen the vibrations. In addition, we secured the motor more firmly onto the actual metal base. Finally, we balanced the PCB board in all directions so that the center of mass wouldn’t shift around.
PCB Board
The heart and soul of this project lies with the PCB board. The modular design will be explained below and pictures of our design are at the end of this section.
Main Board
On our main board, the current layout for how the microcontroller pins are used is as follows: PORTA is not used at all, PORTB is used for the SPI and for the test LED (PB0), PORTC is mainly used for direct control of the LED drivers when the board is in direct control mode. Each pin corresponds to a different driver. PORTD is used for USART (GUI / bootloader) control, EEPROMS(CS), and input from the hall sensor
Based on other designs, we selected these values for the components shown in the schematics below: a)10k ohm resistor: pull up resistor and current control for drivers b)0.1uF capacitor: decoupling capacitor c)15pf: Clock capacitor c)150ohm resistor: LED current control d)Red LED/Test LED: test/heartbeat LED e)Crystal: 16Mhz Clock Oscillator f)Hall Sensor: Detects Rotations g) 4.7nF: Hall Sensor Capacitor h)0.33uF Power Input Filter i)68uH inductor: part of 5V line pi filter j)33uF: part of the 5V line pi filter.
LED Control
The LED's are directly controlled by MAX6971 LED drivers. These drivers are able to turn the LEDs on by sending a preset current to them which is calculated with the equation I = 18/Rset. This equation is given in the datasheet and Rset is the resistor that we use to connect the SET pin to ground. Initially we used 1.8k resistors to give the LEDs 10mA, but they were far too bright to view. After some testing, we found that 10K resistors got the brightness down to a decent level. This means that the LED's were getting 1.8mA each, so the final current drawn by all 96 LEDs is 173mA. We store the LEDs on off state in a register with each bit corresponding to an LED in their shift registers. However, the LED states are only updated from this register when the Latch enable pin is high in order to prevent LEDs from switching states in the middle of a frame.
Our MCU has two mechanisms with which to control the LEDs, one of which is direct control. Here the shift-in pin of each driver is connected to one of the eight pins of PORTC. The serial clock is controlled by PORTB7. A single bit in all eight drivers is set simultaneously by pulsing PORTB7 to feed in the PORTC values. This means that it takes a minimum of 4 cycles (mem load,PORTB toggle, set PORTC, PORTB toggle) to update a bit or about 4*16=64 cycles to update all the LEDs.
The second method of controlling the LEDs is by loading data from the external EEPROMs. Half of the eight LED drivers are controlled directly by the EEPROMs and these four drivers control the other four by passing their shift output into the other driver’s shift input. This process starts by initializing the EEPROMs to start reading from some address. After that the picture data can be read indefinitely by pulsing the SPI SCK line controlled by PORTB7. Since each LED driver requires two bytes of data, each pair of drivers (the directly controlled driver and the driver it shifts the data out to) require 4 bytes. Since all the drivers are updated in parallel and the max SCK speed is Fosc/2, updating all the drivers takes at least 2(SCK pulse time)*8(bits in a byte)*4(bytes to read)=64 clock cycles. This method is slightly faster than direct control since the data is preloaded while direct control requires going through a loop to get data.
Our daisy chaining the EEPROMs has some pros and cons. Using eight ROMs would double the storage capacity and update speed. However, it also means doubling the space requirement and the cost of the EEPROMs. We could have also used less EEPROMs, but to demonstrate the flexibility of our design with a reasonable space requirement, we ended up choosing to use four EEPROMs.
Hall Sensor
Our project hinges on knowing what position of the board is at any given time, and we used a hall sensor to help us with this problem. The hall sensor we used is uni-polar, which means that it turns on in the presence of one direction of magnetic field and turns off in its absence. For us, the hall sensor pulls down the line to counteract a pull up resistor, making its inclusion very simple. By mounting a magnet on a stand to our metal base, the hall sensor was accurately able to trigger once per rotation. By coupling this to the input capture interrupt we were able to get the length of time for each rotation and use this to guess the position of the board at any time(current time/rotation time*360 = angle in rotation).
XBee
Click HERE for more information on the XBee.
We used for our wireless serial communication. The XBee is actually quite powerful, but we only used its most basic functions. The four pins are power, gnd, rx, and tx. Basically everything works right out of the box and only needs to be plugged in. The XBee also supplies us with software that we used a little. With it, we are able to change the baud rate, but we stayed with the default 9600bits/second. We could have increased the baud rate, but there is no point since the benefits will be unnoticeable. In addition, we didn’t have any dropped packets so the default baud rate was also very reliable. We changed the channel on the XBee to prevent interfering with other XBees that were being used.
Making the Board
If you are interested in the process, click HERE.
We would like to thank Sunstone for giving us money and making our PCB board for us. We would also like to thank the open source community of GEDA for giving us a free platform to make our schematic and PCB design. Finally we would like to thank Google for giving us server space to set up SVN.
To actually make our boards, we first had to make the schematics. We were very lucky and had only a few minor problems. The original resistor value for the 3.3V regulator had to be changed and we had to reorganize the LED connector wires. After the schematics were done, we designed the PCB board shown below. In the middle of the PCB board there’s a square box and a round grey circle that corresponds with power and ground respectively. The hall sensor is in the lower right hand corner and the test LED is on the lower left.
RGB LED Board
This board was made with ferric chloride etching. If you are interested in how we made the board, click HERE.
We used the toner transfer method by following a guide a found here. Since we couldn't easily make holes in the board or do complex routing, we decided to use actual wiring instead of printing traces. Also we had to use a dremel to cut out holes for the connectors. Our first attempt came out well, but was damaged by an attempt at reflow soldering and had incorrect footprints for the LEDs. Our second attempt fixed these issues and was used.
Software Design
Updating
The most important part for our design is making sure that we are updating each frame at the right time. If it’s not consistent, the pictures may flicker and even shake. To figure out the time it takes to make a full revolution, we used a magnet sensor that trips whenever a full circle is made. To measure the time, we used the Timer1 Input Capture in conjunction with the Timer1Overflow1. Since a full revolution is slow compared to the 16MHz clock speed, we counted the number of times that Timer1 Overflows to prevent register overflow. To get a more accurate reading of when the InputCapture interrupt triggers, we or ICR1 to the number of times we got an overflow (shifted by 16 to make it the correct value). This gives us quite an accurate measurement of time. Now that we have an accurate time, we divide this by a value called “angle” to get “increment”. Angle is set under chgMode so that we can have different angular resolutions for our different modes (We use 180 angles for our clock while we use 128 for animations). We constantly check to see if the time to the next update has passed in our main loop. This code updates nextTime and lineNum but is also in a critical section. This means that we need to disable interrupts while evaluating this section to avoid a race condition when the timer1 interrupts. We use lineNum in order to see where we are in the rotation to know when to update the LEDs. In order to figure out the value of lineNum, we compare nextTime with our current time to see if the designated incremented time has passed. If it did, it means that we are in the region of a new line, so lineNum is then updated. In the case of the clock, lineNum goes from 0 to 179 to get 180 rotations. In the case of the animations, it goes between 0 and 127. This allows us to determine when to update the LEDs by comparing the time to display certain things to lineNum. The code we used (overflows1>((uint8_t*)&nextTime)[2])||((overflows1==((uint8_t*)&nextTime)[2])&&TCNT1>(uint16_t)nextTime) basically checks for the obvious overflow as well as a more accurate one so that we will update the image at the correct time as soon as possible.
Dynamic Control
In order for direct control to work, there are four important things that we must do. The first thing that we must do is to set the SPCR to 0. This is because we bit-banged the clock. Having SPCR enabled will result in a conflict since our bit-banged clock share the same line with the SPI SCK line. The second thing we must do is to deselect all the EEPROM so that they are all outputting high impedance. This is also to make sure there are no conflicts on the lines when we use direct control. The next thing we need to do is to make sure we have the correct MUX select for direct control. Finally, we set all the bits in PORTC high since PORTC is the one that will feed data into the LED drivers. This turns off the LEDS by default
In our code, we load all the data we need for a frame in a buffer. This buffer is an array that is 16 bytes long. Since we designed our connections between the LED drivers and the LEDs to match up well, the direct control connection was scrambled. To fix this problem, we made a function called setRGB. From its parameters, it will turn on #LED (0 is center and 31 is the farthest away) with the correct color. The problem of calling the incorrect RGB LEDs is fixed by making the code go through a LUT. Now that we have a buffer, we throw this buffer into a function called slowDirSet(). This function bit-bangs the data to the LED drivers, meaning we simulate the clock to load 1 bit of data to each of the LED drivers on each tick. When all 16 bits for all the LED drivers are sent in, we latch all the LEDs so that they will be updated. After that, we immediate unlatch the LEDs so that they won’t be updated whenever their value changes. The functions are explained in more detail under Code Description and the source code.
Serial Communication
Our handleSerial(rxBuff) basically is buffer driven. Whenever we reach that part of the code, we check the buffer to see if anything is there. If there is, we start handling the data that is buffered. The current UARTstate determines which state to go to. Once we are in a state, we assume that all the data that we receive is correct and we read one byte from the buffer whenever we reach the handleSerial function. The only time that we double check the data is when we do a handshake after an image update. This is because a lot of data is being sent wirelessly and our code is very dependent on the registers being correct.
EEPROM
We have four one megabit EEPROMs for storing image data. We allow multi-frame images (animations) with each frame being 512 bytes. This allows us to have 255 frames total which can be divided among up to 255 images.
ADD PIC - a state machine that adds an image to the EEPROM. First it gets the number of fames and figures out where in the EEPROM to store the image. Next it gets the image data and stores it to the EEPROM. Lastly it gets the metadata including the number of repetitions and the name.
GET PERIOD - returns the period of rotation last measured
LIST PICS - returns the names of the currently stored pictures in ID order
DEL PIC - deletes the most recent picture
SHOW PIC - tells the display to start showing a particular picture in memory by requesting its ID
CHANGE MODE - displays a given mode
SET TIME - sets the time to display
SET TEXT - sets the text message to display
Internal EEPROM Bookkeeping
The display needs to store data about the images it contains. We use the internal EEPROM to store this metadata. We store the following information:
a) Number of pictures currently stored
b) Start location of each image in memory (by storing the next start location this also gives us the length and last address of each image)
c) Whether the image is 2D or 3D (this is not used since we only implemented the 2D display)
d) The number of repetitions for each frame
e) The name of each image
Displaying from EEPROM
Updating from the EEPROM can be done with very little overhead. All data requests go to all the EEPROMs in parallel since each one stores a portion of the frame being displayed. Once the initial read address is set, all the MCU does is tick the SCK clock to read out the next bit. When master state is set to update from the ROMs, each update reads 4 bytes (explained in LED Control) to read out the new line. If a rotation is completed, our code checks to see what the next address is. Since each frame is shown some number of repetitions, our code checks to see if this number has been reached. When it has repeated the frame enough times (repeat = 1 is fine), the frame being displayed is incremented and the code checks to see if we reached the end of the image (a group of multiple frames). If the last frame has been reached, it restarts the image. If not, the next frame is displayed.
Storing Images on the EEPROM
The EEPROMs have a 256 byte write buffer after which they need to store the data. This store takes about 6ms. In order to avoid waiting for the EEPROMs to finish we write to the EEPROMs in 256 byte chunks before moving on the next EEPROM. Only one EEPROM is selected at a time and the bytes are transferred with SPI as they come in through the serial.
JAVA GUI
We made a Java GUI to control the POV device through the wireless link. This GUI uses the open source serial library RXTXcomm form http://www.rxtx.org/ to communicate through the XBEE. This code includes a dll to give the java code access to the serial ports on a windows machine. We wrote the rest of the code, but we did use snippets of code from various sites as guides to get the desired functionality. The GUI is a JAVA application that has a main function that launches a frame. This frame has different GUI elements that trigger the desired reaction to control the POV display. First the program must be told to connect to the serial port. Once this is done the various commands will output over the connected COM port and go to the POV device if it’s on. Our functions are as follows:
a) Change the display mode – we send a two byte packet that contains the desired mode
b) Get the current rotational speed – we send a single byte that requests the display to respond with a speed packet
c) Update the time – we send the current month, weekday, day of the month, AMPM, hour and minute gathered from the computers time.
d) Send a text message to display – we send a message of up to 49 characters to display
e) Get a list of the stored images – we request a list of the stored images. The display responds by sending the names of the images in ID order
f) Choose a stored image to display – we send the ID of the images we want to display
g) Load an image from a gif file to be sent – A 63x63 gif can be read in and converted to polar data. This data is then preprocessed to prepare it for transfer to the EEPROMs.
h) Send a single image to be stored – The preprocessed data is transferred to the display
i) Send a series of images to be stored as an animation – Each frame has a number of repetitions associated with it. This is transferred with the image data to make a variable frame rate animation
j) Delete an image from memory – the last image is removed from memory
All responses from the display are buffered by a receive thread. This thread waits for recognized start characters and parses the following data depending on it. This allows us to get the spin speed, image list, and other debugging messages.
The code we wrote can be divided into three main sections: GUI interface, serial control, and image processing. The GUI and serial code is pretty basic uses many different 3rd party libraries. The more interesting code is the image processing. We load the image into JAVA and use a pixel grabber to get the color of pixel at each coordinate. These coordinates are centered in our image and transformed into polar coordinates. These coordinates are then adjusted to be within the limits of our resolution. A window comparing the original to this adjusted version is then shown. Next the image is processed for transfer. Since the display expects the image data to come in a specified order to maximize transfer speed, we rearrange our bytes so that the appropriate polar coordinate data will end up in the correct write EEPROM addresses.
Bootloader
We used the AVR Universal Bootloader AVRUB to allow us to wirelessly update the firmware without using a programmer. By modifying the configuration data, we set the bootloader to work with our particular AVR and clock speed. To trigger starting up in bootload mode we used the input from the hall sensor. When the bootloader is triggered it listens for serial input to reprogram the device.
Code Description
Click here to see the code description.
chgMode(uint8_t mode)
This selects either Direct Control or Rom Control so the corresponding mode works. Angular resolution and masterState is also set here.
main(void)
The first part sets up the default values in the program, including the default message and mode. In the while(1) loop, the first thing that is done is to see if lineNum is updated. As stated before, this is a critical section since we’re also updating the same thing in the interrupts. So, in order to make this section work, we have to disable and re-enable interrupts around this code snippet. When that code snippet runs, update is flagged if lineNum is updated. If update is flagged, it means the next angular line of the image needs to be displayed. We go into a case statement and check which masterState we’re in. Then we run the corresponding code needed to get the correct update. In the case of Clock and Clock2, we also need special code so that the day, dates, month, am/pm are all incremented correctly. This selects either Direct Control or Rom Control so the corresponding mode works. Angular resolution and masterState is also set here.
updateTestRGB(uint8_t line)
This was a simple test that we made. It displays alternating bands of red, green, and blue lines
setRGB(uint8_t* buff,uint8_t LED,uint8_t color)
This sets up the RGB array that holds all the LED information. LED stands for the LED that will be turned on (0 is center, 31 is outer edge). Color is the color the LED will light up (0 is red, 1 is green, 2 is blue, 3 is turn on everything).
updateText(uint8_t* message, uint8_t height, uint8_t line, uint8_t type, uint8_t color, uint8_t length, uint8_t time)
This updates the text that is displayed on the propeller clock when it is in TEXT mode. Essentially, you type in a message that is up to 49 characters long. You input the height of where to display the message (the LED in the center is LED 0). You select the line at which you want to display the text, along with the text type. The two types are: 0) letters face inwards 1)letters face outwards. Color, scroll length, and delay between the scroll can also be selected. The fastest scroll time allowed is 1. This looks a lot like scrollMessage since it uses the same parameters. The only thing is by default, a lot of these values are already preset.
updateClock(uint8_t hours, uint8_t minutes, uint8_t seconds)
This updates the clock. Hours, minutes, seconds are all calculated internally. This makes it so that the minute hand, hour hand, second hand, day, date, and month are all displayed correctly. Scrolling messages is enabled if mode is CLOCK2.
simpleTest(uint8_t line)
Toggles LED next to microcontroller on board on and off.
displayImage(Image* image,uint8_t ID)
This prepares the image to be loaded from the EEPROMs.
init(void)
Initializes everything. Explained in more detail in the comments in the code.
spiInit(void)
Enables SPI.
startWrite(uint8_t currentRom,uint8_t addr,uint8_t isSecondHalf )
Sets the write address for the eeproms so that the eeprom data knows where to go.
startRead(uint8_t addr)
Sets the read address for the eeproms so that the eeprom data knows where it is read from.
StrCpy(char* to,char* from)
Copies strings from one array to another. This is used since we have default names that we want to put data into such as message2 which stores the message that is to be displayed on the propellor clock.
scrollMessage(uint8_t* message, uint8_t* buff, uint8_t height, uint8_t line, uint8_t color, uint8_t type, uint8_t length, uint8_t time)
This produces a scrolling message on the screen. Message is the text that will be displayed. Buff is the buffer where all the data will be stored. Height is the LED where the lower left hand corner of the first char of the message is. Line is the angular line at which the message starts (Our propellor clock moves in a clockwise direction where 12 o’clock is 0. Remember “angle” determines how many lines there are). Color determines what color the message will be in (0 is red, 1 is green, 2 is blue, and 3 is turn on everything). Type 0 means message faces inwards and type 1 means the message faces outwards. Length is the number of angular lines that allows the scroll area. Time determines the wait between when the message scrolls. It is determined by spin speed and max speed is 1.
getStrLen(uint8_t* str)
Gets the length of a string.
printMessage(uint8_t* message, uint8_t* buff, uint8_t height, uint8_t line, uint8_t color, uint8_t type)
Prints a message. All these parameters are exactly the same ones for scrollMessage.
printSymbolU(uint8_t* buff, uint8_t symbol, uint8_t height, uint8_t line,uint8_t color)
This is the character set for all letters and numbers that face outwards. Symbol is the character to be used (must be in UPPERCASE). Buff is the buffer which will store the information for the frame. Height is the lower left hand corner for the first character again. Line is the line at which the message will start to be displayed. Finally color is the color at which the message will be displayed. Once again, 0 is red, 1 is green, 2 is blue and 3 is turn everything on. Everything is 5 pixels tall. Numbers are 3 pixels wide. Letters are 4 pixels wide with the exception of V, W, and M which are 5 pixels wide.
printSymbol(uint8_t* buff, uint8_t symbol, uint8_t height, uint8_t line,uint8_t color)
Same as above but characters face inwards.
slowDirSet(uint8_t* bits)
This is used under direct control to turn on LEDS. Bit banging is used here to load the bits to the LED drivers one at a time. When all the bits are loaded onto the LED driver, we latch the drivers so that all the LEDS are updated. We immediately unlatch the LEDs after the latch so that the leds won’t flicker due to asynchronous changes.
handleSerial(cBuffer* rxBuff)
This handles the serial information. Part of this was explained above. More details are mentioned in the appendix. However, this essentially takes care of the transmission and receiving between the PCB board and the laptop computer.
listPics(void)
Lists the names of the pictures that are stored on the external EEproms by order of ID.
sendMsg(char * str,uint8_t len)
This is used to send messages to the JAVA GUI. One use of this is to display the rotation speed on the GUI interface.
Results
If you want to read about our results, click HERE. If you don't want to, you can watch a video of our device.
The device worked quite well and ran at 10-11 rotations per second which is within our goal. There is minor bit of flickering, but we can easily reduce that by increasing the rotation speed. We usually don’t though due to safety reasons. If we put a clear case around it, it would be possible to speed it up safely. We also know that it can spin faster since we’ve tested the motor up to 12V. Accuracy on our device was accurate up to the pixel, which can be seen with the clock since the 1 and 2 of 12 are at index 0 and 179. The blank space in between the numbers show that the spacing pixel is there (If the accuracy was off, the 1 and 2 would merge together). Safety was a major issue for us since we had a spinning device. We usually tested with the lowest rpm speed that would allow a viewable image. We also put a lot of tape on the free floating wires to secure them. Usability was very friendly since the Java GUI allows anyone to use the device without any programming knowledge.
Conclusion
Our design turned out to work very well for the application. We ended up able to update the LEDs far quicker than needed, which means that our design can be expanded to a faster, higher resolution module. Since our display works without any predetermined spin speed, upgrading our motor and display module would allow us to improve the display with minimal changes. We had a lot of trouble soldering small connections, but future use of reflow solder would help out a lot. The RGB board can be improved to make it easier to wire. Also in a future designs, we may add a time keeping chip and a small battery to preserve time when the device is powered down.
We never got to implement three features that we would have liked. First, it would have been nice to couple the bootloader into the GUI. Second, we wanted to add some interactive applications to the device. We could have easily used the serial link to implement simple 2D games that could be controlled in real time. Lastly we did not have time to finish building a 3D module that would have allowed us to diplay 3D images on a helix monochromatic LED array.
For our AVR code we only used a few small pieces of an open source library Procyon AVRlib. We used their serial methods to make our serial handling a little prettier. On the JAVA side, we used the open source serial API RXTXconn. On the design side, we independently developed all our hardware and software. We also made sure that our project would not cause ethical concerns. While there is a slight chance of injury from the spinning board, we kept the speed low enough to prevent the chance of injury. In order to advance research we intentionally chose multiple methods of control to let us evaluate the relative usability of the different methods. We used both direct and EEPROM control and daisy chained the LED drivers to give us a good sampling of control. We also used a non-default channel on our wireless receiver to avoid interfering with other group’s projects. Since we did not modify the antenna, the XBEE satisfied FCC regulations. Lastly we aided another group by lending our XBEE to computer adapter to help them in testing.
Donors/Contributors
We also want to thank companies and individuals who provided us with parts and time that contributed to our project. Our biggest donation came from Sunstone Circuits who gave us $100 toward our purchase of PCB boards. Without them this project would not have been possible. Atmel, Maxim, Samtec, and Microchip provided samples that allowed our board to stay under budget. We would also like to thank Professor Land and the TA's who took the time to help us with our project. We also want to thank the members of the CUAUV project team that allowed us to use the lab for project related work. Lastly, I would like to thank Eric Wolf, Erin Hoffer, David Martin, and Walter Mostowy. Eric Wolf machined us the connector between the motor and the board. David Martin, and Erin Hoffer took the time to help us minimize vibrations and to make the base prettier. Walter Mostowy, a student from Caltech, helped us with our website.
Appendices
References
- Serial library for Java - http://www.rxtx.org/
- Data sheet for RGB LED - http://www.optekinc.com/datasheets/OVSRRGBCC3.pdf
- LED driver datasheet - http://datasheets.maxim-ic.com/en/ds/MAX6971.pdf
- Procyon AVRlib - http://hubbard.engr.scu.edu/avr/avrlib/index.html
- EEPROM datasheet - http://ww1.microchip.com/downloads/en/DeviceDoc/21836F.pdf
- DIY circuit board tutorial - http://www.instructables.com/id/DIY_Printed_circuit_board/
Code
The firmware for the AVR is available here: Atmega644 source code. For a complete listing of the files used in the project we used a publicly readable SVN server here Complete Project Listing this includes the eclipse project file for the Java code and the AVR studio project file
Part List
Part Name | Description | Amount | Seller | Part # | Cost | Refdes |
Microcontroller(atmega644) | processor | 1 | Atmel | ATMEGA644-20PU | sampled | U1 |
Motor | motor to spin board | 1 | NA | NA | salvaged | NA |
AC to DC wall wart (5V 2.5A) | power supply for motor | 1 | NA | NA | salvaged | NA |
AC to DC wall wart (9V 1.1A) | power supply for LED's and logic | 1 | NA | NA | salvaged | NA |
linear regulator 5V | 9V to 5V regulator for logic besides XBEE | 1 | digikey | LM7805CT-ND | $0.45 | U5 |
linear regulator 3.3V | 5V to 3.3V for XBEE | 1 | digikey | 296-21611-5-ND | $0.84 | U10 |
XBEE wireless tranciever | wireless data link to computer | 1 | digi | NA | already owned | CONN1 |
XBEE to USB board | Computer side transciever | 1 | digi | NA | already owned | NA |
LED driver | shift register and power control to LEDs | 8 | maxim | MAX6971 | sampled | U(3,6,9,11,12,14,15,17) |
EEPROM 1MB | EEPROMs for storing image data | 4 | microchip | 25AA1024 | sampled | U?'s |
4x2:1 mux | mux to choose between driver shift out and direct control | 1 | digikey | 74ACT257SC-ND | $0.40 | U8 |
PCB board for control board (3x6") | PCB with all circuitry except LEDs | 1 | sunstone | NA | $20.00 | NA |
PCB board for 2D display | a modular plug in board with LEDs | 1 | we made | NA | $6 | NA |
RGB LEDs | LEDs for RGB board | 32 | mouser | 828-OVSRRGBCC3 | $25 | NA |
10k resist | pull up resistors and current control for drivers | 14 | digikey | 311-10.0KCCT-ND | lying around | R(1,3,4,5,6,7,8,9,10,11,12,13,14,15) |
.1 uF cap | decoupling capacitors | 3 | digikey | 311-1140-1-ND | lying around | C(1,3,4) |
15pF | clock capacitors | 2 | digikey | 709-1170-1-ND | lying around | C(5,6) |
150 olm resist | LED current control | 1 | digikey | P150ACT-ND | lying around | R2 |
red led | test/hearbeat LED | 1 | digikey | 160-1178-1-ND | lying around | LED |
crystal | 16Mhz clock occilator | 1 | digikey | 631-1024-2-ND | lying around | U2 |
hall sensor | detects rotations | 1 | digikey | US5881EUA-ND | 1.93 | SEN |
4.7nF | hall sensor capacitor | 1 | digikey | 399-1155-1-ND | $0.04 | C2 |
.33uF | power input filter | 2 | digikey | P822-ND | lying around | C(7,11) |
68uH inductor | part of 5V line pi filter | 1 | digikey | 308-1341-1-ND | $1.03 | L1 |
33uF | part of 5V line pi filter | 3 | digikey | P815-ND | lying around | C(9,10) |
base to board brushing | needed to transfer power to spinning board | 1 | we made | NA | scraps | NA |
motor to board conector | mechanical connection between board and motor | 1 | friend made | NA | friend didn't charge | NA |
switches | switches to turn on board and motor seperately | 2 | NA | NA | lying around | NA |
base | base to put motor on | 1 | NA | NA | scraps | NA |
connectors 2x36 female 2mm | connectors to modulorize boards | 16 | samtec | SMM-136-02-S-D | sampled | CONN2-5 |
connectors 2x36 male 2mm | connectors to modulorize boards | 8 | samtec | TMM-136-01-T-D | sampled | NA |
Total | $55.33 |