\   Filename: vis-sdcard.fs
\    Purpose: access sdcard via spi1 bit banging
\        MCU: GD32VF103
\      Board: * , tested with Logan Nano
\       Core: Mecrisp-Quintus 0.27 by Matthias Koch.
\   Required: Mecrisp-Quintus >= 0.27
\     Author: Matthias Koch
\      Date : Jun 2020
\   Requiers: registernames.fs
\ Literature: https://www.convict.lu/pdf/ProdManualSDCardv1.9.pdf
\    Licence: GPLv3
\  Changelog:
\ 2020-07-31:  MaBi002 added "noanswer?"
\ 2020-08-01:  MaBi003 added sd-error?
\ 2020-08-01:  MaBi004 corrected typo in sd-erase
\ 2020-08-18:  MaBi005 renamed sd-error? to sd-exit for multilevel exits
\ 2020-08-19:  MaBi006 correctet error in sd-read


\ ==============================================================================
\ Usage of the error handlers:

\ There are three levels of error handling. Level 0 is a classic 'message and
\ leave' level. It tells the reason why it leaves and performs a quit that will
\ bring you back to the highest (or lowest; depends on the point of view)
\ interpreter level. Level 1 will show a message and level 2 won't. Both levels
\ will do a multilevel exit. That is: They will jump to the level that was saved
\ by a 'catch' done before. The purpose if this is as follow: You will be able
\ to do calls for SD Card functions and in case of failure nothing will
\ happen. Think of a program running for 'ever' doing something important (of
\ course it does!) and logging some data by the way. What happens if the SD Card
\ runs out of memory? Will this failure 'freeze' your very important program? It
\ will if there is no error handling. It will also if you choose error level 0
\ (message and quit), but it won't if you choose error levels 1 or 2. Nothing
\ will disturb the running program. Of course some data will not be stored to
\ the SD Card or read from the SD Card. If you want to inspect the error later
\ on there is a variable last-error that holds the number of last invoked error.
\ ==============================================================================

\ -------------------------------------------------------------
\   Leitungen für die SPI-Schnittstelle definieren
\ -------------------------------------------------------------


1 12 lshift constant CS_TF  \ GD_PB12
1 13 lshift constant SCLK   \ GD_PB13
1 14 lshift constant MISO   \ GD_PB14
1 15 lshift constant MOSI   \ GD_PB15

: miso@ ( -- 0|1) GPIOB_ISTAT @  MISO and 14 rshift ;

: sclk-high    SCLK  GPIOB_BOP h! ;
: sclk-low     SCLK  GPIOB_BC  h! ;

: mosi-high    MOSI  GPIOB_BOP h! ;
: mosi-low     MOSI  GPIOB_BC  h! ;

: -spi1 ( -- )  CS_TF GPIOB_BOP h! ; \ deselect SPI
: +spi1 ( -- )  CS_TF GPIOB_BC  h! ; \ select SPI

: spi-init ( -- )
  $14114444 GPIOB_CTL1 !  \ Set PB12, PB13 and PB15 as output, PB14 as input.
   -spi1  sclk-low  mosi-high \ Fürs Reset benötigter Ruhezustand: /CS and DI=MOSI high, Clock low.
;

\ -------------------------------------------------------------
\   Leitungen prüfen
\ -------------------------------------------------------------

\ : b-sclk spi-init begin sclk-high 1000000 0 do loop sclk-low 1000000 0 do loop key? until ;
\ : b-mosi spi-init begin mosi-high 1000000 0 do loop mosi-low 1000000 0 do loop key? until ;
\ : b-cs   spi-init begin -spi      1000000 0 do loop +spi     1000000 0 do loop key? until ;
\ : s-miso spi-init begin miso hex. cr key? until ;

\ -------------------------------------------------------------
\   Kommunikation über die SPI-Leitungen
\ -------------------------------------------------------------

: >spi> ( c -- c )  \ bit-banged SPI, 8 bits
  8 0 do
    dup $80 and if  mosi-high else  mosi-low then
     sclk-high
    2*  miso@ or
     sclk-low
  loop

  $FF and ;

\ Single byte transfers

: spi> ( -- c ) $FF   >spi> ;  \ read byte from SPI
: >spi ( c -- )  >spi> drop ;  \ write byte to SPI

\ ==============================================================================
\ Errorhandling Errornumbers from SD card are in range of 2-255. MaBi005
\ ==============================================================================

