First implementation

A project log for ╬╝Tris: ATtiny13A Tetris

Falling blocks game on the ATtiny 13A

kevin-cuznerKevin Cuzner 12/23/2016 at 03:550 Comments

The first initial working implementation is complete. The code currently has three sections: The display logic, the uTris game loop, and the input logic. I'll try to describe some of the solutions to various obstacles I encountered.

The display logic is fairly straightforward bitbanging out to shift registers. Since the microcontroller has only 5 or so usable pins (ok, 6, but who wants to give up ~RESET?) the shift registers were an absolute must for running an 8x8 display. The arrangement consists of two 8-bit shift registers in series, the first for the rows and the last for the columns. I had a bit of trouble sourcing a second 74HC595 from my parts bin, so I had to go to Fry's electronics down the street and pick up some equivalent chips. I couldn't believe that they didn't have any 74HC595s in their massive selection of overpriced, rebranded 7400 series ICs. I ended up combining a 74HC164 (8-bit shift register with no latches) and 74HC374 (octal D flip-flop) to form something roughly resembling a 74HC595 so I could chain it to the one I already had. I use three GPIOs total for this arrangement: One for the shift register clock, one for the storage register clock, and the third for the serial data output. Display data is stored internally as an 8-byte array. For each row, I output one byte containing the inverted column data for that row and one byte containing the active-high row mask. The column data is inverted because the second shift register (which drives the columns) is attached to the cathodes of my anode-row dot matrix display. After the two bytes are clocked out, I strobe the storage register clock and move on to the next row. I don't need to worry about ghosting between rows because both the rows and columns have the same storage register clock and are strobed at the same time.

I have no set framerate and the microcontroller will shift out the row data as fast as it can. In between rows I run the main game logic tick function. Since the function takes roughly the same amount of time for each run, this contributes to making all of the rows on the display have the same brightness, more or less.

The game logic is fairly standard for falling block games. I think the only real interesting thing about it is that it's completely agnostic of the actual piece shapes and sizes. The piece data is stored in a bitmap format that is rather wasteful, but takes comparatively few instructions to load. Changing the game to tetrominos from trominos is just a matter of swapping out the bitmap (and finding the additional 120 bytes or so). I'm still working on optimizing the balance between the bitmap size and the code size (so far they seem to have an inverse relationship, as is expected). Other than the bitmap, the only other challenge implementing this part was the balance between function calls and available RAM. When the stack overruns the display buffer, it makes gameplay a little difficult. I used some bit-level packing of various game variables and removed display double buffering capability to increase the amount of RAM available for the stack to compensate for this problem.

The input logic is something I haven't tried before. I use a single ADC pin to measure the voltage of a resistor divider formed between a fixed upper resistor and a lower resistor changed out by button presses. I've chosen the resistor values for the upper and lower resistors so that the input voltage range of the ADC is divided into five quadrants, one for each state: no button pressed, down pressed, rotate pressed, left pressed, and right pressed. A capacitor is present on the pin for debouncing and to reduce noise. The program captures the lowest voltage value it sees until the voltage returns to the "no button pressed" level. This compensates for the fact that the voltage level needs to travel down and back up again during a button press at some finite speed. With some tuning, I found this system to work very reliably, though I imagine it would need to be tuned again if I chose different resistors.

I suppose a different method I could have used for doing input would have been to attach a parallel input shift register to the display shift register chain and clocked in the status of the buttons as I clocked out display data. However, I think getting the timing right on this would be more error prone when implementing it, due to the display's need to strobe the storage registers after clocking and the hypothetical input register's need to strobe the storage registers before clocking. Alternately, I could get a parallel-in shift register without input latches. The benefit of this arrangement would be less variability due to resistors and I think I could do it in less code. Anyway, I'm still considering it...