Week 3 - Math, Flow Control & Lab 2

Week 3 - Math, Flow Control & Lab 2

·

23 min read

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 JSR

  • RTS. - 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.

  1. 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.

  2. 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

  1. 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
  1. 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