Calculator in 6502

Our project is a basic calculator that does the following:

  • Clears the screen using a ROM routine.

  • Displays a title (“Calculator”).

  • Prompts the user to enter two numbers (each from 0 to 99).

  • Adds the two numbers together using Binary-Coded Decimal (BCD) arithmetic.

  • Displays the result as both text and custom 5×5 pixel images for each digit.

Let’s explore how this is done!


Setting Up: Symbols and Variables

At the top of the code, we define several helpful symbols (aliases for memory addresses) and variables. This makes the code easier to read and manage.

assemblyCopydefine SCINIT   $ff81 ; Initialize/Clear the screen
define CHRIN    $ffcf ; Get a character from the keyboard
define CHROUT   $ffd2 ; Output a character to the screen

Think of these definitions as shortcuts. Instead of remembering that $ff81 clears the screen, we simply use SCINIT.

We also define some zero-page variables (memory locations from $00 to $FF that the 6502 can access quickly):

assemblyCopydefine PRINT_PTR  $10   ; Pointer for printing text
define value      $14   ; Stores the number entered by the user
define XPOS       $20   ; Horizontal screen position for drawing
define YPOS       $21   ; Vertical screen position for drawing

These variables help us keep track of where our text and images should go on the screen.


Step 1: Preparing the Display

Before doing any math, our program sets up the display:

  1. Defining the Image Size:
    Our digits are drawn as 5×5 pixel images. We load the value 5 into the A register and store it in memory locations that define the image width and height.

     assemblyCopyLDA #$05
     STA $12       ; IMAGE WIDTH
     STA $13       ; IMAGE HEIGHT
    
  2. Setting the Starting Position:
    We set our drawing cursor to start at position (1,1) on the screen.

     assemblyCopyLDA #$01
     STA XPOS
     STA YPOS
    
  3. Clearing the Screen and Displaying the Title:
    We call SCINIT to clear the screen, wait for a key press with CHRIN, and then print the title “Calculator”.

     assemblyCopyjsr SCINIT        ; Clear the screen
     jsr CHRIN         ; Wait for a key press
     jsr PRINT
     dcb "C","a","l","c","u","l","a","t","o","r",00
    

    The dcb directive defines a series of bytes—in this case, characters that form our title. The 00 byte tells the printer routine where the text ends.


Step 2: Getting Input from the User

Prompting for Numbers

The program prints a prompt like this:

assemblyCopyjsr PRINT
dcb $0d,$0d,"E","n","t","e","r",32,"a",32,"n","u","m","b","e","r", ... ,00
  • $0d is the carriage return (a new line).

  • 32 represents a space.

  • The text is ended with a 00 byte.

Reading the Number

After printing the prompt, the program calls the GETNUM subroutine, which:

  • Waits for you to type digits.

  • Only accepts characters between '0' and '9'.

  • Allows you to use Backspace to correct mistakes.

  • Converts your input into a two-digit Binary-Coded Decimal (BCD) number.

The number is then stored in a memory location (like value), ready for the addition.


Step 3: Doing the Math

Now that we have two numbers, the next step is to add them together:

assemblyCopysed        ; Set the Decimal flag to use BCD arithmetic
clc        ; Clear the carry flag
adc value  ; Add the value from memory to the number in the A register
cld        ; Clear the Decimal flag (back to normal binary arithmetic)
sta value  ; Store the result in "value"
bcc result ; If there’s no carry, jump to result
inc value_h; If there is a carry (e.g., result is 100 or more), increment the high-digit
  • BCD Arithmetic:
    The SED instruction tells the processor to treat numbers as BCD, which is perfect for our decimal-based calculator.

  • Carry Handling:
    If the sum exceeds 99, the program adjusts by incrementing a high-digit variable.


Step 4: Displaying the Result

The program prints the result both as text and by drawing custom digit images on the screen:

  1. Printing as Text:
    The digits are converted into their ASCII equivalents (for example, converting the number 3 into the character '3') and output using the CHROUT routine.

  2. Drawing the Digit Images:
    After printing, the program calls update_value to set a pointer to the correct 5×5 image for that digit. It then uses the DRAW subroutine to copy the image into the video memory.

     assemblyCopy; Convert the digit to ASCII and print it
     lda value
     and #$0f
     ora #$30
     jsr CHROUT
    
     ; Update image pointer and draw the digit image
     jsr update_value
     LDA #$10  ; Address of the image pointer in zero-page
     LDX XPOS  ; X position on screen
     LDY YPOS  ; Y position on screen
     JSR DRAW  ; Call the DRAW routine to render the image
    

    After drawing each digit, the X position is incremented so that the next digit appears to the right.


Step 5: Understanding the DRAW Subroutine

The DRAW subroutine is like a little helper function that takes an image and places it on the screen. Here’s what happens inside DRAW:

  1. Saving the Cursor Position:
    The current X and Y coordinates are saved so that we know where to place the image.

  2. Reading the Image Data:
    The subroutine loads the image pointer, width, and height from the data structure in memory.

  3. Calculating the Screen Memory Address:
    The screen on many 6502-based systems starts at address $0200. The code calculates the correct address by taking the X and Y coordinates into account (remember, each row on the screen is 32 bytes long).

  4. Copying the Image:
    A loop copies each row of the 5×5 image into the calculated screen memory address, making the image appear on your display.


Screenshots
Handling a two digit addition and displaying

Handling a two digit addition with a three digit result

Github Link

Final Thoughts

At first, this simple calculator program felt overwhelming, but I found that breaking things down made a huge difference. Starting small—just printing text and then adding a key press routine—helped me build confidence. Understanding basic instructions like LDA (load), STA (store), JSR (jump to subroutine), and RTS (return from subroutine) proved essential, as they’re the building blocks of any program. The real fun began when I started experimenting—changing prompts, tweaking image data, and adding new features.