Software

Data acquisition: accelerometer-handling routines

As described in the hardware section, the accelerometer outputs a square wave with a pulse width, T1,  that represents the acceleration measured. A valid method to measure T1 would have been to constantly poll the signal, and check whether the signal is high or low. A more elegant way of doing this is using the external interrupts INT0 and INT1 that are read on PORTD bits 2 and 3. Each of the interrupts is reserved for its respective axis. Both interrupts have similar initialization and ISRs.

The interrupt was initialized to be triggered on a positive edge. When the interrupt is entered, a counter is started (counter updates in timer1 compare on match ISR), and the interrupt is switched to be triggered on a negative edge. The next time the ISR is entered, the T1 is recorded as the value at the counter, and it is switched back into a positive edge trigger. In this way, T1 is changed on the fly, and it can be referenced whenever it is needed.

Calculating tilt
Now that we have T1, we can calculate the tilt on each axis. This is the function of the get_tiltx() and get_tilty() methods. Calculating the acceleration from T1 is easy, since we have all the needed information and the calculation involves only standard math functions. Yet this is not the case to calculate the tilt, we need to calculate the inverse sine of the acceleration, which is computationally extremely expensive. Even a Taylor approximation of sine is long, and we do not need a very high level of accuracy, so we used a look-up table to figure out the tilt. The tilts are calculated to the nearest 7.5 degrees.

Calculating speed
Now that we have the tilt on both the x and y axis, we can easily discretize this into 3 speeds in each direction. If the tilt angle is 7.5o or 15o, the speed is ‘1’, if the tilt is 22.5o, 30o, or 37.5o, the speed is ‘2’, and if the tilt is greater, the speed is ‘3’. If the tilt is negative, the sign on the speed is also negative.

Output
Maze/Ball coordinate system
A 24x16 pixel area represented each ‘square’ in our maze array. In the G321D, pixels are grouped by bytes, and so each square was three bytes wide. The same area defined the ball, although some pixels on its edge were turned off to give it a ‘rounder’ look. The coordinates of the ball were represented in terms of its position in the maze array. Such a coordinate system made it almost trivial to determine whether or not the ball would be colliding with a wall or not. To make for smooth
motion, we had to allow for ball positions that were not always centered on any given array-coordinate. The solution was to define the balls position with two extra parameters, namely the x- and y- direction offsets. With the possibility of offset, the ball could be lying in more than one ‘square’ at a given time. We therefore had to modify our collision detection rule to:

Given array coordinates BallX and BallY, the ball will collide with a wall if (a) there is a block at position(BallX, BallY), (b) there is a block at position(BallX, BallY+1), if there is a y-direction offset, or (c) there is a block at position(BallX+1, BallY), if there is an x-direction offset.
The fact that the ball was the same size as a ‘square’ meant that there could be no offset that existed such that the ball would span more than two ‘squares’ in any one axis. Also, since we could divide both its width (24 pixels) and its height (18 pixels) by 3, it was natural to allow for offsets of 0, 1 and 2. To keep the coordinates simple, we disallowed any negative offsets. Anytime there was potential for a negative offset, we simply decremented the BallX (or BallY coordinate, as the case may have
been), and then set the offset to 3+<negative offset>.

LCD Drivers
Initialization was made by initLCD(), as described in the SED1330F documentation.

The G321D has got multiple layers, each of which can be configured for either text or graphics display. We used layer 1 (text) and layer 2 (graphics). Displaying text is straight forward, because the SED1330F has got an internal character generator. To display a line of text, we followed the following  protocol:

There were three main routines used for graphics display: drawBlock(), and drawBall(),used in concert with clearBall().

The routine used to draw the individual squares of the maze is drawBlock(). The parameters passed into it were coordinates and a flag. Given an asserted flag, the routine would draw a 24x17 filled-in square at a position specified by the coordinates. If the flag was off, nothing is drawn.

The crucial part of drawBlock() was the calculation of the display memory address, given a pair of coordinates from our 'maze array coordinate system'. The G321D is 320x200, and we wanted to represent a 13x12 array. Therefore, each 'square' in the array would occupy 24 pixels accross and 17 pixels down (one row of the array would have to be 'chopped off'). Given array position (x, y), the corresponding display memory address would be:

address = 3*x + 17*40*y +  GraphicsAddressOffset (i.e., the address of the top-right corner of the graphics screen).
The filled-in square is drawn by writing the following sequence: 0b01111111, 0b11111111, 0b11111110; moving the cursor to the next line; and repeating the sequence. A '1' turns on a pixel, while a '0' turns it off.

To draw the ball, we wrote the drawBall() routine, which has as parameters, an x- and a y-axis offset. The coordinates of the ball are stored in two global variables, BallX and BallY. The actual drawing of the ball was similar to what drawBlock() used, except that the binary sequence was different. Translating the address to write to in display memory had the added challenge of having to take the offsets into account. This calculation involved several multiplications. Initially, we did not realize that some of them were 16-bit multiplications. Several overflow warnings from the compiler, as well as mysterious behavior from the LCD finally alerted us to the problem, and we had to rewrite the calculation to better fit the 8-bit architecture of the AT90s8515.

Different ball speeds are achieved by altering the offsets passed into the drawBall() routine (see above, Maze/Ball coordinate system).

Before drawing a new ball, drawBall() calls clearBall(), which clears the ball from its former location.

The Game FlowChart illustrates how all of the different parts of the project come together.

Source Code