# AT89C52-Based Modern Tetris Game Console with MAX7219 Display **Repository Path**: pomimi/tetris ## Basic Information - **Project Name**: AT89C52-Based Modern Tetris Game Console with MAX7219 Display - **Description**: AT89C52-based, developed using Keil C51 in assembly language - **Primary Language**: Assembly - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-11-16 - **Last Updated**: 2025-11-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

AT89C52-Based Modern Tetris Game Console with MAX7219 Display


Developer: adipy
June 14, 2023

##### 1. Design Requirements - The matrix keyboard can correctly read key inputs and provide corresponding feedback. - The digital tube serves as a menu interface, enabling users to view the following screens and parameters, and modify the parameters: - Display of the game start interface - Display of the timer interface - Display of pause animation - Display of line clearance count - Modification of DAS, ARR, SDF, DCD, falling speed, lock delay, and other parameters - The buzzer can play background music and switch between tracks via key presses. - The game interface can be correctly displayed on the LED dot matrix screen. - Implementation of all fundamental rules of modern Tetris, mainly including: - Correct display of the playfield (10×16, height not conforming to the standard rulebook specification) and tetrominoes - Hold function for tetrominoes and display of the held piece - Movement of tetrominoes within the playfield, configuration of DAS and ARR parameters, and priority determination for left/right movement - Falling and locking of tetrominoes within the playfield, and configuration of SDF and DCD parameters - Rotation of tetrominoes within the playfield and implementation of the SRS (Super Rotation System) - Gravity-induced falling of tetrominoes and lock delay mechanism - Line clearing of the playfield and display of line clearance animation - Display of tetromino previews (four pieces) and generation following the 7-bag randomizer rule - Game-over detection for tetrominoes (utilizing only the block-out condition)
##### 2. Design Analysis and System Scheme Design All program functions are implemented within interrupt service routines. T0 serves as the processing interrupt, executing 60 times per second; the T1 timer is used to drive the buzzer. Modular programming is adopted, with functionalities hierarchically subdivided through multi-layer subroutine nesting to achieve the target functionality. The implementation involves extensive table lookups and branching structure judgments, which will not be elaborated upon in detail herein.
##### 3. System Circuit Diagram
![输入图片说明](image/lib3-1.png)
The MCU's XTAL1 and XTAL2 pins are connected to an external 12MHz crystal oscillator, and the EA pin is pulled high

![输入图片说明](image/lib3-2.png)
Matrix keyboard connected to Port P0

![输入图片说明](image/lib3-3.png)
Buzzer I/O pin connected to P2.4

![输入图片说明](image/lib3-4.png)
Two 595-driven 8-digit digital tube modules: DIO connected to P1.0, RCLK connected to P1.1, SCLK connected to P1.2

![输入图片说明](image/lib3-5.png)
Four MAX7219-driven 16×16 dot matrix screen modules: DIN connected to P2.0, CS connected to P2.1, CLK connected to P2.2, cascaded via DIN-to-DOUT connection with shared CS and CLK pins

##### 4. Hardware Circuit Functional Description of Peripheral Interface Modules
![输入图片说明](image/lib4-1.png)
Matrix keyboard employs row-scanning method for key detection

![输入图片说明](image/lib4-2.png)
Passive buzzer, low-level triggered

![输入图片说明](image/lib4-3.png)
Two 595-driven 8-digit display modules. Each 595 contains an 8-bit register—one for digit selection and one for segment patterns—operating on an SPI-like protocol. Each transmission consists of 16 bits, with the high 8 bits representing segment code and the low 8 bits representing digit selection code

![输入图片说明](image/lib4-4.png)
Four MAX7219-driven 16×16 dot matrix screen modules. The MAX7219 operates based on the SPI protocol, transmitting 16-bit data packets per transaction (high 8 bits for address, low 8 bits for data). It contains 14 addressable data and control registers internally. The data register addresses range from 01H to 08H, enabling autonomous dynamic display for both digital tubes and dot matrix screens