\ I use the catch throw mecanism. Catch and throw lived in common/mulitask.fs. I
\ made slightly canges: don't care about multitasker; give warning if throw is
\ used without a preceeded catch.

0 Variable handler

: quit ( -- )  \ quit takes care of handler
  0 handler ! quit
; 

: catch ( x1 .. xn xt -- y1 .. yn throwcode / z1 .. zm 0 )
  (do) \ push I and I'
  sp@ >r handler @ >r rp@ handler !  execute
  r> handler !  rdrop  0 unloop ;

: throw ( throwcode -- )
  dup
  IF
    handler @ 0=
    IF \ unhandled error: quit MaBi
      ." Throw without catch! Quit!" quit
    THEN 
    handler @ rp! r> handler ! r> swap >r sp! drop r>
    UNLOOP  EXIT
  ELSE
    drop
  THEN ;

  
\ I take in account three types of error:
\ - card saying there is an error (2-254),
\ - no respond from card (256)
\ - This is no error: empty answer (255)
\ so all 'valid' errors are in the range 2-256


1 Variable sd-error-verbose-level \ 0=verbose and quit, 1=verbose, 2=silent
0 Variable last-error

: sd-error-level+- ( n -- ) \ change verbosity of SD card errormessages
  sd-error-verbose-level
  dup @ rot + 3 mod
  swap !
;

: sd-error-level+ ( n -- ) \ increase verbosity of SD card errormessages
  1 sd-error-level+-
;

: sd-error-level- ( n -- ) \ increase verbosity of SD card errormessages  
  -1 sd-error-level+-
;

: sd-error-level? ( -- ) \ show error level
  sd-error-verbose-level @
  CASE
   0 OF ." Verbose and quit!" ENDOF
   1 OF ." Verbose exit!" ENDOF
   2 OF ." Silent exit!" ENDOF    
    drop
  ENDCASE
;
  
: error$! ( n / string -- )
  [char] " parse rot , string,
;


\ this list is a "null terminated" list

Create error$
$100 error$! No answer from SPI! Missing SD Card or Initialisation!"
2 error$! Card is idle!"
4 error$! Erase command beyond border!"
8 error$! Illegal command!"
16 error$! CRC Error!"
32 error$! Wrong sequence of erase commands!"
64 error$! Misaligned address!"
128 error$! Wrong parameter (blocknumber?)!"
0 , \ error$! Out of error list!"


: error. ( n -- )
  error$
  BEGIN
    2dup @
  WHILE
      over @ and 
      IF
        cr dup cell+ count type
      THEN
      cell+ count + even
  REPEAT
  2drop
;  

: sd-error. ( f -- f )
  sd-error-verbose-level @
  2 <>
  IF error. THEN
;

: sd-exit ( n -- n )
  dup
  dup last-error !
  $1FE and over 255 <> and 
  IF
    sd-error-verbose-level @
    IF
      sd-error. throw
    ELSE
      sd-error. quit
    THEN
  THEN
;
  
0 Variable answer#  \ MaBi002

: noanswer? ( n -- flag )  \ MaBi005
  answer# @ <            
  1 answer# +!
  IF $100 sd-exit THEN
;


\ end of error handling routines
\ ==============================================================================


\ ==============================================================================
\   Kommunikation mit der SD-Karte
\ ==============================================================================

0 variable crc

: (sd-cmd) ( arg cmd -- u )
  2 us
   +spi1
            $FF  >spi
         $40 or  >spi \ Command
  dup 24 rshift  >spi \ Argument, 32 Bits, 31-24
  dup 16 rshift  >spi \ 23-16
  dup  8 rshift  >spi \ 15-8
                 >spi \ 7-0
          crc @  >spi \ CRC-Feld, welches bei SPI-Schnittstelle nur für den ersten Befehl gebraucht und sonst ignoriert wird.

  0 answer# ! \ MaBi002
  begin $FF    \ Auf die Antwort von der SD-Karte warten 
    10 noanswer?   \ MaBi005
    >spi>
    sd-exit \ MaBi005
    dup $80 and
  while drop
  repeat 
;

: sd-cmd ( arg cmd -- u ) (sd-cmd) -spi1 ;

512 buffer: sd.buf

: sd-copy ( f n -- )
  swap
  begin
    $FE <>
  while $FF  >spi> 
  repeat
  0 do
    $FF  >spi> sd.buf i + c!
  loop
  $FF dup  >spi  >spi
