For this lab, we are going to take an existing code and make changes to it using Math in 6502! After last week's lab, I've understood the fundamentals of this Assembly language, and now we are using the Math instructions available to use to implement a small program.
If you are following along, please use the 6502 Emulator to test these codes out!
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
When you assemble and run this code, it will generate either an 'X' or an 'O' (depending on the variable used) which will move across the screen diagonally.
One thing to note here is that when the image reaches the end of its path, it will regenerate at the starting position and loop again.
Now the change we want to do is to make sure it bounces on every tap of the corner. The changes are highlighted in the next section.
Bouncing Graphic
1. New Variables!
We will create two new variables; they will store the current direction of the graphic along the X and Y axes.
The directional values will be either +1 ($01
) or -1 ($FF
).
define XINC $22 ; X increment (-1 or +1)
define YINC $23 ; Y increment (-1 or +1)
2. Change Initial Positions
The current starting position is in the top left corner. We will change it to ensure that the image starts from somewhere that is not touching the corner.
While we're at it, we will also initialize the XINC and YINC variables that we defined. Both of them are first initialized to +1 ($01
)
; Set Initial Position
LDA #$0A ; 10 in decimal
STA XPOS
LDA #$05 ; 5 in decimal
STA YPOS
; Initialize increments: XINC = +1, YINC = +1
LDA #$01 ; +1 represented as $01
STA XINC
STA YINC
3. Update Positions Based on Increments
Let's start with just looking at the X axis. We will determine if the XPOS
(the X position of the image) should increment or decrement (move right or left) based on the XINC being +1 or -1.
We do this by branching to INC_XPOS
if equal to +1. If not, it will just decrement.
Logically, the next step after moving the image is to check whether it is hitting a boundary or not. We jump to CHECK_X_BOUNDARY
at the end of this block.
; Update position based on increments
; Check X direction
LDA XINC
CMP #$01
BEQ INC_XPOS
; If XINC = $FF (-1), decrement XPOS
LDA XPOS
SEC
SBC #$01
STA XPOS
JMP CHECK_X_BOUNDARY
INC_XPOS:
; If XINC = +1, increment XPOS
INC XPOS
4. Boundary Checking for X Axis
Both right edge and left edge will be checked. Basically if XPOS
is equal or greater than 27 ($1B
), move left. The value is 27 because the screen is 32 pixels wide and the image is 5 pixels. (32 - 5). If it's less, move right.
CHECK_X_BOUNDARY:
; Check if XPOS has hit screen boundaries
; Screen width is 32 pixels and image width is 5
; Max XPOS = 32 - 5 = 27 ($1B)
LDA XPOS
CMP #$1B
BCC NO_CHANGE_X
; If XPOS >= 27, reverse XINC to -1
LDA #$FF
STA XINC
JMP UPDATE_Y
NO_CHANGE_X:
; Check if XPOS <= 0
LDA XPOS
CMP #$00
BNE UPDATE_Y
; If XPOS <= 0, reverse XINC to +1
LDA #$01
STA XINC
5. Repeat 3 and 4 for Y axis
We will repeat the above two steps for the Y-axis as well.
⭐ Final Code
This brings us to the finalized code. If you run this code on the Emulator,
;
; 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 ; X increment (-1 or +1)
define YINC $23 ; 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 #$05 ; 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 position based on increments
; Check X direction
LDA XINC
CMP #$01
BEQ INC_XPOS
; If XINC = $FF (-1), decrement XPOS
LDA XPOS
SEC
SBC #$01
STA XPOS
JMP CHECK_X_BOUNDARY
INC_XPOS:
; If XINC = +1, increment XPOS
INC XPOS
CHECK_X_BOUNDARY:
; Check if XPOS has hit screen boundaries
; Screen width is 32 pixels and image width is 5
; Max XPOS = 32 - 5 = 27 ($1B)
LDA XPOS
CMP #$1B
BCC NO_CHANGE_X
; If XPOS >= 27, reverse XINC to -1
LDA #$FF
STA XINC
JMP UPDATE_Y
NO_CHANGE_X:
; Check if XPOS <= 0
LDA XPOS
CMP #$00
BNE UPDATE_Y
; If XPOS <= 0, reverse XINC to +1
LDA #$01
STA XINC
UPDATE_Y:
; Check Y direction
LDA YINC
CMP #$01
BEQ INC_YPOS
; If YINC = $FF (-1), decrement YPOS
LDA YPOS
SEC
SBC #$01
STA YPOS
JMP CHECK_Y_BOUNDARY
INC_YPOS:
; If YINC = +1, increment YPOS
INC YPOS
CHECK_Y_BOUNDARY:
; Check if YPOS has hit screen boundaries
LDA YPOS
CMP #$1B
BCC NO_CHANGE_Y
; If YPOS >= 27, reverse YINC to -1
LDA #$FF
STA YINC
JMP MAINLOOP
NO_CHANGE_Y:
; Check if YPOS <= 0
LDA YPOS
CMP #$00
BNE MAINLOOP
; If YPOS <= 0, reverse YINC to +1
LDA #$01
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
Conclusion
I learned on more advanced instructions such as subroutines, loops and jumps working on this lab. I did have trouble wrapping my mind around decimal values which are represented as hex in the 6502 system. Adding and subtracting values are clear in decimals but I had to take some time to actually implement them using memory addresses and hex notation.
All in all, 6502 Math Lab has taught me more to think in the 6502 language. Logics such as if/else, loops and functions are very easy to implement in languages I know such as JavaScript, but it is interesting to see how a low level language can implement these logics!
Thank you so much for reading! 😄
Top comments (0)