##### 5. Main Variable Definitions in Main Program
| Variable Name | RAM Unit/Register | Function | | --- | --- | --- | | FALL_T | None | Falling timer parameter | | LOCK_T | None | Lock delay timer parameter | | DAS_T | None | Delayed Auto Shift timer parameter | | ARR_T | None | Auto Repeat Rate timer parameter | | SDF_T | None | Soft Drop speed timer parameter | | DCD_T | None | DAS charge delay (actually hard drop accident prevention timer parameter) | | CLR_T | None | Line clear delay timer parameter | | D_DAS_T | None | DAS for digital tube | | D_ARR_T | None | ARR for digital tube | | MATRIX_KEY | P0 | I/O pins occupied by matrix keyboard (with external pull-up resistors) | | DIO_DIGIT | P1.0 | 595 data pin | | SCLK_DIGIT | P1.1 | 595 clock pin | | RCLK_DIGIT | P1.2 | 595 register latch pin | | DIN_LEDOTS | P2.0 | MAX7219 data input pin | | CS_LEDOTS | P2.1 | MAX7219 chip select pin | | CLK_LEDOTS | P2.2 | MAX7219 clock pin | | BUZZER | P2.4 | Buzzer pin | | TMR_30_2 | 06H | 30-frame timer | | CYCLE_TMR | 07H | Pause animation timer | | PAGE_SEQ | 08H | Menu page sequence number | | BIT_SEQ | 09H | Digital tube digit adjustment position index | | D_ARR_TMR | 0AH | Digital tube ARR timer | | D_DAS_TMR | 0BH | Digital tube DAS timer | | ATTACK_COUNT | 0CH | Records attack count | | LINE_COUNT | 0DH | Records line clear count | | LINE_H | 0EH | Records line clear status | | LINE_L | 0FH | | | TH0_INIT | 10H | Timer 0 initial value | | TL0_INIT | 11H | | | TH1_INIT | 12H | Timer 1 initial value | | TL1_INIT | 13H | | | TH1_INIT2 | 14H | Timer 1 backup initial value (for dual-tone) | | TL1_INIT2 | 15H | | | NOTE_TMR0 | 16H | Note length timer | | NOTE_TMR1 | 17H | Note playback length timer | | NOTE_TMR1_BUF | 18H | Backup of NOTE_TMR1 | | NOTE_COUNT_L | 19H | Current note index (expanded to maximum of 32,768) | | NOTE_COUNT_H | 1AH | | | MUSIC_SELECT | 1BH | Music selection | | TMR_H | 1CH | Clock hours | | TMR_M | 1DH | Minutes | | TMR_S | 1EH | Seconds | | TMR_30 | 1FH | 30-frame timer | | TMR_2 | 20H.0 | Secondary timer | | PLAY_ENABLE | 20H.1 | Music playback enable bit | | NOTE_DOUBLE | 20H.2 | Dual-tone playback standard bit | | CLR_PIECE_TRUE | 20H.3 | Set to 1 when clearing piece, cleared to 0 when drawing piece | | MOVE_L_PRIOR | 20H.4 | Movement priority flag | | WAIT_FLAG | 20H.5 | Wait flag | | START_FLAG | 20H.6 | Game start flag | | DEATH_FLAG | 20H.7 | Game over flag | | KEY_L | 21H | Matrix keyboard key value storage | | KEY_S16 | 21H.0 | | | KEY_S15 | 21H.1 | | | KEY_S14 | 21H.2 | | | KEY_S13 | 21H.3 | | | KEY_S12 | 21H.4 | | | KEY_S11 | 21H.5 | | | KEY_S10 | 21H.6 | | | KEY_S9 | 21H.7 | | | KEY_H | 22H | | | KEY_S7 | 22H.0 | | | KEY_S6 | 22H.1 | | | KEY_S5 | 22H.2 | | | KEY_S4 | 22H.3 | | | KEY_S3 | 22H.4 | | | KEY_S2 | 22H.5 | | | KEY_S1 | 22H.6 | | | KEY_S0 | 22H.7 | | | KEY_MOVE_L | KEY_S13 | Move left | | KEY_MOVE_R | KEY_S10 | Move right | | KEY_ROTATE_L | KEY_S7 | Rotate left (counter-clockwise) | | KEY_ROTATE_R | KEY_S4 | Rotate right (clockwise) | | KEY_ROTATE_2 | KEY_S3 | 180° rotation | | KEY_HOLD | KEY_S9 | Hold | | KEY_SOFT_DROP | KEY_S14 | Soft drop | | KEY_HARD_DROP | KEY_S8 | Hard drop | | KEY_RETRY | KEY_S1 | Retry | | KEY_SET | KEY_S16 | Switch music | | KEY_SHIFT | KEY_S2 | Edit | | KEY_PAUSE | KEY_S6 | Pause | | KEY_UP | KEY_S11 | Up | | KEY_DOWN | KEY_S16 | Down | | KEY_LEFT | KEY_S15 | Left | | KEY_RIGHT | KEY_S12 | Right | | KEY_NOW | 23H | Current key value | | KEY_BUF1 | 24H | Previous key value backup | | BUF_MOVE_L | 24H.0 | | | BUF_MOVE_R | 24H.1 | | | BUF_ROTATE_L | 24H.2 | | | BUF_ROTATE_R | 24H.3 | | | BUF_ROTATE_2 | 24H.4 | | | BUF_HOLD | 24H.5 | | | BUF_SOFT_DROP | 24H.6 | | | BUF_HARD_DROP | 24H.7 | | | KEY_BUF2 | 24H | Previous key value backup | | BUF_RETRY | 24H.0 | | | BUF_SET | 24H.1 | | | BUF_SHIFT | 24H.2 | | | BUF_PAUSE | 24H.3 | | | BUF_UP | 24H.4 | | | BUF_DOWN | 24H.5 | | | BUF_LEFT | 24H.6 | | | BUF_RIGHT | 24H.7 | | | KEY_CHG1 | 25H | Whether just pressed at this moment | | CHG_MOVE_L | 25H.0 | | | CHG_MOVE_R | 25H.1 | | | CHG_ROTATE_L | 25H.2 | | | CHG_ROTATE_R | 25H.3 | | | CHG_ROTATE_2 | 25H.4 | | | CHG_HOLD | 25H.5 | | | CHG_SOFT_DROP | 25H.6 | | | CHG_HARD_DROP | 25H.7 | | | KEY_CHG2 | 26H | Whether just pressed at this moment | | CHG_RETRY | 26H.0 | | | CHG_SET | 26H.1 | | | CHG_SHIFT | 26H.2 | | | CHG_PAUSE | 26H.3 | | | CHG_UP | 26H.4 | | | CHG_DOWN | 26H.5 | | | CHG_LEFT | 26H.6 | | | CHG_RIGHT | 26H.7 | | | ARR_FLAG | 27H.0 | Whether ARR is being timed | | DAS_FLAG | 27H.1 | Whether DAS is being timed | | SDF_FLAG | 27H.2 | Whether SDF is being timed | | DCD_FLAG | 27H.3 | Whether DCD is being timed | | HOLD_FLAG | 27H.4 | Whether a piece is currently held | | LOCK_FLAG | 27H.5 | Whether current piece is locked | | FALL_FLAG | 27H.6 | Whether gravity exists | | CLEAR_FLAG | 27H.7 | Whether lines have been cleared | | D_ARR_FLAG | 28H.0 | Whether digital tube ARR is being timed | | D_DAS_FLAG | 28H.1 | Whether digital tube DAS is being timed | | UP_PRIOR | 28H.2 | Value adjustment priority flag | | ADJUST_FLAG | 28H.3 | Value adjustment flag | | PAUSE_FLAG | 28H.4 | Pause flag | | WAIT_TMR | 29H | Wait timer | | FALL_TMR | 2AH | Falling timer | | LOCK_TMR | 2BH | Lock delay timer | | DAS_TMR | 2CH | DAS timer | | ARR_TMR | 2DH | ARR timer | | SDF_TMR | 2EH | SDF timer | | DCD_TMR | 2FH | DCD timer | | FIELD_HEAD | 30H-4FH | Records playfield information | | BLOCK_TYPE | 50H | Lower 4 bits for active piece type, upper 4 bits for held piece type | | BLOCK_CENTRE_X | 51H | Tetromino center X-coordinate | | BLOCK_CENTRE_Y | 52H | Tetromino center Y-coordinate | | BLOCK_DRIECTION | 53H | Tetromino orientation | | NEXT_TYPE | 54H, 55H | Four upcoming tetrominoes | | BAG_COUNT | 56H | Records current piece number in the bag | | TYPE_RECORD | 57H | Records pieces already appeared in the bag | | RAND_TMR | 58H | Random number timer | | RAND | 59H | Random number | | FALL_TMR_INIT | 5AH | Falling timer initial value | | LOCK_TMR_INIT | 5BH | Lock delay timer initial value | | DAS_TMR_INIT | 5CH | DAS timer initial value | | ARR_TMR_INIT | 5DH | ARR timer initial value | | SDF_TMR_INIT | 5EH | SDF timer initial value | | DCD_TMR_INIT | 5FH | DCD timer initial value |
##### 6. Flowchart
![输入图片说明](image/lib6-1.png)

