1"""
2MicroPython driver for SD cards using SPI bus.
3
4Requires an SPI bus and a CS pin.  Provides readblocks and writeblocks
5methods so the device can be mounted as a filesystem.
6
7Example usage on pyboard:
8
9    import pyb, sdcard, os
10    sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5)
11    pyb.mount(sd, '/sd2')
12    os.listdir('/')
13
14Example usage on ESP8266:
15
16    import machine, sdcard, os
17    sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))
18    os.mount(sd, '/sd')
19    os.listdir('/')
20
21"""
22
23from micropython import const
24import time
25
26
27_CMD_TIMEOUT = const(100)
28
29_R1_IDLE_STATE = const(1 << 0)
30# R1_ERASE_RESET = const(1 << 1)
31_R1_ILLEGAL_COMMAND = const(1 << 2)
32# R1_COM_CRC_ERROR = const(1 << 3)
33# R1_ERASE_SEQUENCE_ERROR = const(1 << 4)
34# R1_ADDRESS_ERROR = const(1 << 5)
35# R1_PARAMETER_ERROR = const(1 << 6)
36_TOKEN_CMD25 = const(0xFC)
37_TOKEN_STOP_TRAN = const(0xFD)
38_TOKEN_DATA = const(0xFE)
39
40
41class SDCard:
42    def __init__(self, spi, cs):
43        self.spi = spi
44        self.cs = cs
45
46        self.cmdbuf = bytearray(6)
47        self.dummybuf = bytearray(512)
48        self.tokenbuf = bytearray(1)
49        for i in range(512):
50            self.dummybuf[i] = 0xFF
51        self.dummybuf_memoryview = memoryview(self.dummybuf)
52
53        # initialise the card
54        self.init_card()
55
56    def init_spi(self, baudrate):
57        try:
58            master = self.spi.MASTER
59        except AttributeError:
60            # on ESP8266
61            self.spi.init(baudrate=baudrate, phase=0, polarity=0)
62        else:
63            # on pyboard
64            self.spi.init(master, baudrate=baudrate, phase=0, polarity=0)
65
66    def init_card(self):
67        # init CS pin
68        self.cs.init(self.cs.OUT, value=1)
69
70        # init SPI bus; use low data rate for initialisation
71        self.init_spi(100000)
72
73        # clock card at least 100 cycles with cs high
74        for i in range(16):
75            self.spi.write(b"\xff")
76
77        # CMD0: init card; should return _R1_IDLE_STATE (allow 5 attempts)
78        for _ in range(5):
79            if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE:
80                break
81        else:
82            raise OSError("no SD card")
83
84        # CMD8: determine card version
85        r = self.cmd(8, 0x01AA, 0x87, 4)
86        if r == _R1_IDLE_STATE:
87            self.init_card_v2()
88        elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND):
89            self.init_card_v1()
90        else:
91            raise OSError("couldn't determine SD card version")
92
93        # get the number of sectors
94        # CMD9: response R2 (R1 byte + 16-byte block read)
95        if self.cmd(9, 0, 0, 0, False) != 0:
96            raise OSError("no response from SD card")
97        csd = bytearray(16)
98        self.readinto(csd)
99        if csd[0] & 0xC0 == 0x40:  # CSD version 2.0
100            self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024
101        elif csd[0] & 0xC0 == 0x00:  # CSD version 1.0 (old, <=2GB)
102            c_size = csd[6] & 0b11 | csd[7] << 2 | (csd[8] & 0b11000000) << 4
103            c_size_mult = ((csd[9] & 0b11) << 1) | csd[10] >> 7
104            self.sectors = (c_size + 1) * (2 ** (c_size_mult + 2))
105        else:
106            raise OSError("SD card CSD format not supported")
107        # print('sectors', self.sectors)
108
109        # CMD16: set block length to 512 bytes
110        if self.cmd(16, 512, 0) != 0:
111            raise OSError("can't set 512 block size")
112
113        # set to high data rate now that it's initialised
114        self.init_spi(1320000)
115
116    def init_card_v1(self):
117        for i in range(_CMD_TIMEOUT):
118            self.cmd(55, 0, 0)
119            if self.cmd(41, 0, 0) == 0:
120                self.cdv = 512
121                # print("[SDCard] v1 card")
122                return
123        raise OSError("timeout waiting for v1 card")
124
125    def init_card_v2(self):
126        for i in range(_CMD_TIMEOUT):
127            time.sleep_ms(50)
128            self.cmd(58, 0, 0, 4)
129            self.cmd(55, 0, 0)
130            if self.cmd(41, 0x40000000, 0) == 0:
131                self.cmd(58, 0, 0, 4)
132                self.cdv = 1
133                # print("[SDCard] v2 card")
134                return
135        raise OSError("timeout waiting for v2 card")
136
137    def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False):
138        self.cs(0)
139
140        # create and send the command
141        buf = self.cmdbuf
142        buf[0] = 0x40 | cmd
143        buf[1] = arg >> 24
144        buf[2] = arg >> 16
145        buf[3] = arg >> 8
146        buf[4] = arg
147        buf[5] = crc
148        self.spi.write(buf)
149
150        if skip1:
151            self.spi.readinto(self.tokenbuf, 0xFF)
152
153        # wait for the response (response[7] == 0)
154        for i in range(_CMD_TIMEOUT):
155            self.spi.readinto(self.tokenbuf, 0xFF)
156            response = self.tokenbuf[0]
157            if not (response & 0x80):
158                # this could be a big-endian integer that we are getting here
159                for j in range(final):
160                    self.spi.write(b"\xff")
161                if release:
162                    self.cs(1)
163                    self.spi.write(b"\xff")
164                return response
165
166        # timeout
167        self.cs(1)
168        self.spi.write(b"\xff")
169        return -1
170
171    def readinto(self, buf):
172        self.cs(0)
173
174        # read until start byte (0xff)
175        for i in range(_CMD_TIMEOUT):
176            self.spi.readinto(self.tokenbuf, 0xFF)
177            if self.tokenbuf[0] == _TOKEN_DATA:
178                break
179            time.sleep_ms(1)
180        else:
181            self.cs(1)
182            raise OSError("timeout waiting for response")
183
184        # read data
185        mv = self.dummybuf_memoryview
186        if len(buf) != len(mv):
187            mv = mv[: len(buf)]
188        self.spi.write_readinto(mv, buf)
189
190        # read checksum
191        self.spi.write(b"\xff")
192        self.spi.write(b"\xff")
193
194        self.cs(1)
195        self.spi.write(b"\xff")
196
197    def write(self, token, buf):
198        self.cs(0)
199
200        # send: start of block, data, checksum
201        self.spi.read(1, token)
202        self.spi.write(buf)
203        self.spi.write(b"\xff")
204        self.spi.write(b"\xff")
205
206        # check the response
207        if (self.spi.read(1, 0xFF)[0] & 0x1F) != 0x05:
208            self.cs(1)
209            self.spi.write(b"\xff")
210            return
211
212        # wait for write to finish
213        while self.spi.read(1, 0xFF)[0] == 0:
214            pass
215
216        self.cs(1)
217        self.spi.write(b"\xff")
218
219    def write_token(self, token):
220        self.cs(0)
221        self.spi.read(1, token)
222        self.spi.write(b"\xff")
223        # wait for write to finish
224        while self.spi.read(1, 0xFF)[0] == 0x00:
225            pass
226
227        self.cs(1)
228        self.spi.write(b"\xff")
229
230    def readblocks(self, block_num, buf):
231        nblocks = len(buf) // 512
232        assert nblocks and not len(buf) % 512, "Buffer length is invalid"
233        if nblocks == 1:
234            # CMD17: set read address for single block
235            if self.cmd(17, block_num * self.cdv, 0, release=False) != 0:
236                # release the card
237                self.cs(1)
238                raise OSError(5)  # EIO
239            # receive the data and release card
240            self.readinto(buf)
241        else:
242            # CMD18: set read address for multiple blocks
243            if self.cmd(18, block_num * self.cdv, 0, release=False) != 0:
244                # release the card
245                self.cs(1)
246                raise OSError(5)  # EIO
247            offset = 0
248            mv = memoryview(buf)
249            while nblocks:
250                # receive the data and release card
251                self.readinto(mv[offset : offset + 512])
252                offset += 512
253                nblocks -= 1
254            if self.cmd(12, 0, 0xFF, skip1=True):
255                raise OSError(5)  # EIO
256
257    def writeblocks(self, block_num, buf):
258        nblocks, err = divmod(len(buf), 512)
259        assert nblocks and not err, "Buffer length is invalid"
260        if nblocks == 1:
261            # CMD24: set write address for single block
262            if self.cmd(24, block_num * self.cdv, 0) != 0:
263                raise OSError(5)  # EIO
264
265            # send the data
266            self.write(_TOKEN_DATA, buf)
267        else:
268            # CMD25: set write address for first block
269            if self.cmd(25, block_num * self.cdv, 0) != 0:
270                raise OSError(5)  # EIO
271            # send the data
272            offset = 0
273            mv = memoryview(buf)
274            while nblocks:
275                self.write(_TOKEN_CMD25, mv[offset : offset + 512])
276                offset += 512
277                nblocks -= 1
278            self.write_token(_TOKEN_STOP_TRAN)
279
280    def ioctl(self, op, arg):
281        if op == 4:  # get number of blocks
282            return self.sectors
283