;

: sd-cmd-r3-r7 ( arg cmd -- u response )
  (sd-cmd)

   spi>    8 lshift
   spi> or 8 lshift
   spi> or 8 lshift
   spi> or

   -spi1
;

: sd-cmd-r2 ( arg cmd -- u ) \ 17-Bytes lange Antwort, die ersten 16 davon im Puffer zuückgeben.
  (sd-cmd)
  16 sd-copy
   -spi1
;

\ -------------------------------------------------------------
\   Größe der Karte bestimmen
\ -------------------------------------------------------------

0 variable #sd-blocks

: read-sd-size ( -- )  \ Return card size in 512-byte blocks

  0 9 sd-cmd-r2 \ Send CSD

  sd.buf 7 + c@    8 lshift
  sd.buf 8 + c@ or 8 lshift
  sd.buf 9 + c@ or
  1+ 10 lshift

  #sd-blocks ! \ Zahl der Blöcke speichern
;

: sd-size ( -- u ) #sd-blocks @ ;

\ -------------------------------------------------------------
\   Initialisierung
\ -------------------------------------------------------------

: sd-init ( -- )  \ Initialize card, show messages
   spi-init
  100 ms

  10 0 do $FF  >spi loop \ Mindestens 74 Taktpulse mit /CS high

  begin
    $95 crc ! 0 0 sd-cmd  \ CMD0 go idle
  $01 = until

  1 crc !
  0 59 sd-cmd drop \ CRC off

  ." SD-Card type: "
  $87 crc ! $1AA 8 sd-cmd-r3-r7 hex. dup hex. 1 =

  if \ Ver 2.00 or later SD Memory Card

    begin
              0 55 sd-cmd drop \ Es folgt einer der ACMD-Kommandos
      $40000000 41 sd-cmd       \ ACMD41, mit HCS=1, da wir hier hohe Kapazitäten unterstützen
    0= until

    0 58 sd-cmd-r3-r7 ." OCR: " hex. hex. \ Read OCR register. Das ist ein R3-Antworttyp, aber die haben die gleiche Länge.

    512 16 sd-cmd ?dup if ." Wrong block size: " hex. then \ Blockgröße auf 512 Bytes setzen

    read-sd-size
  else
    ." Ver 1.X SD Memory Card or not SD Memory Card" cr
    exit
  then

  ." with " sd-size hex.
  ." blocks or " sd-size 2/ 1024 / .
  ." MB initialised." cr
;

