Data Logger com Raspberry Pi Pico e Cartão Micro SD

Tempo de leitura: 11 minutes

Neste tutorial, vamos criar um registrador de dados usando Raspberry Pi Pico e MicroPython. Registraremos as leituras de temperatura e umidade adquiridas do sensor DHT22 em um cartão microSD. Criaremos um arquivo .txt em nosso cartão microSD através da programação de nossa placa e, consequentemente, registraremos as leituras de temperatura e umidade nesse arquivo a cada 30 segundos. Os usuários podem usar este tutorial para aprender como adquirir dados do sensor DHT22 e armazená-los em um cartão microSD usando Raspberry Pi Pico.

Este artigo está dividido nestas seções:

  • Introdução ao sensor DHT22 e sua conexão com o Raspberry Pi Pico e o módulo de cartão microSD
  • Formatando o cartão microSD
  • Registro de dados DHT22 para cartão microSD (instalando bibliotecas, esboço e demonstração do MicroPython)

 

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.

Componentes necessários

  • Placa Raspberry Pi Pico
  • Cartão microSD
  • Módulo de cartão microSD
  • Sensor DHT22
  • Protoboard
  • Fios de conexão

DHT22 Introdução

O DHT22 é um sensor barato que mede a umidade relativa e a temperatura. Fornece uma saída digital calibrada com protocolo de 1 fio. Ele mede temperatura e umidade com maior precisão e suporta uma faixa mais ampla em comparação com o DHT11.

Os sensores DHT são pré-calibrados. Podemos conectá-los diretamente ao nosso Raspberry Pi Pico para obter a leitura da saída do sensor. Eles são compostos internamente por um sensor de detecção de umidade e um termistor. Esses dois componentes medem a umidade e a temperatura.

Pinagem DHT22

A figura a seguir mostra o diagrama de pinagem dos sensores DHT. O sensor DHT consiste em quatro pinos. Mas nos módulos DHT apenas três pinos são expostos à pinagem do módulo e o resistor pull-up de 10k ohm é conectado internamente ao pino 2.

A seguir lista a pinagem do sensor DHT e sua breve descrição. O número do pino começa da esquerda para a direita quando você segura o sensor pela extremidade frontal. Também mostra como esses pinos serão conectados à nossa placa.

Pino DHT22 Raspberry Pi Pico
1 (VCC) Este é o pino da fonte de alimentação (3.3V-5V). Vamos conectar com 5V.
2 (Data) Qualquer pino GPIO do Raspberry Pi Pico junto com o resistor pull-up de 10k ohm. Usaremos GPIO2
3 (NC) Não Usado
4 (GND) Terra
  • VCC é o pino da fonte de alimentação. Aplique tensão em uma faixa de 3,3 V a 5,0 V a este pino.
  • Data Out é o pino de saída digital. Ele envia o valor da temperatura e umidade medida na forma de dados seriais
  • N/C não está conectado
  • GND: Conecte o pino GND

Leitura recomendada: DHT11 DHT22 com Raspberry Pi Pico usando MicroPython

Interface Raspberry Pi Pico com módulo DHT22 e cartão microSD

Esta seção mostra como conectar o Raspberry Pi Pico com o sensor DHT22 e o módulo de cartão microSD.

O sensor DHT22 possui 4 terminais que iremos conectar com o Raspberry Pi Pico. Como é tolerante a 5V, vamos conectar o terminal VCC com o pino de 5V da placa. O pino de saída de dados será conectado ao GPIO2 com resistor pull-up de 10k ohm. O resistor não é necessário se você estiver usando o módulo DHT22. Você também pode escolher qualquer outro pino Raspberry Pi Pico GPIO apropriado para se conectar com a saída de dados. O terceiro pino não é usado.

Adicionalmente, iremos conectar o terminal VCC do módulo do cartão microSD com o pino 5V do Raspberry Pi Pico que será comum com o pino VCC do sensor. Todos os três motivos serão comuns. Os pinos SPI GPIO do Raspberry Pi Pico estão sendo usados ​​para conectar com cada um dos terminais SPI restantes do módulo do 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.

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

Controler SPI Pintos GPIO
SPI0_RX GP0/GP4/GP16
SPI0_TX GP3/GP7/GP19
SPI0_SCK GP2/GP6/GP18
SPI0_CSn GP1/GP5/GP17
SPI1_RX GP8/GP12
SPI1_TX GP11/GP15
SPI1_SCK GP10/GP14
SPI1_CSn GP9/GP13

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

Diagrama esquemático

Agora vamos ver como conectar o módulo do cartão microSD e DHT22 com Raspberry Pi Pico.

A tabela abaixo mostra as conexões entre o Raspberry Pi Pico e o módulo de cartão microSD:

