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
- ESP32 development board with MicroPython firmware installed.
- Micro/SD Card breakout board / module with Micro/SD Card inserted.
- Breadboard.
- Some dupont wires.
Pinout
- GND – for the ground pin.
- VCC – for the supply voltage.
- MISO – for the SPI Master Input Slave Output pin.
- MOSI – for the SPI Master Output Slave Input pin.
- SCK – for the SPI Serial Clock pin.
- CS – for the SPI Chip Select pin.
Hardware Instruction
- First, connect the SD module GND pin to ESP32 GND pin.
- Next, connect the SD module VCC pin to ESP32 VIN pin.
- Next, connect the SD module MISO pin to ESP32 GPIO 13.
- Next, connect the SD module MOSI pin to ESP32 GPIO 12.
- Next, connect the SD module SCK pin to ESP32 GPIO 14.
- Lastly, connect the SD module CS pin to ESP32 GPIO 27.
Software Instruction
- Copy the sdcard.py and save it to ESP32 MicroPython root directory by clicking the File menu then select Save As.
- Click MicroPython Device and save it as “sdcard.py” and click “OK”.
- Copy and paste the example source code.
- 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
Posts in this series
- 026 - ESP32 MicroPython: MFRC522 RFID Module
- 025 - ESP32 MicroPython: ESP32 Bluetooth Low Energy
- 023 - ESP32 MicroPython: Binary Clock
- 022 - ESP32 MicroPython: MQTT Part 2: Subscribe
- 021 - ESP32 MicroPython: MQTT Part 1: Publish
- 020 - ESP32 MicroPython: RESTful APIs | Demo READ and WRITE
- 019 - ESP32 MicroPython: OpenWeather | RESTful APIs
- 018 - ESP32 MicroPython: Thingspeak | RESTful APIs
- 017 - ESP32 MicroPython: DHT Values Auto Updates using AJAX
- 016 - ESP32 MicroPython: Web Server | ESP32 Access Point
- 015 - ESP32 MicroPython: Web Server | ESP32 Station Mode in MicroPython
- 014 - ESP32 MicroPython: SIM800L GSM Module in MicroPython
- 013 - ESP32 MicroPython: UART Serial in MicroPython
- 012 - ESP32 MicroPython: HC-SR04 Ultrasonic Sensor in MicroPython
- 011 - ESP32 MicroPython: DHT11, DHT22 in MicroPython
- 010 - ESP32 MicroPython: 0.96 OLED in MicroPython
- 009 - ESP32 MicroPython: Non-blocking Delays and Multithreading | Multitasking
- 008 - ESP32 MicroPython: Hardware Timer Interrupts
- 007 - ESP32 MicroPython: How to make some sound with MicroPython
- 006 - ESP32 MicroPython: How to control servo motor with MicroPython
- 005 - ESP32 MicroPython: Pulse Width Modulation
- 004 - ESP32 MicroPython: External Interrupts
- 003 - ESP32 MicroPython: General Purpose Input Output | GPIO Pins
- 001 - ESP32 MicroPython: What is MicroPython
- 000 - ESP32 MicroPython: How to Get Started with MicroPython
No comments yet!