Módulo de cartão micro SD de interface com Raspberry Pi Pico

Tempo de leitura: 10 minutes

Neste guia do usuário, aprenderemos como fazer a interface de um cartão micro SD com o Raspberry Pi Pico usando o módulo de cartão microSD. Este módulo fornece uma interface SPI para conectar um módulo de cartão SD com qualquer microcontrolador que suporte a interface de comunicação SPI. Usar um cartão micro SD torna-se muito útil para aplicativos onde precisamos armazenar arquivos ou quaisquer dados.

Além disso, aprenderemos a lidar com arquivos no cartão microSD, incluindo leitura/gravação em um arquivo. Os dados de armazenamento, incluindo arquivos de texto, vídeo, áudio, CSV, HTML, JavaScript e CSS, podem ser convenientemente armazenados no cartão microSD. É uma das formas mais confiáveis e práticas de armazenar dados em dispositivos como telefones celulares, laptops e computadores pessoais.

 

Pré-requisitos

Antes de começarmos esta lição, certifique-se de estar familiarizado e ter a versão mais recente do Python3 instalada em seu sistema e configurar o MicroPython em seu Raspberry Pi Pico. Além disso, você deve ter um ambiente de desenvolvimento integrado (IDE) em execução para fazer a programação.

 

Introdução ao módulo de cartão microSD

Os módulos de cartão microSD são projetados para se comunicar com os cartões MicroSD. Esses conectores fornecem o hardware e pinagem necessários para conectar cartões SD com microcontroladores como ESP32, Arduino, ESP8266, Raspberry Pi, etc. Embora sejam compatíveis com quase todos os cartões SD que são comumente usados em telefones celulares. Mas eles podem lidar com um máximo de cartões microSD de 16 GB de capacidade e apenas 2 GB de capacidade para cartões SD padrão.

Com a ajuda desses módulos, poderemos ler e gravar dados de e para cartões SD através do protocolo de comunicação SPI. Existem vários tipos diferentes de módulos de cartão microSD facilmente disponíveis no mercado. Mas, o que usaremos neste artigo é mostrado abaixo:

 

Pinagem

Este módulo de cartão microSD possui 6 terminais que consistem em terminais SPI e fonte de alimentação. Abaixo você pode ver a pinagem deste módulo com alguma descrição dos pinos individuais.

Pino NomeDescrição
GNDEste é o pino terra que deve ser conectado com o pino terra do microcontrolador.
VCCEste pino fornece energia ao módulo. A fonte de alimentação de ~4.5V-5.5V. O adaptador também consiste em um circuito regulador de tensão de 3,3 V. Ele está conectado com o pino de 5V do microcontrolador.
CSEste é o pino Chip Select para comunicação SPI.
MOSIIsso é chamado de ‘Master Out Slave In.’ É usado como a entrada SPI para o módulo.
SCKIsso é chamado de pino ‘Serial Clock’ que é usado na saída do relógio serial SPI.
MISOIsso é chamado de ‘Master in Slave Out.’ É usado como a saída SPI do módulo.

Tabela mostrando os nomes dos pinos e suas breves descrições

Módulo de cartão MicroSD Interface com Raspberry Pi Pico

Hardware Necessário:

Os seguintes componentes são necessários:

  • 1 x placa Raspberry Pi Pico
  • Cartão microSD
  • 1 x módulo de cartão MicroSD

Vamos primeiro aprender sobre a interface Raspberry Pi Pico SPI.

Pinos SPI Raspberry Pi Pico

Raspberry Pi Pico suporta dois periféricos SPI. Ambos os pinos do módulo SPI são acessíveis através dos pinos GPIO do Raspberry Pi Pico. A tabela a seguir mostra a conexão dos pinos GPIO com ambos os módulos SPI. Cada conexão dos pinos do controlador SPI pode ser configurada através de vários pinos GPIO conforme mostrado na figura. Mas antes de usar o SPI, você deve configurar no software quais pinos do GPIO você deseja usar com um periférico SP específico.

