As my first
microcontroller project I decided to make a hand-held clone of the
classic game Pong. Normally a graphical display would be required for
this kind of project but I decided to take advantage of the custom
character capabilities of the readily available 1602a LCD. The core
of the project is the updated Basic Stamp from Coridium which based
on the LPC11u37 ARM microcontroller, software for the stamp is
written using an updated version of basic which is very similar to
the first language I had learned, Qbasic.
The wiring diagram
below shows how I connected the Basic Stamp to the LCD and three
inputs needed to play the game. The pushbutton with the pullup
resistor R1 is used to make selections and progress through the small
menu system, R2 and R3 are the potentiometers that return a 0 to 3.3v
signal used to control the paddles and change selections in the menu.
The 10 digital outputs are used to interface with the LCD using it’s
8-bit parallel mode, R4 is a small trimpot used to set the contrast
of the display. The contrast makes a big impact on how clearly the
ball’s motion is visible but only needs to be set once. Finally I
used a USB battery as the power source for the project, this takes
care of charging the battery and providing a stable 5v supply for the
microcontroller.
With the project
wired on a breadboard I could start laying out my program, the
flowchart below shows how the final version of the software turned
out. After being powered on there are a few tasks that only need to
be done once, like sending commands to the LCD to get it started and
initializing all the global variables used in the game. After the
PONG splash screen is shown the user can press the button and is
presented with a choice between One and Two Player Modes, to get this
input I look at the current value of the analog input for the right
hand potentiometer. If the analog input is greater than half of 65472, the maximum input value, then One Player Mode will be selected
when the button is pressed, if the analog input is lower than half
the maximum value then Two Player Mode is selected.
For a single player
the game now begins but for two players there is an additional step,
picking the maximum score for the round. I wanted a range from 10 to
50 using increments of 5 and came up with the following line.
maxScore = ((((65472 - AD(1)) * 9) / 65480) * 5) + 10
"65472 – AD(1)" was
used to reverse the direction of the input to make clockwise motion
of the potentiometer increase the score, the result will still be a
number from 0 to 65472. Multiplying the 0 to 65472 value by 9 and
then taking the quotient of that
value and 65480 (slightly larger than the maximum) results in
integers from 0 to 8. Multiplying that integer value by 5 and adding
it to 10 turns the 0 to 65472 input from the potentiometer into a 10
to 50 output with evenly spaced increments of 5.
Once the game has
started in either One or Two Player Mode the logic is very similar.
On every cycle of the game loop the position of the paddle(s) is
updated by checking the value of the analog inputs and scaling them
to a value from 0 to 16 (the usable height of my display) using the
same method used to pick the max score. Next the ball’s position is
updated by adding COS(BallDirection) * BallSpeed to the current X
coordinate and SIN(BallDirection) * BallSpeed to the current Y
coordinate, by storing the ball’s coordinates, direction and speed
as floating point numbers I was able to get much smoother movement of
the ball than if they were all integers. Instead of cogging from one
pixel to the next the ball moves smoothly and it’s position is
rounded to an integer only when being displayed on screen.
After being sent to
the display the game checks if the ball should bounce off of any of
the walls or paddles and adjusts the direction accordingly. In One
Player Mode bounces off the far wall increase the players score, if
the player misses the ball with their paddle they loose a ball, in
Two Player Mode getting the ball past the other player’s paddle
increases the score up to the chosen maximum. If the ball is to be
served on the next loop because a player missed a bounce then then a
variable called gameState is set to indicate that a serve is needed
and towards which player the serve will go. If the gameState
variable is left untouched then the game continues as normal by
updating the paddle position, ball position and then drawing it on
the LCD. If the button is pushed or one of the ending conditions is
met the game loop ends and the player(s) are returned to the splash
screen by the main loop of the program.
The majority of the
program is handled using simple math, loops and if statements but
interfacing with the LCD was a big challenge for me. The LCD takes
information as 8bits on 8 DB pins as well as a Register Selection pin
which determines of the 8bits of data is a command or data to be
written to the LCD’s memory, finally there is an enable pin which
is toggled to indicate to the LCD that the current input values are
valid. In order to quickly set the values of the 8 DB pins I had to
learn about Bit Masking, this is where you perform a logical AND
function on your data byte and a mask. For example, if I want to know
the state of the x bit in this byte 0000x000 I can perform a logical
AND between it and 0000100. Because all of the undesired bits in the
bitmask 00001000 are zero they will always result in a zero, the
value of x will however be in the result because x AND 1 = x. By
using a different bit mask to check each bit of the byte I want to
send I could determine which DB pins should be on and which should be
off.
Where B is the byte
of data I want to send then the value of each DB pin could be
determined in the following way. When turning an output pin on or off
in Arm Basic applying any non-zero value will turn the pin on so it
doesn’t matter that the pin I use for DB7 will be told “OUT(DB 7 pin)
= 10000000,” the pin will always be on when that bit is on.
DB7 = B AND %10000000
DB6 = B AND %01000000
DB5 = B AND 100000
DB4 = B AND 010000
DB3 = B AND 001000
DB2 = B AND 000100
DB1 = B AND 000010
DB0 = B AND 000001
The other big
hurtle was to take the xy data of the ball and paddle positions and
translate them into something that was compatible with how the LCD
works. Being an alphanumeric display it is only made to show
characters from it’s ROM and 8 custom characters that can be
programmed by the user. These 8 custom characters are 5 pixels wide
and 8 pixels tall, by arranging them in a 4 wide by 2 tall grid it is
possible to create a 20x16 pixel graphical display with room for
score keeping (and debugging) on either side.
Each row of a
character has an address (the CGRAM address) and shows the state of
the 5 lowest bits of the byte stored at that address. So my 20x16
area is split up into 64 addresses with each address containing 5
pixels each.
By mapping out the locations of the addresses I was able to make a pair of functions that would return the CGRAM address and the value of the byte that should be stored in that address for any given x and y pixel position, I could then write this byte to the appropriate CGRAM address and display the playing field.
For a more
detailed explanation of the functions I used you can check out a
video I did on the subject linked here: Bit Mapping on the 1602a.
Finally after about
a month of development and testing I was ready to box up my portable
Pong project in a suitably retro looking enclosure and call it
complete.
No comments:
Post a Comment