Nova Strike is a 2D space shooter game implemented with an Atmel ATmega32 microcontroller.
The inspiration came from our love of video games and fond memories of playing space shooters on our TI-89 graphing calculators in high school (instead of paying attention in calculus class).
Our project is based on the revolutionary arcade game, Galaxian, made by Namco. The main goal of Galaxian is to pilot a spaceship and rid the galaxy of enemy spaceships. Successive waves of spaceships are more aggressive, making the game more challenging as it progresses.
Nova Strike was completed as a design project for our ECE 476 microcontroller course (Cornell University, Spring 2004). It features a single player mode in which the player battles waves of enemies in an intense intergalactic shootout. A versus mode allows two players to go head-to-head in a contest for the title of “Space Ace.” Players use Sega Genesis controllers to interact with the game, which can be played on a TV screen.
The design process focused on setting realistic goals for the amount of development time we had (1 month), and staying within the budget ($40.00).
Our rationale for choosing this project was based on our experience with TV signal generation in previous laboratory experiments for ECE 476. Since the video output signal takes up much of the microcontroller’s processing power, we decided it would be fun to tear our hair trying to write a highly interactive game while maintaining the timing constraints imposed by the TV.
Given the limitations of the hardware, we decided to be conservative when setting our goals and gradually pushed the hardware to its limits once they had been achieved. We are both double-majors in Electrical & Computer Engineering and Computer Science so we reasoned that a game would give us design experience in both fields. Our conceptual basis, Galaxian, is copyright Namco.
Players can interact with the game using Sega Genesis gamepads. We chose this input device since it provides a good human interface and is easy to work with electrically. We can get all of the input signals with one port of the microcontroller dedicated for each of the two gamepads.
Additionally, the gamepads have a standard female DB9 plug so it was easy to find connectors that we could solder to our board. The game requires six buttons (for: up, down, left, right, fire, start/pause) out of the eight that are on the controller.
The video output signal is based on the RS-170 standard for monochrome video, which gives the appearance of an authentic classic arcade game. Bruce Land’s examples provided a basis for generating the signal. The screen is treated as a 128×100 pixel array. Since the signal is black and white, only one bit is needed for each pixel, allowing us to store the video buffer as a vector of 1,600 bytes. Unfortunately, this representation of the screen buffer uses a large portion of the on-chip SRAM and forced us to be conservative when setting goals for our project.
The game starts off at a splash screen that displays the Nova Strike logo and allows the player to select between single player and versus mode. In single player, the player starts with three lives at Level 1, which has a difficulty reminiscent of shooting fish in a barrel. Twenty-five enemy ships appear at the top of the screen and continuously scroll from side to side, randomly shooting at the player. Occasionally, a ship breaks off and tries a kamikaze attack on the player. The player must move side to side to avoid enemy fire while taking shots at the enemies. A level is cleared once the player survives the onslaught and destroys all enemy ships. Successive levels are made more difficult with increasingly aggressive enemies that move faster, have faster weapons, and shoot more frequently. If the player is hit by an enemy ship or enemy fire, a life is lost. A life can be gained for every 100 enemies destroyed, up to a maximum of three lives. To make the game fair, we decided to make the player invulnerable for a few seconds after dying. In effect, the player is given a chance to dodge out of the way of enemies when resuming play. The single player game can be paused (for those times when you reach the threshold of neglecting personal hygiene). A soft reset can also be performed by hitting the correct combination of buttons while paused (holding down the fire button while pressing and releasing start/pause).
Our circuit is straightforward in design and should be reproducible using standard components. We developed our project on the Atmel ATSTK500 development board, though our end result is self contained on a single solderboard. The main components that we needed were the ATmega32 microcontroller, a simple Digital-to-Analog Converter (DAC) for video output, and two game controllers for input.
Video Output Circuit
The video signal is based on the RS-170 standard so there is a single waveform that is used to control both timing and luminance by varying the voltage. There are three voltage levels: white (1 V), black (0.3 V), and sync (0 V).
One pin of PORTD (configured as output) is used to generate the black/white output, while the other is used to generate sync pulses. A DAC combines the signals for output to the TV. The design for the DAC comes from Bruce Land’s ECE 476 lab assignments.
Sega Genesis Controller
To use the gamepads, we had to find a pinout of the DB9 connector. Several past design projects for ECE 476 made use of these controllers so it was easy to find. The pinout along with assignments appears below.
The controller has eight buttons, but only 6 wires for reading buttons. A mux select line allows the other two buttons to be read. Since we only needed six buttons, we tied the select line to high. The gamepad is powered directly from the 5V supply.
Things that didn’t work
In our original design, we planned on using a Seiko G321D LCD panel with an SED1330F controller. The LCD panel/controller was used in several past projects with good results. The LCD can be treated as a random access device so the cycles that would have been devoted to generating a video signal would have been saved, allowing us more flexibility with the game design. We constructed a power circuit and interfaced the LCD to the microcontroller without success. After a week of failure, we tested our panel with another group’s functioning circuit and found that our panel was defunct. As we had planned in case the LCD did not work, we switched to using TV for video output.
The video code was based on Bruce Land’s TV generation code. The Timer1 interrupt is used to read a line of the 128×100 pixel video buffer and write the it to the TV screen. Since the sync pulse timing is critical, the main program loop is stalled by an idle sleep command which makes the entry into the Timer1 interrupt service routine (ISR) uniform. It is critical that the code in the main program loop finishes executing before the ISR is triggered.
One of the most challenging parts of the software was staying within the limits of the hardware when it came to graphics. Our finished game has up to twenty-five enemy ships, the player, and other status indicators that are updated as necessary. In order to draw everything to screen each frame, we had to write fast graphics routines. The ships and text are drawn as 3×5 pixel bitmaps.
We could not use the original 3×5 character drawing routine (video_smallchar) that Bruce Land wrote for the TV generation code for most of the game, since it requires that characters be aligned to every fourth pixel. The screen buffer is stored as a vector of 1600 bytes, dedicating 1 bit to each pixel (1 for white, 0 for black). First we tried writing a routine similar to the one that prints unaligned 5×7 characters. We were barely able to draw 10 ships before we saw corruption since it made too many function calls, which introduced additional delays. We decided to write a new routine from scratch. Since unaligned characters may cross byte boundaries, it was necessary to read two bytes from the screen, mask the appropriate bits, and then write the new data back. The resulting routine (video_smallchar_ua) is fast enough to handle the graphics updates.
To erase areas of the screen, at first we tried drawing blank characters over the areas to be updated. This caused some corruption of the video signal since it practically doubled the number of calls to video_smallchar_ua and the instructions did not finish executing before the Timer1 ISR was triggered. We instead tried using a minimalist approach by drawing black vertical lines. The direct screen buffer manipulation of video_vertline_black was fast enough to erase parts of the screen to do updates and it allowed us to add more graphics.
The awesome logo on the menu screen was meticulously hand-coded in hex from a drawing and stored in flash memory. The draw_logo routine reads the raster and quickly writes it to the proper location in the screen buffer. erase_logo blanks the same area on screen by writing directly to the vector.
The game controllers on PORTB and PORTC are polled every execution of the main program loop (i.e., once per frame). A simple release-debounce state machine reads the input from each button on the controllers and provides button release signals to the rest of the code. The behavior of the individual buttons depends on the game state.
Controller 1 is designated as the main controller. The up/down buttons on the directional pad manipulate the menu options. The B button displays the high scores list. To select the game mode and begin, the C button is used. In the single player game, left/right move the player’s ship, B fires, and C pauses/unpauses the game. In pause mode, holding B while pressing and releasing C causes the game to do a soft reset. At the high scores entry screen, up and down change the character and left and right switch between the three initials. Controller 2 is active in versus mode. Left/right move the ship and B fires.
The bulk of the game code is contained in the main program loop. The game state machine, button debounce, and animations are all handled here. A picture of our gameflow follows.
The game starts off by displaying the menu and allowing the player to choose a game mode. The state machine goes to the appropriate initialization state as appropriate. Each frame, the single player game state performs many tasks:
-enemy ships are scrolled
-the player’s input is read
-the player’s ship is moved
-enemy bullets’ positions are updated/created
-the player’s bullet is updated/created
-an enemy ship is selected to break off at random
-collisions with bullets are checked
-the score is updated
-lives are updated
-invulnerability status is updated
The versus mode reads input and updates ships, bullets, and lives for both players.
When the player’s ship is destroyed, an explosion animation is played while the game continues. The new ship slides into position and then invulnerability is granted. The animation is handled by using a timer to count frames and update the sequence accordingly. An animation for the game over screen is handled similarly.
The five highest scores and associated initials are stored in EEPROM so the values remain even after the power is turned off. When the player dies, the score is checked against each of the scores in the list, starting from the highest. If the new score exceeds the stored one, the other scores are shifted down the list and the player is given an opportunity to enter initials.
One of the challenging parts of the code was getting the ship that breaks off from the formation in single player mode to not wipe out the graphics of ships it flies over. We were able to solve this problem by being careful about the order in which we drew the array of ships and the kamikaze ship. Another tricky part of the code was getting the collision of enemy bullets with the player accurate down to the last pixel. We tried using video_set to check the status of a pixel but this did not work well with our code. We instead defined bounding boxes for each ship and check if bullets occupy the space in the box, which counts as a collision. This proved to be faster and more reliable than using video_set. Finally, the sheer mass of the code made it difficult to keep track of everything. The source file is over 3000 lines long. We did not run into problems with using too mush of program memory but did come close (~92% of flash memory used).
Despite having to change from using an LCD panel to a TV, the project was a major success. The modified TV generation code lets us draw many characters to the screen in one frame without any artifacts or other TV glitches. The gameplay is fast, smooth, and enjoyable thanks to the speed of the TV code. The game state is updated every frame with careful attention to the order of events in order to provide good concurrency. The game responds immediately and appropriately to button presses of the gamepad. The measured power consumption of the circuit running from the 9V battery is approximately 30 mA.
The video signal timing is pretty good, but does not entirely correspond to the RS-170 standard. The standard requires 63.55 uSec/line, however Bruce Land’s TV code runs at 63.625 uSec/line. The deviation does not create a noticeable difference in the TV signal, and generates black and white TV excellently. The collision detection for both player and enemy bullets works as desired. The AI for a ship that has broken formation, simple as it may be, functions properly. From our testing, the game runs as expected. However, due to the nature of software, there may still be bugs in the code, but hopefully they do not affect gameplay.
The main safety concern we have with using a TV as the output for our game is the possibility of causing epileptic seizures. The nature of television signal generation makes this possible, but only as likely as any other television uses. To prevent seizures from occurring as much as possible, our game does not contain any flashing screens (besides the refreshing of the TV screen) or flashing animations. Also, our circuit does not contain any dangerous parts, except maybe some sharp leads on the bottom of the solder board, possibility of lead poisoning from the solder, and risk of electric shock, but these are minimal.
Other projects in the lab did not interfere with our circuit (at least not noticeably). It is unlikely that our circuit interfered with other projects. The only sources of interference in our circuit would be noise from from the MCU oscillation and the generation of the TV signal.
Our project is very usable for just about anyone. The interface is simple and does not require any special knowledge. To play the game, all that is necessary is being able to see the TV screen and pressing the buttons on the controller.
|Atmel ATmega32 Microcontroller PDIP||1||$8.00|
|Sega Genesis Gamepad||2||$4.50|
|Large solder board||1||$2.50|
|9V battery clip||1||$0.35|
|LM 340 5V Voltage Regulator||1||$0.99|
|22 pF capacitor||2||$0.00|
|0.1 uF capacitor||3||$0.00|
|100 kOhm resistor||1||$0.00|
|1 kOhm resistor||1||$0.00|
|330 Ohm resistor||1||$0.00|
|75 Ohm resistor||1||$0.00|
|100 Ohm resistor||1||$0.00|
|16 MHz crystal||1||$0.00|
|DB9 solder connector|
(Radio Shack 276-1537)
For more detail: Nova Strike video game