eris2010

Documentation: http://frombelow.net/projects/eris2010/
Clone: git clone https://git.frombelow.net/eris2010.git
Log | Files | Refs | Submodules | README | LICENSE

commit a303e6a386627a93262f5c3cfb94a24a3ea48a6f
parent f0aebcf4f7222faa4b54012cd42fa5495cdd098f
Author: Gerd Beuster <gerd@frombelow.net>
Date:   Sat,  5 Dec 2020 22:10:07 +0100

Support for SD Cards

Diffstat:
Mdoc/Documentation.txt | 4++++
Mmisc/sd_card/card_info.csv | 2+-
Mroms/boot/boot.asm | 452++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mroms/boot/boot_macros.inc | 301+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msw/Makefile.common | 2+-
Asw/sd_card/Makefile | 3+++
Asw/sd_card/sd_card.asm | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msw/stack_test/stack_test.asm | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msw/ttt/Makefile | 2+-
Rroms/boot/boot.py -> tools/boot.py | 0
Atools/gfs.py | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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()