In this user guide, we will learn how to interface a micro SD card with Raspberry Pi Pico using the microSD card module. This module provides an SPI interface to connect an SD card module with any microcontroller which supports the SPI communication interface. Using a micro SD card becomes very handy for applications where we need to store files or any data.
Before we start this lesson, make sure you are familiar with and have the latest version of Python3 installed in your system and set up MicroPython in your Raspberry Pi Pico. Additionally, you should have a running Integrated Development Environment(IDE) to do the programming. We will be using the same Thonny IDE as we have done previously when we learned how to blink and chase LEDs in MicroPython here:
If you are using uPyCraft IDE, you can check this getting started guide:
MicroSD Card Module Introduction
The microSD card Modules are designed to communicate with the MicroSD cards. These connectors provide the required hardware and pinout to connect SD cards with microcontrollers such as ESP32, Arduino, ESP8266, Raspberry Pi, etc. Although, they are compatible with almost all SD cards which are commonly used in cell phones. But they can handle a maximum of 16GB capacity microSD cards and only 2GB capacity for standard SD cards.
With the help of these modules, we will be able to read and write data to and from SD cards through the SPI communication protocol. There are several different types of microSD card modules easily available in the market. But, the one which we will be using in this article is shown below:
This microSD card module has 6 terminals consisting of SPI and power supply terminals. Below you can view the pinout of this module with some description of the individual pins.
|GND||This is the ground pin which should be connected with the ground pin of the microcontroller.|
|VCC||This pin supplies power to the module. The power supply of ~4.5V-5.5V. The adapter consists of a 3.3V voltage regulator circuit as well. It is connected with 5V pin of the microcontroller.|
|CS||This is the Chip Select pin for SPI communication.|
|MOSI||This is called the ‘Master Out Slave In.’ It is used as the SPI input to the module.|
|SCK||This is called the ‘Serial Clock’ pin which is used in SPI serial clock output.|
|MISO||This is called the ‘Master in Slave Out.’ It is used as the SPI output from the module.|
MicroSD card module Interfacing with Raspberry Pi Pico
Following components are required:
- 1 x Raspberry Pi Pico board
- MicroSD card
- 1 x MicroSD card module
Let us first learn about the Raspberry Pi Pico SPI interface.
Raspberry Pi Pico SPI Pins
Raspberry Pi Pico supports two SPI peripherals. Both SPI module pins are accessible through GPIO pins of Raspberry Pi Pico. The following table shows the connection of GPIO pins with both SPI modules. Each connection of SPI controller pins can be configured through multiple GPIO pins as shown in the figure. But before using SPI, you should configure in software which GPIO pins you want to use with a specific SP peripheral.
|SPI Controller||GPIO Pins|
The figure below shows the SPI pins of Raspberry Pi Pico.
Each SPI controller supports master and slave mode and are compatible with the following four modes:
- Motorola SPI Interface
- TI Serial Interface
- National Semiconductor Serial Interface
It has 8 buffers for each transmitter and receiver of the SPI controller. Moreover, it can also be driven with interrupt or DMA.
Now let us see how to connect the microSD card module and Raspberry Pi Pico. The table below shows the connections between the two devices:
|MicroSD card module||Raspberry Pi Pico|
As shown from the table, we will connect the VCC terminal of the MicroSD card module with 5V pin of Raspberry Pi Pico. Both grounds will be common. We have used the same connections for SPI pins as specified in the table above. However you can use other combinations of SPI pins as well.
Now, as we know how to interface the microSD card module and Raspberry Pi Pico together let us learn how to prepare the microSD card to handle files in Thonny IDE using MicroPython.
Formatting the MicroSD card
As we have to use our microSD card with Raspberry Pi Pico, so we would have to format it as FAT32. We will have to follow a series of steps to accomplish it successfully.
- First, insert your microSD card in your laptop/computer. Now go to ‘This PC’ and click on SD card icon. Then click on Format by right clicking the SD card icon.
- The following window will appear. Select FAT32 from the dialog box of ‘File System’ and click on ‘START.’
- You will receive a warning message that formatting will erase all previous data saved on the microSD card. Click ‘OK.’
- After a few moments, your microSD card will be formatted successfully. Click ‘OK.’
Installing SD Card Library
For this project we will require sdcard.py library. Copy this library and save it in your Raspberry Pi Pico with the respective file name from the GitHub link.
Open a new file in Thonny. Copy the library given below or from the link given above. Save it to Raspberry Pi Pico with the name sdcard.py under the lib folder.
""" MicroPython driver for SD cards using SPI bus. Requires an SPI bus and a CS pin. Provides readblocks and writeblocks methods so the device can be mounted as a filesystem. Example usage on pyboard: import pyb, sdcard, os sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5) pyb.mount(sd, '/sd2') os.listdir('/') Example usage on ESP8266: import machine, sdcard, os sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15)) os.mount(sd, '/sd') os.listdir('/') """ from micropython import const import time _CMD_TIMEOUT = const(100) _R1_IDLE_STATE = const(1 << 0) # R1_ERASE_RESET = const(1 << 1) _R1_ILLEGAL_COMMAND = const(1 << 2) # R1_COM_CRC_ERROR = const(1 << 3) # R1_ERASE_SEQUENCE_ERROR = const(1 << 4) # R1_ADDRESS_ERROR = const(1 << 5) # R1_PARAMETER_ERROR = const(1 << 6) _TOKEN_CMD25 = const(0xFC) _TOKEN_STOP_TRAN = const(0xFD) _TOKEN_DATA = const(0xFE) class SDCard: def __init__(self, spi, cs, baudrate=1320000): self.spi = spi self.cs = cs self.cmdbuf = bytearray(6) self.dummybuf = bytearray(512) self.tokenbuf = bytearray(1) for i in range(512): self.dummybuf[i] = 0xFF self.dummybuf_memoryview = memoryview(self.dummybuf) # initialise the card self.init_card(baudrate) def init_spi(self, baudrate): try: master = self.spi.MASTER except AttributeError: # on ESP8266 self.spi.init(baudrate=baudrate, phase=0, polarity=0) else: # on pyboard self.spi.init(master, baudrate=baudrate, phase=0, polarity=0) def init_card(self, baudrate): # init CS pin self.cs.init(self.cs.OUT, value=1) # init SPI bus; use low data rate for initialisation self.init_spi(100000) # clock card at least 100 cycles with cs high for i in range(16): self.spi.write(b"\xff") # CMD0: init card; should return _R1_IDLE_STATE (allow 5 attempts) for _ in range(5): if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE: break else: raise OSError("no SD card") # CMD8: determine card version r = self.cmd(8, 0x01AA, 0x87, 4) if r == _R1_IDLE_STATE: self.init_card_v2() elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND): self.init_card_v1() else: raise OSError("couldn't determine SD card version") # get the number of sectors # CMD9: response R2 (R1 byte + 16-byte block read) if self.cmd(9, 0, 0, 0, False) != 0: raise OSError("no response from SD card") csd = bytearray(16) self.readinto(csd) if csd & 0xC0 == 0x40: # CSD version 2.0 self.sectors = ((csd << 8 | csd) + 1) * 1024 elif csd & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB) c_size = csd & 0b11 | csd << 2 | (csd & 0b11000000) << 4 c_size_mult = ((csd & 0b11) << 1) | csd >> 7 self.sectors = (c_size + 1) * (2 ** (c_size_mult + 2)) else: raise OSError("SD card CSD format not supported") # print('sectors', self.sectors) # CMD16: set block length to 512 bytes if self.cmd(16, 512, 0) != 0: raise OSError("can't set 512 block size") # set to high data rate now that it's initialised self.init_spi(baudrate) def init_card_v1(self): for i in range(_CMD_TIMEOUT): self.cmd(55, 0, 0) if self.cmd(41, 0, 0) == 0: self.cdv = 512 # print("[SDCard] v1 card") return raise OSError("timeout waiting for v1 card") def init_card_v2(self): for i in range(_CMD_TIMEOUT): time.sleep_ms(50) self.cmd(58, 0, 0, 4) self.cmd(55, 0, 0) if self.cmd(41, 0x40000000, 0) == 0: self.cmd(58, 0, 0, 4) self.cdv = 1 # print("[SDCard] v2 card") return raise OSError("timeout waiting for v2 card") def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False): self.cs(0) # create and send the command buf = self.cmdbuf buf = 0x40 | cmd buf = arg >> 24 buf = arg >> 16 buf = arg >> 8 buf = arg buf = crc self.spi.write(buf) if skip1: self.spi.readinto(self.tokenbuf, 0xFF) # wait for the response (response == 0) for i in range(_CMD_TIMEOUT): self.spi.readinto(self.tokenbuf, 0xFF) response = self.tokenbuf if not (response & 0x80): # this could be a big-endian integer that we are getting here for j in range(final): self.spi.write(b"\xff") if release: self.cs(1) self.spi.write(b"\xff") return response # timeout self.cs(1) self.spi.write(b"\xff") return -1 def readinto(self, buf): self.cs(0) # read until start byte (0xff) for i in range(_CMD_TIMEOUT): self.spi.readinto(self.tokenbuf, 0xFF) if self.tokenbuf == _TOKEN_DATA: break time.sleep_ms(1) else: self.cs(1) raise OSError("timeout waiting for response") # read data mv = self.dummybuf_memoryview if len(buf) != len(mv): mv = mv[: len(buf)] self.spi.write_readinto(mv, buf) # read checksum self.spi.write(b"\xff") self.spi.write(b"\xff") self.cs(1) self.spi.write(b"\xff") def write(self, token, buf): self.cs(0) # send: start of block, data, checksum self.spi.read(1, token) self.spi.write(buf) self.spi.write(b"\xff") self.spi.write(b"\xff") # check the response if (self.spi.read(1, 0xFF) & 0x1F) != 0x05: self.cs(1) self.spi.write(b"\xff") return # wait for write to finish while self.spi.read(1, 0xFF) == 0: pass self.cs(1) self.spi.write(b"\xff") def write_token(self, token): self.cs(0) self.spi.read(1, token) self.spi.write(b"\xff") # wait for write to finish while self.spi.read(1, 0xFF) == 0x00: pass self.cs(1) self.spi.write(b"\xff") def readblocks(self, block_num, buf): nblocks = len(buf) // 512 assert nblocks and not len(buf) % 512, "Buffer length is invalid" if nblocks == 1: # CMD17: set read address for single block if self.cmd(17, block_num * self.cdv, 0, release=False) != 0: # release the card self.cs(1) raise OSError(5) # EIO # receive the data and release card self.readinto(buf) else: # CMD18: set read address for multiple blocks if self.cmd(18, block_num * self.cdv, 0, release=False) != 0: # release the card self.cs(1) raise OSError(5) # EIO offset = 0 mv = memoryview(buf) while nblocks: # receive the data and release card self.readinto(mv[offset : offset + 512]) offset += 512 nblocks -= 1 if self.cmd(12, 0, 0xFF, skip1=True): raise OSError(5) # EIO def writeblocks(self, block_num, buf): nblocks, err = divmod(len(buf), 512) assert nblocks and not err, "Buffer length is invalid" if nblocks == 1: # CMD24: set write address for single block if self.cmd(24, block_num * self.cdv, 0) != 0: raise OSError(5) # EIO # send the data self.write(_TOKEN_DATA, buf) else: # CMD25: set write address for first block if self.cmd(25, block_num * self.cdv, 0) != 0: raise OSError(5) # EIO # send the data offset = 0 mv = memoryview(buf) while nblocks: self.write(_TOKEN_CMD25, mv[offset : offset + 512]) offset += 512 nblocks -= 1 self.write_token(_TOKEN_STOP_TRAN) def ioctl(self, op, arg): if op == 4: # get number of blocks return self.sectors
MicroPython Sketch: Read/Write on a File
Open your Thonny IDE and go to File > New to open a new file. Copy the following code in that file. This sketch will read and write data on a .txt file that will get saved on our microSD card.
import machine import sdcard import uos CS = machine.Pin(9, machine.Pin.OUT) spi = machine.SPI(1,baudrate=1000000,polarity=0,phase=0,bits=8,firstbit=machine.SPI.MSB,sck=machine.Pin(10),mosi=machine.Pin(11),miso=machine.Pin(8)) sd = sdcard.SDCard(spi,CS) vfs = uos.VfsFat(sd) uos.mount(vfs, "/sd") # Create a file and write something to it with open("/sd/data.txt", "w") as file: print("Writing to data.txt...") file.write("Welcome to microcontrollerslab!\r\n") file.write("This is a test\r\n") # Open the file we just created and read from it with open("/sd/data.txt", "r") as file: print("Reading data.txt...") data = file.read() print(data)
How the Code Works?
Now let us understand how each part of the code works.
The first step is to include all the libraries that are necessary for this project. We will import machine, sdcard and uos modules required for this task.
import machine import sdcard import uos
Configure the GPIO pin connected with the CS pin as an output pin. This is done by using the Pin() method and passing the GPIO number as the first parameter and Pin.OUT as the second parameter.
CS = machine.Pin(9, machine.Pin.OUT)
The next step is to initialize the SPI interface with the required parameters including the SPI channel number, baud rate, SPI pins etc:
spi = machine.SPI(1,baudrate=1000000,polarity=0,phase=0,bits=8,firstbit=machine.SPI.MSB,sck=machine.Pin(10),mosi=machine.Pin(11),miso=machine.Pin(8))
Initializing the microSD card
The following line of code will initialize the microSD card using the SDcard() function on the sdcard library. The function takes in the spi object as the first parameter and the CS Pin as an second parameter. Thus, it will start the SPI communication at the stated parameters.
sd = sdcard.SDCard(spi,CS)
Next, we will mount the filesystem using the following lines of code:
vfs = uos.VfsFat(sd) uos.mount(vfs, "/sd")
Writing data to file
Next, we will open the data.txt file on the microSD card using open() and specify the first parameter as the file name and second parameter ‘w’ indicating that we want to write on the file. If the file does not exist, it will get created.
In the Thonny shell terminal we will print “Writing to data.txt…”
with open("/sd/data.txt", "w") as file: print("Writing to data.txt...")
Using the write() method on the file object, we will write ‘Welcome to microcontrollerlab!’ in the data.txt file. Next, we will write “This is a test” on the second line in the file.
file.write("Welcome to microcontrollerslab!\r\n") file.write("This is a test\r\n")
Reading data from file
Now, we will read the data which we just wrote on our data.txt file. To do that we will first open the data.txt file by using open() method and specify the file name as the first parameter and ‘r’ as the second parameter indicating that we want to read from the file. Then by using read() method on the file object, we will read the data.txt file and print the data in the serial monitor.
with open("/sd/data.txt", "r") as file: print("Reading data.txt...") data = file.read() print(data)
Upload the above code as main.py file to Raspberry Pi Pico. You will view the messages in the Thonny shell terminal that we are writing to the data.txt file and then reading from it. The data written to the file (highlighted in red) can also be seen printed on the terminal:
Take the microSD card out of the module and insert it in your system to view the data.txt file.
Open the data.txt file. Inside it you will be able to view the two messages that we wrote to the file:
In conclusion, we learned how to use a micro SD card with Raspberry Pi Pico. We successfully initialized our microSD card and were able to read and write to a .txt file.
You may like to read these SD card guides for Arduino and sensors:
- BME280 Data Logger with Arduino and Micro SD Card
- DHT22 Data Logger with Arduino and Micro SD Card
- GPS Data Logger with Arduino and Micro SD Card – GPS Tracker
SD card interfacing with other development boards: