たまりば

パソコン・インターネット パソコン・インターネット三鷹市 三鷹市

SDカードから1セクタ読み取るまでの手順解説
2015年10月05日 01:09

数年前からマイコンでSDカードを読みたいと思い続けていたが、やっと実行に移した。(そしてそれをブログに書くまでに1ヶ月…)
使うマイコンはもちろん慣れ親しんだPIC。中でも最近一番のお気に入りの10F200…といきたかったのだが多少無理があったので12F510に。
10F20012F510
Flash256ワード1024ワード
RAM16バイト38バイト
I/Oピン4本6本
主に問題なのはI/Oピン数。SDの操作に4ピン使うので、10F200では他にピンが余らない。またFlash容量も心許ない。読み取りだけならなんとかなるにしてもデバッグ用に出力などする余裕が全くないだろう。

回路はこちら。単純につないだだけ。
回路写真
現在、第0セクタ512バイトを読んだところである。
SDカード_1セクタ読み出し
次はファイルシステムを解釈してファイルの読み書きをしようと思っているが、その前に今回得た知見をブログにまとめておくことにする。



まず参考資料。

1. https://www.sdcard.org/downloads/pls/simplified_specs/
数年前からSDカードの仕様は公開されている。完全な仕様書ではなく「Simplified」ではあるものの、れっきとした公式資料。
これが公開される前はマルチメディアカード(MMC)としてのコマンドで扱うしかなかった。SPIモード部分についてはMMCとSDは互換性があるのでそれでも扱えるのだが、4ピンを使う高速なSDカード本来のプロトコルは使えない。
この仕様書の公開によってついにSDカード本来のプロトコルが使えるように…なったかと思いきや実は足りない部分があってMMCの仕様書を見て埋めないといけないとも聞く。
まあとりあえず今のところSPIモードしか使うつもりはないので困らない。

2. MMC/SDCの使いかた
ChaNさんのサイト。日本語のサイトの中ではもっとも詳しく説明されていると思う。何度見たか分からない。

この2つの資料で分からない事はそのたびに適当に検索して調べていたのでどのサイトを見たかあまり覚えていない。
思い出せるところでこのへんか。
http://bluefish.orz.hm/sdoc/psoc_mmc.html
http://bitcraft.web.fc2.com/embedded/microchip/microchip.html



次にSDから1セクタ読むまでの手順…の前に色々解説。

SDのSPIモードでの操作は、(当たり前ではあるが)すべてSPIのプロトコルに則った通信で行われる。つまりすべての通信は1バイト単位で行われる。
そしてすべての通信は機器側がマスター、SDカードがスレーブで行われる。つまりSDカードが自発的にデータを送ってくることはなく、常にマスターがクロックを送ることでデータを読みだすことになる。
なおSPIは全二重の通信ができる規格だが、SDカードの操作では大抵どちらかがデータを出すときはもう一方はデータを出さない(0xFFを出す)。両方がデータを出しているのはデータパケットの受信を止める時くらいか。
SDモードでは1本のデータ線で半二重通信をするのでそれと合わせたのかもしれない。あるいはそもそも必要ないしそっちの方が楽ということかもしれない。
SPIモードに入ってからの通信はすべてコマンドを送信して返答を読み取るトランザクション単位で行われる。
そしてトランザクションはパケット単位で行われる。1つのトランザクションの流れは次のようになる。
・コマンド送信
・レスポンス受信
・(あれば)データ送信or受信
なおコマンド送信後すぐにレスポンスが読み取れるとは限らない。0xFFが読めた場合それを読み飛ばす必要がある。
1つのトランザクションが終わった後には1バイト以上のクロックを送信しなければならないという奇妙な仕様がある。
マスターはクロックをいつでも中断できる(ACMD41の最中を除く)。

コマンドは、6バイト固定で次のような構造をしている。
・1バイトのコマンド番号
 うち頭2bitはスタートビットと、通信方向を示すTransmission Bit
・4バイトの引数
・1バイトのCRC
 うち末尾1bitはEnd Bit(って書いてあるけどストップビットって名前のほうが馴染みがある)
