;SDIO TEST PROGRAM tests the D16/M SD card interface module and any installed SD ; flash memory cards under Mode 1 (state-machine control). It ; determines the presence/write protect status of the cards, ; and then writes and reads each logical block of 512 bytes on ; a selected unit. It begins with block 0 and proceeds until ; a block write or read operation fails. The largest block ; address permitted by this version of the program is FFFFH; a ; 32 MB SD card "fails" at block address E980H. ; CPU "D16.TBL" ;program is D16 assembly language HOF "MOT16" ;output Motorola 16-bit hex format WDLN 2 ;word length = 2 bytes (not 1) ; ;Constants, addresses, and definitions. ; ETX EQU 03H ;ASCII Ctrl-C CR EQU 0DH ;ASCII carriage return LF EQU 0AH ;ASCII line feed SPACE EQU 20H ;ASCII space BACK EQU 08H ;ASCII backspace, Ctrl-H NUL EQU 00H ;ASCII null character ESC EQU 1BH ;ASCII escape character ZERO EQU 30H ;ASCII 0 SDSTATUS EQU 1000H ;SD card I/F STATUS port address SDMODE EQU 1001H ;SD card I/F MODE port address SDCTRL EQU 1002H ;SD card I/F CTRL port address SD_RD EQU 1003H ;SD card I/F shift register read addr SD_RW EQU 1004H ;SD card I/F shift register R/W addr SERAD EQU 2000H ;Serial Port A Data SERAS EQU 2001H ;Serial Port A Status ; ;Initializations. ; ORG 9000H ;program origin in RAM DII ;explicitly disable hardware interrupts JMP START ;and go ; ;Scratch memory and data storage. ; SEED1 DFS 1 ;seed no. 1 for random number generator SEED2 DFS 1 ;seed no. 2 for random number generator BASE DFS 1 ;base address scratch SDSTATVAL DFS 1 ;SD card interface STATUS value scratch CHAR1 DFS 1 ;first user input character CHAR2 DFS 1 ;second user input character INITCTR DFS 1 ;counter word for SDINIT subroutine RESPCTR DFS 1 ;counter word for SDRES subroutine RWCTR DFS 1 ;counter word for READBLOCK, WRITEBLOCK UNIT DFS 1 ;storage for logged SD card unit number UNITEN DFS 1 ;storage for logged SD card enable word BLOCK_NO DFS 1 ;storage for current test block number BLOCK0 DFS 256 ;storage for 256 word memory block 0 BLOCK1 DFS 256 ;storage for 256 word memory block 1 ; ;Text messages for display on the console. The number of characters in any ;message (including the nulls) must be even, or the assembler will malfunction ;(software bug). ; M1 DFB "SD CARD/INTERFACE TEST PROGRAM", NUL, NUL M2 DFB "UNIT ", NUL M3 DFB " IS PRESENT. IT IS WRITE ", NUL, NUL M4 DFB " IS ABSENT.", NUL M5 DFB "PROTECTED.", NUL, NUL M6 DFB "ENABLED.", NUL, NUL M7 DFB "ENTER UNIT TO TEST: ", NUL, NUL M8 DFB "UNIT ABSENT OR INVALID", NUL, NUL M9 DFB "TESTING BLOCK ", NUL, NUL M10 DFB "READ ERROR.", NUL M11 DFB "WRITE ERROR.", NUL, NUL M12 DFB "R/W COMPARE ERROR.", NUL, NUL ; ;Main program begins here. ; START LDS 0FFFFH ;initialize stack LDA 418 ;initialize first RNG seed STA (SEED1) LDA 1279 ;initialize second RNG seed (note odd) STA (SEED2) CSR CRLF ;move down the screen a bit CSR CRLF LDA M1 ;display sign-on message CSR MESSAGE CSR CRLF ; ;Examine SD card interface status to determine which cards are inserted, and ;whether they are write protected or write enabled. Display results. ; SDIOCHECK CSR CRLF INP (SDSTATUS) ;read the SD card status STA (SDSTATVAL) ;save it LDA 0 ;preset the unit number to 0 STA (UNIT) ; PRESENTLOOP CSR CRLF LDA M2 ;display "UNIT" CSR MESSAGE LDA (UNIT) ;display unit number CSR BIN2ASCII CSR CHOUT LDA (SDSTATVAL) ;get SD card status AND 0001 ;mask off all but /CARD INSERTED bit JOZ PRESENT ABSENT LDA M4 ;if bit is not zero, display "IS ABSENT" CSR MESSAGE LDA (SDSTATVAL) ;and go check the next one JMP NEXTCHECK PRESENT LDA M3 ;but if not, display "IS PRESENT" CSR MESSAGE ; PROTCHECK LDA (SDSTATVAL) ;get status again AND 0002 ;mask off all but /WRITE ENABLED bit JOZ ENABLED PROTECTED LDA M5 ;if bit isn't zero, display "PROTECTED" CSR MESSAGE LDA (SDSTATVAL) JMP NEXTCHECK ENABLED LDA M6 ;else display "ENABLED" CSR MESSAGE LDA (SDSTATVAL) ; NEXTCHECK LSR ;advance to next unit LSR STA (SDSTATVAL) LDA (UNIT) INC STA (UNIT) SUB 4 ;have we completed unit 3? Then JOZ DONECHECK ;recover status for later use, and quit JMP PRESENTLOOP ;otherwise, check next unit ; DONECHECK INP (SDSTATUS) ;save status again for subsequent code STA (SDSTATVAL) ; ;Next, ask which unit to test. ; WHATUNIT CSR CRLF CSR CRLF LDA M7 ;ask for unit number CSR MESSAGE ; FIRSTCHAR CSR CHIN ;get first character from the console STA (CHAR1) ;save it SUB ETX ;Ctrl-C? JOZ BAILOUT ;then return to the console monitor LDA (CHAR1) SUB BACK ;backspace? JOZ FIRSTCHAR ;then go get another LDA (CHAR1) SUB CR ;carriage return? JOZ SET0 ;then just assume unit 0 LDA (CHAR1) SUB ESC ;ESCAPE? JOZ START ;then restart the program LDA (CHAR1) ;if anything else, echo to the console CSR CHOUT SECONDCHAR CSR CHIN ;now, get a second character STA (CHAR2) ;save it SUB ETX ;Ctrl-C? JOZ BAILOUT ;then return to the console monitor LDA (CHAR2) SUB BACK ;backspace? JNZ CHKCR LDA BACK ;if so, backspace and erase first char CSR CHOUT LDA SPACE CSR CHOUT LDA BACK CSR CHOUT JMP FIRSTCHAR ;and go get a new one CHKCR LDA (CHAR2) SUB CR ;carriage return? JOZ SDSETUP ;then done. Go set up LDA (CHAR2) SUB ESC ;ESCAPE? JOZ START ;then restart the program ; JMP SECONDCHAR ;if anything else, ignore, keep looking ; SET0 LDA ZERO ;set default unit 0 STA (CHAR1) ; ;we have a user-input unit number now. We log the selected SD card and set up ;the UNITEN word for the WRITEBLOCK and SDEN subroutines. ; SDSETUP LDA (CHAR1) ;get the input character (unit number) CSR LOGUNIT ;log the unit JNC SDINITLOOP ;if no error, go initialize SD card CSR CRLF ;otherwise display "INVALID UNIT" LDA M8 CSR MESSAGE JMP WHATUNIT ;and ask for a new unit number ; ;now we initialize the SD card. ; SDINITLOOP CSR SDINIT ;initialize the SD card for normal ops NOP JOC SDINITLOOP ;if initialization fails, try it again ; ;then, proceed with test. ; CLR ;set starting block addr of 0000H STA (BLOCK_NO) CSR CRLF ;move down the page LDA M9 ;display "TESTING BLOCK " CSR MESSAGE ; MAIN_LOOP LDA (BLOCK_NO) ;get the current block number AND 0F000H ;isolate nybble 3 LSR ;place it into nybble 0 LSR LSR LSR BSR CSR BIN2ASCII ;convert it to ASCII CSR CHOUT ;and display it ; LDA (BLOCK_NO) ;isolate nybble 2 AND 0F00H BSR ;place it into nybble 0 CSR BIN2ASCII ;convert CSR CHOUT ;display ; LDA (BLOCK_NO) ;isolate nybble 1 AND 00F0H LSR ;place it into nybble 0 LSR LSR LSR CSR BIN2ASCII ;convert CSR CHOUT ;display ; LDA (BLOCK_NO) ;isolate nybble 0 AND 000FH CSR BIN2ASCII ;convert CSR CHOUT ;display ; ;now we initialize two blocks of memory ; LDX BLOCK0 ;fill memory block 0 with pseudo- LDY 256 ;random numbers CSR FILLR ; LDX BLOCK1 ;fill memory block 1 with FFFFH LDY 256 LDA 0FFFFH CSR FILL ; ;write the contents of BLOCK0 to the SD card, at the current block address ; LDY BLOCK0 LDX (BLOCK_NO) CSR WRITEBLOCK JNC READIT ;if write OK, go to read routine CSR CRLF ;otherwise, print WRITE error message CSR CRLF LDA M11 CSR MESSAGE JMP BAILOUT ; ;and read it back, into BLOCK1 ; READIT LDY BLOCK1 LDX (BLOCK_NO) CSR READBLOCK JNC COMPIT ;if write OK, go to compare routine CSR CRLF ;otherwise, print READ error message CSR CRLF LDA M10 CSR MESSAGE JMP BAILOUT ; ;now, we compare the blocks to see if we read what we wrote ; COMPIT LDX BLOCK0 ;compare block 0 with block 1 LDY BLOCK1 LDA 256 CSR COMPARE JNC FAIL ; ;if we get here, the compare is presumed to be good, and we proceed to the ;next logical block. ; LDA (BLOCK_NO) ;increment the block number INC STA (BLOCK_NO) LDA BACK ;send four backspaces to the console CSR CHOUT CSR CHOUT CSR CHOUT CSR CHOUT JMP MAIN_LOOP ;and loop again! ; FAIL CSR CRLF ;if comparison fails, error CSR CRLF LDA M12 CSR MESSAGE ; BAILOUT JMP 0000H ;return to console monitor ; ;PROGRAM SUBROUTINES begin here. ; ;ASCII2BIN SUBROUTINE accepts an ASCII character in bits 6-0 of the Accumulator, ; presumed to be representative of a hex digit from 0 to F, ; and converts it to binary. It then returns the binary ; number in bits 3-0 of the Accumulator, with bits 15-4 and ; the Carry flag cleared to zero. The subroutine checks to ; ensure that the ASCII input ranges from 0 to F; the alphas ; may be either upper or lower case. If the input is out of ; range, the subroutine returns the original character, with ; the Carry flag set. ; ASCII2BIN AND 007FH ;zero out all but bits 6-0 PSA ;save the result ; TEST1 SUB 0030H ;is number >= 30H? JMI OUT ;if not, out of range SUB 000AH ;is number >= 3AH? JMI _0TO9_1 ;if not, number is 30H to 39H ; TEST2 ADD 003AH ;A to F? Recover original character AND 005FH ;force upper case SUB 0041H ;is number >= 41H? JMI OUT ;if not, out of range SUB 0006H ;is number >= 47H? JMI ATOF ;if not, number is 41H to 46H JMP OUT ;but if it is, out of range ; _0TO9_1 POA ;get original character SUB 0030H ;subtract 30H to get binary CCF ;clear Carry RTN ;done ; ATOF POA ;get original character AND 005FH ;force upper case SUB 0037H ;subtract 37H to get binary CCF ;clear Carry RTN ;done ; OUT POA ;restore original character SCF ;assert Carry RTN ;done ; ;BIN2ASCII SUBROUTINE accepts the contents of the Accumulator and converts the ; least-significant four bits to ASCII. It then returns the ; ASCII character in bits 6-0 of the Accumulator, with bits ; 15-7 cleared to zero. ; BIN2ASCII AND 000FH ;zero out all but LS nybble SUB 10 ;test-- JMI _0TO9_2 ;if less than 10, must be 0 to 9 ADD 7 ;if A to F, add 55 total to orig value _0TO9_2 ADD 58 ;if 0 to 9, add 48 total to orig value RTN ;done ; ;CHIN SUBROUTINE reads an ASCII character from Serial Port A, and returns it in ; the Accumulator, with the MS byte cleared. ; CHIN INP (SERAS) ;get Serial Port A status AND 0008H ;mask all but Data Received bit JOZ CHIN ;if data not available, keep looking INP (SERAD) ;otherwise, get character AND 007FH ;mask all but least sig 7 bits RTN ;done ; ;CHOUT SUBROUTINE writes an ASCII character, in the LS byte of the Accumulator, ; to Serial Port A. ; CHOUT PSA ;save input character CHOU1 INP (SERAS) ;UART output buffer empty? AND 0010H JOZ CHOU1 POA ;when it is, recover the character AND 007FH ;mask all but least sig 7 bits OUT (SERAD) ;and transmit RTN ; ;COMPARE SUBROUTINE compares the contents of two identically-sized blocks of ; memory. It accepts the starting address of block 1 in IX, ; of block 2 in IY, and the number of words in each block in ; the Accumulator. If the blocks are the same, the subroutine ; returns with the Carry flag set; if any pair of words is ; found to be different, the subroutine aborts and returns ; with the Carry flag clear. In either case, the index ; registers will contain the addresses of the last words ; checked, and the Accumulator will contain the number of ; words remaining in each block. ; COMPARE PSA ;save the word count LDA (0+IX) ;get value from block 1 SUB (0+IY) ;compare to value from block 2 JNZ COMPARE1 ;quit if they're different POA ;but if the same, get word count DEC ;subtract one JOZ COMPARE2 ;zero? INX ;if not, point to next words INY JMP COMPARE ;and keep comparing ; COMPARE1 POA ;get word count (restoring stack) DEC ;subtract one CCF ;flag no compare RTN ;and quit immediately ; COMPARE2 SCF ;flag valid compare (stack is OK) RTN ; ;CRLF SUBROUTINE sends a carriage return and a line-feed to the console. It ; calls the CHOUT subroutine. ; CRLF LDA CR ;send a carriage return CSR CHOUT LDA LF ;send a line-feed CSR CHOUT RTN ;that's it ; ;FILL SUBROUTINE fills a block of memory with a value. It accepts the starting ; address of the block in IX, the number of words in IY, and the ; filling value in the Accumulator. ; FILL STA (0+IX) ;store value in current block address INX ;point to the next block address PSA ;set the value aside MYA ;get the word count DEC ;subtract one JOZ FILL1 ;zero? MAY ;if not, put count back POA ;recover the value JMP FILL ;and fill the next word in the block ; FILL1 POA ;done: correct the stack RTN ;before returning ; ;FILLR SUBROUTINE is just like the FILL subroutine, except that it fills the ; block with pseudorandom numbers instead of a fixed value. ; It calls the RANDOM subroutine. ; FILLR CSR RANDOM ;generate a random number STA (0+IX) ;store it in current block address INX ;point to the next block address MYA ;get the word count DEC ;subtract one JOZ FILLR1 ;zero? MAY ;if not, put count back JMP FILLR ;and fill the next word in the block ; FILLR1 RTN ; ;LOGUNIT SUBROUTINE logs a specific SD card unit for reading and writing. It ; sets up the unit enable word for the SDEN subroutine, as ; well as the write protect status. It accepts an ASCII unit ; number, taken from the command tail, and if the character ; does not represent a valid unit or if the unit is not ; present, the subroutine returns with the Carry flag set. ; LOGUNIT CSR ASCII2BIN ;convert unit char to binary unit number PSA ;set it aside JNZ LOGU1 ;zero? LDA (SDSTATVAL) ;yes AND 01H ;mask off all but presence bit JNZ LOGU4 ;error exit if unit not present LDA (SDSTATVAL) ;but if it is there, then AND 02H ;isolate write protect bit BSL ;and put it in MS byte of select word ORA 03H ;set "zero" select JMP LOGU5 ;and go save the select word LOGU1 SUB 1 ;one? JNZ LOGU2 LDA (SDSTATVAL) AND 04H JNZ LOGU4 LDA (SDSTATVAL) AND 08H BSL ORA 05H ;set "one" select JMP LOGU5 LOGU2 SUB 1 ;two? JNZ LOGU3 LDA (SDSTATVAL) AND 10H JNZ LOGU4 LDA (SDSTATVAL) AND 20H BSL ORA 09H ;set "two" select JMP LOGU5 LOGU3 SUB 1 ;three? JNZ LOGU4 LDA (SDSTATVAL) AND 40H JNZ LOGU4 LDA (SDSTATVAL) AND 80H BSL ORA 11H ;set "three" select JMP LOGU5 ; LOGU4 SCF ;not valid unit, or absent. Flag error POA ;restore stack RTN ;and quit ; LOGU5 STA (UNITEN) ;save completed select word POA ;recover unit number and store it STA (UNIT) RTN ;done ; ;MESSAGE SUBROUTINE sends a text message, whose starting address is in the ; Accumulator, to the console. It stops when it reaches the ; ASCII NUL character placed at the end of the string in ; the DFB statement. This subroutine uses the scratch word ; BASE, and calls the CHOUT subroutine. ; MESSAGE STA (BASE) ;set the base address LDX 0 ;set the index MESS1 LDA [BASE+IX] ;get the first (MS) character BSR JOZ MESS2 ;zero? Then we're done CSR CHOUT ;if not, output the character LDA [BASE+IX] ;now get the second (LS) character AND 00FFH JOZ MESS2 ;if zero we're done CSR CHOUT ;else output the character INX ;then point to next character pair JMP MESS1 ;and keep going MESS2 RTN ; ;PROMPT SUBROUTINE outputs a prompt (CR,LF,>) to the console. It calls the ; CHOUT subroutine. ; PROMPT LDA CR ;send a carriage return CSR CHOUT LDA LF ;send a line feed CSR CHOUT LDA ">" ;display the prompt CSR CHOUT RTN ; ;RANDOM SUBROUTINE generates a pseudorandom number using a Fibonacci sequence. ; It operates on numbers stored in memory locations SEED1 and ; SEED2, and it returns the 16-bit number in the Accumulator. ; WARNING: This generator is useful for electronic testing, ; but it is not suitable for critical applications such as ; cryptography. ; RANDOM LDA (SEED1) ;get the first seed PSA ;set it aside for a moment LDA (SEED2) ;get the second seed STA (SEED1) ;save it as SEED1 for next run POA ;recover the first seed again SHL ;rotate left (not through CF) 4 times ADC 0 ;(multiplying by 16) SHL ADC 0 SHL ADC 0 SHL ADC 0 ADD (SEED2) ;add to SEED2 STA (SEED2) ;and save as new SEED2 for next run RTN ;random number is still in Accumulator ; ;READBLOCK SUBROUTINE reads a block from the selected SD card. It accepts the ; logical sector number (0-FFFFH) in IX and the address of ; the 256-word memory buffer in IY. If the read was ; successful, it returns with the Carry flag clear. ; READBLOCK PSA ;save registers PSX PSY ;(must be pushed last) ; CSR SDEN ;enable the SD card LDA 17 ;issue block READ command to SD card CSR SDCMD ;(17,*512,95H) ADD 0FFFFH ;error exit if response was not 00H JOC RESTORE ; CSR SDRES ;read the start byte, FEH ; LDA 256 ;and then, for 256 words-- STA (RWCTR) READ01 CSR READBYTE ;read a byte from the SD card BSL ;pack it into the MS byte of the word STA (0+IY) CSR READBYTE ;read a second byte ORA (0+IY) ;pack it into the LS byte STA (0+IY) ;store completed word into the buffer INY ;point to next word DSZ (RWCTR) JMP READ01 ; CSR READBYTE ;read 16-bit checksum and ignore it CSR READBYTE CCF ;indicate successful read ; JMP RESTORE ;return via SDINIT RESTORE routine ; ;READBYTE SUBROUTINE receives a byte from the SD card and returns it in the ; low Accumulator. ; READBYTE SET ;send a dummy byte (FF) OUT (SD_RW) INP (SD_RD) ;read card response byte AND 0FFH RTN ; ;SDCMD SUBROUTINE sends a command to the SD card and returns a response. It ; accepts the command in AC low and the argument (a sector or ; block address) in IX. It "falls into" the SDRES subroutine, ; which returns the response byte in AC low. ; SDCMD ORA 0040H ;fit "start bits" to command byte CSR WRITEBYTE ;and send it ; MXA ;get the block address SHL ;multiply by 2 MAX ;store it back CLR ;then, if the multiply op carried, ADC 0 ;send 01H as arg0 CSR WRITEBYTE ;but if not, send 00H as arg0 ; MXA ;send high byte of IX as arg1 BSR CSR WRITEBYTE ; MXA ;send low byte of IX as arg2 CSR WRITEBYTE ; CLR ;send 00H as arg3 (thus, we have CSR WRITEBYTE ;multiplied the block address by 512) ; LDA 0095H ;send CRC byte (actual for power-up init, CSR WRITEBYTE ;dummy for subsequent commands) ; ;SDRES SUBROUTINE receives the response from the SD card, which may arrive at up ; at up to 8 bytes after the command. Its code is executed on ; every SDCMD call, as well as on SDRES calls. ; SDRES LDA 64 ;but, loop up to 64 times! STA (RESPCTR) SDRES01 CSR READBYTE ;read a response byte SUB 0FFH ;exit loop if response is any but FFH JNZ SDRES02 DSZ (RESPCTR) JMP SDRES01 SDRES02 ADD 00FFH ;restore accumulator RTN ; ;SDDIS SUBROUTINE negates the chip enable on the SD card. ; SDDIS LDA 01H ;program SD card disabled, MODE 1 OUT (SDMODE) RTN ; ;SDEN SUBROUTINE asserts the chip enable on the SD card. ; SDEN LDA (UNITEN) ;program SD card enabled, MODE 1 OUT (SDMODE) RTN ; ;SDINIT SUBROUTINE initializes the SD card for use after power-up. If the ; process is successful, it returns with the Carry flag clear. ; Its RESTORE routine is shared with other subroutines ; (READBLOCK and WRITEBLOCK), which jump to it rather than ; incorporating a copy themselves. ; ; SDINIT PSA ;save registers PSX PSY ;(must be pushed last) ; CSR SDDIS ;de-select the SD card LDA 10 ;transmit 80 dummy clock cycles STA (INITCTR) SDINIT01 CSR READBYTE DSZ (INITCTR) JMP SDINIT01 CSR SDEN ;re-select the SD card ; CLR ;send command 0 to SD card (select SPI) LDX 0000H CSR SDCMD ;(00,00,00,00,00,95H) DEC ;if response is not 01H then exit with SCF ;carry flag set to indicate error JNZ RESTORE ; SDINIT02 CSR READBYTE ;generate 8 dummy clocks on SD card LDA 01H ;send command 1 (args, CRC don't matter) CSR SDCMD ;(01,00,00,XX,XX,95) DEC ;loop until response not 01H JOZ SDINIT02 INC ;clear Carry flag if response was 00H ADD 0FFFFH ;else set it to indicate error ; RESTORE PSF ;save Carry flag CSR SDDIS ;disable the SD card CSR READBYTE ;generate 8 dummy clock cycles POF ;restore Carry flag ; POY ;recover registers POX POA RTN ; ;WRITEBLOCK SUBROUTINE writes a block to the selected SD card. It accepts the ; logical sector number (0-FFFFH) in IX and the address of ; the 256-word memory buffer in IY. If the write was ; successful, it returns with the Carry flag clear. ; WRITEBLOCK PSA ;save registers PSX PSY ;(must be pushed last) ; LDA (UNITEN) ;are we write protected? AND 0FF00H JOZ WRIT01 ;if not, proceed SCF ;otherwise, flag error JMP RESTORE ;and exit WRIT01 CSR SDEN LDA 24 ;issue SD card block WRITE command CSR SDCMD ;(24,*512,95H) ADD 0FFFFH ;error exit if response is not 00H JOC RESTORE CSR READBYTE ;send 8 dummy clock cycles LDA 00FEH ;send start byte to SD card CSR WRITEBYTE ; LDA 256 ;for 256 words-- STA (RWCTR) WRIT02 LDA (0+IY) ;get word from buffer BSR ;send MS byte to SD card CSR WRITEBYTE LDA (0+IY) ;get word again CSR WRITEBYTE ;and send the LS byte to the card INY ;point to the next buffer word DSZ (RWCTR) JMP WRIT02 ; CSR READBYTE ;send dummy checksum (FFFFH) CSR READBYTE ; CSR READBYTE ;read data response byte (05H) ; WRIT03 CSR READBYTE ;and loop until no longer busy JOZ WRIT03 ;(could take a while) CCF ;indicate successful write op JMP RESTORE ;and return via SDINIT RESTORE code ; ;WRITEBYTE SUBROUTINE accepts a byte in low Accumulator and sends it to the ; SD card interface. ; WRITEBYTE OUT (SD_RW) ;send the byte RTN ; END ;end of program code