\ MaBi005
: sd-init ( -- )  \ define cached sd-init over old sd-init
  ['] sd-init catch drop ;


: sd-init-silent ( -- )  \ Same as sd-init but silent mode (no messages)
   spi-init
  100 ms

  10 0 do $FF  >spi loop \ Mindestens 74 Taktpulse mit /CS high

  begin
    $95 crc ! 0 0 sd-cmd  \ CMD0 go idle
  $01 = until

  1 crc !
  0 59 sd-cmd drop \ CRC off

  $86 crc ! $1AA 8 sd-cmd-r3-r7 drop 1 =

  if \ Ver 2.00 or later Memory Card

    begin
              0 55 sd-cmd drop \ Es folgt einer der ACMD-Kommandos
      $40000000 41 sd-cmd       \ ACMD41, mit HCS=1, da wir hier hohe Kapazitäten unterstützen
    0= until

    0 58 sd-cmd-r3-r7 drop drop \ Read OCR register. Das ist ein R3-Antworttyp, aber die haben die gleiche Länge.

    512 16 sd-cmd ?dup if ( error: wrong block size ) drop then \ Blockgröße auf 512 Bytes setzen

    read-sd-size

  else
    ( error: Ver 1.X Memory Card or not an Memory Card )
    exit
  then

  ( no error?. card is initialized )
;

\ MaBi005
: sd-init-silent ( -- )  \ define cached sd-init-silent over old sd-init-silent
  ['] sd-init-silent catch drop
;


\ -------------------------------------------------------------
\   Identifikation anzeigen
\ -------------------------------------------------------------

\ : show-sd-size ( -- )
\   0 9 sd-cmd-r2 \ Send CSD
\   sd.buf 16 dump
\ ;

: show-sd-size ( -- )  \ MaBi005
  0 9 ['] sd-cmd-r2 catch
  \ 0=
  IF
    2drop
  ELSE    
    sd.buf 16 dump
  THEN
;

\ : show-sd-id ( -- )
\   0 10 sd-cmd-r2 \ Send CID
\   sd.buf 16 dump
\ ;

: show-sd-id ( -- )  \ MaBi005
  0 10 ['] sd-cmd-r2 catch \ Send CID
 \ 0=
  IF
    2drop
  ELSE    
    sd.buf 16 dump
  THEN
;
\

\ -------------------------------------------------------------
\   Block lesen und schreiben
\ -------------------------------------------------------------

: sd-read ( block -- ) \ Einen 512-Byte-Block von der SD-Karte lesen
  17 (sd-cmd) \ Single block read
  512 sd-copy
  -spi1
;

\ MaBi006
: sd-read ( block -- )  \ define cached sd-read over old sd-read
  ['] sd-read catch IF drop THEN \ MaBi006
  -spi1
;

: sd-write ( block -- ) \ Einen 512-Byte-Block auf die SD-Karte schreiben
  24 (sd-cmd) 
    
  $FE  >spi   \ DATA_START_BLOCK


  512 0 do
    sd.buf i + c@  >spi 
  loop

   spi> drop  spi> drop \ ." CRC " spi> 8 lshift spi> or hex. \ Data response
  begin  spi> $FF = until \ Warte, bis Busy-Flag verschwindet
  -spi1
  \ THEN
;

\ MaBi005
: sd-write ( block -- )  \ define cached sd-write over old sd-write
  ['] sd-write catch  2drop
  -spi1
;


\ -------------------------------------------------------------
\   Alles löschen
\ -------------------------------------------------------------

: sd-erase ( -- )
           0 32  sd-cmd ( sd-error?. ) hex. \ Startblock fürs Löschen
  sd-size 1- 33  sd-cmd ( sd-error?. ) hex. \   Endblock MaBi004

        0 38 (sd-cmd) ( sd-error?. ) hex. \ Löschen ausführen
  begin  spi> $FF = until \ Warte, bis Busy-Flag verschwindet
   -spi1
;

\ MaBi005
: sd-erase ( -- )  \ define cached sd-erase over old sd-erase
  ['] sd-erase catch drop
;


\ -------------------------------------------------------------
\   Ausprobieren
\ -------------------------------------------------------------

: db ( -- ) sd.buf 512 dump ;  \ Dump sector buffer to screen

: view ( u -- ) sd-read db ;  \ Read sector u and dump to screen

: sector-empty? ( -- flag )  \ Test if sector is completely filled with zeros
  true  \ assume that sector is empty
  512 0
  do
    sd.buf i + 
    @ 0<>
    if
      drop false leave  \ sector is not empty 
    then
  4
  +loop
;

: n-view ( limit-sector start-sector -- )
         \ Prints content of a number of sectors that are not empty.
         \ If empty, only the sector number is printed.
         \ Example: 9000 8192 n-view
         \ Reads in sectors 8192-8999 and shows content or sectornumber only.
  cr
  do
    i sd-read
    sector-empty?
    if
      i .
    else
      cr i .  i view cr
    then
  loop
;

: pattern1 ( -- )
  512 0 do i i sd.buf + c! loop
;

: pattern2 ( -- )
  512 0 do i 1 rshift i sd.buf + c! loop
;

: empty ( -- )
  sd.buf 512 0 fill
;


\ sd-init      \ Muss als erstes aufgerufen werden
\ 10 0 n-view  \ Die ersten 10 Sektoren lesen und wenn nicht leer, dann anzeigen

\ ==============================================================================
\ some tests MaBi
\ ==============================================================================

0 Variable parameter

: cs ( many -- )  \ cs is clear stack
  depth 0
  DO drop
  LOOP
;
  
: t ( xt n -- ?? ) \ calls the xt with parameter n; runs through all error levels 
  parameter !
  ' pad !
  0 sd-error-verbose-level !
  3 0 DO
    cr
    cs 1 2 parameter @
    cr sd-error-verbose-level @ ." Error level: " 1+ . 
    sd-error-level+
    pad @ execute cr ." … … … " ." Stack: " .s
  LOOP
  cr
;

\ Try the following code with an inserted SD Card and with an empty SD slot

\ 1 t sd-init \ doesn't need a parameter, but 't' does.
\ 1 t sd-write
\ 1 t sd-read
\ -1 t sd-write
\ -1 t sd-read
\ 1 t sd-erase \ doesn't need a parameter, but 't' does.

    
      