1バイト単位なのにスタート・ストップビットがあるという奇妙な構造だが、1bit単位で通信を行うSDモードと共通化したためであろう。通信方向を示すのもSPIモードでは送受信に別の線が割り当てられているしCSもあるのだから不要である。
CRCはSPIモードでは基本的にはチェックされないため適当な値を入れればよいが、そうでないところもあり要注意だ。
レスポンスには以下の種類がある。
・R1: 1バイトで、単純な状態を返す。
・R1b: R1の後にビジー応答(連続したLowレベル)を返す。
・R2: 2バイトで、R1に加え詳細なエラー状態を返す。
・R3/R7: R1に加え4バイトの情報を返す。
・R4/R5: SDIOのための予約らしい。
・R6: どうやらSDモード専用のようだ。
データパケットは、任意バイト数のデータを1バイトのヘッダと2バイトのCRCで挟んだ構造である。
コマンドと異なりデータパケットのヘッダは先頭bitが0ではない。こちらはSPIモード専用なので1バイト単位で読む前提のようだ。



改めて、SDから1セクタ読むまでの手順。

・コマンド送信前の儀式
まずSDカードの電源を投入してから1ms待ち、74個以上のクロックを送ることでコマンドを受け付ける準備が完了する。
74とは中途半端だが、この時点ではSDモードなのでクロック数が8の倍数である必要がないためだ。もっともSPIで制御するなら切りよく80クロック送るのが楽だろう。

これ以下はSPIモードと考えて良い。

・CMD0 リセット
このコマンドは本来の機能の他にSPIモードへの切り替え機能を持っており、CSをLowにした状態で送ることでSDカードをSPIモードにすることができる。このコマンドを送信する時点ではSPIモードではないのだが、コマンド送信についてはSDモードとSPIモードでほぼ同じ形式なのでSPIモードのつもりで問題ない。
SDモードで使うときにはCSをHighでこのコマンドを送ることで、SPIモードのレスポンスとは違う何かが起こる(よく知らない)。
引数は任意と書かれているところと0固定と書かれているところがあってよく分からない。たぶん任意だと思うが、まああえて0以外を送る理由もない。
SPIモードでないためCRC必須。引数が0の場合CRCは0x95である(ストップビット含む)。
返答はidleフラグのみの立った「0x01」であるはずである。

・CMD8 インターフェースコンディション確認
引数は、0x000001XX。
XXは任意。0xAAが推奨されているが、私は0xABをおすすめする。これにすると、CRCの値をCMD0と同じにすることができる。
仕様書にはSPIモードでもCRC必須とある。
ネット上の情報では「CMD0のみCRC必須」と書かれているものがあるが、どうもCMD8はSDカードのver.2からできたコマンドのようで、それ以前はCMD0のみがCRC必須のコマンドだったせいで混乱しているように見える。
このコマンドは対応している電圧範囲を取得するコマンドであり、レスポンスはR7で引数に指定された電圧範囲に対応しているかと、上で「任意」と書いたチェックパターンがそのまま返ってくる。
ただ、これは全電圧範囲に対応しているか否かを調べるだけで、普通は対応しているし、対応していなかったらどうしようもないので、調べる意味はあまりない。
が、これを送ることでSDHC/SDXCはCMD58とCMD41を有効化するということなので、送信は必須のようだ。
なおこれはver2以降のカードの場合であり、それより前のカードでは不正コマンドとなり、返答はR1で「0x05」となる。つまりver2以降か否かを調べるという効果もある。
1バイトしか返答がないところ5バイト読むと、残りはデータ線がHighのままなので0xFFが読める(害はない)。

・CMD1またはACMD41 初期化
初期化コマンドは仕様書を読む限り、「CMD1を使うとMMCとSDの区別がつかないので非推奨」と読めるのだが、手元のいくつかのカードで試してみたところ、CMD1を受け付けないカードがいくつかあった。ACMD41を使わなければならないのだろうか。しかしそれではSDのSimplified仕様書が公開される前にMMCとして扱えていたという情報と矛盾するように思う。
なんにせよ今回使ったSDはCMD1に反応したので今回のコードではCMD1のままである。
CMD1は引数任意でレスポンスはR1。初期化が完了するまではidleを示す0x01、完了したらエラー無しの0x00が返る。
ACMD41の引数はホストのSDHC/XCサポートの有無を示すHCSの1bitのみ(他のbitは予約)、応答はR3でOCRレジスタの内容が返る。
idleでなくなるまでCMD1またはACMD41を送り続ける。

