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