Controler SPIPino GPIO
SPI0_RXGP0/GP4/GP16
SPI0_TXGP3/GP7/GP19
SPI0_SCKGP2/GP6/GP18
SPI0_CSnGP1/GP5/GP17
SPI1_RXGP8/GP12
SPI1_TXGP11/GP15
SPI1_SCKGP10/GP14
SPI1_CSnGP9/GP13

A figura abaixo mostra os pinos SPI do Raspberry Pi Pico.

Cada controlador SPI suporta o modo mestre e escravo e é compatível com os quatro modos a seguir:

  • Interface SPI da Motorola
  • Interface Serial TI
  • Interface Serial Nacional de Semicondutores

Possui 8 buffers para cada transmissor e receptor do controlador SPI. Além disso, também pode ser acionado com interrupção ou DMA.

Diagrama esquemático

Agora vamos ver como conectar o módulo do cartão microSD e o Raspberry Pi Pico. A tabela abaixo mostra as conexões entre os dois dispositivos:

Módulo de cartão microSDRaspberry Pi Pico
GNDGND
VCC5V
CSGP9 (SPI1_CSn)
MOSIGP11 (SPI1_TX)
SCKGP10 (SPI1_SCK)
MISOGP8 (SPI1_RX)

Conforme mostrado na tabela, vamos conectar o terminal VCC do módulo do cartão MicroSD com o pino de 5V do Raspberry Pi Pico. Ambos os motivos serão comuns. Usamos as mesmas conexões para pinos SPI conforme especificado na tabela acima. No entanto, você também pode usar outras combinações de pinos SPI.

Raspberry Pi Pico com diagrama de conexão do módulo de cartão microSD
Raspberry Pi Pico com diagrama de conexão do módulo de cartão microSD

Agora, como sabemos como conectar o módulo do cartão microSD e o Raspberry Pi Pico juntos, vamos aprender como preparar o cartão microSD para lidar com arquivos no Thonny IDE usando o MicroPython.

Formatando o cartão MicroSD

Como temos que usar nosso cartão microSD com Raspberry Pi Pico, teríamos que formatá-lo como FAT32. Teremos que seguir uma série de etapas para realizá-lo com sucesso.

  • Primeiro, insira seu cartão microSD em seu laptop/computador. Agora vá para ‘Este PC’ e clique no ícone do cartão SD. Em seguida, clique em Formatar clicando com o botão direito do mouse no ícone do cartão SD.

  • A seguinte janela irá aparecer. Selecione FAT32 na caixa de diálogo de ‘Sistema de Arquivos’ e clique em ‘INICIAR’.

  • Você receberá uma mensagem de aviso de que a formatação apagará todos os dados anteriores salvos no cartão microSD. Clique OK.’

  • Após alguns instantes, seu cartão microSD será formatado com sucesso. Clique OK.’

Instalando a biblioteca do cartão SD

Para este projeto, precisaremos da biblioteca sdcard.py. Copie esta biblioteca e salve-a em seu Raspberry Pi Pico com o respectivo nome de arquivo no link do GitHub.

Abra um novo arquivo no Thonny. Copie a biblioteca fornecida abaixo ou do link fornecido acima. Salve-o no Raspberry Pi Pico com o nome sdcard.py na pasta lib.

sdcard.py

