024 - ESP32 MicroPython: How to Use SD Card in MicroPython

Introduction

In this video, we will learn how to interface and use SD Card with ESP32 using MicroPython programming language.

Bill Of Materials

  1. ESP32 development board with MicroPython firmware installed.
  2. Micro/SD Card breakout board / module with Micro/SD Card inserted.
  3. Breadboard.
  4. Some dupont wires.

Pinout

  1. GND – for the ground pin.
  2. VCC – for the supply voltage.
  3. MISO – for the SPI Master Input Slave Output pin.
  4. MOSI – for the SPI Master Output Slave Input pin.
  5. SCK – for the SPI Serial Clock pin.
  6. CS – for the SPI Chip Select pin.

Hardware Instruction

  1. First, connect the SD module GND pin to ESP32 GND pin.
  2. Next, connect the SD module VCC pin to ESP32 VIN pin.
  3. Next, connect the SD module MISO pin to ESP32 GPIO 13.
  4. Next, connect the SD module MOSI pin to ESP32 GPIO 12.
  5. Next, connect the SD module SCK pin to ESP32 GPIO 14.
  6. Lastly, connect the SD module CS pin to ESP32 GPIO 27.

Software Instruction

  1. Copy the sdcard.py and save it to ESP32 MicroPython root directory by clicking the File menu then select Save As.
  2. Click MicroPython Device and save it as “sdcard.py” and click “OK”.
  3. Copy and paste the example source code.
  4. Play with it, adapt according to your needs and most of all is enjoy what you are learning.

Video Demonstration

Call To Action

For any concern, write your message in the comment section.

You might also like to support my journey on Youtube by Subscribing. Click this to Subscribe to TechToTinker.

Thank you and have a good days ahead.

See you,

– George Bantique | tech.to.tinker@gmail.com

Source Code

1. sdcard.py, SD Card driver library:

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

2. Example demo in using / accessing an SD Card:

 1
 2# More details can be found in TechToTinker.blogspot.com 
 3# George Bantique | tech.to.tinker@gmail.com
 4
 5import os
 6from machine import Pin, SoftSPI
 7from sdcard import SDCard
 8
 9# Pin assignment:
10# MISO -> GPIO 13
11# MOSI -> GPIO 12
12# SCK  -> GPIO 14
13# CS   -> GPIO 27
14spisd = SoftSPI(-1, miso=Pin(13), mosi=Pin(12), sck=Pin(14))
15sd = SDCard(spisd, Pin(27))
16
17
18print('Root directory:{}'.format(os.listdir()))
19vfs = os.VfsFat(sd)
20os.mount(vfs, '/sd')
21print('Root directory:{}'.format(os.listdir()))
22os.chdir('sd')
23print('SD Card contains:{}'.format(os.listdir()))
24
25
26# 1. To read file from the root directory:
27# f = open('sample.txt', 'r')
28# print(f.read())
29# f.close()
30
31# 2. To create a new file for writing:
32# f = open('sample2.txt', 'w')
33# f.write('Some text for sample 2')
34# f.close()
35
36# 3. To append some text in existing file:
37# f = open('sample3.txt', 'a')
38# f.write('Some text for sample 3')
39# f.close()
40
41# 4. To delete a file:
42# os.remove('file to delete')
43
44# 5. To list all directories and files:
45# os.listdir()
46
47# 6. To create a new folder:
48# os.mkdir('sample folder')
49
50# 7. To change directory:
51# os.chdir('directory you want to open')
52
53# 8. To delete a folder:
54# os.rmdir('folder to delete')
55
56# 9.  To rename a file or a folder:
57# os.rename('current name', 'desired name')

References And Credits

  1. sdcard.py: https://github.com/micropython/micropython/blob/master/drivers/sdcard/sdcard.py


Posts in this series



No comments yet!

GitHub-flavored Markdown & a sane subset of HTML is supported.