・CMD58 OCRレジスタ読み取り
レスポンスはR3で、32bitのOperation Conditions Registerの値を読み取る。内容は対応している電圧範囲、SDHC/SDXCか否かを示すbit、ビジーフラグ。電圧範囲はCMD8でも確認してこちらでも確認するのだろうか。よく分からない仕様だ。
なお今書いていて気づいたのだが、このコマンドはSDカードでは予約扱いになっており、OCR読み取りにはACMD41を使う前提のようだ。しかし使ったカードではきちんとレスポンスを返してくれた。よく分からない。

・CMD9 CSDレジスタ読み取り
Card-Specific Dataレジスタの値を読み取る。カードの容量など様々な情報が含まれている。
このレジスタはOCRレジスタと異なり16バイトあるためレスポンスの中身ではなくデータパケットとして送られてくる。
なお今回のコードではこの内容は読むだけで使っていない。

・CMD16 ブロックサイズ指定
SDHC/SDXCではこのコマンドは無視され512バイト固定だが、SDの一部でデフォルトが512バイトでない場合がありうるので必要らしい。
面倒なので今回のコードでは省略した。

・CMD17 1ブロック読み取り
このコマンドでカード本体の内容を読める。
引数は読み取り開始アドレス。
無印SDカードは1バイト単位、SDHCおよびSDXCはセクタ(512バイト)単位で指定する。
1ブロック分のデータパケットが得られる。



以下自分の悩んだ点をまとめておく

・CSの操作
1回のトランザクションごとにCSをHighに戻す。
つまり、CSをLowにアサート、コマンド送信、レスポンス受信、(データパケットがあれば送受信、)CSをHighに。
CSの動作として当たり前なのだろうが、これが確信が持てなくてだいぶ悩んだ。

・ダミークロック
トランザクションごとに1バイト分のダミークロックが必要。
ダミークロックについては仕様書の(SPIモードでなく)SDモードの部分に書いてある。
CSはHighでもLowでも構わないと書いてあるサイトがあった。とりあえずHighにしている。

・ACMDの処理
CMD55,CMDnのシークエンスをACMDnという。
それはいいのだが、CMD55と次のコマンドの間の処理がよくわからなかった。
CMD55を送って返答の1バイトが返ってくるまでで1トランザクション、ここでダミークロックまで送り、次のコマンド本体とその返答がまた1トランザクションということのようだ。

・クロック周波数
初期化中のクロック周波数は100kHz~400kHzと書かれているが、仕様書を読むともっと詳しく書かれている。
ACMD41を発行しカードの初期化が完了するまで、ホストは次の2つのうちどちらかを行わなければならない。
1) 周波数100kHz~400kHzの連続的なクロックを出力する
2) ホストがクロックを停止したいならば、50ms以内の間隔でACMD41によりビジービットをポーリングする
マイコンでSPIモードを使うならふつうクロックは断続的になるだろうから見るべきは2番の方である。
ところで50ms以内の間隔ならクロックは100kHzを下回ってもよいのだろうか。
よいならば最低クロックはACMD41とレスポンスで50msになる値、レスポンスが即時に返ってくるとして6+1+6+5=18バイト分なので、(18*8)/0.05=2880Hzということに…
なるかと思いきや、よく図を見るとトランザクション中の時間は50msに含まれていない。
SD仕様書_クロックの制限
するとクロックが50ms以上止まらなければよいのだろうか。であれば1/(50ms*2)=10Hz…?
これはちょっと実験してみたいところだ。
なお、ACMD41を除いてクロックは好きな時に停止してよいようだ。これは助かる。

・CRC
分からない。
色々な所を見たが、見る所ごとに書いてあることが違う(ように感じる)。
結局、ここのコードを中身を理解しないままJavaScriptに移植して使ったところ、思ったのと1bit違うところで答えが出たのでそのまま使った。
http://nabe.blog.abk.nu/0355

・自分のプログラムのバグ
他人の役に立つとは思えないが、面白いバグだったので書き留めておく。
起こった現象
初期化シーケンスに対する返答が順に以下のようであった。
CMD0に0x01が返る。idleフラグのみ立った状態であり、これは正しい。
CMD8に0x00FFFFFFFFが返る。つまり1バイト目はエラー無しを示しているのにその後に続くはずのデータが来ない。古いSDでCMD8に非対応だからだろうか。
CMD1に常に1回で0x00が返る。idle待ちがあるはずだが…まあきっと通信が遅いせいだろう。たぶんおかしくない。
CMD58に0x00FFFFFFFFが返る。つまりCMD8と同様エラー無しなのにデータが無い。

