Lab2
Introduction
In this lab, I worked on a 6502 assembly language project where I animated a small graphic, making it move across the screen and bounce within predefined screen boundaries. The main objective was to write assembly code that would control the movement of a 5x5 pixel graphic, simulate the bouncing effect when it hit the screen's edges, and explore how changing movement increments could impact performance. The task helped me understand the challenges of low-level programming and the limitations of early computer hardware.
The process began with creating a basic routine to move a 5x5 pixel graphic (represented as a blue "O") across the screen. The graphic's position was adjusted along both the X and Y axes. Once the basic movement was working, I added a bouncing effect when the graphic hit the screen's edges. I also experimented with different increments to refine the graphic's motion.
Lab 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 ; Set up the data structure ; The syntax #<LABEL returns the low byte of LABEL ; The syntax #>LABEL returns the high byte of LABEL LDA #<G_X ; POINTER TO GRAPHIC STA $10 LDA #>G_X STA $11 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 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 $0600 ; ========================================== ; ; 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
This code mainly revolves around the DRAW subroutine, which is responsible for placing the graphic at the appropriate screen coordinates. It calculates the address of the image data and copies it to the screen, creating the desired visual effect.
Adding Bouncing Behavior
To add boundary checks to make the graphic "bounce" by reversing direction at the screen edges. This is managed using X_DIR and Y_DIR flags, which control movement direction based on position.
Here’s the code segment that handles bouncing behavior:
Experimenting with Non-Integer Increments
Final 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 for position and movement
define X_POS $20 ; X position of image
define Y_POS $21 ; Y position of image
define X_DIR $22 ; X direction (0 = right, 1 = left)
define Y_DIR $23 ; Y direction (0 = down, 1 = up)
; Set up the image data structure and initial position/directions
LDA #<G_O ; Pointer to image graphic
STA $10
LDA #>G_O
STA $11
LDA #$05
STA $12 ; Image width
STA $13 ; Image height
; Set initial position and movement direction
LDA #$08 ; Starting X position
STA X_POS
LDA #$08 ; Starting Y position
STA Y_POS
LDA #$00 ; Initial X direction (moving right)
STA X_DIR
STA Y_DIR ; Initial Y direction (moving down)
; Main loop for animation
MAIN_LOOP:
; Draw the image at the current position
LDX X_POS
LDY Y_POS
JSR DRAW_IMAGE ; Draw image at (X_POS, Y_POS)
; Delay to control speed
JSR DELAY
; Clear the current image
JSR CLEAR_IMAGE ; Clear image at (X_POS, Y_POS)
; Update the X position and direction
LDA X_DIR
BEQ MOVE_RIGHT
DEC X_POS ; Moving left
LDA X_POS
CMP #$00 ; Check left screen edge
BNE CHECK_Y_DIRECTION
LDA #$00 ; Switch to moving right
STA X_DIR
JMP CHECK_Y_DIRECTION
MOVE_RIGHT:
INC X_POS ; Moving right
LDA X_POS
CMP #27 ; Check right screen edge (assumes screen width)
BNE CHECK_Y_DIRECTION
LDA #$01 ; Switch to moving left
STA X_DIR
CHECK_Y_DIRECTION:
; Update the Y position and direction
LDA Y_DIR
BEQ MOVE_DOWN
DEC Y_POS ; Moving up
LDA Y_POS
CMP #$00 ; Check top screen edge
BNE MAIN_LOOP
LDA #$00 ; Switch to moving down
STA Y_DIR
JMP MAIN_LOOP
MOVE_DOWN:
INC Y_POS ; Moving down
LDA Y_POS
CMP #26 ; Check bottom screen edge (assumes screen height)
BNE MAIN_LOOP
LDA #$01 ; Switch to moving up
STA Y_DIR
JMP MAIN_LOOP ; Loop back to continue animation
; Delay subroutine to slow down movement
DELAY:
LDY #$00
LDX #$50
DELAY_LOOP:
DEY
BNE DELAY_LOOP
DEX
BNE DELAY_LOOP
RTS
; Subroutine to draw the image at the current position
DRAW_IMAGE:
LDA #<G_O ; Load pointer to graphic
STA $10
LDA #>G_O
STA $11
LDA #$05 ; Set width to 5
STA $12
STA $13 ; Set height to 5
LDA #$10 ; Zero-page address of structure
LDX X_POS ; X position
LDY Y_POS ; Y position
JSR DRAW ; Call DRAW subroutine to render
RTS
; Subroutine to clear the image at the current position
CLEAR_IMAGE:
LDA #<G_BLANK ; Load pointer to blank graphic
STA $10
LDA #>G_BLANK
STA $11
LDA #$05 ; Set width to 5
STA $12
STA $13 ; Set height to 5
LDA #$10 ; Zero-page address of structure
LDX X_POS ; X position
LDY Y_POS ; Y position
JSR DRAW ; Call DRAW subroutine to clear
RTS
; ==========================================
; DRAW :: Subroutine to draw an image on the screen
;
; 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
;
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 register values
STY SCRY
STX SCRX
; Load data structure pointer
TAY
LDA $0000,Y
STA IMGPTR
LDA $0001,Y
STA IMGPTRH
LDA $0002,Y
STA IMGWIDTH
LDA $0003,Y
STA IMGHEIGHT
; Calculate screen position ($0200 + X + Y * 32)
LDA #$00
STA SCRPTRH
LDA SCRY
STA SCRPTR
LDY #$05 ; Multiply Y by 32 (5 left shifts)
MULTIPLY:
ASL SCRPTR ; Perform 16-bit left shift
ROL SCRPTRH
DEY
BNE MULTIPLY
; Add X value
LDA SCRX
CLC
ADC SCRPTR
STA SCRPTR
LDA #$00
ADC SCRPTRH
STA SCRPTRH
; Add base address of screen ($0200)
LDA #$02
CLC
ADC SCRPTRH
STA SCRPTRH
; Copy row of image data to screen memory
COPY_ROW:
LDY #$00
ROW_LOOP:
LDA (IMGPTR),Y
STA (SCRPTR),Y
INY
CPY IMGWIDTH
BNE ROW_LOOP
; Advance to next row in image
LDA IMGWIDTH
CLC
ADC IMGPTR
STA IMGPTR
LDA #$00
ADC IMGPTRH
STA IMGPTRH
LDA #32
CLC
ADC SCRPTR
STA SCRPTR
LDA #$00
ADC SCRPTRH
STA SCRPTRH
; Decrement image height and repeat for next row
DEC IMGHEIGHT
BNE COPY_ROW
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 black square (blank image)
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,$00Challenges and Results
Challenge 1: Fractional Increments
Using scaled increments provided smoother movement but had some limitations. Due to the 6502 processor’s lack of floating-point support, fractional movements required integer adjustments, which caused minor jumps.
Challenge 2: Switching Graphics on Bounce
To make the animation more dynamic, I updated the code to switch the graphic image each time it bounced. This made the movement visually interesting, adding variety to the animation.
Conclusion
This lab provided a thorough introduction to working with 6502 assembly language, especially when dealing with low-level graphics. It taught me how to manipulate memory directly and control graphical output in a way that is efficient and constrained by the capabilities of early hardware.
By experimenting with different movement behaviors, such as bouncing and smooth motion, I gained a deeper understanding of assembly programming and how to work around its limitations. Overall, the lab was both challenging and educational, offering valuable insights into the world of low-level programming.
Comments
Post a Comment