##### 7. System Debugging Results, Problem Analysis, and Design Insights & Recommendations
![输入图片说明](image/lib7-1.png) ![输入图片说明](image/lib7-2.png) ![输入图片说明](image/lib7-3.png) ![输入图片说明](image/lib7-4.png)

The system can operate games normally and display parameters; the buzzer, LED dot matrix screen, and digital tubes all function properly. Software Environment: Keil 5 Hardware Environment: STC89C52RC Minimum System Design Language: 51 Microcontroller Assembly Language Problems Encountered and Design Experience: See the conclusion and design experience below.
##### 8. Conclusion and Design Experience This section primarily elaborates on the insights and summary gained during this experimental design. Developing a project from scratch proves exceptionally challenging, particularly when using assembly language. Although the fundamentals of 51 microcontroller assembly programming were previously studied, practical experience remained limited, with no prior completion of comprehensive programs. Laboratory courses primarily involved modifying existing textbook examples. During independent development, numerous unforeseen issues arise without reference implementations. Instructors cannot always provide solutions, some information is unavailable online, and in certain instances the root cause remains elusive, requiring independent deduction through iterative verification. This process is arduous, as problems may stem from either the program or peripheral devices, yet isolating the source proves difficult. As the codebase expands, debugging becomes increasingly problematic because certain functions span multiple subroutines, and locating minor errors necessitates extensive troubleshooting. The inherently poor readability of assembly code further compounds debugging difficulties. Dormitory-based debugging lacked emulator support, and due to keyboard input requirements, debuggers were unusable, forcing reliance on peripheral observations for verification. Implementing branch structures in assembly is exceptionally difficult, with logic reversal errors occurring frequently. Additionally, occasional errors arose from instruction unfamiliarity, such as miscalculating CJNE instruction lengths causing incorrect jumps, unawareness that ANL and INC instructions do not affect the zero flag leading to improper branching, and forgetting that SUBB always subtracts the carry flag causing computational deviations. The most intolerable issues encountered include: - **Dual-tone Note Problem:** Musical scores contain sections requiring simultaneous dual-note playback, yet only two timers exist—it is impossible to allocate both timers to drive two buzzers while one remains necessary for program processing. The initial approach alternated timing between two frequencies, resulting in discordant output. Subsequent analysis confirmed this failure: given the buzzer's low-level triggering, flipping its state once per timing interval actually requires two flips per period, producing a constant square wave at some frequency between the two intended tones, likely non-standard. A revised method employed two timing initial values, timing the shorter interval each cycle, then subtracting it from the longer value upon interrupt entry, resetting the short value, and iterating. However, complications emerged: First, this logic is relatively complex and consumes dozens of machine cycles. Due to subtraction operations, occasional ultra-short timing intervals of approximately ten machine cycles occur, preventing accurate buzzer-level flipping at precise moments with uncertain consequences. Second, the feasibility of a single buzzer simultaneously emitting two distinct frequency square waves remains questionable, as proper wave superposition should yield three voltage states (0, 1, 2). While 0 and 1 suffice for single tones, dual tones require superposition (1+1=2). However, passive buzzers cannot adjust amplitude since microcontroller I/O pin output levels are fixed. Implementing this would require a digital-to-analog converter for proper waveform output, but at that stage, replacing the buzzer with a speaker would be preferable—unfortunately, time and energy constraints prevented rewriting the entire audio system. The final solution splits the note duration: the first half plays the first tone, the latter half plays the second. The auditory effect is suboptimal, but represents the achievable compromise. Perfecting this dual-tone functionality consumed substantial effort, though ultimately proved less valuable than anticipated. - **MAX7219 Debugging Display Issues:** The vendor provided no example code for 4×4 LED dot matrix modules, only 1×1, 1×2, and 2×8 configurations. All attempted examples produced incorrect results, with one matrix row displaying abnormally, raising concerns of hardware damage. The purchased modules lacked pre-soldered pins, requiring manual soldering without flux. Initial soldering was incorrect, requiring several minutes of rework—the board became scalding hot, nearly causing skin burns. Hardware damage concerns prompted consideration of purchasing a replacement. No functional examples were available online, forcing direct manual consultation. Although initially daunting, the datasheet proved less difficult than anticipated, clarifying data transmission to specific locations. Test mode confirmed all LEDs remained functional. However, subsequent data input attempts failed to produce normal display despite repeated manual verification. Only address 0CH responded, though not a data input address. Other dimensional examples illuminated serially, revealing no discernible pattern. After extensive troubleshooting, the discovery emerged that MAX7219 chips require initialization—control registers must be properly configured for normal operation. Implementation of the initialization routine resolved the issue. - **Linking Multiple ASM Files:** Initially, using CODE SEGMENT and RSEG directives combined with include statements successfully distributed subroutines across separate ASM files for improved debugging convenience. However, during peripheral module integration, this approach failed—the main program entry address was allocated to incorrect locations, preventing proper jumps. This behavior proved erratic and its failure conditions remain unclear, though it appears incompatible with large-scale projects. With no alternative, consolidation into a single ASM file exceeding 2,000 lines became necessary. While functional, locating specific subroutines became cumbersome. This approach was genuinely undesirable but unavoidable due to unresolved linking mechanics. An additional complication arose: unlicensed Keil only compiles 2,000 instructions. Upon exceeding this limit, feature reduction seemed imminent until online research revealed cracking methods extending the limit to nearly 4,000 instructions. - **Left/Right Movement Priority and Delay Debugging:** This issue constituted a branching structure nightmare, requiring consideration of numerous potential errors, such as failing to erase old active pieces, forgetting to invert movement direction after changing move keys, or pushing return parameters onto the stack. While the errors appeared straightforward in retrospect, they proved elusive during debugging, requiring considerable time to resolve. Fortunately, subsequent large-scale branch debugging became somewhat more manageable. - **Rotation Wall Kicks Not Matching Expectations:** Although wall kicks functioned, their positions were completely incorrect. Repeated verification of the copied wall kick tables revealed no errors. Gradual debugging revealed that only the first wall kick was correct; subsequent kicks were corrupted. Moving later wall kick tables forward remained correct, but later kicks still failed. Exhaustive checks revealed nothing. Near despair, the final discovery identified that the array pointer DPTR was not pushed onto the stack, causing its value to change after subroutine nesting. This was truly unexpected—prior experience never encountered programs requiring DPTR stack operations. A valuable lesson learned. - **Random Program Crashes:** During next piece system implementation, random microcontroller resets occurred—the crash timing and piece counts were completely unpredictable and patternless. This was devastating, as even commenting out newly integrated code failed to prevent the phenomenon, meaning backtracking offered no solution. This suggested fundamental architectural flaws potentially requiring complete reconstruction. Prolonged debugging proved unsuccessful, nearly prompting project abandonment due to inability to identify the root cause. All functions operated correctly individually, yet the program crashed intermittently. Stack space of 20H seemed sufficient. Near despair, the discovery emerged that powering solely through the power cable without USB connection prevented crashes. This indicated a peripheral issue—the serial port could not adequately power the microcontroller. Thankfully, persistence allowed project continuation. - **7-Bag Piece Generation Rule Implementation:** The previous implementation was overly complex—even the author could not comprehend it upon review. Ultimate abandonment and rewriting still produced nonfunctional code. Subsequent discovery revealed the random number generator used T1 values, but with the buzzer inactive, no randomness occurred. Additionally, register B in the RNG was not pushed onto the stack, overwriting B in the 7-bag generation routine and producing bizarre tetrominoes. Debugging this with the original code would have been impossible. - **Death Condition Implementation:** Enabling death conditions prevented normal first hold operations. This was absurd for such a simple conditional check. Extensive testing revealed the condition was inverted and hold generation failed to clear active pieces from the playfield, causing invisible overlapping positions. This stemmed from the 51 microcontroller's limited RAM—only approximately 90B available for variables, with a single 32B playfield. All updates occur on this playfield, requiring active piece clearance before each position validation, otherwise overlap is inevitable. This significantly complicated the program. - **Line Clear Animation Drawing:** This proved quite difficult within the existing architecture. The original plan involved delaying within the line clear subroutine, exiting only after animation completion. This was unrealistic as other peripherals required servicing. Consequently, cleared lines had to be recorded externally with a loop preventing game operations until animation completion. Line clear recording occurred within the lock detection routine, requiring delay flags and timers. However, debugging revealed the set clear flag always read as 0 after exit, even with minimal branch conditions. Ultimately, the issue was a miswritten jump address in the lock detection routine. Nevertheless, this course project significantly improved assembly language proficiency and debugging capabilities. The final product is reasonably satisfactory. The overall process was engaging, though periods of prolonged unsuccessful debugging proved exceedingly frustrating. Collaborative technical exchange would have been beneficial.
##### References Microcontroller Principles Experiment Tutorial, Qin Xiaomei, Wang Kaiyu, Chao Ming, Zhao Quanke