別のカードを使ってみると違う結果が返るものがあり、
CMD8に0x00000001AAが返るものがある。idleでないのが不可解だがデータは正しい。
CMD58に0x0080FF8000や0x0000FF80が返るものがある。これは正しい。

さて困った。正しいデータが返るものもあるのだから、SPIやシリアル通信のプログラムが間違っているとも思えない。

CMD58に返答が返るものを使えばとりあえず先に進めるのだからよく分からないがこれを使っておくか…
…と思ったが、色々考えてみるとやはりCMD8に0x00が返ってくるのはおかしい。非対応ならエラーフラグの立った0x05が返るはずである。
CMD1のidle待ちが無いのも不可解だ。いくら遅いとはいえ1ループ数ミリ秒で回しているのだが、このidle待ちは「数百msかかることがある」というもので、早くても数十msはかかるのではないか。

つまり、頭の1バイトだけなぜか0x00に化けているのではないか。しかしそんな都合のいいバグが…あった。
どういうバグかというと、
ダミークロックを送る関数でバンク0に用意したループカウンタを回すつもりだがバンクを変えておらず、
運良くシリアル送信との兼ね合いでバンク0にしていた1回目のCMD0を除き、みなバンク1の変数を回していた。
そしてそこにちょうど読んだデータの1バイト目があったため、カウントダウンしてループを抜けるときには常に0になっていたというわけだ。
ミッドレンジのPICならバンク切り替えの必要は無かったので、もっと余裕をもった石を使うべきだったかもしれない。(もっとも10F200でなく12F510にした時点で余裕をもったつもりだったのだが)



最後にコード
ページ0/1を両方使っているが、ページ1はメッセージだけなのでそれを除けば512バイトのPICにも収めることができる。まあそんなPICはほとんど無いのでできてどうするという話ではあるが。

・コード内訳
000-1FF メインコード・サブルーチン
200-2FF メッセージ
300-3FF 未使用
なおベースラインPICの仕様上ページの前半にしかCALLで飛べないため、サブルーチンの本体は後半に置きつつ前半に一度CALLしてからGOTOで本体に飛ばす構造になっている。


    list    p=12F510
    #include p12F510.inc
    radix dec

    ;    12F510
    ;      Vdd+-v-+Vss
    ;      GP5|   |GP0 DAT
    ;      GP4|   |GP1 CLK
    ;    (GP3)+---+GP2

    ;-    /--
    ;CS  |
    ;DI  |
    ;Vss |
    ;Vcc | 表
    ;SCL |
    ;Vss |
    ;CO  |
    ;-   +---

    variable env
env=    _IOSCFS_ON
env&=    _MCLRE_OFF
env&=    _CP_OFF
env&=    _WDT_OFF
env&=    _IntRC_OSC
    __config env

    cblock 0x0A ;バンク共通
        bitcnt, bytecnt, skipcnt
        commdat, spioutbuf
    ;バンク0
        flag
        cnt0, cnt1, cnt2, cnt3
        temp, temp2
        arg0, arg1, arg2, arg3
        msgptr
    endc
    ;バンク1は読み取ったデータの保存領域

;ram alias

;bytecnt equ bytecnt

;flag bit
RXFLAG  equ 0
TIMEOUT equ 1

MOSIPIN equ 0
MISOPIN equ 3
SCLKPIN equ 1
CSPIN   equ 2
RXPIN   equ 4
TXPIN   equ 5
MORSEPIN equ 5
MOSIBIT equ 1<<MOSIPIN
MISOBIT equ 1<<MISOPIN
SCLKBIT equ 1<<SCLKPIN
CSBIT   equ 1<<CSPIN
RXBIT   equ 1<<RXPIN
TXBIT   equ 1<<TXPIN

CMD equ 0x40
CRC_CMD08 equ 0x95
;CMD0: 40 00 00 00 00 と
;CMD8: 48 00 00 01 AB で兼用

#define flagWAIT STATUS,GPWUF
#define flagCMD8 STATUS,CWUF


;##### EntryPoint #####
    org 0x00
    goto init