MicroSD card module Raspberry Pi Pico
GND GND
VCC 5V
CS GP9 (SPI1_CSn)
MOSI GP11 (SPI1_TX)
SCK GP10 (SPI1_SCK)
MISO GP8 (SPI1_RX)

A tabela abaixo mostra as conexões entre o Raspberry Pi Pico e o sensor DHT22:

DHT22 Sensor Raspberry Pi Pico
1 ( VCC) 5V
2 (Data Out) GP2
3 (NC) NC
4 (GND) GND

Conforme mostrado nas tabelas, vamos conectar o pino de 5V do Raspberry Pi Pico com o terminal VCC do módulo do cartão MicroSD e o pino VCC do sensor DHT22. Todos os três motivos serão comuns. Para o módulo de cartão microSD, 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 módulo de cartão microSD e diagrama de conexão do sensor DHT22
Raspberry Pi Pico com módulo de cartão microSD e diagrama de conexão do sensor DHT22

Agora, como sabemos como conectar o módulo do cartão microSD, o sensor DHT22 e o Raspberry Pi Pico juntos, vamos aprender como preparar o cartão microSD para manipulação de arquivos no 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

 

Raspberry Pi Pico MicroPython: Registro de dados DHT22 no cartão microSD

Abra seu Thonny IDE e vá em File > New para abrir um novo arquivo. Copie o código a seguir nesse arquivo.

Este esboço irá adquirir os dados do sensor do DHT22 e salvá-los em um arquivo .txt no cartão micro SD. Os dados do sensor consistirão em temperatura atual em Celsius e umidade em porcentagem. Novas leituras serão adicionadas a cada 30 segundos.

from machine import Pin
from time import sleep
import dht
import sdcard
import uos

sensor = dht.DHT22(Pin(2)) 

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")
 
while True:
    sensor.measure()
    Temperature = sensor.temperature()
    Humidity = sensor.humidity()
    print("Temperature: {}°C   Humidity: {:.0f}% ".format(Temperature, Humidity))
    
    file = open("/sd/data.txt", "a")
    print("Writing to data.txt...")
    file.write("Temperature: {}°C   Humidity: {:.0f}% \r\n".format(Temperature, Humidity))
    file.close()
    
    print("Done")
    print("")
    sleep(30)

 

Como o Código Funciona?

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

Importando Bibliotecas

Começaremos importando a classe Pin do módulo machine e a classe sleep do módulo time. Também importaremos os módulos dht, sdcard e uos necessários para esta tarefa.

from machine import Pin
from time import sleep
import dht
import sdcard
import uos

Em seguida, definiremos um objeto dht chamado ‘sensor’ e atribuiremos o pino de dados a ele. Aqui estamos usando o sensor DHT22 com pino de dados conectado no GPIO2.

sensor = dht.DHT22(Pin(2))

Configure o pino GPIO conectado ao pino CS do módulo de cartão microSD 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")

 

Registro de dados

Dentro do loop infinito, obteremos a leitura de temperatura e umidade e a salvaremos em ‘Temperatura’ e ‘Umidade’ respectivamente. Eles serão impressos no console do shell após um atraso de 2 segundos.

sensor.measure()
    Temperature = sensor.temperature()
    Humidity = sensor.humidity()
    print("Temperature: {}°C   Humidity: {:.0f}% ".format(Temperature, Humidity))

Em seguida, abriremos o arquivo data.txt no cartão microSD usando open() e especificaremos o primeiro parâmetro como o nome do arquivo e o segundo parâmetro ‘a’ indicando que queremos anexar ao arquivo de texto. Se o arquivo não existir, ele será criado. No terminal Thonny shell, imprimiremos “Writing to data.txt…”

Usando o método write() no objeto de arquivo, registraremos as leituras de temperatura e umidade no arquivo data.txt. Depois disso, vamos fechar o arquivo usando file.close(). Isso garantirá que os dados gravados no arquivo também sejam salvos. No terminal Thonny shell, imprimiremos “Concluído”.

Adquiriremos os dados do sensor a cada 30 segundos e os salvaremos em nosso arquivo data.txt.

file = open("/sd/data.txt", "a")
    print("Writing to data.txt...")
    file.write("Temperature: {}°C   Humidity: {:.0f}% \r\n".format(Temperature, Humidity))
    file.close()
    
    print("Done")
    print("")
    sleep(30)

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("Reading 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. Os dados gravados no arquivo (destacados em vermelho) também podem ser vistos impressos no terminal:

Após alguns minutos, retire o cartão microSD de seu módulo e insira-o em seu sistema para visualizar o arquivo data.txt.

Dentro do arquivo você poderá visualizar as leituras do sensor que consistem em Temperatura em graus Celsius seguida de umidade em porcentagem.

Conclusão

Em conclusão, aprendemos como registrar com sucesso os dados do sensor DHT22 em um cartão micro SD com Raspberry Pi Pico.

Visits: 5 Visits: 1126045