Welcome to My 6502 Programming Journey
Explore my labs and projects.
Lab: Introduction to Mob Programming
In this lab, we want to introduce Mob Programming. It’s another form of Pair Programming. It means a group of people working on a single piece of code collaboratively. The task was to modify the 6502 Assembly Program that moves a 5x5 graphic diagonally around the screen. We assemble and then run the code on http://6502.cdot.systems. The goal for this lab is to make it bounce off the edges and also challenge it by changing the x and y to make it move differently.
1. Setup
We start by opening http://6502.cdot.systems and pasting the following code:
; draw-image-subroutine.6502
; Moves a 5x5 graphic 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
MAINLOOP:
; Set pointer to the image
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 $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 (omitted for brevity)
; 5x5 pixel images (omitted for brevity)
2. Testing the Code
Now we need to click on the assembly button and run. We also have the option to change the speed from the top part. The Speed button controls the execution speed of the emulator. Basically, it adjusts how fast the program runs.
3. Different Values for X and Y
By modifying the following piece of code:
; Set initial position X=10, Y=5
LDA #10
STA XPOS
LDA #5
STA YPOS
Now with this change, the graphic will start from (10, 5) and move diagonally as the X and Y values are incremented in the MAINLOOP, same as before.
4. Setting Movement and Direction with Increment Values
To implement an X increment and Y increment that can be -1 or +1, we use single bytes to represent them. For example:
- +1 as
$01
- -1 as
$FF
We can define two new zero-page variables to hold the X and Y increments:
; Zero-page variables
define XPOS $20 ; Current X position
define YPOS $21 ; Current Y position
define XINC $22 ; X increment (can be $01 or $FF)
define YINC $23 ; Y increment (can be $01 or $FF)
Then we can update the code to set these increments at the start of the program and use them to change the XPOS and YPOS in the MAINLOOP.
; Set initial increments (XINC = +1, YINC = -1)
LDA #$01 ; +1
STA XINC
LDA #$FF ; -1
STA YINC
If we test the code now, we don’t see any difference. However, by updating the position using the increment right after drawing the blank graphic to clear the old image and before continuing for 29 frames of animation, using the following instructions:
LDA XPOS
CLC
ADC XINC
STA XPOS
LDA YPOS
CLC
ADC YINC
STA YPOS
The ADD with carry instruction handles the signed arithmetic.
- If
XINC = $01
,XPOS
increases by 1. - If
XINC = $FF
,XPOS
decreases by 1.
We can always customize the movement by changing the value of XINC
and YINC
to create different movement patterns. Now, the starting position and direction of the movement have been changed as follows:
- If
XINC = $01
andYINC = $FF
:- The graphic will move right and up.
- If
XINC = $FF
andYINC = $01
:- The graphic will move left and down.
- If
XINC = $01
andYINC = $01
:- The graphic will move right and down.
- If
XINC = $FF
andYINC = $FF
:- The graphic will move left and up.
Example of Movement
If XINC = $01
and YINC = $FF
, the graphic will move as follows:
Frame | XPOS | YPOS | Direction |
---|---|---|---|
1 | 10 | 5 | Start at (10, 5) |
2 | 11 | 4 | Right and up |
3 | 12 | 3 | Right and up |
4 | 13 | 2 | Right and up |
5. Graphic Bounce
To be honest, I couldn’t figure out how to make the graphic bounce, and I was still working on it to make it happen. However, my teammate from week 3, Kaung Khant Zaw, did it. I’ll break it down into steps so you and I can understand what’s happening:
Zero-page Variables for Position and Direction
The XPOS
and YPOS
store the current position, but we need to define XINC
and YINC
to make it easy to access in the 6502 CPU.
Question: What are
XINC
andYINC
?They store the direction of the movements.
XINC = 1
moves rightXINC = -1
or#FF
moves leftYINC = 1
moves downYINC = -1
moves up
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)
Initialization in Start
Question: What are the differences between
XPOS
,YPOS
andXINC
,YINC
?
Key Difference | Snippet | What It Controls | Example Values |
---|---|---|---|
STA XPOS, STA YPOS | Initial Position | Where it starts | XPOS = 0, YPOS = 0 (Top-left) |
STA XINC, STA YINC | Movement Direction | Where it moves | XINC = 1, YINC = 1 (Moves right & down) |
MAINLOOP Logic
-
Updating the X Position:
; 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
-
Checking the X Boundaries:
CHECK_X_BOUNDARY: LDA XPOS CMP #$1B ; 27 (screen width - image width) 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
-
Updating the Y Position:
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
-
Checking the Y Boundaries:
CHECK_Y_BOUNDARY: 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
By repeating the logic with the JMP MAINLOOP
instruction, the graphic moves diagonally and bounces off the edges.
Full Code Example
Here is the full code. You can give it a try:
;
; 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 high byte of LABEL
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 $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
6. Result
As we can see, we finally figured out step by step how to make the bouncing 5×5 graphic move diagonally. I see this as a big achievement. Although I didn’t do it all on my own, that’s what mob programming is all about—teamwork, right? This lab basically taught us how to manage memory efficiently by using zero-page variables and how to handle basic graphics using assembly programming and loops.