"""
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[0] & 0xC0 == 0x40:  # CSD version 2.0
            self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024
        elif csd[0] & 0xC0 == 0x00:  # CSD version 1.0 (old, <=2GB)
            c_size = csd[6] & 0b11 | csd[7] << 2 | (csd[8] & 0b11000000) << 4
            c_size_mult = ((csd[9] & 0b11) << 1) | csd[10] >> 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[0] = 0x40 | cmd
        buf[1] = arg >> 24
        buf[2] = arg >> 16
        buf[3] = arg >> 8
        buf[4] = arg
        buf[5] = crc
        self.spi.write(buf)

        if skip1:
            self.spi.readinto(self.tokenbuf, 0xFF)

        # wait for the response (response[7] == 0)
        for i in range(_CMD_TIMEOUT):
            self.spi.readinto(self.tokenbuf, 0xFF)
            response = self.tokenbuf[0]
            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[0] == _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)[0] & 0x1F) != 0x05:
            self.cs(1)
            self.spi.write(b"\xff")
            return

        # wait for write to finish
        while self.spi.read(1, 0xFF)[0] == 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)[0] == 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: ler/gravar em um arquivo

Abra seu Thonny IDE e vá em File > New para abrir um novo arquivo. Copie o código a seguir nesse arquivo. Este sketch lerá e gravará dados em um arquivo .txt que será salvo em nosso cartão microSD.

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")

# Crie um arquivo e escreva algo nele
with open("/sd/data.txt", "w") as file:
    print(“Escrevendo no data.txt...")
    file.write(“Bem Vindo ao CapSistema!\r\n")
    file.write(" Isto é um teste \r\n")

# Abra o arquivo que acabamos de criar e leia a partir dele
with open("/sd/data.txt", "r") as file:
    print(“Lendo o data.txt...")
    data = file.read()
    print(data)

 

Como o Código Funciona?

Agora vamos entender como funciona cada parte do código.

Importando Bibliotecas

O primeiro passo é incluir todas as bibliotecas necessárias para este projeto. Importaremos os módulos machine, sdcard e uos necessários para esta tarefa.

import machine
import sdcard
import uos

Configure o pino GPIO conectado ao pino CS como um pino de saída. Isso é feito usando o método Pin() e passando o número GPIO como o primeiro parâmetro e Pin.OUT como o segundo parâmetro.

CS = machine.Pin(9, machine.Pin.OUT)

O próximo passo é inicializar a interface SPI com os parâmetros necessários, incluindo o número do canal SPI, taxa de transmissão, pinos SPI 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))

Inicializando o cartão microSD

A linha de código a seguir inicializará o cartão microSD usando a função SDcard() na biblioteca sdcard. A função recebe o objeto spi como primeiro parâmetro e o CS Pin como segundo parâmetro. Assim, iniciará a comunicação SPI nos parâmetros indicados.

sd = sdcard.SDCard(spi,CS)

Em seguida, vamos montar o sistema de arquivos usando as seguintes linhas de código:

vfs = uos.VfsFat(sd)
uos.mount(vfs, "/sd")

Gravando dados em arquivo

Em seguida, vamos abrir o arquivo data.txt no cartão microSD usando open() e especificar o primeiro parâmetro como o nome do arquivo e o segundo parâmetro ‘w’ indicando que queremos escrever no arquivo. Se o arquivo não existir, ele será criado.

No terminal Thonny shell, imprimiremos “Writing to data.txt…”

with open("/sd/data.txt", "w") as file:
   print("Escrevendo no data.txt...")

Usando o método write() no objeto de arquivo, escreveremos ‘Bem Vindo ao CapSistema’ no arquivo data.txt. Em seguida, escreveremos “Este é um teste” na segunda linha do arquivo.

file.write("Bem Vindo ao CapSistema\r\n")
   file.write("Isso é um teste\r\n")

Lendo dados do arquivo

Agora, vamos ler os dados que acabamos de escrever em nosso arquivo data.txt. Para fazer isso, primeiro abriremos o arquivo data.txt usando o método open() e especificaremos o nome do arquivo como o primeiro parâmetro e ‘r’ como o segundo parâmetro, indicando que queremos ler o arquivo. Em seguida, usando o método read() no objeto arquivo, leremos o arquivo data.txt e imprimiremos os dados no monitor serial.

with open("/sd/data.txt", "r") as file:
    print("Lendo o data.txt...")
    data = file.read()
    print(data)

 

Demonstração

Carregue o código acima como arquivo main.py para Raspberry Pi Pico. Você verá as mensagens no terminal Thonny shell que estamos gravando no arquivo data.txt e depois lendo a partir dele. Os dados gravados no arquivo (destacados em vermelho) também podem ser vistos impressos no terminal:

Retire o cartão microSD do módulo e insira-o em seu sistema para visualizar o arquivo data.txt.

Abra o arquivo data.txt. Dentro dele você poderá visualizar as duas mensagens que escrevemos no arquivo:

Conclusão

Em conclusão, aprendemos como usar um cartão micro SD com Raspberry Pi Pico. Inicializamos com sucesso nosso cartão microSD e conseguimos ler e gravar em um arquivo .txt.

Você pode gostar de ler estes guias de cartão SD para Arduino e sensores: