Lab 2 - 6502 Math Lab
Introduction
In this blog post, I will share my experience working on the second lab of the SPO (Systems Programming and Operating Systems) course. The goal of this lab was to create a subroutine in 6502 Assembly Language that draws an image on a bitmapped screen and then animate it by making it bounce around the screen. The lab involved writing code to move a graphic diagonally across the screen and then modifying it to make the graphic bounce off the edges of the screen.
The Code
The initial code provided draws a 5x5 pixel image (either an "O" or an "X") on the screen and moves it diagonally from the top-left corner to the bottom-right corner. The image is cleared and redrawn in a new position to create the illusion of movement. The code uses a delay loop to slow down the animation so that it is visible to the human eye.
Here is the initial code:
; ; draw-image-subroutine.6502 ; ; This is a routine that can place an arbitrary ; rectangular image on to the screen at given ; coordinates. ; ; Chris Tyler 2024-09-17 ; Licensed under GPLv2+ ; ; ; The subroutine is below starting at the ; label "DRAW:" ; ; Test code for our subroutine ; Moves an image diagonally across the screen ; Zero-page variables define XPOS $20 define YPOS $21 START: ; Set up the width and height elements of the data structure LDA #$05 STA $12 ; IMAGE WIDTH STA $13 ; IMAGE HEIGHT ; Set initial position X=Y=0 LDA #$00 STA XPOS STA YPOS ; Main loop for diagonal animation MAINLOOP: ; Set pointer to the image ; Use G_O or G_X as desired ; The syntax #<LABEL returns the low byte of LABEL ; The syntax #>LABEL returns the high byte of LABEL LDA #<G_O STA $10 LDA #>G_O STA $11 ; Place the image on the screen LDA #$10 ; Address in zeropage of the data structure LDX XPOS ; X position LDY YPOS ; Y position JSR DRAW ; Call the subroutine ; Delay to show the image LDY #$00 LDX #$50 DELAY: DEY BNE DELAY DEX BNE DELAY ; Set pointer to the blank graphic LDA #<G_BLANK STA $10 LDA #>G_BLANK STA $11 ; Draw the blank graphic to clear the old image LDA #$10 ; LOCATION OF DATA STRUCTURE LDX XPOS LDY YPOS JSR DRAW ; Increment the position INC XPOS INC YPOS ; Continue for 29 frames of animation LDA #28 CMP XPOS BNE MAINLOOP ; Repeat infinitely JMP START ; ========================================== ; ; DRAW :: Subroutine to draw an image on ; the bitmapped display ; ; Entry conditions: ; A - location in zero page of: ; a pointer to the image (2 bytes) ; followed by the image width (1 byte) ; followed by the image height (1 byte) ; X - horizontal location to put the image ; Y - vertical location to put the image ; ; Exit conditions: ; All registers are undefined ; ; Zero-page memory locations define IMGPTR $A0 define IMGPTRH $A1 define IMGWIDTH $A2 define IMGHEIGHT $A3 define SCRPTR $A4 define SCRPTRH $A5 define SCRX $A6 define SCRY $A7 DRAW: ; SAVE THE X AND Y REG VALUES STY SCRY STX SCRX ; GET THE DATA STRUCTURE TAY LDA $0000,Y STA IMGPTR LDA $0001,Y STA IMGPTRH LDA $0002,Y STA IMGWIDTH LDA $0003,Y STA IMGHEIGHT ; CALCULATE THE START OF THE IMAGE ON ; SCREEN AND PLACE IN SCRPTRH ; ; THIS IS $0200 (START OF SCREEN) + ; SCRX + SCRY * 32 ; ; WE'LL DO THE MULTIPLICATION FIRST ; START BY PLACING SCRY INTO SCRPTR LDA #$00 STA SCRPTRH LDA SCRY STA SCRPTR ; NOW DO 5 LEFT SHIFTS TO MULTIPLY BY 32 LDY #$05 ; NUMBER OF SHIFTS MULT: ASL SCRPTR ; PERFORM 16-BIT LEFT SHIFT ROL SCRPTRH DEY BNE MULT ; NOW ADD THE X VALUE LDA SCRX CLC ADC SCRPTR STA SCRPTR LDA #$00 ADC SCRPTRH STA SCRPTRH ; NOW ADD THE SCREEN BASE ADDRESS OF $0200 ; SINCE THE LOW BYTE IS $00 WE CAN IGNORE IT LDA #$02 CLC ADC SCRPTRH STA SCRPTRH ; NOTE WE COULD HAVE DONE TWO: INC SCRPTRH ; NOW WE HAVE A POINTER TO THE IMAGE IN MEM ; COPY A ROW OF IMAGE DATA COPYROW: LDY #$00 ROWLOOP: LDA (IMGPTR),Y STA (SCRPTR),Y INY CPY IMGWIDTH BNE ROWLOOP ; NOW WE NEED TO ADVANCE TO THE NEXT ROW ; ADD IMGWIDTH TO THE IMGPTR LDA IMGWIDTH CLC ADC IMGPTR STA IMGPTR LDA #$00 ADC IMGPTRH STA IMGPTRH ; ADD 32 TO THE SCRPTR LDA #32 CLC ADC SCRPTR STA SCRPTR LDA #$00 ADC SCRPTRH STA SCRPTRH ; DECREMENT THE LINE COUNT AND SEE IF WE'RE ; DONE DEC IMGHEIGHT BNE COPYROW RTS ; ========================================== ; 5x5 pixel images ; Image of a blue "O" on black background G_O: DCB $00,$0e,$0e,$0e,$00 DCB $0e,$00,$00,$00,$0e DCB $0e,$00,$00,$00,$0e DCB $0e,$00,$00,$00,$0e DCB $00,$0e,$0e,$0e,$00 ; Image of a yellow "X" on a black background G_X: DCB $07,$00,$00,$00,$07 DCB $00,$07,$00,$07,$00 DCB $00,$00,$07,$00,$00 DCB $00,$07,$00,$07,$00 DCB $07,$00,$00,$00,$07 ; Image of a black square G_BLANK: DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00
Modifying the Code for Bouncing
To make the graphic bounce off the edges of the screen, I made the following modifications:
- Starting Position: I chose a starting position where X and Y have different values. For example, X = 10 and Y = 5.
- Increments: I introduced two new zero-page variables, XINC and YINC, to hold the increments for the X and Y positions. These can be either -1 or +1, depending on the direction of movement.
- Bouncing Logic: I added logic to check if the graphic has hit the edge of the screen. If it has, the corresponding increment is negated to reverse the direction.
; ; draw-image-subroutine.bouncing.6502 ; ; This routine places an arbitrary ; rectangular image on the screen at given ; coordinates and makes it bounce within ; the screen boundaries. ; ; Chris Tyler 2024-09-17 ; Licensed under GPLv2+ ; ; ; The subroutine is below starting at the ; label "DRAW:" ; ; Test code for our subroutine ; Moves an image diagonally across the screen and bounces it off the edges ; Zero-page variables define XPOS $20 ; Current X position define YPOS $21 ; Current Y position define XINC $5 ; X increment (-1 or +1) define YINC $6 ; Y increment (-1 or +1) START: ; Set up the width and height elements of the data structure LDA #$05 STA $12 ; IMAGE WIDTH STA $13 ; IMAGE HEIGHT ; Set initial position LDA #$0A ; 10 in decimal STA XPOS LDA #$08 ; 5 in decimal STA YPOS ; Initialize increments: XINC = +1, YINC = +1 LDA #$01 ; +1 represented as $01 STA XINC STA YINC MAINLOOP: ; Set pointer to the image ; Use G_O or G_X as desired ; The syntax #<LABEL returns the low byte of LABEL ; The syntax #>LABEL returns the high byte of LABEL LDA #<G_O STA $10 LDA #>G_O STA $11 ; Place the image on the screen LDA #$10 ; Address in zeropage of the data structure LDX XPOS ; X position LDY YPOS ; Y position JSR DRAW ; Call the subroutine ; Delay to show the image LDY #$00 LDX #$50 DELAY: DEY BNE DELAY DEX BNE DELAY ; Set pointer to the blank graphic LDA #<G_BLANK STA $10 LDA #>G_BLANK STA $11 ; Draw the blank graphic to clear the old image LDA #$10 ; LOCATION OF DATA STRUCTURE LDX XPOS LDY YPOS JSR DRAW ; Update X position LDA XPOS CLC ADC XINC STA XPOS ; Check X boundary CMP #$1B ; Check if XPOS >= 27 BCC CHECK_X_MIN LDA #$FF ; Reverse direction STA XINC JMP UPDATE_Y CHECK_X_MIN: CMP #$00 ; Check if XPOS <= 0 BNE UPDATE_Y LDA #$01 ; Reverse direction STA XINC UPDATE_Y: ; Update Y position LDA YPOS CLC ADC YINC STA YPOS ; Check Y boundary CMP #$1B ; Check if YPOS >= 27 BCC CHECK_Y_MIN LDA #$FF ; Reverse direction STA YINC JMP MAINLOOP CHECK_Y_MIN: CMP #$00 ; Check if YPOS <= 0 BNE MAINLOOP LDA #$01 ; Reverse direction STA YINC JMP MAINLOOP ; ========================================== ; ; DRAW :: Subroutine to draw an image on ; the bitmapped display ; ; Entry conditions: ; A - location in zero page of: ; a pointer to the image (2 bytes) ; followed by the image width (1 byte) ; followed by the image height (1 byte) ; X - horizontal location to put the image ; Y - vertical location to put the image ; ; Exit conditions: ; All registers are undefined ; ; Zero-page memory locations define IMGPTR $A0 define IMGPTRH $A1 define IMGWIDTH $A2 define IMGHEIGHT $A3 define SCRPTR $A4 define SCRPTRH $A5 define SCRX $A6 define SCRY $A7 DRAW: ; SAVE THE X AND Y REG VALUES STY SCRY STX SCRX ; GET THE DATA STRUCTURE TAY LDA $0000,Y STA IMGPTR LDA $0001,Y STA IMGPTRH LDA $0002,Y STA IMGWIDTH LDA $0003,Y STA IMGHEIGHT ; CALCULATE THE START OF THE IMAGE ON ; SCREEN AND PLACE IN SCRPTRH ; ; THIS IS $0200 (START OF SCREEN) + ; SCRX + SCRY * 32 ; ; WE'LL DO THE MULTIPLICATION FIRST ; START BY PLACING SCRY INTO SCRPTR LDA #$00 STA SCRPTRH LDA SCRY STA SCRPTR ; NOW DO 5 LEFT SHIFTS TO MULTIPLY BY 32 LDY #$05 ; NUMBER OF SHIFTS MULT: ASL SCRPTR ; PERFORM 16-BIT LEFT SHIFT ROL SCRPTRH DEY BNE MULT ; NOW ADD THE X VALUE LDA SCRX CLC ADC SCRPTR STA SCRPTR LDA #$00 ADC SCRPTRH STA SCRPTRH ; NOW ADD THE SCREEN BASE ADDRESS OF $0200 ; SINCE THE LOW BYTE IS $00 WE CAN IGNORE IT LDA #$02 CLC ADC SCRPTRH STA SCRPTRH ; NOTE WE COULD HAVE DONE TWO: INC SCRPTRH ; NOW WE HAVE A POINTER TO THE IMAGE IN MEM ; COPY A ROW OF IMAGE DATA COPYROW: LDY #$00 ROWLOOP: LDA (IMGPTR),Y STA (SCRPTR),Y INY CPY IMGWIDTH BNE ROWLOOP ; NOW WE NEED TO ADVANCE TO THE NEXT ROW ; ADD IMGWIDTH TO THE IMGPTR LDA IMGWIDTH CLC ADC IMGPTR STA IMGPTR LDA #$00 ADC IMGPTRH STA IMGPTRH ; ADD 32 TO THE SCRPTR LDA #32 CLC ADC SCRPTR STA SCRPTR LDA #$00 ADC SCRPTRH STA SCRPTRH ; DECREMENT THE LINE COUNT AND SEE IF WE'RE ; DONE DEC IMGHEIGHT BNE COPYROW RTS ; ========================================== ; 5x5 pixel images ; Image of a blue "O" on black background G_O: DCB $00,$0e,$0e,$0e,$00 DCB $0e,$00,$00,$00,$0e DCB $0e,$00,$00,$00,$0e DCB $0e,$00,$00,$00,$0e DCB $00,$0e,$0e,$0e,$00 ; Image of a yellow "X" on a black background G_X: DCB $07,$00,$00,$00,$07 DCB $00,$07,$00,$07,$00 DCB $00,$00,$07,$00,$00 DCB $00,$07,$00,$07,$00 DCB $07,$00,$00,$00,$07 ; Image of a black square G_BLANK: DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00
- Initialization:
- The graphic starts at (XPOS = 10, YPOS = 8).
- The increments XINC and YINC are set to +1, making the graphic move diagonally down and to the right.
- Main Loop:
- The graphic is drawn at the current (XPOS, YPOS) using the DRAW subroutine.
- A delay loop ensures the animation is visible.
- The graphic is cleared by drawing a blank image at the current position.
- The XPOS and YPOS are updated by adding XINC and YINC.
- Boundary Checking:
- If XPOS reaches 0 or 27, XINC is reversed.
- If YPOS reaches 0 or 27, YINC is reversed.
- Drawing the Graphic:
- The DRAW subroutine calculates the screen address for the graphic and copies the image data to the screen.
Results
The graphic moves diagonally across the screen, bouncing off the edges. The animation is smooth, and the graphic stays within the visible area of the screen. The delay loop ensures the movement is visible to the human eye.
Reflections
This lab was an excellent exercise in understanding 6502 Assembly Language and working with bitmapped displays. The bouncing logic added complexity but was rewarding to implement. I gained a deeper understanding of memory manipulation, boundary checking, and animation techniques in low-level programming.
Comments
Post a Comment