Lab 2 - Lab Results

Introduction

In this blog post, I will share my experience working on a lab where we animated a small 5x5 graphic to move diagonally across the screen and bounce off the edges. The lab required an understanding of low-level memory manipulation, conditional logic, and efficient screen rendering. I'll walk you through the code, the changes made to implement bouncing logic, and my reflections on the learning process.

**Since we collaborated as a group on optimizing the code, my work may resemble that of my teammates

===================================================================

Initial Code

The following code moves a 5×5 graphic diagonally across the screen:

;
; 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

Code Implementation and Explanation

Initially, our code moved the graphic diagonally across the screen in a straight path without bouncing. The challenge was to modify it so that the image reverses direction when it reaches the screen's boundaries.

Here’s an overview of the key changes:

Introducing Movement Variables:

We defined XINC and YINC to hold the values +1 or -1, determining the movement direction.

Code example:
define XINC    $22      ; X increment (-1 or +1)
define YINC    $23      ; Y increment (-1 or +1)

Updating Position:

Instead of just incrementing XPOS and YPOS, we modify them using XINC and YINC.

Code example:
LDA XINC
CMP #$01
BEQ INC_XPOS
LDA XPOS
SEC
SBC #$01
STA XPOS
JMP CHECK_X_BOUNDARY

INC_XPOS:
INC XPOS

Checking for Screen Boundaries:

If the graphic reaches the left/right edges, we reverse XINC. If the graphic reaches the top/bottom edges, we reverse YINC. The same logic applies to YPOS with a check against the screen height.

Code example:
CHECK_X_BOUNDARY:
LDA XPOS
CMP #$1B   ; Max XPOS = 27 (32 - 5)
BCC NO_CHANGE_X
LDA #$FF   ; Reverse XINC to -1
STA XINC
JMP UPDATE_Y

NO_CHANGE_X:
LDA XPOS
CMP #$00   ; If XPOS <= 0
BNE UPDATE_Y
LDA #$01   ; Reverse XINC to +1
STA XINC

Drawing and Clearing the Image:

We first draw the image at the new position. A short delay ensures the motion is visible. We then draw a blank image to clear the old position before updating movement.

Code example:
LDA #<G_O
STA $10
LDA #>G_O
STA $11
LDA #$10      ; Address in zeropage of data structure
LDX XPOS      ; X position
LDY YPOS      ; Y position
JSR DRAW      ; Draw image



Final version of code:
;
; 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    $22      
define YINC    $23      

START:

    
    LDA #$05
    STA $12      
    STA $13       

    
    LDA #$0A      
    STA XPOS
    LDA #$05      
    STA YPOS

    
    LDA #$01     
    STA XINC
    STA YINC

MAINLOOP:

   

    LDA #<G_O
    STA $10
    LDA #>G_O
    STA $11

    
    LDA #$10      
    LDX XPOS      
    LDY YPOS      
    JSR DRAW      
    
    LDY #$00
    LDX #$50
DELAY:
    DEY
    BNE DELAY
    DEX
    BNE DELAY

    
    LDA #<G_BLANK
    STA $10
    LDA #>G_BLANK
    STA $11

   
    LDA #$10      
    LDX XPOS
    LDY YPOS
    JSR DRAW

    
    LDA XINC
    CMP #$01
    BEQ INC_XPOS
    
    LDA XPOS
    SEC
    SBC #$01
    STA XPOS
    JMP CHECK_X_BOUNDARY

INC_XPOS:
    
    INC XPOS

CHECK_X_BOUNDARY:
    
    LDA XPOS
    CMP #$1B
    BCC NO_CHANGE_X
    
    LDA #$FF
    STA XINC
    JMP UPDATE_Y

NO_CHANGE_X:
    
    LDA XPOS
    CMP #$00
    BNE UPDATE_Y
    
    LDA #$01
    STA XINC

UPDATE_Y:
    
    LDA YINC
    CMP #$01
    BEQ INC_YPOS
    
    LDA YPOS
    SEC
    SBC #$01
    STA YPOS
    JMP CHECK_Y_BOUNDARY

INC_YPOS:
    
    INC YPOS

CHECK_Y_BOUNDARY:
    
    LDA YPOS
    CMP #$1B
    BCC NO_CHANGE_Y
    
    LDA #$FF
    STA YINC
    JMP MAINLOOP

NO_CHANGE_Y:
    
    LDA YPOS
    CMP #$00
    BNE MAINLOOP
    
    LDA #$01
    STA YINC

    JMP MAINLOOP

; ==========================================


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:
    
    STY SCRY
    STX SCRX

    
    TAY
    LDA $0000,Y
    STA IMGPTR
    LDA $0001,Y
    STA IMGPTRH
    LDA $0002,Y
    STA IMGWIDTH
    LDA $0003,Y
    STA IMGHEIGHT

    
    LDA #$00
    STA SCRPTRH
    LDA SCRY
    STA SCRPTR
    
    LDY #$05     
MULT:
    ASL SCRPTR   
    ROL SCRPTRH
    DEY
    BNE MULT

   
    LDA SCRX
    CLC
    ADC SCRPTR
    STA SCRPTR
    LDA #$00
    ADC SCRPTRH
    STA SCRPTRH

    
    LDA #$02
    CLC
    ADC SCRPTRH
    STA SCRPTRH
    
COPYROW:
    LDY #$00
ROWLOOP:
    LDA (IMGPTR),Y
    STA (SCRPTR),Y
    INY
    CPY IMGWIDTH
    BNE ROWLOOP

    
    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

    
    DEC IMGHEIGHT
    BNE COPYROW

    RTS

; ==========================================


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


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


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

Please feel free to test it on 6502 assembler/simulator :)



==================================================================================

Challenge Section Results

One of the challenges involved tweaking the delay loop to adjust the animation speed. A longer delay made movement smoother, while a shorter delay made it appear too fast or flickery. The balance was achieved with:

LDY #$00
LDX #$50
DELAY:
    DEY
    BNE DELAY
    DEX
    BNE DELAY

Additionally, another challenge was modifying the image to have different starting values for XPOS and YPOS instead of (0,0), making it more visually dynamic. Conclusion

Through working on this lab, I gained a deeper understanding of advanced concepts like subroutines, loops, and jumps. I faced some difficulty grasping decimal values, as they are represented in hex in the 6502 system. While adding and subtracting values are straightforward in decimals, it took me some time to figure out how to implement them using memory addresses and hex notation.

Overall, the 6502 Math Lab has helped me think more in the 6502 language. While concepts like if/else, loops, and functions are easy to implement in languages I’m familiar with, such as JavaScript, it was fascinating to see how these logic structures are handled in a low-level language!

Comments

Popular posts from this blog

Project Stage 1

Lab 5 - Part 1 - Aarch 64