Sheng Dai (sd267) and Paul Vishayanuroj (pv47)
BordFree is a resurrection of the classic Microsoft hit SkiFree featuring an innovative tilt-control scheme.
BordFree places users in the boots of a snowboarder navigating a challenging ski slope. BordFree players will see their character on a color TV scrolling from bottom to top on a course with various obstacles. The player will need to navigate around these obstacles lest he or she hits one and gets slowed down. The objective of the game is to maximize the end score, which will be based on the total distance traveled before being eaten by the Pacman. The character will be able to move in directions ranging from diagonal in each direction to fully forward.
The control interface for BordFree will involve the user standing on a “Freebord”, which is essentially a six-wheeled skateboard that allows sideways tilting. By shifting their weight to tilt the board, the player see their movements reflected in the character's change of direction. The tilt is monitored by the Mega32 microcontroller using an accelerometer attached to the freebord.
We decided to use the freebord because it would provide players with a more interactive experience than using a simple controller.
High Level Design
In past years, there have been cases where unfortunate Cornellians have been suckered into purchasing season passes to a nearby ski resort. In a few particular cases, some of those Cornellians even opted to purchase their own snowboards to avoid the rental fees. A combination of a typical Cornell workload and surprisingly snowless weather resulted in an inability to utilize their purchases and fulfill their burning desire for snowboarding.
BordFree will demonstrate that it is possible to simulate an entertaining snowboarding simulation for less than half the cost of a season pass (about $129 at the lowest) in the comfort of your own home.
The design of the game was inspired by the aforementioned Microsoft game, SkiFree, developed by Chris Pirih and released in 1991.
The high level design is detailed in the figure below. The Mega32 receives control input from an 1-axis accelerometer mounted on a freebord. The Mega32 does the game processing and outputs RGB values at the correct time based on values received from the sync generator. The RGB signals are converted into an NTSC signal by the converter.
We decided early on against the need to use external SRAM as a buffer to store images. This was done to avoid the extra difficulty of having to deal with another level of complexity on top of the accelerometer, game code, and color TV output. By not using external SRAM, we had to make do with the 2KB of internal SRAM. With a 1600 char array (1.6KB already), we decided to trade off color for resolution. The result was a 64x100 resolution with 4 colors.
In our game code, due to the time constraints imposed by generating an NTSC signal, we limited the number of on-screen obstacles to 10. Anymore than this would result in very noticeable flickering.
We will be working with NTSC color video encoding to generate the visual interface of our game. NTSC achieves a flicker free refresh frequency of about 60Hz by interlacing its 525 scanlines. Color encoding for NTSC uses a luminance-chrominance system. Luminance carries information on the black-and-white, or brightness, information. Chrominance carries color information, in primary colors R, G, and B. Chrominance is encoded using two 3.58MHz signals that are 90 degrees out of phase, known as I (in-phase) and Q (quadrature). A reference "color-burst" signal is also transmitted to recover I/Q phase chrominance signals by synchronizing the color television to this reference at the beginning of each scanline.
Patents, Trademarks, and Copyrights
We have tried searching for any IP or patent claims for the Microsoft game "SkiFree" which our game is based off of, but have not found any on the WIPO or the USPTO. Furthermore, our game is only loosely based off SkiFree and is different from it in many respects.
The hardware design of this project was divided into two portions - outputting a color TV signal and accelerometer input.
To go into further detail about NTSC encoding, each scanline is exactly 63.5 us. The first 1.5 us is the front porch region from the previous scanline. The next 4.7 us of a scanline is the horizontal sync pulse, essentially denoting the beginning of the line. After the horizontal sync pulse, another there is another 4.7 us region called the backporch region, a non-visible pre-scan region of the TV. The rest the scanline time is the actual color TV signal that contains the information for that particular line. A complete frame is 262 lines, 242 of which are visible. During the last 20 lines, the electron beam returns to the top left of the screen, a period called vertical sync. See the figure below for a visual representation of this explanation.
To generate a color TV signal, it is essential to know when the horizontal and vertical syncs occur. The ELM304 sync generator takes care of this detail. On its spec sheet, the following is explained:
We can see that horizontal sync is denoted by the transition of V1 from Low to High. As later explained in software, the rising edge of this transition is what triggers the external interrupt. However, this transition alone is not enough to distinguish between a horizontal and vertical sync pulse. In the case of vertical sync, the horizontal pulse will be have an inverted shape. Rather than trying to catch this, we use the value of V2 after the backporch phase to determine if a vertical sync is actually occurring. If V2 is still High at this time, this denotes a horizontal sync. If V2 is Low at this time, this denotes a vertical sync. The Mode pin of the ELM304 must be set to high to generate a solid white raster. In order for the Mega32 to interrupt on the V1 transition, V1 is connected to port D.2, which is reserved for external interrupts, while V2 is connected to port D.3. XT1 and XT2 are connections for the reference crystal.
Color TV signals are sent by the Mega32 out of Ports C.0 and C.1, representing red and green, since we are using 2-bit color encoding. These go to the R and G inputs of the AD724 encoder after passing the an RC network as specified by the encoder datasheet. The standard pin (Pin 1) on the encoder is set to high to specify NTSC encoding. The encode pin (Pin 5) is set high to enable encoding mode. The select pin (Pin 12) is set to low to enable normal FSC (frequency subcarrier) mode. The HSYNC pin (Pin 16) receives its input from the V1 output of the ELM304, and the VSYNC pin (Pin 15) is set to high, since we are receiving a true composite input on the HSYNC. Both the AD724 and the ELM304 require a 3.579 MHz (the NTSC frequency) reference signal. At first we used two separate crystals, but realized later that we got better image quality by using the same crystal going to both chips. Output to the TV from the COMP pin (Pin 10) passes through a 220 uF and 75 Ohm resistor in order to lower voltage and allow the 30 Hz frequencies to pass. This is input to the TV using a standard composite RCA video cable.
We used a Freescale MMA6261Q 1.5g accelerometer as control input. Although it was a 2 axis model, we only required the use of the Y-axis, since the player would only be able to turn left and right. One problem we encountered was that the accelerometer requires a 3.3 V supply voltage, while the Mega32 only provides a 5 V supply. This seems easily resolved by a voltage divider with a 0.66 ratio, however the accelerometer has an input impedance of ~50 Ohms that must be accounted for. We also increased the resolution of the accelerometer by passing its output through a L358 opamp to double the output voltage and make use of the full range of the Mega32’s ADC. This output was wired to port A.0.
The x-axis tilt of our accelerometer is converted through the A/D input of the MCU. Once at the beginning of every frame that our game runs, the accelerometer input is read from the ADCH register into a char variable Ain and a new conversion is started by driving the ADCSR.6 bit high.
Our active range for Ain is between 98 to 188, with 143 being a perfectly horizontal orientation. This was determined experimentally by printing Ain to hyperterminal with the accelerometer at various tilts that we expected our Freebord controller to achieve. The active range is split into three sections: 98-121 for turning left, 122-163 for going straight, and 164-188 for turning right. These three states decrement, do nothing, or increment a game variable called tilt, which we explain in the game elements section.
Setting up our external interrupt from the ELM304 NTSC sync generator was the first order of business. After looking through various schemes for external interrupt design in past projects, we determined that it was best to take advantage of the ELM's multiple NTSC sync signal outputs. We used two of three signals, V1 and V2, and ignored V3 because we did not plan to modulate the grayscale level of our video.
Our single interrupt is triggered externally by the rising edge of V1 from the ELM. This marks the end of each scanline's sync pulse. We then wait for roughly 4.7us for the "backporch" of the scanline to end before reading V2, which determines whether we are drawing the visible raster or in vertical blanking.
If V2 is low, this indicates the vertical blanking period. Nothing explicit is done here except resetting the LineCount to 1, though game code will be doing computations during this interval. Originally we were doing nothing when V2 was detected low because LineCount was handle differently. We were incrementing LineCount each interrupt and automatically resetting it to 1 whenever it reached 263. However, this caused problems that we spent many hours trying to debug. The cause was discovered via oscilloscope to be V1 triggering more than 263 times per frame, specifically that it does not stay low like V2 during blanking because it needs to provide inverted horizontal sync waveforms as vertical sync pulses. So instead of trying to figure out the upper bound on LineCount, we simply increment it every scanline when not blanking and reset it to 1 during blanking.
If V2 is high, then we can draw horizontal lines from our 16-by-100 char screen array. We draw during scanlines 20 to 220, which means we have 128 bytes across per line with lines that are two scanlines tall.
The actual video output is based on Professor Bruce Land's assembly code for serially blasting line bytes to screen, which he wrote for ECE476. The only modification we needed to make was to interpret every two bits as one pixel, since the assembly code already read 128 bytes per line with each line repeated over two scanlines. This involved writing the "blastit" macro to sequentially store two bit pairs into bits 0 and 1 of PortC before outputting PortC to the AD724. This happens four times until the whole byte is output as 4 pixels. Three nops were added before each output on PortC to stretch the horizontal dimension of each pixel to better match their vertical heights.
Finally, our screen array is populated by the C function writePixel(x, y, color). This function will overwrite two bits in the screen array if the given x,y coordinates are within the bounds of 0<= x < 64, 0 <= y < 100. We note here that for the sake of game consistency we do allow x,y coordinates outside these bounds so that trees that go offscreen a certain distance will still be stored in case the player turns in that direction again. If the x,y coordinates are in visible bounds, writePixel will calculate the byte index in the screen array by calling getByteLoc(x, y), which multiplies the y coordinate by 16 and divides the x coordinate by 8 before summing the two. Once the byte index is known, writePixel masks and shifts the new color, which is also byte sized, to the appropriate bit alignment before ORing with the existing byte in the screen array. Various bitmaps, such as the player, trees, and the abominable pacmonster, have their own functions to write individual color pixels one at a time to the screen array using many writePixel calls. We also adapted Bruce Land's C functions for writing strings and characters to screen, which calls writePixel according to pre-defined bitmaps stored in flash memory.
Our major game variables include those for determining player orientations (tilt, index), for translating motion depending on player orientation (vxtable, vytable), for storing locations of up to numObj obstacles (objX[numObj], objY[numObj]), for accumulating score, and for keeping track of the game state.
There are five player orientations, denoted by the variable called index. Index 0 and 4 are horizontal, which makes the player stop in place. Indices 1, 2, and 3 are the player traveling diagonally left, straight up, or diagonally right respectively. The tilt variable governs the transitions between various player orientations. As mentioned above, the A/D input, Ain, can increment or decrement the tilt value. The tilt value itself is also split into five regions in the range of 0 to 122, one for each orientation index. This makes it so that tilting the Freebord left does not abruptly turn the player left in-game. Rather, it decrements the tilt value until tilt falls below a certain threshold to change the player orientation index to be the next one that is more leftward pointing.
Like in SkiFree, our game always draws the player sprite in a fixed position on the screen, and moves all other objects. There are no realistic physics to Skifree or our game, namely acceleration does not exist. With this convenient characteristic, we simply initialize fixed velocity tables for x and y velocities, called vxtable and vytable respectively. Both of these tables have 5 hardcoded entries corresponding to the available player orientation indices. In each frame that the player can move, the x,y positions of all existing obstacles (trees) will accumulate the constant x,y velocities for the current orientation index and the trees are redrawn. Because frames happen many times a second, we needed to use 16-bit fixed point representation for our velocities and positions to slow down how many pixels an object moves each second. No new macros were needed, since we only add velocities to positions. The actual positions used to draw pixels would be the fixed point int variables shifted right by 8 and casted as char.
The positions of trees are stored in objX and objY coordinate arrays. There can exist up to numObj trees in the game at any time, numObj being a #defined constant in code. Not all of these trees are necessarily visible and drawn on screen, as their x coordinates can be outside the 0-64 range of visible horizontal pixels. We use y coordinates, in objY, to pop old trees and push newly spawned ones. At first, all objY values will be initialized to -1, which to our code means that the array is "empty" of trees. During each game frame, there is a 3% chance per "empty" array element to spawn a new tree at the top of the screen with a random x coordinate. Array elements that are not "empty" simply follow the velocity accumulation scheme mentioned above. However, once existing tree T goes beyond the maximum visible y coordinate of 100 pixels, it is made "empty" by setting objY[T] to -1 again, making room next frame for another random spawn. Random numbers are generated using the rand() function included in stdlib.h.
The final design of the game transitions between three states: Game -> Bonus -> Score. Score accumulates based on how much vertical distance the player has traveled while in the Game and Bonus states. After the first time the player crashes in the Game state, they enter the Bonus state, where they continue to play for a little while as the abominable pacmonster chases them down from the left and eats them. Upon being eaten, their score is converted to string using sprintf and displayed on screen. The most important contribution of our state machine is to partition the game code so that only sections that matter are run each frame. We were originally running into problems where the amount of lines in our game code was pushing the border beyond the blanking phase of our video generation. Sprintf would cause unwanted behavior in our video output when we tried to add it. By partitioning the game into our three states, we branch through all the lines of code that would normally run for updating tree positions and player orientation when it comes time to do sprintf.
Our game operates with constant velocities, which translates to the user seeing pixels move at roughly 60 pixels per second vertically and 30 pixels per second horizontally. The resolution of the visible screen is 64x100. Together these factors make for a reasonably challenging pace to the game. Gradual transitions for turning also make for a somewhat realistic simulation of turning on a snowboard that makes the game interesting. There appeared to be no concurrency problems between the ADC input and the video interrupt code.
During development, there were cases where the game code might have been missing the video deadline, which resulted in noticeable flicker and improper printing of strings to screen. However, most of these issues appear to have been successfully optimized away in our final design. Only in some instances, the numerical score does not fully write to screen.
No major electronic safety issues surround our project. We did once connect a polarized 220uF capacitor backwards between the AD724 and the TV, though nothing terrible came of it.
Our project also does not output any signals that would interfere with other projects in the lab.
On the subject of usability, our game is meant for a snowboard-like experience in the form of a video game. There may be physical limitations and safety concerns about standing on and balancing the Freebord that is used as a controller. As such, we recommend beginners to have some mechanism to support themselves on to avoid accidents as a result of falling off the game controller.
Our game successfully embodies the play style we envisioned from the start. Unfortunately, we did have to make some cut backs on certain features related to our color TV interface. Given more time and planning, we would have been able to realize our vision of complete 8-bit color with better resolution for drawing interesting sprites. But our initial plan did not include integrating SRAM into the design, so we were heartbreakingly bottlenecked on the memory front. Nonetheless, we learned a lot about NTSC color video generation, and we were able to bring an interesting control scheme into one of our classic childhood gaming experiences. Overall, it was an enjoyable course project that produced a fun product.
We used components that conform to NTSC standards, with a hardware configuration that follows from the recommended setups in the data sheets of the AD724 RGB to NTSC encoder and the ELM304 NTSC sync generator. Our software did not introduce additional control signals that interfere with the sole external interrupt based around the NTSC composite sync signal provided by the ELM. The code was also optimized to meet the timing constraints of our visible raster drawing. We feel that NTSC standards are properly met in our design.
Intellectual Property Considerations
There are no IP considerations that we can think of. As mentioned previously, we have found no IP claims relating to SkiFree. All source code in our project was written by us with a few exceptions that were taken from Professor Land’s Lab 4 (Lunar Lander video game) code. The final concept for interpreting sync signals from the ELM was based on Keith Jamison and Morgan Winer's High Resolution Color TV project. We also followed the C code structure for writing pixels to the screen array from Alan Levy's Color TV project. There are not likely to be any patent opportunities resulting from our project.
Our project fully complied with all points of the IEEE Code of Ethics. All of the decisions made by the BordFree team have been consistent in preserving the safety, health, and welfare of the public. We have disclosed the few (if any) factors that may be a danger to personal safety. To avoid any conflicts of interest, we have throughly researched any IP claims or patent claims on the original Windows game SkiFree and failed to find any. In writing our proposal, we have been honest in any claims and estimates we made. In this project report, we have been as accurate as possible in stating any measurements made. We have selflessly rejected all forms of bribery put forth on us by large unnamed corporations. We feel that working in this project has advanced our knowledge of microcontrollers, the C programming language, and NTSC standards. All criticisms made towards our project have been taken seriously, and we have credited all those that have helped us in this project at the end of this report. BordFree is open for all persons to play, regardless of race, religion, gender, age, or national origin. We have assiduously avoided destroying any equipment, property, and reputations while working on BordFree. Lastly, we have tried whenever possible to offer helpful advice to our friends and colleagues in this class.
Our project does not use any RF components, nor does it create any noticeable interference other electronic equipment. We have no intent on marketing and/or selling our game.
Video Generation Circuit
|1||Color TV Rental||$10.00|
Delegation of Tasks:
|Accelerometer Hardware||Paul & Sheng|
|Video Generation Hardware||Paul & Sheng|
|Accelerometer Code||Paul & Sheng|
|Video Generation Code||Paul & Sheng|
|Game Code||Paul & Sheng|
|Final Report||Paul & Sheng|
The video generation circuit and the accelerometer circuit
The freebord and a gameplay image
AD724 - http://www.analog.com/UploadedFiles/Data_Sheets/AD724.pdf
ELM304 - http://www.elmelectronics.com/DSheets/ELM304DS.pdf
MMA6261Q - http://www.digchip.com/datasheets/download_datasheet.php?id=669231&part-number=MMA6261Q
Mega32 - http://instruct1.cit.cornell.edu/courses/ee476/AtmelStuff/full32.pdf
Keith Jamison and Morgan Winer's High Resolution Color TV Project
Alan Levy's Color TV Project
“TV Paint” - http://www.stanford.edu/class/ee281/handouts/lab4.pdf
“TV Tutorials” - http://www.williamson-labs.com/480_tv.htm