;##### Subroutine Entry #####
;関数のcall先。ベースラインではページの前半しかcallできないためここにまとめてある。
;関数の本体はメインコードの後に置いてある。
txmsg232:
    goto txmsg232core
getmsgbyte: ;これのみページ1へ飛ばす
    bsf STATUS,PA0
    movwf PCL
skipandread1: ;Wに1を入れてskipandreadn。すなわち1バイト読む
    movlw .1
skipandreadn: ;0xFFのバイトを読み飛ばした後にNバイト読む
    bsf flagWAIT
readn: ;Nバイト読む
    goto readncore
skip2np1:
    goto skip2np1core
skip2n:
    goto skip2ncore
tx232hex:
    goto tx232hexcore
;readresponse1:
;    bsf respcnt,0
;readresponse:
;    goto readresponsecore
sendnibble:
    goto sendnibblecore
sendsdcmdarg0:
    goto sendsdcmdarg0core
sendsdcmd:
    goto sendsdcmdcore
;morse8:
;    movwf dat
;    goto f1
;morse:
;    goto morsecore
;morsespace:
;    ;interword(7)-interchar(3) =4
;    movlw .8
;    goto waithalfdit

readspi: ;SPIで0xFFを送信する
    movlw 0xFF
spi:
    goto spicore
tx232:
    bsf GPIO,TXPIN ;0
    bcf flag,RXFLAG
    movwf commdat
    goto tx232core
rx232:
    btfss GPIO,RXPIN ;[0,3)
    goto rx232
    goto rx232core
rx232withtimeout:
    bcf flag,TIMEOUT
rx232waitloop:
    btfsc GPIO,RXPIN ;[0,5)
    goto rx232core
    incfsz cnt0,F
    goto rx232waitloop
    incf cnt1,F ;待ち時間を正しくカウントするために必要
    btfsc GPIO,RXPIN
    goto rx232core
    incfsz cnt1,F
    goto rx232waitloop
    incf cnt2,F ;
    btfsc GPIO,RXPIN
    goto rx232core
    incfsz cnt2,F
    goto rx232waitloop
    bsf flag,TIMEOUT
    retlw 0

;##### Main Code #####
init
    movwf OSCCAL
   
    movlw b'10001000'
    ;       /GPWU
    ;        /GPPU=0: PullUp ON
    ;         T0CS=0: Fosc/4,
    ;          T0SE Don'tCare
    ;           PSA Don'tCare
    ;            PS2:0 Don'tCare
    option
   
    clrf ADCON0
    bcf CM1CON0,C1ON
   
    movlw MOSIBIT|CSBIT
    movwf GPIO
    movlw MISOBIT|RXBIT
    tris GPIO

    movlw 0x2A
    movwf FSR
b12:
    clrf INDF
    bcf FSR,5
    clrf INDF
    bsf FSR,5
    incfsz FSR,F
    goto b12

initsd: ;1ms待ってから74個以上のクロックを出力
    ;wait 1ms
    clrf cnt0
b9:
    goto $+1
    goto $+1
    nop
    decfsz cnt0,F
    goto b9
   
    movlw .5 ;10byte>74
    call skip2np1

test:

;メッセージ出力
    movlw msg_cmd0
    call txmsg232

    bcf GPIO,CSPIN

;CMD0を引数0で送信
    movlw CMD|.0
    call sendsdcmdarg0
;受信しつつ応答が来るまでスキップし、応答を1バイト読む
    call skipandread1
;応答が来るまでにスキップした回数と受信内容を出力
    decf skipcnt,W
    call tx232hex
    movlw ','
    call tx232
   
    movlw 0x30
    movwf FSR
    movf INDF,W
    bcf FSR,5
    call tx232hex
;CSをHighに
    bsf GPIO,CSPIN
;1バイトダミークロック出力
    movlw .0
    call skip2np1

;CMD8で同様
    movlw msg_cmd8
    call txmsg232

    bcf GPIO,CSPIN
   
    movlw 0x01
    movwf arg2
    movlw 0xAB
    movwf arg3
    movlw CMD|.8
    call sendsdcmd
;CMD8の応答は5バイト
    movlw .5
    call skipandreadn
   
    bsf GPIO,CSPIN
   
    movlw .0
    call skip2np1
   
    decf skipcnt,W
    call tx232hex
    movlw ','
    call tx232
;5バイト出力。もうちょっとまともにやりたい。
    movlw 0x30
    movwf FSR
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
   
;CMD1。ACMD41を試してた名残りがある。
    movlw msg_cmd1
    call txmsg232
   
b13:

;    movlw CMD|.55
;    call sendsdcmdarg0
;   
;    call skipandread1
;    bsf GPIO,CSPIN
;    movlw 0x30
;    movwf FSR
;    movf INDF,W
;    bcf FSR,5
;    call tx232hex
   
    bcf GPIO,CSPIN
    movlw CMD|.1
    call sendsdcmdarg0
   
    call skipandread1
    bsf GPIO,CSPIN
   
    movlw .0
    call skip2np1
   
    movlw '/'
    call tx232

    decf skipcnt,W
    call tx232hex
    movlw ','
    call tx232
   
    movlw 0x30
    movwf FSR
    movf INDF,W
    bcf FSR,5
    movwf temp2
    call tx232hex
       
    movf temp2,W
    btfss STATUS,Z
    goto b13
;CMD58
    movlw msg_cmd58
    call txmsg232
   
    bcf GPIO,CSPIN
    movlw CMD|.58
    call sendsdcmdarg0
   
    movlw .5
    call skipandreadn
   
    bsf GPIO,CSPIN
   
    movlw .0
    call skip2np1
   
    decf skipcnt,W
    call tx232hex
    movlw ','
    call tx232
   
    movlw 0x30
    movwf FSR
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
;CMD9
    movlw msg_cmd9
    call txmsg232
   
    bcf GPIO,CSPIN
    movlw CMD|.9
    call sendsdcmdarg0
    call skipandread1
   
    decf skipcnt,W
    call tx232hex
    movlw ','
    call tx232
   
    movlw 0x30
    movwf FSR
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
   
    movlw .16
    call skipandreadn
   
    decf skipcnt,W
    call tx232hex
    movlw ','
    call tx232
   
    movlw 0x30
b18:
    movwf FSR
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    movf FSR,W
    andlw 0x1F
    btfss STATUS,Z   
    goto b18
   
    movlw .3
    call readn
   
    movlw 0x30
    movwf FSR
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
   
    bsf GPIO,CSPIN
   
    movlw .0
    call skip2np1
   
;---
;CMD17
    movlw msg_cmd17
    call txmsg232
   
    bcf GPIO,CSPIN
    movlw CMD|.17
    call sendsdcmdarg0
    call skipandread1
   
    decf skipcnt,W
    call tx232hex
    movlw ','
    call tx232
   
    movlw 0x30
    movwf FSR
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
   
    movlw '/'
    call tx232
   
    call skipandread1
   
    decf skipcnt,W
    call tx232hex
    movlw ','
    call tx232
   
    movlw 0x30
    movwf FSR
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex

;512バイトを16バイトごとに受信して表示
    movlw .512/.16
    movwf cnt0
b17:
    movlw msg_nl
    call txmsg232
   
    movlw .16
    call skipandreadn
   
    movlw 0x30
b16:
    movwf FSR
    bsf FSR,5
    movf INDF,W
    bcf FSR,5
    call tx232hex
    incf FSR,F
    movf FSR,W
    andlw 0x1F
    btfss STATUS,Z   
    goto b16
   
    bcf FSR,5
    decfsz cnt0,F
    goto b17
       
    goto $

busychk: ;あれ、なんでこんなところに置いてあるんだろう。ページ前半でないとcallできない。
    goto busychkcore

;##### Subroutine Core #####
busychkcore: ;ビジーチェック…のはずだけどどうやら使っていない。
    call readspi
    movf commdat,W
    btfsc STATUS,Z
    goto busychkcore
    retlw 0

readncore: ;Nバイト読み取り
    movwf bytecnt
    movlw 0x30
    movwf FSR
    clrf skipcnt
readloop:
    call readspi
    btfss flagWAIT
    goto f2
    incf skipcnt,F
    incf commdat,W
    btfsc STATUS,Z
    goto readloop
    bcf flagWAIT
f2:
    movf commdat,W
    movwf INDF
    incf FSR,F
    decfsz bytecnt,F
    goto readloop
    bcf FSR,5
    retlw 0

sendsdcmdarg0core: ;SDのコマンドを引数を0にして送信
    clrf arg0
    clrf arg1
    clrf arg2
    clrf arg3
sendsdcmdcore: ;SDのコマンドを送信
    call spi
    movf arg0,W
    call spi
    movf arg1,W
    call spi
    movf arg2,W
    call spi
    movf arg3,W
    call spi
    movlw CRC_CMD08
    call spi
    retlw 0

skip2np1core: ;2N+1バイト分のダミークロックを出力
    bsf bitcnt,3
b14:
    bsf GPIO,SCLKPIN
    goto $+1
    bcf GPIO,SCLKPIN
    decfsz bitcnt,F
    goto b14
skip2ncore: ;2Nバイト分のダミークロックを出力
    movwf bytecnt
    movf bytecnt,W
    btfsc STATUS,Z
    retlw 0
b11:
    bsf bitcnt,3
b10:
    bsf GPIO,SCLKPIN
    goto $+1
    bcf GPIO,SCLKPIN
    goto $+1
    bsf GPIO,SCLKPIN
    goto $+1
    bcf GPIO,SCLKPIN
    decfsz bitcnt,F
    goto b10
    decfsz bytecnt,F
    goto b11
    retlw 0

spicore: ;SPI 1バイト送受信
    movwf commdat
    rrf GPIO,W
    andlw ~(0x80|SCLKBIT>>1|CSBIT>>1|MISOBIT>>1)
    movwf spioutbuf
;    bcf GPIO,CSPIN
   
    bcf STATUS,C
    bsf bitcnt,3
spiloop:
    rlf commdat,F
    rlf spioutbuf,W ;CS:L SCLK:L MOSI<-C / c0
    movwf GPIO
    goto $+1
    bsf GPIO,SCLKPIN
    btfsc GPIO,MISOPIN
    bsf commdat,0
    decfsz bitcnt,F
    goto spiloop
   
    bcf GPIO,SCLKPIN
   
    retlw 0

;### RS232C
;RS232Cシリアル関連のコード。

txmsg232core: ;文字列を送信
    movwf msgptr
msgloop:
    call getmsgbyte
    bcf STATUS,PA0
    movwf temp
    andlw 0x7F
    call tx232
    btfsc temp,7
    retlw 0
    incf msgptr,F
    movf msgptr,W
    goto msgloop
    retlw 0

tx232hexcore: ;1バイトを16進表記で送信
    movwf temp
    swapf temp,W
    call sendnibble
    movf temp,W
    call sendnibble
    retlw 0
   
sendnibblecore: ;下位ニブルを送信
    andlw 0x0F
    movwf commdat
    movlw .6
    addwf commdat,F
    movlw '0'-.6
    btfsc STATUS,DC
    movlw 'A'-.10-.6
    bsf GPIO,TXPIN ;0
    bcf flag,RXFLAG
    addwf commdat,F
    goto tx232core

rx232core: ;1バイト受信。使っていない。
    bsf flag,RXFLAG ;[4,7) mean5.5
    movlw 0xFF
    movwf commdat
    goto $+1
;    btfsc flag,BAUD300
;    goto wait0r5a
;waitend0r5a:
    goto $+1
    goto $+1
    goto $+1
    goto $+1
    goto $+1

;tx232:
;    bsf GPIO,TXPIN ;0
;    bcf flag,RXFLAG
;    movwf commdat
;    goto tx232core
tx232core: ;1バイト送信
    nop ;5
    bsf bitcnt,3

loop232:
    goto $+1
;    btfsc flag,BAUD300 ;ボーレートを115200と300で選べるようにしようと思ったが結局115200固定に。
;    goto wait1b
;waitend1b:
    goto $+1
    nop
    bcf STATUS,C
    btfss GPIO,RXPIN ;read 28.5 +17n
    bsf STATUS,C
    rrf commdat,F
    movf GPIO,W
    andlw ~TXBIT
    btfss STATUS,C
    iorlw TXBIT
    movwf GPIO ;send 20 +17n
   
    decfsz bitcnt,F
    goto loop232
   
;    btfsc flag,BAUD300
;    goto wait1c
;waitend1c:
    ;連続受信を極力早くするためここでreturn(2命令挟んで次のcallでストップビット中央)
    btfsc flag,RXFLAG
    retlw 0

    goto $+1
    goto $+1
    goto $+1
    goto $+1
    goto $+1
    goto $+1
   
    bcf GPIO,TXPIN ;send stop +17
    goto $+1
