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,$00
Challenges 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