Eye Snake - Low-Level Design
Our EOG circuit can be successfully run on two 9V batteries for each of the horizontal sensing and vertical sensing circuit boards. The power circuit, taken from a previous BioNB440 RF-linked EMG final project, is designed to split the rails in +4.5 and –4.5 rails. Note: The reference electrode placed behind your ear or forehead is hooked to ground.
The program started off with the video framework from the course,
except with no sound.
The first thing added was a basic one-pixel-per-length snake to move around the screen. This part was fairly simple and had no complications. After that, screen boundary conditions and collision detection with a randomly generated fruit were added, both of which again were fairly simple as the random generation is simply done by two calls to the Math.rand() function and the collision detection consists of just checking the position of the snake's head with the other positions (fruit, rest of snake).
The next step consisted of updating the graphics. Now, indead of single-pixel snake and "fruits", we created 6x6 bitmap images of five different types of fruits and the snake's head and tail.
Initially, they were 8x8 bitmaps with the snake's width being 6 pixels, but it seemed that the logic to determine the snake's "cornering" (image transition from horizontal to vertical or vice versa, since snake does not fill entire bitmap) seemed to be taking too long and causing sync issues, so they were reduced to 6x6. Additional logic had to be added to draw the snake's head and tail pointing in the correct direction, as well as constraining the randomly generated fruit locations to align to the screen grid of 6x6 cells.
After that, we added up/down buttons to the initial left/right to control the snake. Now, instead of left/right being 90deg turns, "up" corresponds to screen up, "down" to screen down, "left" to screen left, and "right" to screen right. Thus, if the snake is traveling horizontally, you can only turn up or down, and if it's traveling vertically on the screen, you can only turn left or right.
Finally, we started adding external input from the electrodes to control the screen. Initially, we started with the left/right only control scheme, since the left/right signal was much cleaner and easier to calibrate. Even then, however, the signal had a tendency to go flat and drop below threshholds for left/right, causing the "center" signal to evaluate to left and the "right" signal to evaluate to an indeterminate value. However, if the signal levels were distinct enough and the threshhold values in the code were properly set, reading and decoding the signal from the ADC was straight-forward.
Calibrating the up/down signal was even more troublesome, but when it was set up correctly, there were still initially problems with the directions being decoded. As it seemed that the values were being read in correctly, we thought that it might be a problem with debouncing. Since reading up/down and left/right requires two A/D conversions, we split it up between frames - one frame left/right is read, then mux is set to the other pin and the next frame up/down is read.
However, this causes a debounce for up/down or left/right to take 8 frames to go from not-pushed to pushed back to not-pushed. So since left/right and up/down commands need to be alternated for there to be any effect (eg. turning "up" while already going up or down has no effect), we decided that removing debouncing would have no effect and was worth a try; and indeed it did work.
After getting those working (mostly), we moved on to adding multiple levels and lives. For each new level, the starting speed increases, the starting length of the snake is incremented, and a new obstacle is added. The obstacles are randomly generated in the same way as the fruit, and when you die, your lives are decremented, and the speed and length are reset to the level's starting values (length and speed are incremented each time you eat a fruit).
All in all, the biggest software problem we encountered was trying to draw/erase too many pixels at a time to the screen in one frame. To resolve the issue, we added several state machines to divide the drawing/erasing over several frames. And anything that had to be erased and redrawn in the same frame was always put together to occur in the same frame so no flickering occured.