Introdução
Neste artigo vamos ver um sistema que registra automaticamente a data e hora atual em um cartão RFID MIFARE quando ele é aproximado do leitor/gravador. A figura abaixo mostra o diagrama de blocos do nosso projeto:
A Raspberry Pi Pico fornece a inteligência do nosso sistema, rodando uma aplicação escrita em MicroPython. O módulo RTC DS1307 é usado para manter a data e hora atual. O Leitor (ou mais apropriadamente leitor/gravador) MFRC522 lê e atualiza as informações no cartão RFID MIFARE. Um LED bicolor (verde/vermelho) é usado para indicar o sucesso ou falha das operações. Um botão é usado para colocar o sistema no modo de manutenção, onde é feita a preparação do cartão e a descarga dos dados nele armazenados. Para usar o modo manutenção é necessário ligar um micro à Pi Pico através da USB.
Nos próximos itens vamos conhecer um pouco mais sobre esses componentes.
Raspberry Pi Pico
A “Pi Pico” é uma placa com o poderoso microcontrolador RP2040, que possui respeitáveis capacidades de processamento, memória e entrada e saída. Você vai encontrar detalhes sobre ela em vários artigos aqui no blog, resumo aqui os recursos importantes para o nosso projeto:
- A interface USB pode ser usada como porta de comunicação (o que vamos usar no modo manutenção)
- Suporta comunicação serial nos padrões I2C e SPI (usados para conversar com o módulo RTC e o leitor RFID)
- A maioria dos seus pinos podem ser usados para entrada ou saída digital (o que usaremos para o LED e botão)
Módulo RTC DS1307
Este módulo combina um relógio de tempo real DS1307 (com bateria) e uma memória EEPROM 2432 (que não usaremos neste projeto). Ambos os componentes se comunicam através de I2C (você pode ver aqui um artigo sobre essa comunicação que usa este módulo como exemplo).
Cartão MiFare e Leitor MFRC522
O padrão MiFare de RFID foi desenvolvido para aplicações de bilhetagem. Ele suporta a leitura e escrita de dados em cartões ou tags RFID sem contato, a curta distância, de forma segura. Em um post anterior eu descrevi com mais detalhes o uso de cartões padrão MiFare, o que precisamos saber aqui é que os cartões que vamos usar possuem 1K de memória divididos em 16 setores, cada um com quatro blocos de dezesseis bytes. O último bloco de cada setor é usado para controle de acesso (não vamos usar este recurso neste projeto) e o primeiro bloco do primeiro setor não pode ser alterado. Este primeiro bloco inclui uma identificação única do cartão.
O leitor MFRC522 se comunica com um microcontrolador via SPI e fornece os comandos necessários para detectar a presença de um cartão e ler e escrever na sua memória.
LED Bicolor
O LED que vamos usar é na verdade constituído por dois LEDs, um verde e um vermelho, encapsulados juntos. Estes dois LEDs estão invertidos um em relação ao outro, a cor é definida pelo sentido da tensão aplicada entre os dois pinos.
Material Necessário
Para a parte prática você precisará:
- Kit Módulo Leitor Rfid Mfrc522 Mifare
- Módulo RTC DS1307
- Conversor de nível lógico I2C
- Raspberry Pi Pico
- Chave táctil
- LED bicolor
- Protoboard
- Jumpers para conexão
Você pode substituir o LED bicolor por um LED vermelho e um LED verde em paralelo, tomando o cuidado de colocar um invertido em relação a outro.
IMPORTANTE: Como vamos escrever no cartão e reconfigurá-lo, use somente um cartão vazio e que você possa descartar caso algo ocorra errado.
Montagem
A figura abaixo mostra a montagem que vamos utilizar:
Software
O software foi desenvolvido em MicroPython, veja neste artigo como preparar a Raspberry Pi Pico e o seu microcomputador.
A comunicação com o leitor MFRC522 será feito através de uma biblioteca. Baixe o zip de https://github.com/danjperron/micropython-mfrc522 (conforme a figura abaixo), expanda em um diretório e use o Thonny para instalar o arquivo mfrc522.py na Raspberry Pi Pico.
Vamos usar o segundo bloco do primeiro setor do cartão para identificar que ele será usado pela nossa aplicação e para indicar onde será feita a próxima gravação. Vamos gravar as datas e horas em 4 bytes, o permite 4 marcações por bloco. Os dados serão gravados a partir do segundo setor, o que nos fornece uma capacidade de 180 marcações (15 setores x 3 blocos/setor x 4 marcações/bloco). Por simplificação, quando o cartão ficar cheio será necessário reiniciá-lo para uso.
O código completo é esse:
# Registra data e hora em cartão (ou token) Mifare from machine import Pin from machine import I2C from mfrc522 import MFRC522 import time import utime # Conexões pinSCK = 2 pinMISO = 4 pinMOSI = 3 pinCS = 1 pinRST = 0 pinBotao = Pin(11, Pin.IN, Pin.PULL_UP) pinRED = Pin(12, Pin.OUT) pinGREEN = Pin(13, Pin.OUT) pinSDA = Pin(14) pinSCL = Pin(15) # Classe para controle de LED Bicolor class LED: OFF = 0 RED = 1 GREEN = 2 def __init__(self, pinR, pinG): self.pinR = pinR self.pinG = pinG self.write(self.OFF) def write(self, modo): if modo == self.RED: self.pinR.high() self.pinG.low() elif modo == self.GREEN: self.pinR.low() self.pinG.high() else: self.pinR.low() self.pinG.low() # Classe simples para acesso ao RTC DS1307 class RTC: # Converte de BCD para inteiro def bcd2num(x): return ((x >> 4)*10) + (x & 0x0F) # Converte de inteiro para BDC def num2bcd(x): return ((x // 10)<<4) + (x % 10) # Iniciação def __init__(self, i2c, addr = 0x68): self.i2c = i2c self.addr = addr # Retorna data e hora no formato time do Unix def getTime(self): regs = self.i2c.readfrom_mem(self.addr, 0, 7) segundo = RTC.bcd2num(regs[0] & 0x7F) minuto = RTC.bcd2num(regs[1]) hora = RTC.bcd2num(regs[2] & 0x3F) dia = RTC.bcd2num(regs[4]) mes = RTC.bcd2num(regs[5]) ano = RTC.bcd2num(regs[6]) return time.mktime ((ano+2000,mes,dia,hora,minuto,segundo,0,0)) # Lê data e hora def leRelogio(self): regs = self.i2c.readfrom_mem(self.addr, 0, 7) segundo = RTC.bcd2num(regs[0] & 0x7F) minuto = RTC.bcd2num(regs[1]) hora = RTC.bcd2num(regs[2] & 0x3F) dia = RTC.bcd2num(regs[4]) mes = RTC.bcd2num(regs[5]) ano = RTC.bcd2num(regs[6]) return '%02d/%02d/%02d% 02d:%02d:%02d' % (dia, mes, ano, hora, minuto, segundo) # Acerta o relogio (dh = ddmmaaHHMMSS) def acertaRelogio(self,dh): dia = int(dh[0:2]) mes = int(dh[2:4]) ano = int(dh[4:6]) hora = int(dh[6:8]) minuto = int(dh[8:10]) segundo = int(dh[10:12]) regs = bytearray(7) regs[0] = RTC.num2bcd(segundo) regs[1] = RTC.num2bcd(minuto) regs[2] = RTC.num2bcd(hora) regs[3] = 1 regs[4] = RTC.num2bcd(dia) regs[5] = RTC.num2bcd(mes) regs[6] = RTC.num2bcd(ano) self.i2c.writeto_mem(self.addr, 0, regs) # Classe para encapsular acessos ao cartão class Cartao: # Formato do bloco 1 do setor 0 # bytes 0 a 3: marca # byte 4: indice para próxima gravação # bytes 5 a 15: não usados chave = [ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ] # padrão de fábrica marca = [ 0x46, 0x4c, 0x4f, 0x50 ] # Iniciacao def __init__(self, timeout=5000): self.card = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0) self.card.init() self.cartaoAtual = [0] self.lidoEm = 0 self.timeout = timeout # Testa se tem cartão para registro def presente(self): # Ver se tem cartão (stat, tag_type) = self.card.request(self.card.REQIDL) if stat == self.card.OK: # Tem um cartão (stat, uid) = self.card.SelectTagSN() if (stat == self.card.OK) and (uid != self.cartaoAtual): # Cartão a ser tratado self.cartaoAtual = uid self.lidoEm = time.ticks_ms() return True elif self.cartaoAtual != [0]: # Esquecer cartão anterior depois de um tempo delta = time.ticks_diff(time.ticks_ms(), self.lidoEm) if delta > self.timeout: self.cartaoAtual = [0] return False # Aguarda cartão para manutenção def espera(self): timeout = time.ticks_ms() + 5000 while timeout > time.ticks_ms(): utime.sleep_ms(50) (stat, tag_type) = self.card.request(self.card.REQIDL) if stat == self.card.OK: (stat, uid) = self.card.SelectTagSN() if stat == self.card.OK: self.cartaoAtual = uid return uid print ('Desistindo') return [0] # Libera cartão ao final do uso def libera(self): self.card.stop_crypto1() # Retorna identificação do cartão atual def uid(self): return self.cartaoAtual # Verifica se o cartão atual é válido e com espaço def valido(self): (stat, self.blocoMestre) = self.card.readSectorBlock( self.cartaoAtual, 0, 1, self.chave) if (stat == self.card.OK) and \ (self.marca == self.blocoMestre[0:4]): self.proxReg = self.blocoMestre[4] if self.proxReg < 180: return True self.libera() return False # Inicia cartão def inicia(self): dados = [] dados.extend(self.marca) dados.extend([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]) ret = self.card.writeSectorBlock(self.cartaoAtual, 0, 1, dados, self.chave) == self.card.OK self.libera() return ret # Converte indice em setor bloco pos def endereca(self, indice): return ((indice // 12) + 1, (indice % 12) // 4, (indice % 4) * 4) # Anota data e hora atual no cartão def anotaHora(self): ret = False (setor, bloco, pos) = self.endereca(self.proxReg) (stat, dado) = self.card.readSectorBlock(self.cartaoAtual, setor, bloco, self.chave) if stat == self.card.OK: hora = rtc.getTime() dado[pos:pos+4] = list(hora.to_bytes(4, 'big')) if self.card.writeSectorBlock(self.cartaoAtual, setor, bloco, dado, self.chave) == self.card.OK: self.proxReg = self.proxReg+1 self.blocoMestre[4] = self.proxReg if self.card.writeSectorBlock(self.cartaoAtual, 0, 1, self.blocoMestre, self.chave) == self.card.OK: ret = True else: print ('Erro ao atualizar próximo bloco') else: print ('Erro ao gravar data e hora') else: print ('Erro ao ler cartão') return ret # Retorna registros no cartão def pegaRegistros(self): for indice in range(0, self.proxReg): (setor, bloco, pos) = self.endereca(indice) (stat, dado) = self.card.readSectorBlock(self.cartaoAtual, setor, bloco, self.chave) if stat == self.card.OK: hora = time.localtime( int.from_bytes(bytes(dado[pos:pos+4]), 'big')) yield '{:02d}/{:02d}/{:04d} {:02d}:{:02d}:{:02d}'.format( hora[2],hora[1],hora[0],hora[3],hora[4], hora[5]) else: print ('Erro ao ler cartão') break cartao.libera() # Iniciações cartao = Cartao() rtc = RTC(I2C(1, scl=pinSCL, sda=pinSDA, freq=100000)) print ('Data e hora atual: '+rtc.leRelogio()) led = LED(pinRED, pinGREEN) # Tratamento de leitura de cartão def trataCartao(uid): print("Cartão lido {}".format( hex(int.from_bytes(bytes(uid),"little",False)).upper())) cartao.anotaHora() cartao.libera() led.write(led.GREEN) utime.sleep_ms(500) led.write(led.OFF) # Solicita cartão para manutenção def solicitaCartao(): print ('Coloque o cartão perto do leitor') return cartao.espera() # Inicia o cartão para uso def iniciaCartao(): uid = solicitaCartao() if uid == [0]: return if cartao.inicia(): print ('Sucesso') led.write(led.GREEN) else: print ('Erro na gravação') led.write(led.RED) utime.sleep_ms(500) led.write(led.OFF) def listaCartao(): uid = solicitaCartao() if uid == [0] : return if cartao.valido(): for reg in cartao.pegaRegistros(): print (reg) led.write(led.GREEN) else: print ('Cartão inválido') led.write(led.RED) utime.sleep_ms(500) led.write(led.OFF) # Modo manutenção def manutencao(): print () print ("Modo MANUTENÇÃO") print ("I: inicia cartão") print ("L: lista dados no cartão") print ("Rddmmaahhmmss: acerta relogio") print ("F: encerra") while True: cmd = input('Comando: ').upper() if cmd == 'F': break elif cmd == 'I': resp = input('Confirma (S/N)?').upper() if resp == 'S': iniciaCartao() elif cmd == 'L': listaCartao() elif cmd.startswith('R') and len(cmd)==13: rtc.acertaRelogio(cmd[1:]) print ('Data e hora atual: '+rtc.leRelogio()) while pinBotao.value() == 0: utime.sleep_ms(50) # Loop principal try: while True: # Testa se apertou o botão if pinBotao.value() == 0: manutencao() # Verifica se leu um cartão if cartao.presente(): if cartao.valido(): trataCartao(cartao.uid()) else: print ('Cartão inválido') led.write (led.RED) utime.sleep_ms(500) led.write(led.OFF) # Dá um tempo utime.sleep_ms(50) except KeyboardInterrupt: print ('Fim') except Exception as e: print (e)
Operação
Após a carga e execução da aplicação, aperte o botão para colocar no modo de manutenção e digite no shell “Rddmmaahhmmss [Enter]” (dia mês ano hora minuto segundo) para colocar a data e hora atuais no módulo de relógio.
Em seguida inicie os cartões que serão usados:
- Digite “I [Enter]”
- Aproxime um cartão da antena do leitor
- Quando solicitado, digite “S [Enter]” para confirmar a iniciação do cartão ou N para desistir
- Aguarde o LED piscar verde, indicando que a iniciação foi bem sucedida
- Repita os passos anteriores para todos os cartões
- Ao final digite “F [Enter]” para sair do modo manutenção
Para registrar a data e horário atuais basta aproximar o cartão da antena, o LED piscará verde para indicar que o registro foi bem sucedido. Se o LED piscar vermelho algo deu errado (você pode ver no shell o motivo).
Para ler os registros em um cartão, aperte o botão para entrar no modo manutenção, digite “L [Enter]” e aproxime o cartão da antena.
A imagem abaixo exemplifica o uso do sistema:
Conclusão
Este projeto mostrou como usar a Raspberry Pi Pico com diversos dispositivos que podem ser úteis em outros projetos: botão, LED bicolor, módulo RTC e leitor/gravador de RFID.
Você pode usar o projeto exatamente como apresentado ou aperfeiçoá-lo. Algumas sugestões:
- Substituir o LED por um display (alfanumérico ou gráfico) para um interface com o operador mais sofisticada
- No lugar do botão usar um (ou mais) tags RFID para entrar no modo de manutenção. Você pode usar a EEPROM do módulo RTC para guardar a identificação destes tags.
- Usar os blocos do cartão como uma fila circular, mantendo o número do último bloco enviado ao PC e o número do bloco onde será gravada a próxima leitura.
Então, gostou do artigo? Pretende montar o projeto ou fazer algo derivado dele? Dúvidas? Use os comentários abaixo para conta para nós!
Boa tarde prof. Daniel,
Estou tentando escrever no cartão e estou tentando com o código abaixo:
from mfrc522 import MFRC522
import utime
reader = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0)
texto1=input(“Entre com nome: “)
texto2=str(input(“Entre com idade: “))
print(“Aproxime o cartão para gravar…”)
print(“”)
while True:
reader.init()
(stat, tag_type) = reader.request(reader.REQIDL)
if stat == reader.OK:
(stat, uid) = reader.SelectTagSN()
if stat == reader.OK:
card = int.from_bytes(bytes(uid),”little”,False)
print(‘A identificação do cartão é: ‘+str(card))
reader._wreg(texto1, texto2) #está nessas liinha o erro
utime.sleep_ms(500) #somente mudei essa linha concatenando com o if stat
Pode ser outra função?
Abraços e bom ano para o senhor.
O método _wreg é um método interno para escrever num registrador do controlador (dica: métodos Python com nome começando com _ costumam de ser de uso interno da classe). Para escrever no cartão você deve usar o método writeSectorBlock, como mostrado no meu código.
Bom dia Professor,
Obrigado pela sua gentileza em responder, vou testar isso amanhã no PICO.
Bom dia prof.,
vou resumir para o texto não ficar longo:
alterei a linha como o senhor sugeriu:
reader.writeSectorBlock(texto1+texto2), resultou no erro:
TypeError: function takes 5 positional arguments but 2 were given
Então fui no mfrc522 e copiei a linha e substitui:
reader.writeSectorBlock(self,uid, sector, block, data, keyA=None, keyB = None)
data=texto1+texto2
Deu novo erro: NameError: name ‘self’ isn’t defined, substitui self por 1
Aí percebi que ele vai ficar me pedindo valores para escrever no cartão.
Em qual documentação posso localizar os blocos que posso escrever no cartão.
Abraços.
De uma olhada no meu post anterior, onde explico a organização dos setores:https://www.makerhero.com/blog/sistema-de-passagens-com-modulo-leitor-rfid-e-arduino/