Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ Therefore RAM is usable in a meaningful fashion from $0200 upwards only.

## 2. Used Zero Page Locations

The bootloader needs to use some Zero Page locations: `$00 - $03`. Expect trouble if you overwrite / use them from within your own programs.
The bootloader needs to use some Zero Page locations: `$00 - $05`. Expect trouble if you overwrite / use them from within your own programs.

## 3. Used RAM

Expand All @@ -231,6 +231,47 @@ The bootloader also occupies some RAM. Most part is used as VideoRam to talk to

The Interrupt Service Routine (ISR) implemented at the end of available ROM realizes the serial loading. The way it works is quite simple. As soon as the Arduino set up all 8 bit of a byte at the data ports, it pulls the interrupt pin of the 6502 low for 30 microseconds. This triggers the 6502 to halt the current program, put all registers onto the stack and execute any routine who's starting address can be found in the Interrupt Vector Address (`$fffe-$ffff`) - the ISR. This routine reads the byte, writes it into the RAM, increases the address pointer for the next byte to come and informs the main program that data is still flowing. Consult the source for further details, it's quite straight forward.

## 5. Interrupt Service Routine Handler - ISR_SERVICE

The routine pointed to by vector address $fffe is actually the ISR_SERVICE routine. This routine performs a zero page addressed jump `JMP $(ISR_LOC)` which is configured in bootloader.asm to point to the address of ISR.

User programs are able to overwrite the address stored in ISR_LOC to create their own interrupt routines for IRQ. NMI is not supported yet.

For example:

``` asm
ISR_LOC = $04 ; uses 2 bytes ($04 and $05)
counter = $01
.org $0200

lda #<CUSTOM_ISR ; set up CUSTOM ISR pointer in Zero Page
sta ISR_LOC ; $04 LSB
lda #>CUSTOM_ISR
sta ISR_LOC + 1 ; $05 MSB

; your program here
lda #$00 ; init 16 bit counter variable
sta counter
sta counter + 1

CUSTOM_ISR:
inc counter
bne .end_isr
inc counter+1
.end_isr
rti

```

A complete example of how the Ben Eater Interrupt routine that increments a counter on each button push is included in `examples/irq.asm`. This example is designed to work with the following wiring on Ben Eater's 6502 build.

PUSH BUTTON:

* right leg tied to GROUND, left leg pulled up with 1kohm resistor to 5v.
* Left leg wired to CA1 (pin 40) on the VIA 65C22

IRQ from 65C22 (PIN 21) to IRQ on 65C02 (PIN 4). Leave PIN 4 tied high with a 1kohm resistor.

# Shortcomings

- The loader is slow. Quite slow. Even though 9600 baud as choosen transfer speed is not too bad, there are some significant idle timeouts implemented, to make the data transfer work reliably. You'll find it in `Receiver.ino`, the `Sender.js` does not have any timeouts left other than the necessary but unproblematic connection timeout once at the beginning. The worst is the timeout which allows to reliably read the UART buffer of the Arduino. When reduced, the whole data transfer becomes unreliable.
Expand Down
3 changes: 3 additions & 0 deletions Receiver/Receiver.ino
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ void loop() {
digitalWrite(DATA[n], LOW);
pinMode(DATA[n], OUTPUT);
}
pinMode(INTERRUPT, OUTPUT);
firstRun = false;
}

Expand Down Expand Up @@ -102,6 +103,8 @@ void loop() {
for (int n = 0; n < 8; n += 1) {
pinMode(DATA[n], INPUT);
}
//Leave INTERRUPT pin in INPUT mode pulled high.
pinMode(INTERRUPT, INPUT_PULLUP);
firstRun = true;
}
}
Expand Down
23 changes: 22 additions & 1 deletion bootloader.asm
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Z1 = $01
Z2 = $02
Z3 = $03

ISR_LOC = $04 ; pointer to IRQ Routine. 2 bytes. LSB + MSB

VIDEO_RAM = $3fde ; $3fde - $3ffd - Video RAM for 32 char LCD display
POSITION_MENU = $3fdc ; initialize positions for menu and cursor in RAM
POSITION_CURSOR = $3fdd
Expand Down Expand Up @@ -62,6 +64,11 @@ main: ; boot routine, first thing load
ldx #$ff ; initialize the stackpointer with 0xff
txs

lda #<ISR ; initialize the ISR pointer to be
sta ISR_LOC ; the one stored in rom. This can be
lda #>ISR ; overridden by programs loaded in but
sta ISR_LOC + 1 ; first we need to be able to service Sender.js

jsr LCD__initialize
jsr LCD__clear_video_ram

Expand Down Expand Up @@ -1155,6 +1162,20 @@ CURRENT_RAM_ADDRESS = Z0 ; a RAM address handle for indir

rti

;================================================================================
;
; ISR_SERVICE - Interrupt Service Routine Handler
;
; The ISR handler actually jumps to an address referenced by a location in Zero
; page. The Main entry point of the OS defines this as being the "ISR" routine
; above.
;
; This way we are able to have programs define their own IRQ handling routines
; by updating the address defined at ISR_LOC in Zero Page.
;================================================================================
ISR_SERVICE:
jmp (ISR_LOC)

.org $fffc
.word main ; entry vector main routine
.word ISR ; entry vector interrupt service routine
.word ISR_SERVICE ; entry vector interrupt service routine
243 changes: 243 additions & 0 deletions examples/irq.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
PORTB = $6000
PORTA = $6001
DDRB = $6002
DDRA = $6003

PCR = $600c ; W65C22 VIA Periferal Control Register
IFR = $600d ; W65C22 VIA Interrupt Flag Register
IER = $600e ; W65C22 VIA Interrupt Enable Register

value = $1000 ; 2 bytes
mod10 = $1002 ; 2 bytes
message = $1004 ; 6 bytes
counter = $100a ; 2 bytes

E = %10000000
RW = %01000000
RS = %00100000

ISR_LOC = $04 ; location of IRQ handler

.org $0200

reset:

lda #<irq ; set up IRQ handler for sixty5o2
sta ISR_LOC
lda #>irq
sta ISR_LOC + 1

lda #$ff ; Set up stack pointer
txs

lda #$82 ; Enable CA1 interrupt on the VIA (10000010)
sta IER
lda #$00
sta PCR ; Set CA1 to trigger on negative transition (going low)

lda #%11111111 ; Set all pins on PORTB to output
sta DDRB

lda #%11100001 ; Set top 3 and bottom 1 pins on PORTA to output (middle 4 pins as input)
sta DDRA

lda #%00111000 ; Set 8-bit mode; 2 line display; 5x8 font
jsr lcd_instruction

lda #%00001110 ; Display on; cursor on; blink off
jsr lcd_instruction

lda #%00000110 ; Increment / shift cursor; don't shift display
jsr lcd_instruction

lda #%00000001 ; Clear display
jsr lcd_instruction

lda #0 ; Initialize the counter to zero
sta counter
sta counter + 1
cli ; clear interrupt disable bit (enable interrupts)

loop:
lda #%00000010 ; Move cursor to home
jsr lcd_instruction

bin2dec:
; initialise the output message
lda #0
sta message

sei ; Disable interrupts. (the name of this instruction is misleading)

; initialize the value to convert
lda counter
sta value
lda counter + 1
sta value + 1

cli ; clear interrupt disable bit (enable interrupts)

divide:
; Initialise the remainder to zero
lda #0
sta mod10
sta mod10 + 1
clc

ldx #16
divloop:
; Rotate the quotient and remainder
rol value
rol value + 1
rol mod10
rol mod10 + 1

; a, y = dividend - divisor
sec
lda mod10
sbc #10
tay ; save low byte
lda mod10 + 1
sbc #0
bcc ignore_result ; branch if dividend < divisor

sty mod10
sta mod10 + 1

ignore_result:
dex
bne divloop
rol value ; shift in the last bit of the qotient
rol value + 1

lda mod10
clc
adc #"0"
jsr push_char

; if value is not zero we need to continue dividing
lda value
ora value + 1
bne divide

ldx #0
print:
lda message,x
beq loop
jsr print_char
inx
jmp print

jmp loop

number: .word 1729

; Add the character in the A register to the beginning of the
; null-terminated string `message`
push_char:
pha ; Push new char to the stack
ldy #0
char_loop:
lda message,y ; Get char from the string and put to x reg
tax
pla
sta message,y ; Pull char off the stack and push to the string
iny
txa
pha ; Push char from stirng on to stack
bne char_loop
pla
sta message,y ; Pull the null off the stack and add to end of string
rts

lcd_wait:
pha
lda #%00000000
sta DDRB
lcdbusy:
lda #RW
sta PORTA
lda #(RW | E)
sta PORTA
lda PORTB
and #%10000000
bne lcdbusy

lda #RW
sta PORTA
lda #%11111111
sta DDRB

pla
rts

lcd_instruction:
jsr lcd_wait
sta PORTB
lda #0
sta PORTA ; Clear RS/RW/E bits
lda #E ; Set E bit to send instruction
STA PORTA
lda #0
sta PORTA ; Clear RS/RW/E bits
rts

print_char:
jsr lcd_wait
sta PORTB
lda #RS
sta PORTA ; Set RS; Clear RW/E bits
lda #(RS | E) ; Set E bit to send instruction
STA PORTA
lda #RS
sta PORTA ; Clear RS/RW/E bits
rts


; Y = Length of note 0 - 255
; x = freq.

beep:
stx $10

.beep_1:
ldx $10
lda #$01
sta PORTA
.beep_2:
dex
bne .beep_2
lda #$00
sta PORTA
dey
bne .beep_1

rts

irq:
pha
txa
pha
tya
pha

inc counter
bne exit_irq
inc counter + 1

exit_irq:
ldy #$F0
ldx #$60
jsr beep

bit PORTA

pla
tay
pla
tax
pla

rti


Loading