Some of you may have read the title and said ew, math (I kind of do this at times). Jokes aside, we can agree that math is a fundamental concept of programming. In fact, for assembly language I believe it does require a much more logical understanding as you need to get into the details of memory location and handling.
In this article, I will go into details on two major topics, math and flow control. I will also conclude with a fun and exciting lab, so please grab a coffee or drink, sit down, relax, and get ready to learn!
Math in the 6502 processor
Because the 6502 is a simple processor with limited instructions compared to the higher end ones we have today, the math is quite simple and limited.
The 6502 processor can perform math in binary or decimal mode (Chris Tyler - 6502 Math)
What is binary mode?
When I say the 6502 can perform math in binary mode, it means that mathematical operations are performed using 8-bit values. Let’s break this down further. In last week’s article, I talked about binary numbers, which are made up of 0s and 1s and represent ON/OFF states. Similarly, the 6502 processor works with numbers in this binary form.
For example, if you want to add 1 + 2
, it’s simple for us—we know the answer is 3
. But for the 6502, it sees the numbers differently. The number 1 is represented in binary as 01
, and 2 is represented as 10
. The 6502 can take these two binary values and perform the addition to get 11
, which is 3 in binary.
So, in binary mode, the 6502 is essentially doing math by flipping switches that are either ON (1) or OFF (0), just like we learned before!
What is decimal mode?
Decimal mode is referring to the system we use as humans where we count from 0-9
(Base 10). In the most simple way to explain this, think of the 6502 processor being able to switch to human math. It can take a mathematical operation like 3 + 4
and output 7
. This is built into the processor.
6502 Mathematical Instructions
The 6502 processor has a few instructions that allow us to perform mathematical operations.
ADC - Addition
ADC stands for add with carry and it does the following: it takes the value in the accumulator, the value you specify after ADC, and the value in the carry flag.
Value in accumulator + value you specify + carry flag
Example:
; Lets say you wanted to add two values together
; Lets add 5 and 10
; Instantiate 5 in accumulator
LDA #$05
ADC #$A
; Store it in memory address $01
STA $01
; If you look at memory address $01 ($0001), you will see 0F which is 15
SBC - Subtraction
Subtraction is very similar to the addition instruction. It utilizes the instruction SBC
. Again, the way it works is as follows: Value in accumulator - the value you specify - inverse of carry flag
Here’s another example:
; Lets say you wanted to subtract two values
; Lets subtract 20 and 4 to make 16
; Set the carry flag before subtraction
SEC
; Instantiate 20 in accumulator
LDA #$14
; Use SDC
SBC #$04
; Store result at memory location $02
STA $02
Division & Multiplication
There is no specific division and multiplication instruction in the 6502 processor. However, there are ways to do each.
For Division, we can use an instruction called LSR - Logical Shift Right
. This instruction does division by two. I have a very simple example below:
; Lets divide 100 by 2
; Load 100 into accumulator
LDA #$64
; Use LSR to divide into half (100/2)
LSR
; Now the value inside the accumulator will be 32 which is 50 in decimal
Likewise, for multiplication, we can use the instruction ASL - Arithmetic Shift Left
. This performs multiplication by two as well.
; Lets multiply 2 with 5
; Instantiate accumulator with 5
LDA #$05
; Use ASL to double it (ASL performs a shift which is equivalent to double this number)
ASL
; Now the value inside the accumulator will be 0a which is 10 in decimal
Loops, conditions, functions in 6502
If you are familiar with the fundamentals of programming or have programmed before, you have either used or heard of loops, conditions, and functions. In this section I will discuss each of these concepts in relation to the 6502 processor and provide easy examples so you can quickly grasp the concept.
Please note that before I go into these three topics, it is important to note that each of these concepts allow your program to move to a different code location in memory essentially.
There are three ways you can move your program to a different location in the 6502 processor: jumps, branches, and procedures.
Jump
A jump is a way of telling your program to move to a different location and execute the instruction there. To use this, we simply do the following:
JMP $00
→ $00 is the address we want it to jump to
Branches
Branches access the conditional state of a processor flag. These branches simply check if a certain condition is met and if so can be used to modify the sequence of a program. Think of branches as conditionals in regular programming.
Here’s a really simple example of one of the branch flags using BNE - Branch on not equal. Note: BNE simply jumps if the zero flag is OFF, meaning any calculation that occurred before, if it is not zero which would make the zero flag OFF, the loop will still continue.
I’ll provide an example below comparing regular programming to assembly programming in a way where you can understand:
; I will create a loop that will loop 3 times in both assembly and C++
// C++
int x = 3;
for (int i = 0; i < x; i++) {
// Do something
}
; Assembly
; Load the x register with 3
LDX #$03
LOOP: ; Create the loop
DEX ; Decrement X
BNE Loop ; Jump back to loop if zero flag is not ON
; Code continues here after zero flag is one
Procedures/Subroutines/Functions
We know functions perform a certain action and allow us to create more modular code by forming reusable components. For the 6502 processor, we can create functions which are also called procedures
So how do we create functions? Theres no main instruction, you simply put the name of the function followed by a colon. NameOfFunction:
. Also there are two important instructions to know when working with procedures.
JSR
- Jump to subroutine → Means jump to the subroutine specified after JSRRTS
. - Return from subroutine → Means finish executing the block and go back to the line after JSR
Example:
; An easy example discussing what we learned so far
Main: ; Lets call our main program main
JSR LoadAccumulatorWith2 ; Goes to the part of our program where the function is
JMP Main ; Jumps back to main and loops infinitely
LoadAccumulatorWith2:
LDA #$02;
RTS
Now that you have a much better understanding (I hope) of the math and flow controls involved in the 6502 processor, let’s go over Lab 2 which is a fun and exciting graphics one.
Lab 2
The code is below and as always, please follow along and test it out using the 6502 emulator here.
;
; 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
There are a few questions for this lab I will answer to demonstrate how the program changes and to provide you with a more depth understanding of what’s really going on.
Select a starting location for the graphic where X and Y have different values
; Inside the code, there are three lines inside the START subroutine that ; specifies the X and Y position of the shape ; ... code from before 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 <----- THIS SECTION BELOW LDA #$02 STA XPOS LDA #$04 STA YPOS
If you look at both LDA instructions in the last few lines, I loaded the accumulator with 2 and 4 and stored each into XPOS and YPOS. This allows me to start the graphic at a different location.
Select an X increment that is -1 or +1, and a Y increment that is -1 or +1. You can choose to use either a signed byte or some other representation to hold these values.
- For this question, it was a bit confusing as I wasn’t sure what it really wanted me to do. However, I understand it by saying that I should create an X and Y increment and use it to change the value of another value by 1 or - 1.
; To get started, I need to first create both of these decrements ( I will decrement)
; Zero-page variables
define XPOS $20
define YPOS $21
define XDEC $15
define YDEC $16
START:
; Now we have to add some values to each variable I created above
LDA #$01
STA XDEX
STA YDEC
; 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 #$02
STA XPOS
LDA #$04
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
; ------------------------------------ CODE THAT USES MY XDEC & YDEC!
; Decrement the position
LDA XDEC
CLC
SEC
SBC XPOS
STA XPOS
LDA YDEC
CLC
SEC
SBC YPOS
STA 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
If you run the code above, you will simply see a flashing shape and that was done on purpose. These values were created to show you how easy it is to manipulate the positioning of the shape upon each delay. So because the XPOS
and YPOS
are being decremented with XDEC
and YDEC
, the shape simply won’t move because the value doesn’t change. If I wanted to change it, I can simply use ADC
instead of SBC
as I used above. By using ADC, it will increment just fine and move diagonally. I will do this in the next question
- Successively move the graphic by adding the X and Y increments to the graphic's X and Y position.
; The method is essentially the same as above but ill create two variables for incrementing
; Zero-page variables
define XPOS $20
define YPOS $21
; NEW VARIABLES ------------------------------
define XINCREMENT $15
define YINCREMENT $16
START:
; Now we have to add some values to each variable I created above
LDA #$01
STA XINCREMENT
STA YINCREMENT
; 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 #$02
STA XPOS
LDA #$04
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
; ------------------------------------ CODE THAT USES MY XDEC & YDEC!
; INCREMENT
LDA XPOS
CLC
ADC XINCREMENT
STA XPOS
LDA YPOS
CLC
ADC YINCREMENT
STA 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
Make the graphic bounce when it hits the edge of the bitmapped screen, both vertically (when it hits the top/bottom) and horizontally (when it hits the left/right edge)
- This part of the program took me the longest but I learned quite a bit. Now before I go into the code I want to start off by discussing my steps along the way. When I first started I realized that if I change the position of the XPOS and YPOS to the end and instead of incrementing these, I decrement. It goes in reverse. So the basic logic is that I have to define a variable that holds the end value of the screen, and if it reaches this value, simply reverse it. I did this however, it bounced back once and then incremented again. Therefore, I realized that I also need another flag for the direction and based on that flag I can set the direction if XPOS reaches $#00 or #$19 (end value). In conclusion, I added some jumps and new procedures that looked at whether it hits the end of the screen or is at the beginning. This allowed me to finally come up with a solution :)
;
; 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
; Variable to store value at end of screen
define ENDVALUE $22
; Variable for the direction
define DIRECTION $15
START:
; Initialize the direction
LDA #$00
STA DIRECTION
; Store value 25 to ENDVALUE
LDA #$19
STA ENDVALUE
; 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
; Add logic here to either increment or decrement
LDA DIRECTION
BEQ INCREMENT
DEC XPOS
DEC YPOS
JMP CHECK_IF_DIRECTION_CHANGED
; Increment the position
INCREMENT:
INC XPOS
INC YPOS
CHECK_IF_DIRECTION_CHANGED:
LDA ENDVALUE
CMP XPOS
BNE CHECK_IF_SHAPE_AT_START
LDA #$01
STA DIRECTION
JMP CONTINUE_PROGRAM
CHECK_IF_SHAPE_AT_START:
LDA XPOS
CMP #$00
BNE CONTINUE_PROGRAM
LDA #$00
STA DIRECTION
CONTINUE_PROGRAM:
; 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,$00My
Experiences
In all honestly, I did get quite frustrated when working on this lab because at first I found it quite difficult. The portion I found difficult was making the graphic bounce back. That part took me the longest. However, the reality is you only learn when you really try and put yourself in hard situations.
In this lab I am happy to say I have a better understanding of 6502 programming. I used a variety of concepts that were taught in lecture by Professor Chris Tyler. I created procedures/subroutines and utilized jumps. I also created variables and then used CMP to execute certain subroutines.
Overall I learned a lot in this lab and glad I took the time to break down the code and really understand it. Just a pro tip for programmers out there that helped me was I took a step back, thought about what I wanted to accomplish, think how I would do it in Java/C++ and then implemented it in assembly code.
References
Cover Image by Gerd Altmann from Pixabay
SPO600 Wiki & Lecture content by Chris Tyler