commit a303e6a386627a93262f5c3cfb94a24a3ea48a6f
parent f0aebcf4f7222faa4b54012cd42fa5495cdd098f
Author: Gerd Beuster <gerd@frombelow.net>
Date: Sat, 5 Dec 2020 22:10:07 +0100
Support for SD Cards
Diffstat:
11 files changed, 1076 insertions(+), 119 deletions(-)
diff --git a/doc/Documentation.txt b/doc/Documentation.txt
@@ -48,6 +48,10 @@ hw/bus_logic/BUS_LOGIC.PLD for details.
A standard library is part of the boot ROM located at roms/boot/. In
order to use it, import roms/boot/liba.h into your program.
+The standard library includes a data stack with subroutines and macros
+for local variables and parameter passing. See roms/boot/boot.asm for
+documentation and sw/stack_test/stack_test.asm for examples.
+
** Boot Sequence
The ROM at roms/boot/boot.py loads data via the serial interface to
diff --git a/misc/sd_card/card_info.csv b/misc/sd_card/card_info.csv
@@ -4,7 +4,7 @@
# http://ugweb.cs.ualberta.ca/~c274/resources/hardware/SDcards/SD_SDIO_specsv1.pdf
#https://electronics.stackexchange.com/questions/375357/sd-card-initialization-problem-cmd16-wrong-response
#http://www.rjhcoding.com/avrc-sd-interface-1.php
-http://chlazza.nfshost.com/sdcardinfo.html
+#http://chlazza.nfshost.com/sdcardinfo.html
# Time [s], Analyzer Name, Decoded Protocol Result
diff --git a/roms/boot/boot.asm b/roms/boot/boot.asm
@@ -1,4 +1,8 @@
-CLOCK_SPEED = 4 ;4 Mhz Clock
+
+ DEBUG = false
+ ;; DEBUG = true
+
+ CLOCK_SPEED = 4 ;4 Mhz Clock
.if SYMON
rom_start = $c000
@@ -27,6 +31,12 @@ ds_frame: .word ?
puts_str: .word ?
gets_len: .byte ?
gets_str: .word ?
+ ;; Used by VIA for (de-)serialization
+via_buffer: .byte ?
+sd_data: .word ?
+ ;; sd_type does not need to be on the zero page.
+ ;; We may want to move it to $200-$2ff.
+sd_type: .byte ? ; $05 = SD; $c1 = SDHC
;; Everything beyond these reserved variables available to
;; programs in RAM.
rom_zero_page_end:
@@ -34,17 +44,17 @@ rom_zero_page_end:
.endn
.namespace export
-stack_end = $7ffb
+ram_end = $7fff
.endn
-
+
;;; The actual (hardware) interrupt vectors are located in the ROM
;;; at $ fffa/fffb (nmi) and $fffe/$ffff (irq). Since we want to be
;;; able to handle interrupts in RAM, the ROM isr routines jump to the
;;; addresses given here. After a reset, this RAM address is set to
;;; default_irq_handler.
.namespace export
-nmi_vector = $7ffc
-irq_vector = $7ffe
+nmi_vector = export.ram_end - $3
+irq_vector = export.ram_end - $1
.endn
.section rom
@@ -61,8 +71,9 @@ boot: .block
#STORE_WORD export.default_nmi_handler, export.nmi_vector
cld
cli
- jsr export.init_stack
jsr export.init_acia
+ INIT_STACK export.ram_end-$206+$1 ;Minimal stack size to access SD card
+ jsr export.init_via
jsr check_for_program_download
bcs no_program_download
jmp download_program
@@ -242,7 +253,7 @@ puts:
;; Send string terminated by '\0'
ldy #$00
_puts_loop:
- lda (export.puts_str), y
+ lda (export.puts_str),y
beq _puts_end
phy
jsr export.putc
@@ -468,6 +479,20 @@ terminate_string:
rts
.bend
+;;; Initialize VIA: Switch all ports to output
+;;; and all pins to low
+.namespace export
+init_via:
+ lda #$ff
+ sta export.via_ddra
+ sta export.via_ddrb
+ lda #$00
+ sta export.via_ora
+ sta export.via_orb
+ rts
+.endn
+
+
;;; 16 Bit Galouis LFSR
;;; If you use the whole state as RNG, the quality of the
;;; random numbers is rather poor. Just extracting one
@@ -543,25 +568,49 @@ derefer_ram_irq:
jmp (export.irq_vector)
;;; ----------------------------------------------------------
+;;;
;;; STACK
;;;
;;; ----------------------------------------------------------
-.namespace export
-init_stack:
-.endn
- .block
- #STORE_WORD export.stack_end+1, export.ds_pointer
- rts
- .bend
+;;; Software implementation of data stack. In difference to typical
+;;; stack implementations, this stack grows from bottom to top, because
+;;; we want to access eleements using indirected indexed access with
+;;; the stack frame pointer as base. Therefore the stack should
+;;; be initialized with the first unoccupied RAM address after the
+;;; program code.
+;;;
+;;; PULL and PUSH do not move actual stack data, they just shift
+;;; the stack pointer.
+;;;
+;;; Local variables on the data stack are supported by macro
+;;; VAR. VAR i refers to the ith byte on the stack. PARAM allows
+;;; to pass variables to a callee. PARAM i of the caller becomes
+;;; VAR i of the callee. The callee should reserve stack space
+;;; for both its parameters and local variables.
+;;;
+;;; Stack frames used for these callse are created/destroyed by
+;;; subroutines new_stack_frame and remove_stack_frame. It is
+;;; recommended to use macros CALL and RETURN instead of calling
+;;; new_stack_frame and remove_stack_frame directly.
+;;;
+;;; CALL is like jsr with parameter passing. There addressing
+;;; modes are supported: immediate, zero-page, and local variables.
+;;; For immediate and zero page, the third parameter must be lda.
+;;; For local variables, the third parameter must be lda_LOCAL.
+;;; See sw/stack_test/stack_test.asm for examles.
+
.namespace export
new_stack_frame:
.endn
.block
+ ;; Store old frame pointer on stack.
+ ;; Since our stack grows bottom to top, we write
+ ;; the data before increasing the stack pointer
+ #COPY_WORD_ABSOLUTE_INDIRECT export.ds_frame, (export.ds_pointer)
#PUSH $02 ; Store old frame pointer
- #COPY_WORD export.ds_frame, (export.ds_pointer)
- #COPY_WORD export.ds_pointer, export.ds_frame ; Set new frame pointer
+ #COPY_WORD_ABSOLUTE_INDIRECT export.ds_pointer, export.ds_frame ; Set new frame pointer
rts
.bend
@@ -569,9 +618,9 @@ new_stack_frame:
remove_stack_frame:
.endn
.block
- #COPY_WORD export.ds_frame, export.ds_pointer ; Reset stack pointer to begin of frame.
- #COPY_WORD (export.ds_pointer), export.ds_frame ; Restore old frame pointer.
- #PULL $02 ; Remove obsolete frame pointer from stack.
+ #COPY_WORD_ABSOLUTE_INDIRECT export.ds_frame, export.ds_pointer ; Reset stack pointer to begin of frame.
+ #PULL $02 ; Move stack pointer to frame reference
+ #COPY_WORD_ABSOLUTE_INDIRECT (export.ds_pointer), export.ds_frame ; Restore old frame pointer.
rts
.bend
@@ -579,10 +628,8 @@ remove_stack_frame:
push_a:
.endn
.block
- pha
- #PUSH $01
- pla
sta (export.ds_pointer)
+ #PUSH $01
rts
.bend
@@ -590,12 +637,364 @@ push_a:
pull_a:
.endn
.block
- lda (export.ds_pointer)
- pha
#PULL $01
- pla
+ lda (export.ds_pointer)
+ rts
+ .bend
+
+;;; -----------------------------------
+;;;
+;;; SPI read and write operations
+;;;
+;;; -----------------------------------
+
+ ;; Wiring:
+ ;; PA0: CS
+ CS = %00000001
+ ;; PA1: SCK
+ SCK = %00000010
+ ;; PA2: MOSI
+ MOSI = %00000100
+ ;; PA3: MISO
+ MISO = %00001000
+
+
+;;; Blocking read - read until
+;;; byte != $ff is read or retry counter
+;;; expires.
+.namespace export
+spi_get:
+.endn
+ .block
+ ldy #$00 ; Try up to $ff times
+retry_loop:
+ jsr export.spi_get_nonblocking
+ ;; If we read $ff, we did not receive
+ ;; a byte.
+ cmp #$ff
+ bne done
+ dey ; Try again unless retry
+ bne retry_loop ; counter expired.
+done:
+ CLEAR_BITS export.via_ora, #(MOSI|SCK) ; Set MOSI and SCK low on completion.
+ lda export.via_buffer
rts
.bend
+
+;;; Return next byte; may be $ff in case of no transmission
+.namespace export
+spi_get_nonblocking:
+.endn
+ SET_BITS export.via_ora, #MOSI ; Keep MOSI high during read
+ lda #$00 ; Clear
+ sta export.via_buffer ; receive buffer.
+ ldx #$08 ; 8 bit to read.
+read_bits_loop:
+ .block
+ asl export.via_buffer ; Make room for next bit
+ CLEAR_BITS export.via_ora, #SCK ; Generate clock impulse
+ SET_BITS export.via_ora, #SCK
+ lda export.via_ira ; Get next bit.
+ and #MISO
+ cmp #MISO
+ bne cont ; Nothing to do if we got a zero bit.
+ inc export.via_buffer ; Set lowest bit in via_buffer
+cont:
+ dex
+ bne read_bits_loop
+ CLEAR_BITS export.via_ora, #(MOSI|SCK) ; Set MOSI and SCK low on completion.
+ lda export.via_buffer
+ rts
+ .bend
+
+
+
+;;; Write byte via SPI
+.namespace export
+spi_set:
+.endn
+ .block
+ sta export.via_buffer ; We send from via_buffer
+ ldx #$08 ; 8 bit to write
+write_bits_loop:
+ CLEAR_BITS export.via_ora, #SCK ; Set bits on low clock
+ CLEAR_BITS export.via_ora,#MOSI
+ lda export.via_buffer ; Get next bit to send
+ and #%10000000 ; Isolate bit to send.
+ beq cont ; Set MOSI accordingly.
+ SET_BITS export.via_ora,#MOSI
+ jmp cont
+cont:
+ SET_BITS export.via_ora, #SCK ; Send bits on high clock
+ asl export.via_buffer ; Advance to next bit
+ dex
+ bne write_bits_loop
+ CLEAR_BITS export.via_ora,#SCK
+ rts
+ .bend
+
+;;; -----------------------------------
+;;;
+;;; SD card communication interface
+;;;
+;;; -----------------------------------
+
+
+;;; Send command
+.namespace export
+sd_send_cmd:
+.endn
+ .block
+ ldy #$00
+cmd_bytes_loop:
+ lda (export.sd_data),y
+ jsr export.spi_set
+ iny
+ cpy #$06
+ bne cmd_bytes_loop
+ lda #$ff ; Finalize transmission
+ jsr export.spi_set ; with dummy byte
+ rts
+ .bend
+
+
+.namespace export
+sd_open:
+.endn
+ .block
+.if DEBUG
+ PRINTSNL "Initializing SD Card"
+.endif
+ ;; Configure ports for in- and output
+ SET_BITS export.via_ddra, #(CS | SCK | MOSI)
+ CLEAR_BITS export.via_ddra, #MISO
+ ldy #$10 ; Error counter
+ phy
+start_init:
+ ;; Card power on sequence:
+ ;; Keep CS high, toggle clock for at least 74 cycles.
+ SET_BITS export.via_ora, #(CS | MOSI)
+ ;; Wait for 80 clock pulses
+ ldx #160
+boot_wait_loop:
+ TOGGLE_BITS export.via_ora, #SCK
+ dex
+ bne boot_wait_loop
+ CLEAR_BITS export.via_ora, #(CS|MOSI|SCK)
+ SD_SEND_CMD [$40, $00, $00, $00, $00, $95] ; CMD0
+ SD_RECEIVE $1
+ cmp #$01 ; Should be $01 (In idle state).
+ beq cont
+ ply
+ dey
+ phy
+ beq failed_init
+ jmp start_init ; If not, start over.
+failed_init:
+ ply
+ PRINTSNL("Error initializing SD card!")
+ sec
+ rts
+cont:
+ ;; Card is in SPI mode
+ ply
+ SD_SEND_CMD [$48, $00, $00, $01, $aa, $87] ; CMD8
+ SD_RECEIVE $1
+ SD_SEND_CMD [$45, $00, $00, $00, $00, $5b] ; CMD5
+ SD_RECEIVE $1 ; $05 = SD; $c1 = SDHC
+ sta export.sd_type
+ SD_SEND_CMD [$7b, $00, $00, $00, $00, $83] ; CMD59
+ SD_RECEIVE $1 ; should be $01
+ ;; Send ACMD41 until card is initialized
+ ldy #$10 ; Fail counter
+ phy
+wait_for_card_initialized:
+ SD_SEND_CMD [$77, $00, $00, $00, $00, $65] ; CMD55
+ SD_RECEIVE $1 ; should be $01
+ SD_SEND_CMD [$69, $40, $00, $00, $00, $95] ; ACMD41 (v1 & v2 card)
+ SD_RECEIVE $1 ; $01 if busy, $00 if ready (initialized)
+ cmp #$00
+ beq initialized
+ ply
+ dey
+ phy
+ bne cont2 ; More tries?
+cont2:
+ jmp wait_for_card_initialized
+initialized:
+ ply
+ SD_SEND_CMD [$50, $00, $00, $02, $00, $15] ; CMD16
+ SD_RECEIVE $1
+ clc
+ rts
+ .bend
+
+
+.namespace export
+print_block:
+.endn
+ .block
+ ldx #$02
+ ldy #$00
+loop:
+ phx
+ phy
+ lda (export.sd_data),y
+ jsr export.puth
+ PRINTS " "
+ ply
+ plx
+ iny
+ bne loop
+ ADD_WORD export.sd_data, $0100
+ dex
+ bne loop
+ PRINTNL
+ SUB_WORD export.sd_data, $0200
+ rts
+ .bend
+
+.namespace export
+sd_read_block:
+.endn
+ ;; Read a block of data
+ ;; Block number is passed via stack.
+ .block
+.if DEBUG
+ PRINTSNL "Sending CMD17"
+.endif
+ PUSH $04 ; Parameters (block number)
+ lda #$51 ; CMD17 (READ_SINGLE_BLOCK)
+ jsr export.spi_set
+ jsr sd_send_block_number
+ lda #$01 ; Dummy CRC
+ jsr export.spi_set
+ jsr export.spi_get
+ cmp #$00
+ beq wait_for_start_token
+ jmp read_failed
+wait_for_start_token:
+ jsr export.spi_get_nonblocking
+ cmp #$ff
+ beq wait_for_start_token
+ cmp #$fe
+ bne read_failed
+ ldx #$02
+ ldy #$00
+loop:
+ phx
+ phy
+ jsr export.spi_get_nonblocking
+ ply
+ plx
+ sta (export.sd_data),y
+ iny
+ bne loop
+ ADD_WORD export.sd_data, $0100 ; Second half of block
+ dex
+ bne loop
+ SUB_WORD export.sd_data, $0200 ; Restore pointer
+ clc
+ SD_RECEIVE $2 ; Get CRC
+ RETURN
+read_failed:
+ PRINTSNL("Error reading SD card!")
+ sec
+ RETURN
+ .bend
+
+sd_send_block_number:
+ .block
+ lda export.sd_type ; Old cards expect the position
+ cmp #$05 ; as bytes, not blocks
+ beq old_card
+ lda_LOCAL 0 ; Block number
+ jsr export.spi_set
+ lda_LOCAL 1
+ jsr export.spi_set
+ lda_LOCAL 2
+ jsr export.spi_set
+ lda_LOCAL 3
+ jsr export.spi_set
+ rts
+old_card:
+ lda_LOCAL 3
+ asl a
+ sta_LOCAL 3
+ lda_LOCAL 2
+ rol a
+ sta_LOCAL 2
+ lda_LOCAL 1
+ rol a
+ jsr export.spi_set
+ lda_LOCAL 2
+ jsr export.spi_set
+ lda_LOCAL 3
+ jsr export.spi_set
+ lda #$00
+ jsr export.spi_set
+ rts
+ .bend
+
+.namespace export
+sd_write_block:
+.endn
+ ;; Write block at sd_data to card
+ ;; Block number is passed via stack
+ .block
+.if DEBUG
+ PRINTSNL "Sending CMD24"
+.endif
+ PUSH $04 ; Parameters (block number)
+ lda #$58 ; CMD24 (WRITE_BLOCK)
+ jsr export.spi_set
+ jsr sd_send_block_number
+ lda #$01 ; Dummy CRC
+ jsr export.spi_set
+ lda #$ff
+ jsr export.spi_set ; Dummy byte
+ SD_RECEIVE $1
+ ;; Send start token
+ lda #$fe
+ jsr export.spi_set
+ ;; Send data
+ ldx #$02
+ ldy #$00
+write_loop:
+ phx
+ phy
+ lda (export.sd_data),y
+ jsr export.spi_set
+ ply
+ plx
+ iny
+ cpy #$00
+ bne write_loop
+ ADD_WORD export.sd_data, $0100 ; Second half of block
+ dex
+ cpx #$00
+ bne write_loop
+ SUB_WORD export.sd_data, $0200 ; Restore pointer
+ ;; Wait for write operation to complete
+ SD_RECEIVE $1
+ cmp #$e5
+ bne error_writing
+wait_loop:
+ SD_RECEIVE $1
+ beq wait_loop
+ clc
+ RETURN
+error_writing:
+ PRINTSNL("Error writing to SD card!")
+ sec
+ RETURN
+ .bend
+
+sd_close:
+ #SET_BITS export.via_ora, #CS
+ rts
+
+
;;; Default NMI handler. Unless the user program changes
;;; the nmi_vector, NMIs are handled here.
@@ -611,7 +1010,8 @@ derefer_ram_nmi:
default_program:
;; .include "../../sw/10print/10print.asm"
-.include "../../sw/ttt/ttt.asm"
+;; .include "../../sw/ttt/ttt.asm"
+.include "../../sw/sd_card/sd_card.asm"
;;; Vectors
.fill $fffa-*, $ff
diff --git a/roms/boot/boot_macros.inc b/roms/boot/boot_macros.inc
@@ -1,69 +1,4 @@
-;;; PRINT(addr)
-;;; Send zero terminated string at addr via acia.
-;;; Input:
-;;; addr:
-;;; Address of zero terminated string (<= 256 characters)
-;;; Output:
-;;; -
-;;; Changes:
-;;; a, x, y, puts_str, put_str+1
-
-;;; PRINTS(string) (<= 255 characters)
-;;; Send string via acia
-;;; Input:
-;;; string:
-;;; String to be send.
-;;; Output:
-;;; -
-;;; Changes:
-;;; a, x, y, puts_str, put_str+1
-
-;;; PRINTSNL(string) (<= 253 characters)
-;;; Send string and newline via acia
-;;; Input:
-;;; string:
-;;; String to be send.
-;;; Output:
-;;; -
-;;; Changes:
-;;; a, x, y, puts_str, put_str+1
-
-;;; INPUTS(addr, len)
-;;; Read up to len characters from acai and store them at addr
-;;; This is a wrapper for gets
-;;; Input:
-;;; addr:
-;;; Target address for zero-terminated string
-;;; len:
-;;; Maximal length of string to be read (not terminating zero!)
-;;; +-------------------------------------------------------+
-;;; |IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! |
-;;; +-------------------------------------------------------+
-;;; Note that a terminating zero is added to the string.
-;;; Therefore len+1 bytes may be written!
-;;; Output:
-;;; -
-;;; Changes:
-;;; a, y, ACAI registers
-
-
-;;; PRINTNL
-;;; Send newline via acia
-;;; Input:
-;;; -
-;;; Output:
-;;; -
-;;; Changes:
-;;; a, x, acai registers
-
-;;; STORE_WORD(word,dst)
-;;; Store word at dst
-;;; Input:
-;;; word
-;;; Output:
-;;; -
-;;; Changes:
-;;; a, dst
+;;; -*- asm -*-
;;; This file may be included from boot.asm or from
;;; some userland program. If included from a
@@ -79,24 +14,35 @@
.if CLOCK_SPEED == 0
export .namespace
puts = puts
+ puth = puth
puts_str = puts_str
gets_str = gets_str
gets_len = gets_len
gets = gets
putc = putc
- init_stack = init_stack
new_stack_frame = new_stack_frame
remove_stack_frame = remove_stack_frame
push_a = push_a
pull_a = pull_a
ds_pointer = ds_pointer
ds_frame = ds_frame
+ ram_end = ram_end
.endn
BOOT_EMBEDDED = false
.else
BOOT_EMBEDDED = true
.endif
+;;; PRINT(addr)
+;;; Send zero terminated string at addr via acia.
+;;; Input:
+;;; addr:
+;;; Address of zero terminated string (<= 256 characters)
+;;; Output:
+;;; -
+;;; Changes:
+;;; a, x, y, puts_str, put_str+1
+
PRINT .macro
lda #<\1
sta export.puts_str
@@ -105,22 +51,60 @@ PRINT .macro
jsr export.puts
.endm
+;;; PRINTS(string) (<= 255 characters)
+;;; Send string via acia
+;;; Input:
+;;; string:
+;;; String to be send.
+;;; Output:
+;;; -
+;;; Changes:
+;;; a, x, y, puts_str, put_str+1
+
PRINTS .macro
PRINT(saddr)
- jmp cont
+ jmp cont_PRINTS
saddr:
.null \1
-cont:
+cont_PRINTS:
.endm
+;;; PRINTSNL(string) (<= 253 characters)
+;;; Send string and newline via acia
+;;; Input:
+;;; string:
+;;; String to be send.
+;;; Output:
+;;; -
+;;; Changes:
+;;; a, x, y, puts_str, put_str+1
+
PRINTSNL .macro
PRINT(saddr)
- jmp cont
+ jmp cont_PRINTSNL
saddr:
.null \1, $0d, $0a
-cont:
+cont_PRINTSNL:
.endm
+;;; INPUTS(addr, len)
+;;; Read up to len characters from acai and store them at addr
+;;; This is a wrapper for gets
+;;; Input:
+;;; addr:
+;;; Target address for zero-terminated string
+;;; len:
+;;; Maximal length of string to be read (not terminating zero!)
+;;; +-------------------------------------------------------+
+;;; |IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! |
+;;; +-------------------------------------------------------+
+;;; Note that a terminating zero is added to the string.
+;;; Therefore len+1 bytes may be written!
+;;; Output:
+;;; -
+;;; Changes:
+;;; a, y, ACAI registers
+
INPUTS .macro
lda #<\1
sta export.gets_str
@@ -131,6 +115,15 @@ INPUTS .macro
jsr export.gets
.endm
+;;; PRINTNL
+;;; Send newline via acia
+;;; Input:
+;;; -
+;;; Output:
+;;; -
+;;; Changes:
+;;; a, x, acai registers
+
PRINTNL .macro
lda #$0d
jsr export.putc
@@ -138,6 +131,16 @@ PRINTNL .macro
jsr export.putc
.endm
+;;; STORE_WORD(word,dst)
+;;; Store word at dst
+;;; Input:
+;;; word
+;;; Output:
+;;; -
+;;; Changes:
+;;; a, dst
+
+
STORE_WORD .macro
lda #<\1
sta \2
@@ -145,25 +148,44 @@ STORE_WORD .macro
sta \2+1
.endm
+
+;;; Macros for accessing the data stack
+;;; and managing local variables and
+;;; call parameters. Check boot.asm for
+;;; documentation.
+
+;;; Stack related macros
+
+INIT_STACK: .macro
+ #STORE_WORD \1, export.ds_pointer
+ .endm
+
PUSH: .macro ; Reserve bytes on stack (no actual push operation)
- #SUB_WORD export.ds_pointer, \1
+ #ADD_WORD export.ds_pointer, \1
.endm
PULL: .macro ; Remove bytes from stack (no return value)
- #ADD_WORD export.ds_pointer, \1
+ #SUB_WORD export.ds_pointer, \1
.endm
PUSH_WORD: .macro ; Push a word on the stack
- #PUSH $02
lda #<\1
sta (export.ds_pointer)
lda #>\1
ldy #$01
sta (export.ds_pointer),y
+ #PUSH $02
.endm
-
- ;; By using the Y register, we also support indirect addressing modes
-COPY_WORD: .macro
+
+COPY_WORD_IMMEDIATE: .macro
+ lda #<\1
+ sta \2
+ lda #>\1
+ sta \2+1
+ .endm
+
+ ;; Copy macro for absolute and indirect addressing modes
+COPY_WORD_ABSOLUTE_INDIRECT: .macro
lda \1
sta \2
ldy #$01
@@ -171,6 +193,18 @@ COPY_WORD: .macro
sta \2,y
.endm
+ ;; Push sequence of bytes to stack. Parameters: From, Length
+PUSH_RANGE: .macro
+ ldy #$00
+loop_PUSH_RANGE:
+ lda \1,y
+ sta (ds_pointer),y
+ iny
+ cpy \2
+ bne loop_PUSH_RANGE
+ #PUSH \2
+ .endm
+
SUB_WORD: .macro
lda \1
sec
@@ -190,3 +224,116 @@ ADD_WORD: .macro
adc #>\2
sta \1+1
.endm
+
+
+;;; Local variables & call parameters related macros
+
+LOCAL: .macro
+ ldy #\1
+ \2 (export.ds_frame),y
+ .endm
+lda_LOCAL: .macro
+ LOCAL \1, lda
+ .endm
+sta_LOCAL: .macro
+ LOCAL \1, sta
+ .endm
+adc_LOCAL: .macro
+ LOCAL \1, adc
+ .endm
+PARAM: .macro
+ ldy #\1
+ \2 (ds_pointer),y
+ .endm
+sta_PARAM: .macro
+ PARAM \1+2, sta
+ .endm
+lda_PARAM: .macro
+ PARAM \1+2, lda
+ .endm
+
+CALL: .macro
+ .for i := 0, i < len(\2), i += $1
+ \3 \2[i]
+ sta_PARAM i
+ .next
+ jsr new_stack_frame
+ jsr \1
+ .endm
+
+RETURN: .macro
+ jsr export.remove_stack_frame
+ rts
+ .endm
+
+ ;; Macros for accessing SD cards
+
+SD_SEND_CMD .macro
+.if DEBUG
+ PRINTS "Sending CMD$"
+ lda cmd
+ sec
+ sbc #$40
+ jsr export.puth
+ PRINTNL
+.endif
+ lda #<cmd
+ sta export.sd_data
+ lda #>cmd
+ sta export.sd_data+1
+ jsr export.sd_send_cmd
+ jmp cont_SD_SEND_CMD
+cmd: .byte \1
+cont_SD_SEND_CMD:
+ .endm
+
+
+
+SD_RECEIVE .macro
+ ldx #\1
+loop_SD_RECEIVE:
+ phx
+ jsr export.spi_get
+.if DEBUG
+ pha
+ jsr export.puth
+ PRINTS " "
+ pla
+.endif
+ plx
+ dex
+ bne loop_SD_RECEIVE
+ pha
+.if DEBUG
+ PRINTNL
+.endif
+ ;; Read non-existing dummy byte for snychronization
+ jsr export.spi_get_nonblocking
+ pla
+ .endm
+
+;;; -----------------------------------
+;;;
+;;; Macros for bit manipulation
+;;;
+;;; -----------------------------------
+
+SET_BITS .macro
+ lda \1
+ ora \2
+ sta \1
+ .endm
+
+CLEAR_BITS .macro
+ lda \1
+ and ~\2
+ sta \1
+ .endm
+
+TOGGLE_BITS .macro
+ lda \1
+ eor \2
+ sta \1
+ .endm
+
+
diff --git a/sw/Makefile.common b/sw/Makefile.common
@@ -9,7 +9,7 @@ all: $(TARGET).bin $(TARGET)_symon.bin
64tass $(CFLAGS) -DSYMON=true -l $(TARGET)_symon.l -L $(TARGET)_symon.lst "$<" -o "$@"
upload: $(TARGET).bin
- ../../roms/boot/boot.py $(TARGET).bin
+ ../../tools/boot.py $(TARGET).bin
clean:
rm -f *.bin *.l *.lst
diff --git a/sw/sd_card/Makefile b/sw/sd_card/Makefile
@@ -0,0 +1,3 @@
+TARGET=sd_card
+
+include ../Makefile.common
diff --git a/sw/sd_card/sd_card.asm b/sw/sd_card/sd_card.asm
@@ -0,0 +1,182 @@
+;;; Start program form SD card.
+;;; If we are included in boot.asm, we
+;;; should not set the start address
+.weak
+ BOOT_EMBEDDED = false
+.endweak
+.if BOOT_EMBEDDED
+ init_acia = export.init_acia
+ init_via = export.init_via
+ getc = export.getc
+ putc = export.putc
+ new_stack_frame = export.new_stack_frame
+ ds_pointer = export.ds_pointer
+ sd_data = export.sd_data
+ sd_read_block = export.sd_read_block
+ sd_open = export.sd_open
+ puts_str = export.puts_str
+ puts = export.puts
+ puth = export.puth
+.else
+ .if SYMON
+ .include "boot_symon.l"
+ .else
+ .include "boot.l"
+ .endif
+ .include "boot_macros.inc"
+ * = rom_zero_page_end
+ .dsection zero_page
+ * = $0300
+.endif
+.dsection code
+
+.section code
+
+;;; -----------------------------------
+;;;
+;;; Main
+;;;
+;;; -----------------------------------
+
+init:
+ .block
+.if SYMON
+ cld
+ jsr init_acia
+ jsr init_via
+.endif
+.if !BOOT_EMBEDDED
+ ;; Wait for key to start
+ jsr getc
+.endif
+ INIT_STACK $7fff-$0400
+loop:
+ CALL ls, [], lda
+ jsr choose_program
+ bcs loop
+ jsr execute
+ jmp init
+ .bend
+
+ load_address = $0300
+
+choose_program:
+ PRINTSNL("Choose program (0-9). Any other key reloads card.")
+ jsr getc
+ sec
+ sbc #'0'
+ cmp #$0a ; Sets carry if not a digit
+ rts
+
+
+execute:
+ .block
+ pha
+ jsr new_stack_frame
+ pla
+ ;; Each app has 2**16 blocks, app number is in A, therefore
+ ;; the address of the app is [#$00, A, #$00, #$01]
+ sta_LOCAL 1
+ PRINTS "Executing program "
+ lda_LOCAL 1
+ clc
+ adc #'0'
+ jsr putc
+ PRINTSNL "."
+ lda #$00
+ sta_LOCAL 0
+ sta_LOCAL 2
+ lda #$01
+ sta_LOCAL 3
+ PUSH 4
+ COPY_WORD_ABSOLUTE_INDIRECT ds_pointer, sd_data ; Read block
+ PUSH $0200 ; to stack
+ CALL sd_read_block, [0, 1, 2, 3], lda_LOCAL
+ PULL $0200 ; Reset stack pointer to block read
+ COPY_WORD_IMMEDIATE load_address, sd_data ; Set Target address
+ lda (ds_pointer) ; Number of blocks
+read_next_block:
+ pha
+ ;; Advance to next block on card
+ clc
+ lda_LOCAL 3
+ adc #$01
+ sta_LOCAL 3
+ lda_LOCAL 2
+ adc #$00
+ sta_LOCAL 2
+ CALL sd_read_block, [0, 1, 2, 3], lda_LOCAL ; Read block
+ ADD_WORD sd_data, $0200 ; Advance to next block
+ pla
+ dec a
+ bne read_next_block
+ ;; Done. Execute program
+ jmp load_address
+ .bend
+
+
+ ;; List programs on card
+ls:
+ .block
+ jsr sd_open
+ ;; List files on card
+ COPY_WORD_ABSOLUTE_INDIRECT ds_pointer, sd_data ; Read block
+ PUSH $0200 ; to stack
+ CALL sd_read_block, [#$00, #$00, #$00, #$00], lda
+ PULL $0200 ; Reset stack pointer to point to block read
+ lda (ds_pointer)
+ cmp #$00 ; Check FS version
+ beq cont
+ jmp not_supported
+cont:
+ lda #$00 ; File number
+dir_entry_loop:
+ pha
+ clc
+ adc #'0'
+ jsr putc
+ PRINTS ": "
+ ;; Each app has 2**16 blocks, therefore
+ ;; the address of app i is [#$00, #i, #$00, #$01]
+ lda #$00
+ sta_LOCAL 0
+ sta_LOCAL 2
+ lda #$01
+ sta_LOCAL 3
+ pla
+ pha
+ sta_LOCAL 1
+ PUSH 4
+ COPY_WORD_ABSOLUTE_INDIRECT ds_pointer, sd_data ; Read block
+ PUSH $0200 ; to stack
+ CALL sd_read_block, [0, 1, 2, 3], lda_LOCAL
+ PULL $0200 ; Reset stack pointer to block read
+ lda (ds_pointer) ; Number of blocks
+ beq filename_printed ; This spot is not occupied
+ pha
+ COPY_WORD_ABSOLUTE_INDIRECT ds_pointer, puts_str
+ ADD_WORD puts_str, $01 ; Start of filename
+ jsr puts
+ PRINTS " ($"
+ pla
+ jsr puth
+ PRINTS " blocks)"
+filename_printed:
+ PRINTNL
+ PULL 4
+ pla
+ inc a
+ cmp #$0a
+ bcs done
+ jmp dir_entry_loop
+done:
+ clc
+ RETURN
+not_supported:
+ PRINTSNL "This filesystem is not supported."
+ sec
+ RETURN
+ .bend
+
+end_of_code:
+.send code
diff --git a/sw/stack_test/stack_test.asm b/sw/stack_test/stack_test.asm
@@ -11,26 +11,40 @@
.cerror * > $8000, "RAM exhausted"
.section code
+
+ ;; start_of_stack = $7fff-$206+$1 ; Minimal stack as defined in ROM
+ start_of_stack = end_of_code ; Stack starts right after code
init:
+ .block
cld
+ INIT_STACK start_of_stack
jsr init_acia
+.if SYMON
+.else
jsr getc
- jmp stack_test
+.endif
+ jsr stack_test
+ jsr test_local_variables
+ CALL test_recursion, [], lda_LOCAL
+.if false
+.endif
+loop:
+ jmp loop
+ .bend
+
stack_test:
.block
;; Testing empty stack
- jsr init_stack
jsr new_stack_frame
jsr remove_stack_frame
lda ds_pointer
- cmp #<stack_end+1
+ cmp #<start_of_stack
#CHECK_ERROR
lda ds_pointer+1
- cmp #>stack_end+1
+ cmp #>start_of_stack
#CHECK_ERROR
;; Push and pull
jsr new_stack_frame
- PUSH $01
lda #$a0
sta (ds_pointer)
PUSH $01
@@ -39,6 +53,8 @@ stack_test:
PUSH $01
lda #$c0
sta (ds_pointer)
+ PUSH $01
+ PULL $01
lda (ds_pointer)
cmp #$c0
#CHECK_ERROR
@@ -50,14 +66,14 @@ stack_test:
lda (ds_pointer)
cmp #$a0
#CHECK_ERROR
- ;; Push and pull with new frame
PUSH $01
+ ;; Push and pull with new frame
lda #$a1
sta (ds_pointer)
+ PUSH $01
lda ds_pointer ; Save ds_pointer
pha ; for later comparison
jsr new_stack_frame
- PUSH $03
ldy #$00
lda #$b1
sta (ds_pointer),y
@@ -66,6 +82,7 @@ stack_test:
inc y
sta (ds_pointer),y
inc y
+ PUSH $03
PULL $01
lda (ds_pointer)
cmp #$b1
@@ -74,6 +91,7 @@ stack_test:
pla ; Still same ds_pointer
cmp ds_pointer ; as before creating and
#CHECK_ERROR ; deleting frame?
+ PULL $01
lda (ds_pointer)
cmp #$a1
#CHECK_ERROR
@@ -83,21 +101,20 @@ stack_test:
#CHECK_ERROR
jsr remove_stack_frame
lda ds_pointer ; Stack should be
- cmp #<stack_end+1 ; empty again.
+ cmp #<start_of_stack ; empty again.
#CHECK_ERROR
lda ds_pointer+1
- cmp #>stack_end+1
+ cmp #>start_of_stack
#CHECK_ERROR
#PUSH_WORD $abcd
jsr pull_a
- cmp #$cd
+ cmp #$ab
#CHECK_ERROR
jsr pull_a
- cmp #$ab
+ cmp #$cd
#CHECK_ERROR
PRINTSNL("Stack test completed!")
-loop:
- jmp loop
+ rts
.bend
CHECK_ERROR: .macro
@@ -111,4 +128,89 @@ error_cont:
.bend
.endm
+test_local_variables:
+ .block
+ jsr new_stack_frame
+ ;; Working with local variables
+ PUSH $02 ; Create local variables
+ lda #$ab
+ sta_LOCAL 0
+ lda #$00
+ lda_LOCAL 0
+ inc a
+ sta_LOCAL 1
+ lda #$00
+ lda_LOCAL 1
+ cmp #$ac
+ #CHECK_ERROR
+ ;; Passing paramters
+ lda #$01
+ sta_PARAM 0
+ lda #$02
+ sta_PARAM 1
+ jsr test_param
+ lda_PARAM 0
+ cmp #$03
+ #CHECK_ERROR
+ jsr remove_stack_frame
+ PRINTSNL("Local variable test completed!")
+ rts
+ .bend
+
+test_param:
+ .block
+ jsr new_stack_frame
+ PUSH $02 ; Local variables (here: parameters)
+ lda_LOCAL 0
+ clc
+ adc_LOCAL 1
+ sta_LOCAL 0
+ jsr remove_stack_frame
+ rts
+ .bend
+
+test_recursion:
+ .block
+ PUSH $02 ; Create local variables
+ lda #$04 ; Recursion counter
+ sta_LOCAL 0
+ lda #$01 ; Initial value
+ sta_LOCAL 1
+ CALL recursion, [0, 1], lda_LOCAL
+ lda_PARAM 1 ; Check for
+ cmp #$09 ; expected result
+ #CHECK_ERROR
+ jsr remove_stack_frame
+ ;; Check if stack is empty again
+ lda ds_pointer
+ cmp #<start_of_stack
+ #CHECK_ERROR
+ lda ds_pointer+1
+ cmp #>start_of_stack
+ #CHECK_ERROR
+ PRINTSNL("Recursion test completed!")
+ rts
+ .bend
+
+recursion:
+ .block
+ PUSH $02 ; Local variables (parameters)
+ lda_LOCAL 0
+ beq done
+ dec a
+ sta_LOCAL 0
+ lda_LOCAL 1
+ inc a
+ inc a
+ sta_LOCAL 1
+ CALL recursion, [0, 1], lda_LOCAL
+ lda_PARAM 1
+ sta_LOCAL 1
+done:
+ RETURN
+ .bend
+
+
+end_of_code:
.send code
+
diff --git a/sw/ttt/Makefile b/sw/ttt/Makefile
@@ -11,4 +11,4 @@ test: $(TARGET)_test.bin $(TARGET)_symon_test.bin
64tass $(CFLAGS) -DRUN_TESTS=true -DSYMON=true -l $(TARGET)_symon_test.l -L $(TARGET)_symon_test.lst "$<" -o "$@"
upload_test: $(TARGET).bin
- ../../roms/boot/boot.py $(TARGET)_test.bin
+ ../../tools/boot.py $(TARGET)_test.bin
diff --git a/roms/boot/boot.py b/tools/boot.py
diff --git a/tools/gfs.py b/tools/gfs.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+"""Access GATE2010 filesystem"""
+
+import os
+import argparse
+import pdb
+
+blocks_per_app = 2**16
+number_of_apps = 10
+
+class GFS:
+ def __init__(self):
+ self.dev = None
+ dev_file = self.find_device()
+ self.dev = os.open(dev_file, os.O_RDWR)
+ if (self.read_block(block=0))[0] != 0:
+ print("WARNING: Card not formatted for GFS!")
+
+
+ def __del__(self):
+ if self.dev != None:
+ os.close(self.dev)
+
+ def find_device(self):
+ # Find device
+ known_devices = [['Generic-', 'SD_MMC', '512', '968.8M'],
+ ['Generic', 'STORAGE_DEVICE', '512', '968.8M'],
+ ['Generic', 'STORAGE_DEVICE', '512', '3.8G']]
+ cmd = os.popen('lsblk --list -o name,vendor,model,phy-sec,size')
+ dev = None
+ for l in cmd.readlines():
+ l = l.split()
+ if (len(l) == 5):
+ for k in known_devices:
+ if k == l[1:]:
+ (dev, vendor, model, phy_sec, size) = l
+ break
+ if (dev != None):
+ break
+ if dev == None:
+ raise IOError("No suitable SD card found")
+
+ print("Using device /dev/{} {}".format(dev,
+ repr((vendor, model,
+ phy_sec, size))))
+ return(os.path.join('/', 'dev', dev))
+
+ def read_block(self, block):
+ return(os.pread(self.dev, 512, 512*block))
+
+ def write_blocks(self, block, data):
+ return(os.pwrite(self.dev, data, 512*block))
+
+
+ def format(self):
+ """Format SD card for GFS"""
+ self.write_blocks(block=0, data=b'\x00')
+ for a in range(number_of_apps):
+ self.write_blocks(block=a*blocks_per_app+1, data=b'\x00')
+
+ def ls(self):
+ """Return list of files on card."""
+ for a in range(number_of_apps):
+ b = self.read_block(block=a*blocks_per_app+1)
+ number_of_blocks = int(b[0])
+ if number_of_blocks > 0:
+ end = b[1:].find(b'\x00')
+ filename = b[1:end+1]
+ print("{filenumber}: {filename} (0x{blocks:X} blocks)".
+ format(filenumber=a, filename=filename.decode('utf-8'),
+ blocks=number_of_blocks))
+
+ def store(self, app_number, app_name, app_img):
+ with open(app_img, 'rb') as f:
+ img = f.read()
+ number_of_blocks = (len(img) // 512)
+ if (len(img) % 512) != 0:
+ number_of_blocks += 1
+ header = (bytes([number_of_blocks]) + app_name.encode('utf-8') + b'\x00')
+ self.write_blocks(block=app_number*blocks_per_app+1,
+ data=header)
+ self.write_blocks(block=app_number*blocks_per_app+2,
+ data=img)
+
+
+
+if __name__ == '__main__' or True:
+ fs = GFS()
+ list_of_commands = """\
+The following commands are available:
+ format : Format card for use with GFS
+ ls : Print list of apps on card.
+ store <nr> <name> <img> : Store image file <img> as application <nr>
+ with name <name>.
+"""
+
+ parser = argparse.ArgumentParser(description='Access GFS on SD Card.',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog=list_of_commands)
+ parser.add_argument('command', metavar='CMD',
+ help='Command')
+ parser.add_argument('args', nargs='*', metavar='ARGS', default=[],
+ help='Arguments for command')
+ args = parser.parse_args()
+ if args.command == 'format':
+ fs.format()
+ elif args.command == 'ls':
+ fs.ls()
+ elif ((args.command == 'store') and (len(args.args) == 3)):
+ fs.store(int(args.args[0]), args.args[1], args.args[2])
+ else:
+ parser.print_help()
+ if False:
+ b = fs.read_block(0)
+ for c in b:
+ print("{:02x}".format(c), end=" ")
+ print()
+ fs.format()
+ fs.ls()