;    btfsc flag,BAUD300
;    goto wait1d
;waitend1d:
    goto $+1
    goto $+1
    goto $+1
    goto $+1
    nop
    ;連続送信で安全な時間空ける(1命令挟んで次のcallでストップビットが17サイクル)
    retlw 0

;### morse
;デバッグ出力用にモールス信号を出力するコードを書いたが結局使わなかった。

;morsecore:
;    movwf dat
;b3:
;    decf cnt0,F
;    rlf dat,F
;    btfss STATUS,C
;    goto b3
;f1:
;    movlw .8
;    addwf cnt0,F
;
;b4:
;    ;bcf GPIO,OUTPIN
;    rlf dat,F
;    movlw .6 ;dah
;    btfss STATUS,C
;    movlw .2 ;dit
;    movwf cnt2
;
;b5: ;2MHz,4*256cyc: ~1kHz
;    movlw 1<<MORSEPIN
;    decfsz cnt3,F
;    goto b5
;    xorwf GPIO,F
;    decfsz cnt1,F
;    goto b5
;    decfsz cnt2,F
;    goto b5
;
;;bsf GPIO,OUTPIN
;
;;1点間隔
;    movlw .2
;    movwf cnt3
;b7:
;    ;4cyc,2MHz: .13sec
;    nop
;    decfsz cnt1,F
;    goto b7
;    decfsz cnt2,F
;    goto b7
;    decfsz cnt3,F
;    goto b7
;
;    decfsz cnt0,F
;    goto b4
;
;;interchar(3)-interelem(1) =2
;    movlw .4
;;    goto waithalfdit
;
;waithalfdit:
;    movwf cnt3
;b6:
;    ;4cyc,2MHz: .13sec
;    nop
;    decfsz cnt0,F
;    goto b6
;    decfsz cnt2,F
;    goto b6
;    decfsz cnt3,F
;    goto b6
;    retlw 0

;メッセージ。最上位bitを立てることで終端を示している。ASCIIしか使えないが、\0終端と比べ1メッセージあたり1バイト節約できるのが利点。
;GBのアセンブラにあった機能を真似してみた。
    org 0x200
msg_cmd0:
    dt "\r\nCMD0 reset&SPI mode\r", '\n'|0x80
msg_cmd8:
    dt "\r\nCMD8 >ver.2?\r", '\n'|0x80
msg_cmd1:
    dt "\r\nCMD1 initialize\r", '\n'|0x80
msg_cmd58:
    dt "\r\nCMD58 read OCR register\r", '\n'|0x80
msg_cmd9:
    dt "\r\nCMD9 read CSD register\r", '\n'|0x80
msg_cmd17:
    dt "\r\nCMD17 read single block\r", '\n'|0x80
msg_nl:
    dt "\r", '\n'|0x80
    end

  • 同じカテゴリー(プログラム・アルゴリズム)の記事画像
    6段のカレンダーが好きだ
    ファミコンで9×9ドット文字表示(ほか)
    JPEG圧縮を繰り返しても際限なく劣化するわけではない
    ゲームボーイの吸い出し機を作った (後編)
    ファミコンで全画面に任意の画像(ただしモノクロ)を表示
    最近のWindowsのビットマップフォントの太字
    同じカテゴリー(プログラム・アルゴリズム)の記事
     CDのピットの数 (2021-03-05 23:32)
     6段のカレンダーが好きだ (2020-11-15 04:06)
     PIC16のDhrystone MIPSを測ろうとしてみた (2020-04-29 02:23)
     Twitterの画像の扱いがやっとまともになって嬉しい (2019-06-23 00:23)
     ファミコンで9×9ドット文字表示(ほか) (2019-04-08 02:02)
     謎の色名「honeydewtab」とlegacy color valueパース手順 (2018-03-24 12:45)
    この記事へのコメント
    参考にさせていただきました、貴重な情報ありがとうございます!
    Posted by 通りすがり与太郎 at 2020年01月08日 11:20
    URL欄を実験的に消してる間に廃止されてしまいました。まあいいか。
     
    <ご注意>
    書き込まれた内容は公開され、ブログの持ち主だけが削除できます。
    削除
    SDカードから1セクタ読み取るまでの手順解説
      コメント(1)