PCからFT245RL経由でSDメモリーカードにSPIでアクセスしてみる(初期化まで)


AVRやCPLDから大容量のデータを扱いたい場合は,EEPROMだと秋月でI2C接続で最大2MB程度のものが売っていますが,それでは足りない場合,SDカードをSPI接続で使うと良さそうです.2GBを超えるものはSDHCとなり仕様が公開されていない?(->公式に簡易仕様書がありましたがVer.2では初期化方法、ブロック指定方法が違うようです)ので,2GBまでのものを考えます.

まずはSDカードへのアクセスの仕方を学ぶために,パソコンからSDカードへの信号を生成していじってみることにしました.USB接続で,8bitポートを好きに扱えるFT245RLというICのbitbang modeを利用します.

久しぶりに使うので,使い方をまたまとめておく.
FT245RLは秋月で書い,SSOP28pin用のDIP変換基盤にはんだづけした.電源はUSBからの5Vを使う.仕様書末尾あたりのアプリケーションの回路図通りに配線する.仕様書には別のICのピン番号も一緒に書いてあるのでそれと間違えないようにする.(間違えて配線しなおしました.)PWREN#は一応仕様書通りに10 kΩにプルアップした(USBスタンバイを使わなければあまり関係ない気もする.)SDカードを3.3Vで駆動するつもりなのでVCCIOは3V3OUTに接続.

ubuntu gnu/linuxに接続.
dmesg|tailしてみるとこんな感じに認識する


[211054.949557] usb 2-2.1: new full-speed USB device number 4 using uhci_hcd
[211055.246148] usb 2-2.1: New USB device found, idVendor=0403, idProduct=6001
[211055.246157] usb 2-2.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[211055.246568] usb 2-2.1: Product: FT245R USB FIFO
[211055.246578] usb 2-2.1: Manufacturer: FTDI
[211055.246584] usb 2-2.1: SerialNumber:
[211055.517328] usbcore: registered new interface driver usbserial
[211055.517366] usbcore: registered new interface driver usbserial_generic
[211055.517398] usbserial: USB Serial support registered for generic
[211055.614871] usbcore: registered new interface driver ftdi_sio
[211055.624204] usbserial: USB Serial support registered for FTDI USB Serial Device
[211055.628326] ftdi_sio 2-2.1:1.0: FTDI USB Serial Device converter detected
[211055.628400] usb 2-2.1: Detected FT232RL
[211055.628406] usb 2-2.1: Number of endpoints 2
[211055.628411] usb 2-2.1: Endpoint 1 MaxPacketSize 64
[211055.628416] usb 2-2.1: Endpoint 2 MaxPacketSize 64
[211055.628421] usb 2-2.1: Setting MaxPacketSize 64
[211055.638111] usb 2-2.1: FTDI USB Serial Device converter now attached to ttyUSB0

FT245RLのドライバは公式には,VCPというCOMポートとして通信用のドライバと,8bitをソフトウェアから好きにon/offできるbit-bang用のD2XXというドライバがあるが,ここではopen sourceのlibftdiというドライバgoogle:libftdiを利用する.インストールする.


apt-get install libftdi-dev

pythonからそのドライバを叩けるpylibftdiというモジュールgoogle:pylibftdiを使わせてもらう.ダウンロードして適当なフォルダに展開後,


python setup.py install

でインストール.

SDメモリカードをSPI接続で叩くコードを書く.
参考:
http://elm-chan.org/docs/mmc/mmc.html
http://naruken.cweb.tk/labo/mmc/index.html


from pylibftdi import BitBangDevice, Bus
import time

class sd(object):
    """
    The FT245R is wired to the SD as follows:
       DB0 to DI
       DB1 to D0
       DB2 to CLK
       DB3 to CS
    """
    DI = Bus(0)
    DO = Bus(1)
    CLK = Bus(2)
    CS = Bus(3)
    POW = Bus(4)

    def __init__(self, device):
        # The Bus descriptor assumes we have a 'device'
        # attribute which provides a port
        self.device = device

    def _trigger(self):
        "generate a L->H->L"
        self.CLK = 0
        self.CLK = 1
        self.CLK = 0

    def init_spi(self):
        print "power on and wait > 1ms"
        self.POW = 1
        time.sleep(0.01)

        print "dummy clock >= 74 with CS and DI high"
        self.CS = 1
        self.DI = 1
        for _ in range(74): #more than 74 times
            self._trigger()

        print "CMD0 with CS low"
        self.CS = 0
        self.write_cmd(0, 0, 0b1001010)

        print "-> wait response for 0-8 byte"
        while 1:
            if self.read_r1() == 0b1:
                print "cmd0 ok (response == 1)"
                break

        #while cmd1
        print "cmd1"
        while 1:
            self.write_cmd(1, 0, 0)
            if self.read_r1() == 0b0:
                break
        print "cmd1 ok (res == 0)"

    def _write_raw(self, byte):
        for i in range(8):
            self.DI = (byte >> (7-i)) & 0b1
            print "%d" % self.DI,
            self._trigger()
        print

    def write_cmd(self, cmd, addr, crc): #cmd 6bit, addr 8x4bit, crc7bit
        #cmd
        self._write_raw(0b01000000 + cmd)
        #addr
        for i in range(4):
        self._write_raw((addr >> (3-i)*8) & 0xff)
        #CRC
        self._write_raw(0b1 + (crc << 1))
        #up
        self.DI = 1
        #print "DI after cmd: ", self.DI

    def read_r1(self):
        print "read_r1 (timeout >= 8 byte)"
        for i in range(8*8):
            do = self.DO
            self._trigger()
            print "%d" % do,
            if (do == 0):
                break
            if (i==10*8-1):
                print "timeout"
                exit()

        #print "DO is 0"
        res = 0
        for i in range(7):
            res += self.DO << (6-i)
            self._trigger()

        print "response:", res
        return res

def display(device_id=None):

    with BitBangDevice(device_id) as bb:

        # the actual baudrate
        # is 16x this in bitbang mode...
        bb.baudrate = 19200 #19200, 9600, 4800
        bb.direction = 0b00011101
        bb.port = 0x00 #output (not pullup)
        sdc = sd(bb)
        sdc.init_spi()
        print "complete init"


if __name__ == '__main__':
    import sys
    display()

(pylibftdiのlcdディスプレイのexampleコードを書き換えたので,関数名がdisplayのママです・・)

出力は


power on and wait > 1ms
dummy clock >= 74 with CS and DI high
CMD0 with CS low
0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
1 0 0 1 0 1 0 1
-> wait response for 0-8 byte
read_r1 (timeout >= 8 byte)
1 1 1 1 1 1 1 1 0 response: 1
cmd0 ok (response == 1)
cmd1
0 1 0 0 0 0 0 1
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1
read_r1 (timeout >= 8 byte)
1 1 1 1 1 1 1 1 0 response: 1
0 1 0 0 0 0 0 1
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1
read_r1 (timeout >= 8 byte)
1 1 1 1 1 1 1 1 0 response: 0
cmd1 ok (res == 0)
complete init

となり初期化できたようです.

FT245RLのピンをinputに指定してみたところ,1.7Vにpull-upされました.ショートさせているのかとびっくりしました.仕様書をざっと眺めてもなんでそうなるのかわからなかったのでそのままに.
一発で動かなかったので,ロジアナがあるといいなーと思いました.自分でそのうち作りたいところ.今回はPCで入出力を細かく表示させて対応した.コマンドを送った後にクロックを下げれていなくて,コマンドを送りきれてなかった模様.