from machine import Pin, SPI, I2C, PWM
from time import sleep_ms, sleep_us
# ============================================================
# CONTROL ACCES RFID
# Raspberry Pi Pico W + MFRC522 + LCD I2C
# LED-uri + buzzer + servomotor
# MicroPython - Wokwi
# ============================================================
# ============================================================
# PINI ECRAN LCD I2C
# ============================================================
PIN_LCD_SDA = 0
PIN_LCD_SCL = 1
ADRESA_LCD = 0x27
# ============================================================
# PINI LED-URI
# ============================================================
PIN_LED_VERDE = 10
PIN_LED_ALBASTRU = 11
PIN_LED_ROȘU = 12
# ============================================================
# PINI BUZZER ȘI SERVOMOTOR
# ============================================================
PIN_BUZZER = 13
PIN_SERVO = 15
# ============================================================
# PINI RFID MFRC522
# ============================================================
PIN_RFID_MISO = 16
PIN_RFID_CS = 17
PIN_RFID_SCK = 18
PIN_RFID_MOSI = 19
PIN_RFID_RST = 20
# ============================================================
# UID AUTORIZAT
# Constanta UID_AUTORIZAT stochează un tuplu format din 4 octeți (0x01, 0x03, 0x04, 0x00). Acesta reprezintă "codul unic" (CNP-ul electronic)
# al cardului sau brelocului RFID care are permisiunea să deschidă ușa.
# Deși în Wokwi apare 01:02:03:04, controlerul actual citește cardul albastru ca fiind 01:03:04:00.
# ============================================================
UID_AUTORIZAT = (
0x01,
0x03,
0x04,
0x00
)
# ============================================================
# CLASA LCD 16x2 I2C
# ============================================================
class LCD1602I2C:
# Un ecran LCD clasic are nevoie de 8 până la 12 fire pentru a fi controlat direct de o placă precum Raspberry Pi Pico. Ca să nu risipim atâția pini,
# se folosește un adaptor I2C (cipul PCF8574).
# • Acest cip primește comenzi pe doar 2 fire (SDA și SCL).
# • El preia comanda I2C și o transformă instantaneu în 8 pini fizici de ieșire, notați de la P0 la P7.
# Acești 8 pini (P0-P7) formează un octet (8 biți): [P7, P6, P5, P4, P3, P2, P1, P0]
# Producătorul acestui adaptor I2C a decis din fabrică să conecteze pinii cipului la pinii ecranului LCD într-o ordine fixă. Schema standard arată așa:
#• P0 este legat la pinul RS (Register Select) al ecranului.
#• P1 este legat la pinul RW (Read/Write - de obicei pus la masă).
#• P2 este legat la pinul E (Enable / Activare).
#• P3 este legat la tranzistorul care aprinde Lumina de fundal (Backlight).
#• P4, P5, P6, P7 sunt legați la pinii de date ai ecranului (D4, D5, D6, D7).
SELECTARE_REGISTRU = 0x01
# Controlează pinul RS (Register Select) al ecranului.
ACTIVARE = 0x04
# Controlează pinul E (Enable) al ecranului
LUMINĂ_FUNDAL = 0x08
# Controlează pinul care aprinde sau stinge lumina de fundal (backlight) a ecranului.
def __init__(self, i2c, adresa=0x27):
self.i2c = i2c
self.adresa = adresa
self.initializare()
def scriere_expansor(self, date):
valoare = (date | self.LUMINĂ_FUNDAL) & 0xFF
# Rolul ei este să lipească bitul care controlează lumina de fundal la orice mesaj trimiți. Astfel, indiferent ce scrii pe ecran, lumina din spate rămâne
# mereu aprinsă.
self.i2c.writeto(
self.adresa,
bytes([valoare])
)
# Trimite pachetul de curent electric către cipul ecranului.
# Programul ridică pinul de Activare în starea HIGH (1) (deschide ușa ecranului).
def impuls_activare(self, date):
self.scriere_expansor(
date | self.ACTIVARE
)
sleep_us(1)
# Așteaptă o microsecundă (o milionime de secundă), pentru ca ecranul să simtă impulsul electric
self.scriere_expansor(
date & ~self.ACTIVARE
)
# Coboară pinul de Activare în starea LOW (0) (închide ușa). În această secundă, ecranul „îngheață” și salvează ce era pe fire.
sleep_us(50)
# O scurtă pauză de relaxare pentru ecran.
def trimitere_4_biti(self, date):
self.scriere_expansor(date)
self.impuls_activare(date)
#Livrarea rapidă
#Este o funcție ajutătoare foarte simplă. Ea face două lucruri legate direct:
#• Pune biții de date pe fire
#• Bate imediat la ușa ecranului ca el să îi citească
def trimitere(self, valoare, mod=0):
self.trimitere_4_biti(
(valoare & 0xF0) | mod
)
self.trimitere_4_biti(
((valoare << 4) & 0xF0) | mod
)
# Ecranul este configurat în modul pe 4 biți (pentru a folosi mai puține fire).
# Asta înseamnă că el nu poate primi o literă întreagă (care are 8 biți /1 octet) dintr-o singură mișcare. Trebuie să tăiem litera în două jumătăți
# (patru biți de sus și patru biți de jos) și să le trimitem pe rând.
# exemplu litera 'A'. În cod binar, 'A' se scrie ca 0100 0001.
#• Prima jumătate ((valoare & 0xF0) | mod): Izolează primii 4 biți (0100) și îi trimite cu funcția trimitere_4_biti.
#• A doua jumătate (((valoare << 4) & 0xF0) | mod): Mută ceilalți 4 biți (0001) în față și îi trimite și pe ei.
def comanda(self, valoare):
self.trimitere(valoare, 0)
if valoare == 0x01 or valoare == 0x02:
sleep_ms(2)
#Această funcție este folosită atunci când vrei ca ecranul să facă o acțiune tehnică, nu să scrie ceva pe sticlă (de exemplu: șterge textul, stinge cursorul, mută cursorul la linia 2).
#• self.trimitere(valoare, 0): Trimite numărul comenzii (de exemplu 0x01 pentru curățare), dar setează mod = 0. Când ecranul vede 0 pe linia RS, înțelege: "Asta nu este o literă de afișat, este un ordin tehnic pe care trebuie să-l execut intern!".
#• if valoare == 0x01 or valoare == 0x02: sleep_ms(2):
# • 0x01 este comanda pentru Curățarea ecranului (Clear Display).
# • 0x02 este comanda pentru Resetarea cursorului la poziția inițială (Return Home).
# • De ce e nevoie de pauză? Ecranul LCD are un procesor intern foarte vechi și lent. Ștergerea tuturor pixelilor de pe ecran sau mutarea cursorului înapoi la început sunt operațiuni masive pentru el, care durează mult mai mult decât o microsecundă. Dacă Pico ar trimite imediat altă literă fără să aștepte, ecranul s-ar bloca sau ar rata litera. Codul introduce o pauză de 2 milisecunde (sleep_ms(2)) pentru ca ecranul să își termine treaba în liniște.
def data(self, valoare):
self.trimitere(
valoare,
self.SELECTARE_REGISTRU
)
#Această funcție este folosită exclusiv atunci când vrei să desenezi caractere vizibile pe ecran (litere, cifre, semne de punctuație).
#• self.trimitere(valoare, self.SELECTARE_REGISTRU): Reține că sus în cod am definit SELECTARE_REGISTRU = 0x01. Prin urmare, această linie apelează self.trimitere(valoare, 1).
#• Când ecranul vede 1 pe linia RS, procesorul său reacționează complet diferit: "Aha! Aceasta este o literă! O iau, mă uit în tabelul meu de caractere și o desenez imediat pe sticlă, acolo unde se află cursorul în acest moment." după care mută automat cursorul cu o poziție la dreapta pentru următoarea literă.
def initializare(self):
sleep_ms(50)
self.trimitere_4_biti(0x30)
sleep_ms(5)
self.trimitere_4_biti(0x30)
sleep_us(150)
self.trimitere_4_biti(0x30)
self.trimitere_4_biti(0x20)
# Mod 4 biți, 2 linii
self.comanda(0x28)
# Ecran oprit în timpul configurării
self.comanda(0x08)
# Curățare ecran
self.comanda(0x01)
# Cursor spre dreapta
self.comanda(0x06)
# Ecran pornit, cursor ascuns
self.comanda(0x0C)
#La început, ecranul nu știe încă să primească comenzi complete (împărțite în două jumătăți). De aceea, codul folosește funcția trimitere_4_biti pentru a-i trimite direct impulsuri simple.
#• sleep_ms(50): Așteaptă 50 de milisecunde după ce s-a cuplat curentul. Ecranul are nevoie de acest timp pentru ca tensiunea lui electrică să se stabilizeze.
#• self.trimitere_4_biti(0x30) urmat de sleep_ms(5): Îi trimite o primă comandă de resetare. Îi spune ecranului: "Atenție, urmează să ne configurăm!". Se așteaptă 5 milisecunde pentru că ecranul procesează greu acest prim șoc.
#• self.trimitere_4_biti(0x30) urmat de sleep_us(150): Îi trimite a doua oară aceeași comandă, ca o confirmare, în caz că prima a fost ratată din cauza zgomotului electric.
#• self.trimitere_4_biti(0x30): O a treia trimitere (cerută riguros de foaia de specificații). În acest moment, ecranul este complet treaz și stabilizat.
#• self.trimitere_4_biti(0x20): Aceasta este cea mai importantă comandă de început. 0x20 îi ordonă ecranului: "Treci imediat în modul de funcționare pe 4 biți!". Din această clipă, ușa ecranului s-a modificat: el știe că orice comandă viitoare va veni tăiată în două jumătăți.
#Acum că ecranul știe să comunice pe 4 biți, codul nu mai folosește trimitere_4_biti, ci folosește funcția elegantă self.comanda(...) pe care am explicat-o anterior.
#• self.comanda(0x28) (Mod 4 biți, 2 linii): Îi spune ecranului că fizic are 2 linii de afișare și că fontul caracterelor trebuie să fie cel standard (matrice de 5x8 pixeli).
#• self.comanda(0x08) (Ecran oprit): Dezactivează complet afișajul pe sticlă pentru o fracțiune de secundă. Se face acest lucru pentru ca utilizatorul să nu vadă „pâlpâiri” sau caractere ciudate în timp ce se fac restul setărilor.
#• self.comanda(0x01) (Curățare ecran): Șterge orice memorie reziduală sau gunoi de pixeli rămas pe ecran de la pornirea electrică. Ecranul devine complet gol.
#• self.comanda(0x06) (Cursor spre dreapta): Setează direcția textului. Îi spune ecranului: "De fiecare dată când îți trimit o literă, scrie-o și mută cursorul automat cu un pas la dreapta", exact cum scriem noi pe foaie.
#• self.comanda(0x0C) (Ecran pornit, cursor ascuns): Aceasta este comanda finală. Aprinde din nou ecranul (0x0C), dar ascunde cursorul (acea liniuță care clipește sub litere), pentru ca ecranul să arate curat și profesional pentru utilizator.
#codurile oficiale impuse de producătorul japonez Hitachi în tabelul de instrucțiuni (datasheet-ul) al procesorului HD44780, cipul universal care controlează aproape toate ecranele LCD 16x2 din lume
def curatare(self):
self.comanda(0x01)
# comanda de ștergere totală a ecranului (Clear Display)
def pozitie(self, coloana, rand):
if rand == 0:
adresa = 0x00 + coloana
else:
adresa = 0x40 + coloana
self.comanda(
0x80 | adresa
)
#muta cursorul ecranului la o anumită coordonată, adică la un anumit rând și o anumită coloană (de exemplu: rândul 1, coloana 5), înainte de a scrie un text
#Producătorul cipului HD44780 a mapat cele două rânduri ale ecranului la adrese de memorie diferite:
#• Rândul de sus (rand == 0): Începe de la adresa 0x00 și merge până la 0x0F (pentru cele 16 coloane).
#• Rândul de jos (rand == 1): Începe de la adresa 0x40 și merge până la 0x4F.
#cipului HD44780 : microcontroler de ecran
def scriere(self, text):
for caracter in str(text):
self.data(
ord(caracter)
)
#Această funcție, scriere, este cea care face legătura dintre un text obișnuit din Python (cum ar si șirul "ACCES PERMIS") și semnalele electronice brute pe care le înțelege ecranul LCD. Ea descompune cuvântul în litere individuale și le trimite pe rând către ecran.
#Iată cum funcționează, linie cu linie:
#1. str(text) — Protecția împotriva erorilor
#• str(text) transformă orice valoare primește în șir de text (String).
#• Acest lucru este foarte util deoarece, dacă trimiți din greșeală un număr (de exemplu, numărul de încercări de acces: 3 sau un cod numeric), Python l-ar respinge dacă ar încerca să îl citească literă cu literă. Transformându-l în str, numărul 3 devine textul "3", ferind programul de erori (crash).
#2. for caracter in ... — Linia de asamblare
#• Această buclă for ia textul primit și îl parcurge caracter cu caracter, de la stânga la dreapta.
#• De exemplu, dacă textul este "Pico", bucla va rula de 4 ori: la prima rulare caracter va fi 'P', apoi 'i', apoi 'c', iar la final 'o'.
#3. ord(caracter) — Traducerea în codul mașină (ASCII)
#• Ecranul LCD (procesorul HD44780 despre care am vorbit) nu știe ce este o literă desenată. El știe doar numere.
#• Funcția nativă din Python ord() are rolul de a lua o literă și de a returna codul ei numeric universal (numit cod ASCII).
#• Exemplu:
# • ord('A') returnează numărul 65 (în hexazecimal 0x41).
# • ord('B') returnează numărul 66 (în hexazecimal 0x42).
# • ord(' ') (spațiul gol) returnează numărul 32 (în hexazecimal 0x20).
#4. self.data(...) — Trimiterea finală
#• Numărul obținut prin ord(caracter) este pasat imediat funcției self.data(), pe care am explicat-o mai devreme.
#• Funcția data() activează pinul RS (îi spune ecranului: "Asta e o literă, deseneaz-o!"), sparge numărul în două jumătăți și îl trimite prin I2C. Ecranul primește numărul, caută forma literei în memoria lui CGROM și o aprinde pe sticlă.
def completare_linie(self, text):
text = str(text)[:16]
spatii = 16 - len(text)
if spatii > 0:
text = text + (" " * spatii)
return text
#1. text = str(text)[:16] — Tăierea la dimensiune (Trunchierea)
#• Mai întâi, se asigură că datele sunt de tip text folosind str(text).
#• Operatorul [:16] (numit slicing în Python) oprește doar primele 16 caractere din text și le aruncă pe resturile care depășesc lungimea ecranului.
#• De ce? Ecranul nostru are fizic doar 16 coloane pe o linie. Dacă ai încerca să scrii un text foarte lung (de exemplu un mesaj de 25 de caractere), textul s-ar revărsa pe rândul celălalt sau ar dispărea în memoria invizibilă a ecranului, stricând designul.
#2. spatii = 16 - len(text) — Calculul spațiului rămas
#• Funcția len(text) numără câte litere și semne conține textul în acel moment.
#• Scăzând acest număr din 16, programul află exact câte pătrățele rămân goale pe ecran.
#• Exemplu: Pentru cuvântul "Pico", lungimea este 4. Calculul va fi: 16 - 4 = 12. Deci avem nevoie de 12 spații goale.
#3. if spatii > 0: text = text + (" " * spatii) — Umplerea cu spații goale
#• Condiția verifică dacă textul este mai scurt de 16 caractere.
#• În Python, înmulțirea unui caracter cu un număr (" " * spatii) duplică acel caracter de atâtea ori. În exemplul nostru cu "Pico", expresia " " * 12 va genera un șir format din 12 spații goale consecutive.
#• Operatorul + lipește aceste spații la finalul textului inițial. Rezultatul va fi "Pico " (cuvântul urmat de 12 spații).
#4. return text — Trimiterea rezultatului curățat
#• Funcția dă înapoi noul șir de text modificat, care are întotdeauna fix 16 caractere.
#Atunci când acest text va fi trimis spre ecran, spațiile goale de la final vor suprascrie și vor șterge orice literă veche care ar fi putut rămâne pe acele poziții, lăsând ecranul perfect curat și lizibil.
#Următoarea funcție este afisare(linia_1, linia_2), care folosește această rețetă de completare pentru ambele rânduri ale ecranului.
def afisare(self, linia_1="", linia_2=""):
text_1 = self.completare_linie(
linia_1
)
text_2 = self.completare_linie(
linia_2
)
self.pozitie(0, 0)
self.scriere(text_1)
self.pozitie(0, 1)
self.scriere(text_2)
# def afisare(self, linia_1="", linia_2=""): – Primește textul pentru ambele linii. Dacă nu trimiți nimic, linia rămâne goală ("").
# text_1 = self.completare_linie(linia_1) – Taie sau adaugă spații goale la textul de sus ca să aibă exact 16 caractere (ștergând resturile vechi).
# text_2 = self.completare_linie(linia_2) – Face exact același lucru pentru textul de jos.
# self.pozitie(0, 0) – Mută cursorul hardware la începutul rândului de sus.
# self.scriere(text_1) – Scrie fizic cele 16 caractere pe primul rând.
# self.pozitie(0, 1) – Mută cursorul hardware la începutul rândului de jos.
# self.scriere(text_2) – Scrie fizic cele 16 caractere pe al doilea rând.
# ============================================================
# CLASA CITITORULUI RFID MFRC522
# ============================================================
class MFRC522:
REUSIT = 0
EROARE = 1
#Coduri de stare (Rezultat):
# • REUSIT = 0 – Operațiunea s-a terminat cu succes (ex: card detectat corect).
# • EROARE = 1 – Ceva a eșuat (ex: cardul a fost retras prea repede sau lipsește).
# Comenzi MFRC522
PCD_INACTIV = 0x00
PCD_EMISIE_RECEPTIE = 0x0C
PCD_RESETARE_SOFT = 0x0F
# • PCD_INACTIV (0x00) – Pune cipul în repaus, oprind acțiunea curentă.
# • PCD_EMISIE_RECEPTIE (0x0C) – Pornește antena pentru a trimite date spre card și a asculta răspunsul acestuia.
# • PCD_RESETARE_SOFT (0x0F) – Repornește software-ul cipului pentru a curăța erorile.
# Comenzi pentru card
PICC_SOLICITARE_INACTIV = 0x26
PICC_ANTICOLIZIUNE = 0x93
# • PICC_SOLICITARE_INACTIV (0x26) – Strigă în aer: "Este vreun card prezent?".
# • PICC_ANTICOLIZIUNE (0x93) – Îi cere cardului: "Trimite-mi codul tău unic de identificare (UID) de 4 octeți"
# Regiștri MFRC522
REG_COMANDA = 0x01
REG_ACTIVARE_INTRERUPERE_COM = 0x02
REG_SOLICITARE_INTRERUPERE_COM = 0x04
REG_EROARE = 0x06
REG_DATE_FIFO = 0x09
REG_NIVEL_FIFO = 0x0A
REG_CONTROL = 0x0C
REG_INCADRARE_BITI = 0x0D
REG_MOD = 0x11
REG_CONTROL_TRANSMISIE = 0x14
REG_TRANSMISIE_ASK = 0x15
REG_MOD_TEMPORIZATOR = 0x2A
REG_PRESCALER_TEMPORIZATOR = 0x2B
REG_REINCARCARE_H_TEMPORIZATOR = 0x2C
REG_REINCARCARE_L_TEMPORIZATOR = 0x2D
REG_VERSIUNE = 0x37
# • REG_COMANDA (0x01) – Locul unde Pico scrie ce comandă PCD să execute cipul.
# • REG_ACTIVARE_... / REG_SOLICITARE_... (0x02 / 0x04) – Gestionează alarmele interne (când s-a terminat de trimis sau primit un mesaj).
# • REG_EROARE (0x06) – Stochează tipul de eroare apărut la citire.
# • REG_DATE_FIFO (0x09) / REG_NIVEL_FIFO (0x0A) – FIFO este coada de așteptare. În DATE se pun octeții de trimis/primiți, iar NIVEL spune câți octeți sunt în coadă.
# • REG_CONTROL (0x0C) / REG_INCADRARE_BITI (0x0D) – Aliniază și încadrează biții trimiși prin aer.
# • REG_MOD (0x11) / REG_CONTROL_TRANSMISIE (0x14) / REG_TRANSMISIE_ASK (0x15) – Configurații radio; activează și aprind fizic antena cititorului.
# • REG_MOD_TEMPORIZATOR până la _L_TEMPORIZATOR (0x2A - 0x2D) – Configurează un cronometru de siguranță (dacă un card nu răspunde în câteva milisecunde, oprește căutarea ca să nu blocheze programul).
# • REG_VERSIUNE (0x37) – Conține un număr fix din fabrică pentru a verifica dacă cipul RFID este bine conectat la fire.
def __init__(self, spi, cs, rst):
self.spi = spi
self.cs = cs
self.rst = rst
self.cs.init(
Pin.OUT,
value=1
)
self.rst.init(
Pin.OUT,
value=1
)
self.reinitiere()
self.configurare()
# self.spi = spi / self.cs = cs / self.rst = rst
# • Salvează în memoria internă a obiectului conexiunea SPI și pinii de control (CS și RST) transmiși de la placa Pico.
# self.cs.init(Pin.OUT, value=1)
# • Setează pinul CS (Chip Select / Selectare Cip) ca pin de ieșire.
# • Îl pune direct în starea HIGH (1), ceea ce înseamnă în protocolul SPI: "Deocamdată nu vorbesc cu tine, ești în așteptare".
# self.rst.init(Pin.OUT, value=1)
# • Setează pinul RST (Reset) ca pin de ieșire.
# • Îl pune în starea HIGH (1) pentru a menține cipul RFID treaz și activ (dacă ar fi 0, cipul ar sta stins/resetat).
# self.reinitiere() (sau reiniciar())
# • Apelează imediat funcția care dă un impuls electronic de resetare cipului, curățându-i memoria internă înainte de utilizare.
# self.configurare() (sau configurar())
# • Apelează funcția care pornește ceasul intern (Timer-ul) al cititorului și activează antena radio pentru a putea detecta cardurile.
def scriere_registru(self, registru, valoare):
adresa = (
registru << 1
) & 0x7E
self.cs.value(0)
self.spi.write(
bytes([
adresa,
valoare & 0xFF
])
)
self.cs.value(1)
# adresa = (registru << 1) & 0x7E
# • Calcularea adresei speciale: Cipul MFRC522 cere ca adresa registrului să fie modificată înainte de trimitere.
# • << 1 mută toți biții cu o poziție la stânga (împarte spațiul pentru a face loc bitului de control).
# • & 0x7E forțează ca ultimul bit să fie 0, ceea ce înseamnă în manualul cipului: "Atenție, urmează o operațiune de scriere!".
# self.cs.value(0)
# • Coboară pinul CS în starea LOW (0). Acest semnal electronic îi spune cititorului RFID: "Fii atent, acum vorbesc cu tine!".
# self.spi.write(bytes([adresa, valoare & 0xFF]))
# • Trimiterea pachetului prin SPI: Trimite fizic doi octeți pe firul MOSI.
# • Primul octet este adresa sertarului în care vrem să scriem.
# • Al doilea octet este valoare (setarea) pe care vrem să o salvăm acolo (mascată cu & 0xFF pentru siguranță).
# self.cs.value(1)
# • Ridică înapoi pinul CS în starea HIGH (1). Aceasta încheie conversația și îi spune cipului: "Gata, am terminat, poți aplica setarea!".
def citire_registru(self, registru):
adresa = (
((registru << 1) & 0x7E)
| 0x80
)
self.cs.value(0)
self.spi.write(
bytes([adresa])
)
valoare = self.spi.read(
1,
0x00
)[0]
self.cs.value(1)
return valoare
#• adresa = (((registru << 1) & 0x7E) | 0x80)
# • Calculează adresa exactă pentru citire. Operația | 0x80 forțează ca primul bit din pachet să fie 1, ceea ce îi transmite cipului RFID mesajul: "Atenție, vreau să citesc din tine!".
#• self.cs.value(0)
# • Coboară pinul CS în starea LOW (0). Acest semnal electronic trezește cititorul RFID și îi spune: "Fii atent, vorbesc cu tine!".
#• self.spi.write(bytes([adresa]))
# • Trimite prin SPI adresa calculată mai sus, informând cipul exact ce sertar de memorie dorește Pico să verifice.
#• valoare = self.spi.read(1, 0x00)[0]
# • Placa Pico citește un octet primit înapoi de la cip prin firul MISO. Simbolul `` extrage numărul brut primit și îl salvează în variabila valoare.
#• self.cs.value(1)
# • Ridică pinul CS în starea HIGH (1), încheind conversația și eliberând linia SPI pentru alte componente.
#• return valoare
# • Dă înapoi numărul citit din cip către restul programului.
def activare_biti(self, registru, masca):
valoare = self.citire_registru(
registru
)
self.scriere_registru(
registru,
valoare | masca
)
#• valoare = self.citire_registru(registru)
# • Placa Pico citește mai întâi starea curentă a registrului respectiv, salvând valoarea lui actuală (toți cei 8 biți așa cum sunt ei în acel moment).
#• self.scriere_registru(registru, valoare | masca)
# • Se folosește operatorul logic | (SAU pe biți) pentru a combina valoarea citită cu parametrul masca.
# • Această operație matematică forțează biții indicați de masca să devină 1 (activați), lăsând restul biților exact așa cum erau.
# • Valoarea finală combinată este scrisă înapoi în același registru, aplicând instantaneu noua setare hardware.
def dezactivare_biti(self, registru, masca):
valoare = self.citire_registru(
registru
)
self.scriere_registru(
registru,
valoare & (~masca & 0xFF)
)
# • valoare = self.citire_registru(registru)
# • Placa Pico citește starea actuală a registrului pentru a ști ce biți sunt porniți sau opriți în prezent.
# • ~masca & 0xFF
# • Semnul ~ (NOT / Inversare pe biți) întoarce pe dos masca primită: biții care erau 1 devin 0, iar cei care erau 0 devin 1. Filtrul & 0xFF menține rezultatul curat pe 8 biți.
# • self.scriere_registru(registru, valoare & (...))
# • Se folosește operatorul logic & (ȘI pe biți) între valoarea veche și masca inversată.
# • Această operație matematică forțează biții selectați să devină 0 (dezactivați), păstrând neschimbate toate celelalte setări din acel registru.
# • Noua valoare este salvată înapoi în cip, oprind instantaneu funcțiile hardware selectate.
def reinitiere(self):
self.rst.value(0)
sleep_ms(10)
self.rst.value(1)
sleep_ms(10)
self.scriere_registru(
self.REG_COMANDA,
self.PCD_RESETARE_SOFT
)
sleep_ms(50)
# • self.rst.value(0) urmat de sleep_ms(10)
# • Coboară pinul fizic RST (Reset) în starea LOW (0). Acest lucru întrerupe temporar alimentarea logică a cipului RFID, forțându-l să se oprească complet. Se așteaptă 10 milisecunde pentru ca descărcarea electrică să fie completă.
#• self.rst.value(1) urmat de sleep_ms(10)
# • Ridică pinul RST înapoi în starea HIGH (1), ceea ce repornește electric cipul de la zero. Se lasă încă 10 milisecunde pentru ca circuitele interne ale cititorului să se trezească și să devină stabile.
#• self.scriere_registru(self.REG_COMANDA, self.PCD_RESETARE_SOFT)
# • Aplică și o resetare prin software. Trimite comanda oficială PCD_RESETARE_SOFT (0x0F) în registrul de comandă al cipului, obligând procesorul intern al cititorului să își reîncarce toate setările implicite din fabrică.
#• sleep_ms(50)
# • Introduce o pauză de siguranță de 50 de milisecunde pentru a permite cipului RFID să își finalizeze întreaga procedură de auto-testare și repornire înainte ca programul să îi ceară alte instrucțiuni.
def configurare(self):
self.scriere_registru(
self.REG_MOD_TEMPORIZATOR,
0x8D
)
self.scriere_registru(
self.REG_PRESCALER_TEMPORIZATOR,
0x3E
)
self.scriere_registru(
self.REG_REINCARCARE_L_TEMPORIZATOR,
30
)
self.scriere_registru(
self.REG_REINCARCARE_H_TEMPORIZATOR,
0
)
self.scriere_registru(
self.REG_TRANSMISIE_ASK,
0x40
)
self.scriere_registru(
self.REG_MOD,
0x3D
)
# Pornire antenă RFID
valoare = self.citire_registru(
self.REG_CONTROL_TRANSMISIE
)
if (valoare & 0x03) != 0x03:
self.activare_biti(
self.REG_CONTROL_TRANSMISIE,
0x03
)
# • Configurarea Temporizatorului (Primele 4 scrieri):
# • Trimite valorile 0x8D și 0x3E către regiștrii REG_MOD_TEMPORIZATOR și REG_PRESCALER_TEMPORIZATOR, iar numărul 30 (împărțit în Low și High) către regiștrii de reîncărcare.
# • Această matematică setează un cronometru intern (Timer). Dacă un card este detectat dar nu răspunde în câteva milisecunde, cipul oprește automat căutarea (Timeout) pentru ca programul să nu rămână blocat.
#• self.scriere_registru(self.REG_TRANSMISIE_ASK, 0x40)
# • Setează modulația de transmisie radio la 100% ASK (Amplitude Shift Keying), standardul cerut pentru comunicarea cu cardurile de tip MIFARE.
#• self.scriere_registru(self.REG_MOD, 0x3D)
# • Configurează cipul pentru o procesare digitală standard și setează valoarea inițială a protocolului de verificare CRC (verificarea erorilor de date în aer).
#• Pornirea fizică a Antenei (Ultimele linii):
# • valoare = self.citire_registru(self.REG_CONTROL_TRANSMISIE) citește starea curentă a pinilor care alimentează antena.
# • if (valoare & 0x03) != 0x03: verifică dacă cele două diode emițătoare de semnal radio sunt oprite (biții 0 și 1 din acest registru controlează direct curentul din antenă).
# • self.activare_biti(self.REG_CONTROL_TRANSMISIE, 0x03) forțează activarea acestor doi biți, alimentând electric antena. În acest moment, cititorul începe să emită câmpul electromagnetic necesar detectării cardurilor.
def versiune(self):
return self.citire_registru(
self.REG_VERSIUNE
)
#• self.citire_registru(self.REG_VERSIUNE)
# • Placa Pico citește valoarea stocată în registrul REG_VERSIUNE (0x37). Acest sertar de memorie conține un număr fix, înscris în cip direct din fabrică de către producător (de obicei 0x91 sau 0x92 pentru cipurile originale MFRC522).
#• return ...
# • Trimite acest număr înapoi către program. Dacă funcția returnează 0x00 sau 0xFF, programatorul știe instantaneu că cititorul RFID nu este alimentat sau că firele SPI (MISO/MOSI) sunt conectate greșit.
def comunicare(self, comanda, date):
raspuns = []
biti_receptionati = 0
self.scriere_registru(
self.REG_ACTIVARE_INTRERUPERE_COM,
0xF7
)
self.scriere_registru(
self.REG_SOLICITARE_INTRERUPERE_COM,
0x7F
)
# • raspuns = [] și biti_receptionati = 0
# • Creează și inițializează variabilele de lucru: o listă goală în care se vor salva octeții primiți de la card și un contor setat pe zero pentru a număra biții recepționați în aer.
#• self.scriere_registru(self.REG_ACTIVARE_INTRERUPERE_COM, 0xF7)
# • Configurează cipul MFRC522 pentru a activa semnalele de alarmă (întreruperi) de care avem nevoie. Valoarea 0xF7 lasă pornite alarmele esențiale: avertizarea când s-au primit date noi de la card și avertizarea când a expirat timpul de așteptare (timeout).
#• self.scriere_registru(self.REG_SOLICITARE_INTRERUPERE_COM, 0x7F)
# • Resetează și curăță toate stegulețele de alarmă active în acel moment (folosind masca 0x7F). Este ca și cum ai șterge istoricul notificărilor din telefon, lăsând ecranul curat pentru a putea observa doar alarmele noi care vor apărea în timpul acestei transmisii.
# Golire FIFO
self.activare_biti(
self.REG_NIVEL_FIFO,
0x80
)
self.scriere_registru(
self.REG_COMANDA,
self.PCD_INACTIV
)
for data in date:
self.scriere_registru(
self.REG_DATE_FIFO,
data
)
self.scriere_registru(
self.REG_COMANDA,
comanda
)
if comanda == self.PCD_EMISIE_RECEPTIE:
self.activare_biti(
self.REG_INCADRARE_BITI,
0x80
)
contor = 2000
irq = 0
while contor > 0:
irq = self.citire_registru(
self.REG_SOLICITARE_INTRERUPERE_COM
)
if irq & 0x30:
break
if irq & 0x01:
break
contor -= 1
self.dezactivare_biti(
self.REG_INCADRARE_BITI,
0x80
)
if contor == 0:
return self.EROARE, [], 0
eroare = self.citire_registru(
self.REG_EROARE
)
if eroare & 0x1B:
return self.EROARE, [], 0
cantitate = self.citire_registru(
self.REG_NIVEL_FIFO
)
ultimii_biti = (
self.citire_registru(
self.REG_CONTROL
)
& 0x07
)
if ultimii_biti:
biti_receptionati = (
(cantitate - 1) * 8
+ ultimii_biti
)
else:
biti_receptionati = (
cantitate * 8
)
if cantitate > 16:
cantitate = 16
for _ in range(cantitate):
raspuns.append(
self.citire_registru(
self.REG_DATE_FIFO
)
)
return (
self.REUSIT,
raspuns,
biti_receptionati
)
#• Golirea memoriei tampon (FIFO):
# • Prin activarea bitului 0x80 în REG_NIVEL_FIFO, se șterg instantaneu toate datele vechi rămase în memoria cipului.
#• Pregătirea cipului:
# • Se scrie PCD_INACTIV în REG_COMANDA pentru a opri orice acțiune de fundal curentă a cititorului.
#• Încărcarea datelor de trimis:
# • O buclă for parcurge lista de date primită și scrie fiecare octet, unul câte unul, în registrul REG_DATE_FIFO (coada de transmisie).
#• Lansarea comenzii în aer:
# • Se scrie valoarea parametrului comanda în REG_COMANDA. Dacă aceasta este PCD_EMISIE_RECEPTIE (0x0C), se activează și bitul 0x80 în REG_INCADRARE_BITI pentru a forța antena să înceapă imediat emisia radio.
#• Așteptarea răspunsului (Bucla de Timeout):
# • Se pornește un contor = 2000 pentru a preveni blocarea codului. În bucla while, Pico verifică registrul REG_SOLICITARE_INTRERUPERE_COM.
# • if irq & 0x30: oprește bucla dacă transmisia s-a terminat cu succes sau dacă s-au primit date înapoi de la card.
# • if irq & 0x01: oprește bucla dacă a expirat timpul alocat (timeout hardware). Fiecare pas scade valoarea contorului cu 1.
#• Oprirea transmisiei:
# • Se dezactivează bitul 0x80 în REG_INCADRARE_BITI pentru a opri fluxul direct de date.
#• Verificarea erorilor brute:
# • if contor == 0: Dacă bucla a expirat fără semnale, înseamnă că nu există niciun card în zonă; se returnează EROARE.
# • Se citește REG_EROARE. Filtrul & 0x1B verifică dacă au apărut coliziuni (mai multe carduri prezente), erori de protocol sau de paritate. Dacă da, se returnează EROARE.
#• Calcularea cantității de biți primiți:
# • cantitate = self.citire_registru(REG_NIVEL_FIFO) află exact câți octeți au sosit în memoria cipului de la card.
# • ultimii_biti verifică dacă ultimul octet primit este incomplet (are mai puțin de 8 biți). Matematica ce urmează calculează numărul total exact de biti_receptionati (ex: 16 biți înseamnă 2 octeți compleți).
#• Extragerea datelor din FIFO:
# • Programul limitează citirea la maximum 16 octeți pentru siguranță. O buclă for extrage fiecare octet primit din registrul REG_DATE_FIFO și îl adaugă în lista raspuns.
#• Finalizarea:
# • Funcția se încheie cu succes, returnând codul REUSIT, lista raspuns (cu datele cardului) și numărul total de biți citiți.
def detectare_card(self):
self.scriere_registru(
self.REG_INCADRARE_BITI,
0x07
)
stare, raspuns, biti = self.comunicare(
self.PCD_EMISIE_RECEPTIE,
[self.PICC_SOLICITARE_INACTIV]
)
if stare == self.REUSIT and biti == 16:
return True
return False
#• self.scriere_registru(self.REG_INCADRARE_BITI, 0x07)
# • Configurează cipul RFID să folosească doar 7 biți din ultimul octet trimis. Această setare specială este obligatorie conform protocolului MIFARE pentru a trimite corect prima comandă de interogare.
#• Apelarea funcției comunicare:
# • Lansează comanda radio PCD_EMISIE_RECEPTIE și trimite prin aer codul PICC_SOLICITARE_INACTIV (0x26). Acest cod este un strigăt standard către carduri: "Este cineva acolo?".
# • Funcția salvează rezultatele primite în trei variabile: stare (dacă a funcționat), raspuns (datele primite) și biti (numărul de biți recepționați).
#• if stare == self.REUSIT and biti == 16:
# • Verifică dacă transmisia a avut succes și dacă s-au primit înapoi exact 16 biți (adică 2 octeți). Cardurile compatibile răspund întotdeauna la strigătul de trezire cu un cod fix de confirmare format din 16 biți (numit cod ATQA).
#• return True / return False
# • Dacă răspunsul de 16 biți a sosit corect, funcția dă înapoi True (Cardul este prezent și gata de citire!). În caz contrar (eroare sau liniște în aer), returnează False.
def citire_uid(self):
self.scriere_registru(
self.REG_INCADRARE_BITI,
0x00
)
stare, raspuns, biti = self.comunicare(
self.PCD_EMISIE_RECEPTIE,
[
self.PICC_ANTICOLIZIUNE,
0x20
]
)
if stare != self.REUSIT:
return None
if len(raspuns) < 4:
return None
uid = (
raspuns[0],
raspuns[1],
raspuns[2],
raspuns[3]
)
return uid
#• self.scriere_registru(self.REG_INCADRARE_BITI, 0x00)
# • Resetează încadrarea biților la setarea standard (0x00). Spre deosebire de faza de detecție, acum avem nevoie ca toți octeții transmiși și primiți să folosească numărul întreg de 8 biți.
#• Apelarea funcției comunicare:
# • Lansează comanda radio PCD_EMISIE_RECEPTIE și trimite o listă formată din două coduri: PICC_ANTICOLIZIUNE (0x93) și 0x20 (un parametru tehnic care spune cardului că vrem să primim tot UID-ul).
# • Această comandă forțează cardul să-și trimită „buletinul electronic”.
#• if stare != self.REUSIT:
# • Verifică dacă operațiunea radio a dat eroare (de exemplu, dacă utilizatorul a retras cardul prea repede). Dacă a apărut o eroare, funcția se oprește imediat și returnează None (nimic).
#• if len(raspuns) < 4:
# • Verifică dacă lista de octeți primiți înapoi are o lungime mai mică de 4. Un UID valid de tip MIFARE standard are obligatoriu o lungime de exact 4 octeți. Dacă s-au primit mai puțini date, operațiunea este abandonată, returnând None.
#• Extragerea UID-ului:
# • Colectează primii 4 octeți din lista raspuns (raspuns[0] până la raspuns[3]) și îi grupează într-o singură variabilă structurată de tip tuplu numită uid.
#• return uid
# • Returnează tuplul cu cei 4 octeți (buletinul cardului) către program, gata să fie comparat cu ID-ul autorizat pentru a decide dacă se deschide sau nu ușa.
# ============================================================
# CONVERTIRE UID ÎN TEXT
# ============================================================
def uid_in_text(uid):
text = ""
for pozitie in range(len(uid)):
text = text + "{:02X}".format(
uid[pozitie]
)
if pozitie < len(uid) - 1:
text = text + ":"
return text
#• text = ""
# • Inițializează o variabilă goală numită text în care se va construi, literă cu literă, rezultatul final.
#• for pozitie in range(len(uid)):
# • O buclă for parcurge pe rând fiecare număr (octet) din codul unic al cardului, folosind indexul pozitie (de la 0 la 3).
#• "{:02X}".format(uid[pozitie])
# • Aceasta este o rețetă de formatare. Ea ia numărul curent și îl transformă în hexazecimal cu două litere mari (de exemplu, numărul 1 devine textul "01", numărul 10 devine "0A", iar 255 devine "FF").
# • Textul rezultat este lipit imediat la finalul variabilei text.
#• if pozitie < len(uid) - 1: text = text + ":"
# • Verifică dacă octetul curent nu este ultimul din listă. Dacă mai urmează și alte numere, adaugă un semn de două puncte (":") pentru a le separa.
#• return text
# • Trimite înapoi rezultatul final formatat. De exemplu, un tuplu brut de numere precum (1, 3, 4, 0) va fi returnat ca un text curat: "01:03:04:00", gata să fie afișat pe ecranul LCD.
# ============================================================
# INIȚIALIZARE LED-URI
# ============================================================
led_verde = Pin(
PIN_LED_VERDE,
Pin.OUT,
value=0
)
led_albastru = Pin(
PIN_LED_ALBASTRU,
Pin.OUT,
value=0
)
led_rosu = Pin(
PIN_LED_ROȘU,
Pin.OUT,
value=0
)
#• Pin(PIN_LED_..., Pin.OUT)
# • Setează fiecare pin alocat LED-urilor (pinul 10 pentru verde, 11 pentru albastru și 12 pentru roșu) în modul de funcționare Pin.OUT (Ieșire). Acest lucru îi spune plăcii Pico că va trimite curent electric spre aceste componente pentru a le controla, în loc să citească semnale de la ele.
#• value=0
# • Setează starea inițială a pinilor pe 0 logic (LOW / Oprit). Această setare se asigură că în momentul în care pornești sau resetezi dispozitivul, toate LED-urile vor fi stinse, evitând aprinderile accidentale înainte ca sistemul RFID să fie complet gata.
#• Alocarea variabilelor (led_verde, led_albastru, led_rosu)
# • Salvează aceste configurări hardware în trei variabile cu nume clare. Astfel, în restul programului, poți aprinde sau stinge foarte simplu orice LED folosind instrucțiuni intuitive (de exemplu: led_verde.value(1) pentru a-l aprinde sau led_verde.value(0) pentru a-l stinge).
# ============================================================
# INIȚIALIZARE BUZZER
# ============================================================
buzzer = PWM(
Pin(PIN_BUZZER)
)
buzzer.freq(1500)
buzzer.duty_u16(0)
#• buzzer = PWM(Pin(PIN_BUZZER))
# • Placa Pico activează funcția PWM (Modulație în Lățime de Impulsuri) pe pinul alocat buzzer-ului. PWM este tehnica hardware prin care o placă de dezvoltare generează impulsuri electrice rapide (o undă pătrată) pentru a face membrana buzzer-ului să vibreze și să scoată sunete (bipuri).
#• buzzer.freq(1500)
# • Setează frecvența semnalului electric la 1500 Hz (hertzi). Această valoare determină tonalitatea sau nota muzicală pe care o va scoate buzzer-ul: 1500 de vibrații pe secundă generează un sunet ascuțit și strident, ideal pentru alarme sau notificări de avertizare.
#• buzzer.duty_u16(0)
# • Setează factorul de umplere (duty cycle) la 0. Factorul de umplere determină volumul sau intensitatea sunetului (cât timp stă pinul pe 1 versus cât timp stă pe 0). Sătându-l pe zero, curentul este oprit complet, ceea ce înseamnă că buzzer-ul va fi perfect silențios când pornește sistemul, așteptând o comandă viitoare (cum ar fi scanarea unui card) ca să sune.
# ============================================================
# INIȚIALIZARE SERVOMOTOR
# ============================================================
servo = PWM(
Pin(PIN_SERVO)
)
servo.freq(50)
#• servo = PWM(Pin(PIN_SERVO))
# • Activează funcția PWM pe pinul alocat servomotorului. Servomotoarele nu se rotesc pur și simplu când primesc curent continuu; ele au nevoie de impulsuri electrice foarte precise pentru a ști la ce unghi exact (între 0 și 180 de grade) trebuie să își poziționeze brațul (bariera).
#• servo.freq(50)
# • Setează frecvența semnalului la 50 Hz (hertzi). Aceasta este o cerință tehnică universală din fabricația servomotoarelor de tip hobby (cum este cel simulat în Wokwi). O frecvență de 50 Hz înseamnă că placa Pico va trimite un impuls de control către motor exact de 50 de ori pe secundă (adică o dată la fiecare 20 de milisecunde), menținându-i brațul stabil în poziția dorită.
# ============================================================
# FUNCȚIE PENTRENȚĂ PENTRU MIȘCAREA SERVOMOTORULUI
# ============================================================
def miscare_servo(grade):
if grade < 0:
grade = 0
if grade > 180:
grade = 180
# Convertire grade într-un impuls între 500 și 2500 us
impuls_us = 500 + int(
grade * 2000 / 180
)
servo.duty_ns(
impuls_us * 1000
)
#• if grade < 0: grade = 0 și if grade > 180: grade = 180
# • Protecția hardware: Servomotoarele standard se pot roti doar între 0 și 180 de grade. Dacă în program trimiți din greșeală o valoare greșită (cum ar fi -45 sau 200), aceste două condiții plafonează unghiul la limitele sigure pentru a nu forța sau rupe rotițele din plastic ale motorului.
#• impuls_us = 500 + int(grade * 2000 / 180)
# • Matematica din spate: Servomotoarele folosesc lungimea unui impuls electric (în microsecunde) pentru a citi unghiul. Standardul cere ca 0 grade să corespundă unui impuls de 500 microsecunde, iar 180 de grade unui impuls de 2500 microsecunde.
# • Formula face regula de trei simplă: împarte diferența maximă de timp (2000 microsecunde) la cele 180 de grade și adaugă baza de pornire (500). Rezultatul este un număr întreg (int) cuprins între 500 și 2500.
#• servo.duty_ns(impuls_us * 1000)
# • Acționarea fizică: Funcția MicroPython duty_ns controlează motorul folosind unități de timp și mai mici, numite nanosecunde (1 microsecundă = 1000 nanosecunde). Înmulțind rezultatul anterior cu 1000, placa Pico modifică instantaneu semnalul PWM de pe pinul 15, trimițând brațul motorului exact la poziția cerută.
# ============================================================
# FUNCȚII BUZZER
# ============================================================
def oprire_buzzer():
buzzer.duty_u16(0)
def pornire_alarma():
buzzer.freq(1500)
buzzer.duty_u16(32768)
# Sună timp de o secundă
sleep_ms(1000)
buzzer.duty_u16(0)
#
# ============================================================
# OPRIRE TOATE LED-URILE
# ============================================================
def oprire_leduri():
led_verde.value(0)
led_albastru.value(0)
led_rosu.value(0)
#• def oprire_buzzer():
# • buzzer.duty_u16(0) – Setează factorul de umplere al semnalului PWM la 0. Aceasta oprește complet alimentarea electrică a buzzer-ului, făcându-l să devină instantaneu silențios, indiferent ce frecvență era setată anterior.
#• def pornire_alarma():
# • buzzer.freq(1500) – Setează tonalitatea alarmei la o frecvență ascuțită de 1500 Hz (hertzi), creând un sunet strident de avertizare.
# • buzzer.duty_u16(32768) – Pornește sunetul la un volum optim. Valoarea 32768 reprezintă exact jumătate (50%) din capacitatea maximă a registrului de 16 biți (care merge de la 0 la 65535), oferind o undă pătrată perfectă și un sunet puternic.
# • sleep_ms(1000) – Menține buzzer-ul pornit și lasă alarma să sune în aer timp de exact o secundă (1000 de milisecunde), blocând temporar alte acțiuni.
# • buzzer.duty_u16(0) – Pune factorul de umplere înapoi pe zero, oprind automat sunetul după ce s-a scurs acea secundă de alarmă.
# ============================================================
# STARE INIȚIALĂ
# ============================================================
def stare_initiala():
oprire_leduri()
oprire_buzzer()
lcd.afisare(
"Apropiati cardul",
"de cititor RFID"
)
#• oprire_leduri()
# • Apelează funcția secundară (definită în proiectul tău) pentru a stinge instantaneu toate cele trei LED-uri (verde, albastru și roșu), curățând semnalizările vizuale anterioare.
#• oprire_buzzer()
# • Strigă funcția explicată anterior pentru a se asigura că factorul de umplere PWM este pe zero, oprind orice sunet sau bip rămas activ.
#• lcd.afisare(...)
# • Trimite textul "Apropiati cardul" pe primul rând și "de cititor RFID" på al doilea rând al ecranului LCD.
# • Datorită funcțiilor analizate anterior, acest mesaj va suprascrie automat orice text vechi (cum ar fi "ACCES PERMIS" sau "ACCES RESPINS"), lăsând ecranul curat și pregătit pentru o nouă scanare.
# ============================================================
# UTILIZATOR ÎNREGISTRAT
# ============================================================
def utilizator_inregistrat():
# Oprire alarmă și LED roșu
oprire_buzzer()
led_rosu.value(0)
# Pornire LED verde și albastru
led_verde.value(1)
led_albastru.value(1)
# Deschide ușa
miscare_servo(90)
# Afișare mesaj
lcd.afisare(
"Utilizator",
"Inregistrat"
)
print()
print("==============================")
print("UTILIZATOR ÎNREGISTRAT")
print("LED verde: pornit")
print("LED albastru: pornit")
print("Servo: 90 grade")
print("==============================")
#• Siguranță și oprirea alarmei:
# • oprire_buzzer() și led_rosu.value(0) opresc imediat orice sunet sau alertă vizuală de eroare declanșată anterior, asigurându-se că starea de alertă este anulată.
#• Semnalizarea vizuală de succes:
# • Aprinde simultan led_verde.value(1) și led_albastru.value(1). Lumina combinată indică vizual utilizatorului că sistemul a recunoscut cardul și că trecerea este permisă.
#• Deblocarea fizică (Bariera):
# • miscare_servo(90) trimite brațul servomotorului la poziția de 90 de grade (poziția verticală sau „deschis”), ridicând fizic bariera pentru a permite trecerea persoanei.
#• Actualizarea ecranului LCD:
# • lcd.afisare("Utilizator", "Inregistrat") schimbă textul de pe ecran pentru a confirma pe sticlă textul de succes, suprascriind mesajul de așteptare.
#• Trimiterea datelor în consolă (print):
# • Liniile cu print(...) trimit un raport detaliat în terminalul Serial de pe calculator. Acest lucru ajută programatorul (sau administratorul sistemului) să monitorizeze în timp real în consolă ce acțiuni hardware au fost pornite și să verifice că totul funcționează corect.
# ============================================================
# UTILIZATOR NEÎNREGISTRAT
# ============================================================
def utilizator_neinregistrat():
# Oprire LED verde și albastru
led_verde.value(0)
led_albastru.value(0)
# Pornire LED roșu
led_rosu.value(1)
# Închide ușa
miscare_servo(0)
# Afișare mesaj
lcd.afisare(
"Utilizator fara",
"Inregistrare"
)
print()
print("==============================")
print("UTILIZATOR FĂRĂ ÎNREGISTRARE")
print("LED roșu: pornit")
print("Servo: 0 grade")
print("Buzzer: alarmă")
print("==============================")
# Activare alarmă
pornire_alarma()
# • Stingerea semnalelor de succes:
# • led_verde.value(0) și led_albastru.value(0) dezactivează imediat indicatoarele luminoase de acces permis sau de așteptare.
#• Semnalizarea vizuală de eroare:
# • led_rosu.value(1) aprinde LED-ul roșu, indicând clar și vizual de la distanță că accesul a fost respins.
#• Blocarea fizică (Bariera):
# • miscare_servo(0) forțează brațul servomotorului să revină sau să rămână la 0 grade (poziția orizontală sau „închis”), asigurându-se că bariera/ușa este complet blocată.
#• Actualizarea ecranului LCD:
# • lcd.afisare("Utilizator fara", "Inregistrare") modifică mesajul de pe ecran pentru a avertiza persoana că actul ei de acces nu este valid în baza de date.
#• Raportul în consolă (print):
# • Liniile print(...) trimit text în terminalul calculatorului pentru ca administratorul sau cel care testează codul în Wokwi să vadă starea exactă a componentelor hardware în momentul respingerii.
#• Declanșarea alarmei sonore:
# • pornire_alarma() apelează funcția explicată anterior. Aceasta va face buzzer-ul să sune puternic la frecvența de 1500 Hz timp de exact o secundă, după care îl va opri automat.
# ============================================================
# INIȚIALIZARE ECRAN LCD
# ============================================================
i2c = I2C(
0,
sda=Pin(PIN_LCD_SDA),
scl=Pin(PIN_LCD_SCL),
freq=100000
)
dispozitive_i2c = i2c.scan()
print(
"Dispozitive I2C:",
dispozitive_i2c
)
if ADRESA_LCD not in dispozitive_i2c:
raise RuntimeError(
"Nu s-a gasit ecranul LCD la 0x27"
)
lcd = LCD1602I2C(
i2c,
ADRESA_LCD
)
#• i2c = I2C(0, sda=Pin(...), scl=Pin(...), freq=100000)
# • Inițializează perifericul hardware I2C cu numărul 0 de pe placa Pico, alocând pinii definiți anterior pentru date (sda) și ceas (scl). Frecvența de lucru este setată la 100.000 Hz (100 kHz), care reprezintă viteza standard (Standard-mode) de comunicație pentru ecranele LCD.
#• dispozitive_i2c = i2c.scan()
# • Placa Pico trimite câte un semnal rapid pe toate adresele electronice posibile (de la 0 la 127) pentru a vedea ce componente hardware răspund pe fire. Rezultatul (o listă cu adresele găsite) este salvat în variabila dispozitive_i2c.
#• print("Dispozitive I2C:", dispozitive_i2c)
# • Afișează în consola de pe calculator lista cu adresele găsite în urma scanării. Acest lucru te ajută în Wokwi să confirmi vizual că ecranul este online și conectat corect.
#• if ADRESA_LCD not in dispozitive_i2c: raise RuntimeError(...)
# • Verificarea de siguranță: Programul verifică dacă adresa ecranului (definită la început ca fiind 0x27, adică numărul 39 în baza 10) se află în lista dispozitivelor găsite. Dacă ecranul nu răspunde, programul oprește execuția imediat cu o eroare critică (RuntimeError), prevenind blocarea ulterioară a codului.
#• lcd = LCD1602I2C(i2c, ADRESA_LCD)
# • Dacă ecranul a fost găsit cu succes, codul creează obiectul lcd apelând clasa pe care am explicat-o la început. În acest moment se rulează automat și funcția de trezire hardware (initializare), lăsând ecranul aprins și pregătit să primească comenzi de afișare.
# ============================================================
# INIȚIALIZARE BUS SPI
# ============================================================
spi = SPI(
0,
baudrate=1000000,
polarity=0,
phase=0,
bits=8,
firstbit=SPI.MSB,
sck=Pin(PIN_RFID_SCK),
mosi=Pin(PIN_RFID_MOSI),
miso=Pin(PIN_RFID_MISO)
)
#• spi = SPI(0, ...)
# • Activează primul canal hardware de comunicație SPI (canalul 0) disponibil pe placa Pico.
#• baudrate=1000000
# • Setează viteza de transfer a datelor la 1 MHz (1.000.000 de biți pe secundă), o viteză stabilă și sigură pentru citirea corectă a datelor radio de la distanță scurtă.
#• polarity=0 și phase=0
# • Setează modul standard de funcționare SPI (cunoscut sub numele de Modul 0). Acest parametru stabilește sincronizarea electronică: linia de ceas (SCK) stă în starea LOW (0) când nu se transmit date, iar biții sunt citiți pe frontul crescător al semnalului.
#• bits=8
# • Configurează mărimea fiecărui pachet de date trimis la 8 biți (adică exact 1 octet), formatul standard pentru majoritatea componentelor electronice.
#• firstbit=SPI.MSB
# • Stabilește ordinea de transmitere a biților prin fire. MSB (Most Significant Bit) înseamnă că cel mai mare și important bit dintr-un octet este trimis întotdeauna primul pe fir.
#• Alocarea pinilor (sck, mosi, miso)
# • Conectează fizic funcțiile interne ale protocolului SPI la pinii stabiliți anterior ai plăcii Pico: pinul de ceas (SCK), pinul de trimitere date de la Pico spre cititor (MOSI) și pinul de primire date de la cititor spre Pico (MISO).
# ============================================================
# INIȚIALIZARE CITITOR RFID
# ============================================================
rfid = MFRC522(
spi,
Pin(PIN_RFID_CS),
Pin(PIN_RFID_RST)
)
#• rfid = MFRC522(...)
# • Creează obiectul numit rfid prin apelarea clasei MFRC522 pe care am analizat-o anterior. Din acest moment, variabila rfid devine panoul de control de unde poți cere scanarea cardurilor.
#• spi
# • Trimite către cititor configurația magistralei SPI (viteza de 1 MHz, pinii SCK, MOSI, MISO) pe care ai definit-o în pasul anterior, permițând transferul rapid de date.
#• Pin(PIN_RFID_CS)
# • Transmite pinul 17 ca pin de selectare a cipului (Chip Select). Constructorul clasei îl va configura automat ca ieșire și îl va pune pe 1 pentru a gestiona momentele în care Pico vorbește cu cititorul.
#• Pin(PIN_RFID_RST)
# • Transmite pinul 20 ca pin de Reset. Constructorul îl va folosi imediat pentru a rula funcția reinitiere(), dând acel impuls electric de trezire și curățare a cipului.
# ============================================================
# CONFIGURARE INIȚIALĂ A SISTEMULUI
# ============================================================
miscare_servo(0)
stare_initiala()
print("================================")
print("CONTROL ACCES RFID")
print(
"Versiune MFRC522: 0x{:02X}".format(
rfid.versiune()
)
)
print(
"UID autorizat:",
uid_in_text(UID_AUTORIZAT)
)
print("Apropiati un card")
print("================================")
#• miscare_servo(0)
# • Setează starea inițială a barierei/ușii. Trimite un semnal de 0 grade către servomotor pentru a se asigura că brațul se află în poziție orizontală, adică sistemul pornește fiind complet închis și blocat.
#• stare_initiala()
# • Apelează funcția pe care am explicat-o anterior. Aceasta stinge toate LED-urile, oprește buzzer-ul și scrie textul "Apropiati cardul / de cititor RFID" pe ecranul LCD, pregătind interfața vizuală.
#• rfid.versiune()
# • Placa Pico interoghează cititorul pentru a-și afla codul din fabrică. Formatarea 0x{:02X} transformă numărul primit într-un text hexazecimal curat (de exemplu, devine "0x91" sau "0x92"), demonstrând în consolă că cipul RFID este conectat corect și răspunde la comenzi.
#• uid_in_text(UID_AUTORIZAT)
# • Transformă tuplul de numere al cheii autorizate într-un text hexazecimal despărțit prin două puncte ("01:03:04:00"). Acesta este printat în consolă pentru ca administratorul să știe exact ce card este configurat în memorie ca având drept de acces.
#• Mesajul final din terminal:
# • Printează textul "Apropiati un card", marcând vizual în consolă sfârșitul fazei de pornire și faptul că dispozitivul este gata de utilizare.
# ============================================================
# BUCLĂ PRINCIPALĂ
# ============================================================
card_citit = False
while True:
if rfid.detectare_card():
if not card_citit:
uid = rfid.citire_uid()
if uid is not None:
text_uid = uid_in_text(uid)
print()
print("CARD DETECTAT")
print("UID:", text_uid)
# Comparare UID citit
if uid == UID_AUTORIZAT:
utilizator_inregistrat()
else:
utilizator_neinregistrat()
card_citit = True
# Menține rezultatul timp de 3 secunde
sleep_ms(3000)
# Oprire LED-uri și revenire la ecranul inițial
oprire_leduri()
oprire_buzzer()
lcd.afisare(
"Apropiati cardul",
"de cititor RFID"
)
else:
print(
"Eroare la citirea UID-ului"
)
lcd.afisare(
"Eroare citire",
"UID card"
)
sleep_ms(1500)
stare_initiala()
else:
# Permite citirea unui card nou
card_citit = False
sleep_ms(100)
#• card_citit = False
# • O variabilă de control (steguleț) inițializată cu False. Rolul ei este să prevină citirea repetată a aceluiași card de sute de ori pe secundă dacă utilizatorul îl lasă lipit de antenă.
#• if rfid.detectare_card():
# • Placa Pico verifică continuu (prin funcția explicată anterior) dacă există un card în fața cititorului.
#• if not card_citit:
# • Dacă un card este detectat, programul verifică dacă este un card nou sau dacă este cel detectat la pasul anterior. Dacă este nou, trece la citire.
#• uid = rfid.citire_uid()
# • Antena extrage codul unic (UID) al cardului. Dacă citirea reușește (uid is not None), îl transformă în text (text_uid) și îl printează în consolă.
#• Logica de acces (if uid == UID_AUTORIZAT:):
# • Acces Permis: Dacă codul cardului se potrivește perfect cu cel autorizat, se apelează utilizator_inregistrat() (aprinde LED-ul verde, scrie pe ecran și deschide bariera la 90 de grade).
# • Acces Respins: Dacă codul nu coincide, se apelează utilizator_neinregistrat() (aprinde LED-ul roșu, blochează bariera la 0 grade și pornește alarma sonoră).
#• card_citit = True și sleep_ms(3000)
# • Blochează scanările viitoare și menține rezultatul (ușa deschisă sau ecranul de eroare) timp de 3 secunde pentru ca utilizatorul să poată trece sau să vadă mesajul.
#• Resetarea interfeței (După cele 3 secunde):
# • Oprește LED-urile, buzzer-ul și reafișează mesajul de întâmpinare "Apropiati cardul..." pe LCD, lăsând sistemul pregătit pentru următoarea persoană.
#• Gestionarea erorilor (else):
# • Dacă scanarea UID-ului a eșuat la jumătate (card retras prea repede), printează și afișează "Eroare citire UID card", așteaptă 1.5 secunde, apoi revine la starea inițială.
#• Eliberarea senzorului (else: card_citit = False):
# • Dacă bucla principală observă că nu mai există niciun card în fața antenei, resetează stegulețul pe False. Acest lucru îi dă voie sistemului să poată citi un nou card (sau același card din nou) la următoarea apropiere.
#• sleep_ms(100)
# • O mică pauză de 100 de milisecunde la fiecare ciclu pentru a lăsa procesorul plăcii Pico să respire, prevenind supraîncălzirea software sau consumul inutil de energie.