10 PRINT for the KIM-1
By Michael Doornbos
- 7 minutes read - 1407 words
PAL-II
It’s time to explore the PAL-II, a modern clone of the classic KIM-1 6502 microcomputer from the mid-1970s. Original KIM-1 units are now rare and costly, making a clone like the PAL-II ideal for hands-on use without risking damage to a vintage system.
As one of the first single-board computers, the KIM-1 remains popular among retro computing enthusiasts. Its distinctive 7-segment display enables creative visual effects, and it serves as an excellent platform for learning assembly language and low-level programming.
We’ve previously explored the KIM-1 clone in projects like:
- Almost primes with TinyBASIC on the KIM-1 clone: PAL-1
- How fast can a 6502 transfer memory
- A little more speed from the 6502
If purchasing a PAL-II isn’t an option, try the KIM-1 simulator for a reliable virtual experience.
10 PRINT for the KIM-1
We’re going to contunue my side quest of trying to do 10 PRINT on every platform I can get my hands on. (10PRINT on all the things!)
The easy way
The KIM-1 will run Microsoft BASIC, and you can easily do 10 PRINT in BASIC as we have many times before. It takes about 10 minutes to load the BASIC interpreter from paper tape format over a 1200 baud connection, so we’ll just skip to it running the program.
10 PRINT CHR$(47+INT(RND(0)*2)*45);:GOTO 10
This is the same code that will run 10 PRINT on many of the non Commodore platforms. It uses the same logic as the original 10 PRINT, but instead of using the characters 206 ad 206 for “/” and “\”, it uses the ASCII codes for those characters.
The hard way - Assembly and the 7-segment display
The 7 segment display presents a unique challenge, because it’s only one line, and you can’t easily make our 10 PRINT pattern with characters “/ and \”.
So, we’ll have to get a little creative.
When setting the bits to light up the segments, we can use the following mapping:
---0---
| |
5 1
| |
---6---
| |
4 2
| |
---3---
The segments are controlled by setting the bits like this:
; 0 = 0b00000001
; 1 = 0b00000010
; 2 = 0b00000100
; 3 = 0b00001000
; 4 = 0b00010000
; 5 = 0b00100000
; 6 = 0b01000000
The closest we can get to a backslash is to light up segments 1, 6, and 4 and for the forward slash is to light up segments 5, 6, and 2.
; BACKSLASH = 0b01010010 (0x52)
; FORWARD SLASH = 0b01100100 (0x64)
The code below will generate a random pattern of slashes and backslashes and display it on the KIM-1’s 7-segment display. The pattern scrolls to the left, creating a continuous effect.
; 10PRINT FOR KIM-1 USING SCROLLING DISPLAY TECHNIQUE
; BY MICHAEL DOORNBOS <MIKE@IMAPENGUIN.COM> 2025
; SOFT START AT $0200
; THIS PROGRAM GENERATES A RANDOM PATTERN OF SLASHES AND BACKSLASHES
; AND DISPLAYS IT ON THE KIM-1'S 7-SEGMENT DISPLAY.
; THE PATTERN SCROLLS TO THE LEFT, CREATING A CONTINUOUS EFFECT.
; A LOT OF THIS CODE IS BORROWED FROM:
; https://netzherpes.de/blog/index.php?entry=KIM-1-scrolltext
; kim_msg.asm
; testing lin2c64 6510 assembler
; using J. Butterfield's scan display from Wumpus
; 01/03/2013 ces
; CONSTANTS FOR 7-SEGMENT DISPLAY CHARACTERS
BACKSLASH .EQU $64 ; BACKSLASH CHARACTER
SLASH .EQU $52 ; FORWARD SLASH CHARACTER
SPC .EQU $80 ; SPACE CHARACTER
; KIM-1 HARDWARE ADDRESSES
SAD .EQU $1740 ; DATA PORT FOR PINS 1-4
SADD .EQU $1741 ; DATA DIRECTION REGISTER A
SBD .EQU $1742 ; DATA PORT FOR PINS 5-6
SBDD .EQU $1743 ; DATA DIRECTION REGISTER B
TIMER2 .EQU $1747 ; OPTIONAL 2ND 6532 TIMER
LOUT .EQU $7F ; SET PINS AS OUTPUT TO LEFT 4 LEDS
ROUT .EQU $1E ; SET PINS AS OUTPUT TO RIGHT 2 LEDS
; ZERO PAGE VARIABLES
SEED .EQU $00D0 ; RANDOM SEED LOCATION
TMR .EQU $00DB ; TIMER COUNTER
PTR .EQU $00DC ; POINTER
XFRHI .EQU $00DD ; USED FOR CHARACTER BUFFER HIGH BYTE
XFRLO .EQU $00DE ; USED FOR CHARACTER BUFFER LOW BYTE
TMP1 .EQU $00DF ; TEMPORARY STORAGE
CBUFF .EQU $00E8 ; CHARACTER BUFFER (6 BYTES)
MSGBUF .EQU $0180 ; BUFFER FOR GENERATED PATTERNS (30 BYTES)
.ORG $0200 ; START OF PROGRAM CODE
MAIN
; CLEAR THE MESSAGE BUFFER FIRST TO PREVENT GLITCHES
LDX #$00
CLRLOOP LDA #SPC ; USE SPACE CHARACTER TO INITIALIZE
STA MSGBUF,X
INX
CPX #$30 ; CLEAR THE ENTIRE BUFFER AREA
BNE CLRLOOP
LDA #$00 ; ADD NULL TERMINATOR AT THE END
STA MSGBUF+23
; INITIALIZE THE TIMER
LDA #$FF ; LOAD MAXIMUM VALUE
STA TIMER2 ; START TIMER
; USE TIMER VALUE AS SEED
LDA TIMER2 ; READ CURRENT TIMER VALUE
STA SEED ; USE AS RANDOM SEED
BNE SEEDOK ; IF NOT ZERO, IT'S FINE
INC SEED ; OTHERWISE INCREMENT TO MAKE NON-ZERO
SEEDOK JSR GENPAT ; GENERATE INITIAL PATTERN
INFINIT LDY #>MSGBUF ; LOAD BUFFER LOCATION
LDA #<MSGBUF
JSR SCAN ; DISPLAY THE PATTERN
; GENERATE NEW RANDOM SLASH AT END OF BUFFER
JSR RANDOM ; GET RANDOM BIT
BCC GENBACK ; BRANCH IF CARRY CLEAR (50% CHANCE)
LDA #SLASH ; FORWARD SLASH
JMP STORE
GENBACK LDA #BACKSLASH ; BACKSLASH
STORE STA MSGBUF+22 ; ADD NEW CHARACTER TO END OF BUFFER
; SHIFT BUFFER LEFT ONE POSITION (SCROLL EFFECT)
LDX #$00 ; START AT FIRST POSITION
SHIFT LDA MSGBUF+1,X ; GET NEXT CHARACTER
STA MSGBUF,X ; STORE IN CURRENT POSITION
INX ; MOVE TO NEXT POSITION
CPX #$22 ; CHECK IF WE'RE AT END OF BUFFER
BNE SHIFT ; CONTINUE IF NOT AT END
; ENSURE NULL TERMINATOR IS ALWAYS PRESENT
LDA #$00
STA MSGBUF+23
JMP INFINIT ; LOOP FOREVER
; GENERATE INITIAL PATTERN BUFFER WITH RANDOM SLASHES
GENPAT LDX #$00 ; START AT FIRST POSITION
GPLOOP JSR RANDOM ; GET RANDOM BIT
BCC GBACK ; BRANCH IF CARRY CLEAR
LDA #SLASH ; FORWARD SLASH
JMP GSTORE
GBACK LDA #BACKSLASH ; BACKSLASH
GSTORE STA MSGBUF,X ; STORE IN BUFFER
INX ; NEXT POSITION
CPX #$17 ; CHECK IF BUFFER IS FULL
BNE GPLOOP ; CONTINUE IF NOT FULL
LDA #$00 ; ADD NULL TERMINATOR
STA MSGBUF+23 ; AT END OF BUFFER
RTS ; RETURN
; RANDOM NUMBER GENERATOR (8-BIT LFSR)
RANDOM LDA SEED ; LOAD CURRENT SEED
ASL ; SHIFT LEFT (C GETS HIGH BIT)
BCC NOEOR ; SKIP EOR IF BIT 7 WAS 0
EOR #$B4 ; APPLY FEEDBACK POLYNOMIAL
NOEOR STA SEED ; STORE UPDATED SEED
RTS ; RETURN WITH CARRY = RANDOM BIT
; SCANNING ROUTINE FROM ORIGINAL CODE
SCAN STY XFRLO ; Y AND A GET LOADED BEFORE JSR TO SCAN
STA XFRHI
LDA #$07 ; INIT SCAN FORWARD
STA TMP1
LDY #$05 ; INIT Y
CONT LDX #$05 ; INIT X
CHAR LDA (XFRHI),Y ; GET CHARACTER
CMP #$00 ; LAST CHARACTER?
BNE MORE ; IF NOT, CONTINUE
RTS
MORE STA CBUFF,X ; STORE CHAR
DEY ; SET UP NEXT CHAR
DEX ; SET UP NEXT STORE LOC
BPL CHAR ; LOOP IF NOT 6TH CHAR
CLD ; BINARY MODE
CLC ; PREPARE TO ADD (CLEAR CARRY FLAG)
TYA ; GET CHAR POINTER
ADC TMP1 ; UPDATE FOR 6 NEW CHARACTERS
STA PTR ; SAVE NEW POINTER
JSR DSPDLY ; DELAY DISPLAY
LDY PTR ; RESTORE POINTER
JMP CONT ; CONTINUE WITH REST OF MESSAGE
DSPDLY LDX #$0A ; SET THE DELAY RATE HERE
STX TMR ; PUT IN DECR. LOCATION
TIME LDA #$52 ; LOAD TIMER
STA TIMER2 ; START TIMER
LITE JSR DISP ; GOSUB DISPLAY RTN
BIT TIMER2 ; TIMER DONE?
BPL LITE ; IF NOT, LOOP
DEC TMR ; DECREMENT TIMER COUNTER
BNE TIME ; NOT FINISHED
RTS ; NOW GET 6 NEW CHARACTERS
DISP LDA #LOUT ; CHANGE LEFT LED SEGMENTS
STA SADD ; TO OUTPUTS
LDY #$00 ; INIT RECALL INDEX
LDX #$09 ; INIT DIGIT NUMBER
SIX LDA CBUFF,Y ; GET CHARACTER
STY $00FC ; SAVE Y FOR MONITOR DISP ROUTINE
JSR $1F4E ; MONITOR ROUTINE - DISP CHAR, DELAY 500 CYCLES
INY ; SET UP FOR NEXT CHAR
CPY #$06 ; 6 CHAR DISPLAYED?
BCC SIX ; NO
RTS
The random number generator is “random” enough to make it interesting, and the scrolling effect is really nice.
How would you do this on a 7-segment display?
Can you think of any other cool patterns to display?
I think it would be fun to do a “snake” pattern, where the segments light up in a snake-like pattern. Or maybe a “wave” pattern, where the segments light up in a wave-like pattern.
Have fun with it!