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:

; Bouncing behavior for X and Y directions

; Check X boundaries
LDA X_POS
CMP #$00               ; Check left screen edge
BEQ CHANGE_X_DIR_RIGHT
CMP #27                ; Check right screen edge
BEQ CHANGE_X_DIR_LEFT

; Check Y boundaries
LDA Y_POS
CMP #$00               ; Check top screen edge
BEQ CHANGE_Y_DIR_DOWN
CMP #26                ; Check bottom screen edge
BEQ CHANGE_Y_DIR_UP

Experimenting with Non-Integer Increments

To create smoother movement, I experimented with simulating fractional increments by using scaled integer values. Since the 6502 lacks floating-point support, this approach provided smoother movement but with slight jumps due to the limitations of integer-only arithmetic.

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

Popular posts from this blog

Project Stage 2

Lab1

Project Stage 1