Proiectul are ca scop crearea unui modul extern capabil sa transmita un flux video de la distante relativ mari, in dezavantajul calitatii imaginii si a numarului scazut de cadre pe secunda.
Laboratoare folosite: 1
(USART), 2
(Intreruperi), 3
(Timere) si 5
(SPI).
Schema bloc:
Modulul extern este format dintr-un:
Modulul local este format din:
Schema:
Cele doua transceivere LoRa functioneaza in mod half-duplex. In acest design, transceiver-ul de pe modulul local trebuie sa verifice CRC
-ul pachetului si sa transmita inapoi un tip de ACK
.
Pinul DIO0
de pe LoRa genereaza intreruperi. Cele trei care ne intereseaza sunt:
onReceive
. Un pachet nou este disponibil in bufferul LoRa.onTxDone
. Pachetul din buffer a fost trimis. Bufferul poate fi refolosit. Nu se garanteaza ca celalalt LoRa a primit pachetul.onCrcError
. A venit un pachet nou, insa CRC-ul nu este corect. Continutul pachetului nu este disponibil.
Exista un bug documentat, dar inca prezent in cea mai recenta versiune a bibliotecii: nu apare intreruperea lui onTxDone
, chiar daca bufferul a fost golit:
int LoRaClass::endPacket(bool async) { ... if (_onTxDone && (readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == IRQ_TX_DONE_MASK) { handleDio0Rise(); ... }
onCrcError
nu este implementat deloc. Tratamentul standard implica aruncarea silentioasa a pachetelor cu CRC-ul gresit. Am pus interrupt-ul in biblioteca (copiat comportamentul de la onTxDone
si adaugat un else
ca sa previn drop-ul):
void LoRaClass::handleDio0Rise() { ... if ((irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) { ... } else if (_onCrcError) { _onCrcError(); } }
Receiver-ul si sender-ul trecute pe placi de prototipare:
Design sumar al algortimului de compresie:
256 x 256
.v[0..255][0.255]
.d[i][j][l]
si D[i][j][L]
pentru anumiti i, j
din [0..255]
si l
putere a lui 2
.d[i][j][l]
reprezinta valoarea minima a unui pixel din careul [i..i+l-1] x [j..j+l-1]
.D[i][j][l]
reprezinta valoarea maxima a unui pixel din careul [i..i+l-1] x [j..j+l-1]
.scor
o functie care accepta parametrii l, minVal, maxVal
. Daca scor(l, d[i][j][l], D[i][j][l]) ⇐ thresh
(o constanta), nu mai sparg careul in patru cadrane si aproximez toti pixelii din careu cu media valorilor lor.(i, j, l)
in patru cadrane:(i, j, l/2)
,(i, j + l/2, l/2)
,(i + l/2, j, l/2)
,(i + l/2, j + l/2, l/2)
.
Matricele d[][][]
si D[][][]
se construiesc de la l
mic la mare (bottom-up), apoi urmeaza recursia top-down.
Pe ESP32-CAM
d
si D
sunt reprezentate liniar. Interactiunea fiu-tata este foarte similara cu cea de la reprezentarea unui heap in memorie (numarand de la 0
, daca tatal este la pozitia i
, fiii sunt la pozitiile 2i+1
si 2i+2
).
Diferenta este ca aici un nod are 4
fii, iar fiecare nod ocupa 3
pozitii consecutive, una pentru fiecare culoare.
int getChild(int r, int id) { return ((r / 3) * 4 + id) * 3; } int getFather(int r) { return ((r / 3) - 1) / 4 * 3; }
Functia scor are urmatoarea implementare:
dispThresh = 80 def corectorDisp(arie: int) -> float: #daca aria este prea mica, mai las din dispThresh. if arie > 1000: return 1.0 ex = math.exp(arie * 0.00390625) #coef pt 1024x768. cu cat e coef mai mic, cu atat compreseaza mai tare. return ex / (1 + ex) #normalizare pe [0, 1] cu sigmoid. def scor(l, minVal, maxVal): #minVal si maxVal in [0, 255]. return (maxVal - minVal) * corectorDisp(l * l) #spargere daca scor(l, d[i][j][l], D[i][j][l]) > dispThresh.
Pe ESP32-CAM
coeficientii de corectie sunt tinuti direct intr-o tabela de lookup. Normalizarea pe [0, 1]
in loc de [0.5, 1]
produce mult mai putini pixeli cu arii diferite.
static float coef_corector_lookup[9] = {0.501f, 0.5039f, 0.5156f, 0.5622f, 0.7311f, 0.982f, 1.0f, 1.0f, 1.0f}; //0.00390625f
Simularea locala a algortimului de compresie.
0.12%
din pixeli pastrati dupa compresie.
0.3%
din pixeli pastrati dupa compresie.
Schema protocolului pentru trimiterea unui cadru. Exista exemple pentru CRC fail si pentru pachet pierdut.
(Concluzie ulterioara) schema protocolului a ajutat la rezolvarea bugului legat de primirea de pachete eronate daca distanta fata de laptop era putin mai mare (peste cativa metri): ACK-ul trebuia trimis imediat inapoi de catre Receiver. Lasam seriala catre laptop sa transmita octetii primiti inainte de a face asta. Cumva cuanta de timp alocata pentru asta era prea mare.
Mai jos sunt cateva poze obtinute in urma compresiei pe microcontroller:
Un zambet compresat. Paleta deplasata de culori poate fi cauzata de formatul RGB565.
808
pixeli compresati (1.2%
), 2.95KB
.
Unicul IDE si un portal catre cealalta lume (afara).
1174
pixeli compresati (1.8%
), 4.45KB
.
Biroul. Unele detalii sunt mult mai clare (marginile dintre doua culori indeajuns de diferite, fire de pe breadboard).
2107
pixeli compresati (3.2%
), 8.25KB
.
Daisychain ESP32-CAM –(serial)–> ESP32-WROOM –(serial)–> laptop
, 115200
bps. Link video (speedup 2x
)
Poza cu transmiterea prin LoRa functionala:
Test cu LoRa (speedup 4x
, ~4500
bps):
Acum ca stream-ul merge la o distanta mai mare de laptop, pot sa arat poze precum cea de mai jos (luna, 2+
pereti distanta):