Osciloscopul este organizat în jurul a cinci module hardware și trei module software, toate coordonate de ATmega328P.
2.A Module hardware:
Semnalul analogic intră prin mufa jack PJ392 3.5mm, trece prin două diode 1N4148 care îl protejează, apoi prin op-amp-ul MCP6002 configurat ca buffer, și în final printr-un filtru RC care elimină zgomotul de înaltă frecvență înainte de ADC.
Placa ATmega328P Xplained Mini este centrul proiectului. Rulează la 16MHz, se programează direct prin USB și coordonează toate perifericele — ADC, SPI, I2C, timere și întreruperi.
Display-ul ILI9341 de 2.4” se conectează prin SPI și afișează forma de undă în timp real împreună cu scalele și măsurătorile automate. Deoarece operează la 3.3V, level shifter-ul Pololu 2595 face conversia de tensiune față de MCU-ul de 5V.
DAC-ul MCP4921 se conectează pe același bus SPI și generează semnalul intern de test (sinus, triunghi, dreptunghi), care se întoarce la intrarea analogică formând o buclă de demo completă.
Modulul EEPROM AT24C256 se conectează prin I2C și salvează setările utilizatorului între sesiuni.
2.B Module software:
Sampling engine — Timer1 în mod CTC cu polling, fără ISR, fără trigger detection. Capturează 240 eșantioane cu perioadă fixă de 800µs/eșantion.
Display renderer — desenează coloană cu coloană grila 6×6, cursorul vertical și semnalul. Panou de statistici cu Vpp, Vavg, T/div și tensiunea la cursor.
Measurement engine — calculează Vpp și Vavg din buffer.
Listă componente:
| Componentă | Model complet | Cantitate | Rol | Preț estimat |
|---|---|---|---|---|
| Placă MCU | ATmega328P Xplained Mini | 1 | MCU central | De la PM lab |
| Display TFT | Ecran LCD ILI9341, cu slot pentru Card SD, 2.4” | 1 | Afișare semnal | ~67 RON |
| DAC | MCP4921-E/P — DIP8 12-bit SPI | 1 | Generator semnal | ~12 RON |
| Op-Amp | MCP6002-I/P — DIP8 dual rail-to-rail | 1 | Buffer intrare analogică | ~2 RON |
| EEPROM | Modul AT24C256 I2C | 1 | Salvare setări | ~7 RON |
| Level Shifter | Pololu 2595 — 4ch bidirecțional | 1 | 5V↔3.3V SPI | ~16 RON |
| Diode | 1N4148 DO-35 | 2 | Clamp tensiune 0–5V | ~1 RON |
| Rezistori | 1kΩ, 2kΩ, 3.3kΩ, 10kΩ — 1/4W | ~25 | Pull-up, filtru RC, divizori | ~5 RON |
| Condensatori | 100nF ceramic, 10nF ceramic, 10µF electrolitic | ~15 | Decoupling, filtru RC | ~20 RON |
| Mufă jack | PJ392 3.5mm stereo mamă | 1 | Intrare semnal extern | ~3 RON |
| Potențiometru | WH148 10kΩ liniar | 1 | Cursor de măsurare | ~2 RON |
| Butoane | PCB 12×12×7.3mm + capace colorate | 6+4 | Interfață utilizator | ~10 RON |
| Breadboard | 830 puncte | 1-2 | Prototipare | Disponibil |
| Fire | Set jumper 140 fire + dupont M-F 40 fire | 1+1 | Conexiuni | ~20 RON |
Total estimat: ~165 RON
| Pin modul | Pin ATmega328P | Funcție | Justificare |
|---|---|---|---|
| VCC | +5V | Alimentare | Aceeași tensiune ca MCU, evită level shifter pe liniile I²C. |
| GND | GND | Masă | Referință comună obligatorie. |
| SDA | PC4 | I²C Data | Singurul pin hardware SDA al perifericului TWI din ATmega328P. |
| SCL | PC5 | I²C Clock | Singurul pin hardware SCL al perifericului TWI din ATmega328P. |
| Pin modul | Conectat la | Funcție | Justificare |
|---|---|---|---|
| VCC | +5V | Alimentare modul | Modulul are regulator on-board, acceptă 3.3–5V direct. |
| GND | GND | Masă | Referință comună obligatorie. |
| CS | Pololu 2595 — L3 (← H3 ← PD7) | Chip Select display | GPIO liber, ales pentru a păstra PB2 (SS hardware) pentru CS_DAC. |
| RESET | +3.3V cu pull-up 10kΩ | Reset hardware display | Reset-ul se face software prin comandă SPI 0x01, economisind un canal de level shifter. |
| DC/RS | Pololu 2595 — L4 (← H4 ← PD6) | Data/Command select | GPIO liber pe PORTD, comutat la fiecare transfer SPI între comandă și date. |
| SDI (MOSI) | Pololu 2595 — L2 (← H2 ← PB3) | SPI Master Out | Pin hardware MOSI al perifericului SPI din ATmega328P, fixat de arhitectură. |
| SCK | Pololu 2595 — L1 (← H1 ← PB5) | SPI Clock | Pin hardware SCK al perifericului SPI, fixat de arhitectură. |
| LED | +3.3V | Backlight | Alimentare directă pentru backlight, mereu pornit (poate fi controlat ulterior prin PWM). |
| SDO (MISO) | neconectat | SPI Master In | Display-ul nu este citit în acest proiect, economisind un canal de level shifter. |
| SD_CS | neconectat | Chip Select card SD | Slot-ul SD nu este folosit în acest proiect. |
| SD_MOSI | neconectat | SPI Master Out (SD) | Slot-ul SD nu este folosit în acest proiect. |
| SD_MISO | neconectat | SPI Master In (SD) | Slot-ul SD nu este folosit în acest proiect. |
| SD_SCK | neconectat | SPI Clock (SD) | Slot-ul SD nu este folosit în acest proiect. |
| Pin shifter | Conectat la | Funcție | Justificare |
|---|---|---|---|
| HV | +5V | Alimentare partea înaltă | Tensiunea logică a MCU-ului (ATmega328P la 5V). |
| H1 | PB5 (SCK MCU) | Canal 1 — intrare 5V SCK | Linia SCK SPI vine de la MCU la 5V și trebuie shiftată la 3.3V pentru display. |
| H2 | PB3 (MOSI MCU) | Canal 2 — intrare 5V MOSI | Linia MOSI SPI vine de la MCU la 5V și trebuie shiftată la 3.3V pentru display. |
| H3 | PD7 (CS_TFT MCU) | Canal 3 — intrare 5V CS | Selectarea display-ului trebuie shiftată la 3.3V pentru a nu deteriora ILI9341. |
| H4 | PD6 (DC MCU) | Canal 4 — intrare 5V DC | Linia data/command trebuie shiftată la 3.3V, altfel se depășește tensiunea maximă a pinilor ILI9341. |
| LV | +3.3V | Alimentare partea joasă | Tensiunea logică a display-ului ILI9341 (3.3V). |
| L1 | SCK display | Canal 1 — ieșire 3.3V SCK | SCK shiftat ajunge la display la nivel logic sigur. |
| L2 | MOSI display | Canal 2 — ieșire 3.3V MOSI | MOSI shiftat ajunge la display la nivel logic sigur. |
| L3 | CS display | Canal 3 — ieșire 3.3V CS | CS shiftat ajunge la display la nivel logic sigur. |
| L4 | DC display | Canal 4 — ieșire 3.3V DC | DC shiftat ajunge la display la nivel logic sigur. |
| Pin op-amp | Conectat la | Funcție | Justificare |
|---|---|---|---|
| 1 (VOUTA) | R2 → A0 (PC0) | Ieșire canal A | Semnalul buffer-at iese către filtrul RC anti-aliasing și apoi către ADC-ul MCU. |
| 2 (VINA−) | VOUTA (feedback) | Intrare inversoare canal A | Conectată direct la ieșire pentru configurație voltage follower (gain = 1). |
| 3 (VINA+) | Semnal de la jack (după clamp diode) | Intrare neinversoare canal A | Primește semnalul de măsurat, izolând sursa de impedanța joasă a ADC. |
| 4 (VSS) | GND | Masă | Alimentare negativă (single supply, deci GND). |
| 5 (VINB+) | GND | Intrare neinversoare canal B | Legat la GND pentru a termina corect canalul B neutilizat și a preveni oscilații. |
| 6 (VINB−) | VOUTB (pin 7) | Intrare inversoare canal B | Legat la propria ieșire pentru a închide bucla canalului B neutilizat. |
| 7 (VOUTB) | VINB− (pin 6) | Ieșire canal B | Neutilizată, închisă în feedback unity gain pentru stabilitate. |
| 8 (VDD) | +5V (cu 100nF decoupling) | Alimentare pozitivă | Alimentare single supply 5V, comună cu MCU-ul, cu condensator de decuplare lângă pin. |
| Pin DAC | Conectat la | Funcție | Justificare |
|---|---|---|---|
| 1 (VDD) | +5V (cu 100nF decoupling) | Alimentare | Aceeași tensiune cu MCU, permite comunicație SPI directă fără level shifter. |
| 2 (CS) | PB2 | Chip Select | Pin SS hardware al ATmega328P, dedicat selectării DAC-ului pe bus-ul SPI. |
| 3 (SCK) | PB5 | SPI Clock | Pin hardware SCK al perifericului SPI, partajat cu display-ul (CS-uri diferite separă device-urile). |
| 4 (SDI) | PB3 | SPI Master Out | Pin hardware MOSI al perifericului SPI, primește comanda de 16 biți de la MCU. |
| 5 (LDAC) | GND | Latch DAC | Legat la GND pentru actualizare imediată a ieșirii după fiecare scriere SPI. |
| 6 (VREFA) | +5V | Referință tensiune | Permite ieșirii DAC să atingă tot domeniul 0–5V (rezoluție ~1.22 mV/bit). |
| 7 (AVSS) | GND | Masă analogică | Referință comună pentru partea analogică a DAC-ului. |
| 8 (VOUTA) | DAC_OUT → jack intrare | Ieșire analogică | Semnalul generat (sinus, triunghi, dreptunghi) se întoarce către intrarea osciloscopului pentru bucla de demo. |
Toate butoanele folosesc aceeași topologie: un terminal la GND, celălalt la pinul MCU prin rezistor de pull-up extern către VCC. Pin-ul MCU citește LOW când butonul este apăsat.
| Buton | Pin ATmega328P | Funcție | Justificare |
|---|---|---|---|
| HOLD | PD2 | Run/Stop captură | Pin INT0 — întrerupere externă dedicată pentru reacție instantă la oprirea capturii. |
| MODE | PD3 | Ciclare formă de undă | Pin INT1 — întrerupere externă dedicată pentru schimbare rapidă a modului. |
| TIME_P | PD4 | Mărire time/div | Pin GPIO cu suport PCINT20 pentru debouncing prin întrerupere. |
| TIME_M | PD5 | Micșorare time/div | Pin GPIO cu suport PCINT21, alăturat lui TIME_P pentru rutare ușoară. |
| VOLT_P | PB0 | Mărire volt/div | GPIO liber pe PORTB, neutilizat de SPI sau ADC. |
| VOLT_M | PB1 | Micșorare volt/div | GPIO liber pe PORTB, alăturat lui VOLT_P pentru rutare ușoară. |
| Pin pot. | Conectat la | Funcție | Justificare | ||||
|---|---|---|---|---|---|---|---|
| 1 | GND | Capăt minim | Stabilește limita inferioară a domeniului de tensiune (0V). | ||||
| 2 (wiper) | PC1 (A1) | Tensiune variabilă | Pin ADC1 al MCU citește tensiunea wiper-ului ca cursor de măsurare peaxa X. | | 3 | +5V | Capăt maxim | Stabilește limita superioară a domeniului (5V), egală cu VREF al ADC. |
| Pin mufă | Conectat la | Funcție | Justificare |
|---|---|---|---|
| 1 (Sleeve) | GND | Masă comună | Referință de tensiune comună între sursa semnalului și circuit. |
| 2 (Ring) | GND | Canal stereo secundar | Legat la masă pentru a evita zgomot indus din canalul drept neutilizat. |
| 3 (Tip) | Diode clamp 1N4148 → VINA+ (MCP6002 pin 3) | Semnal de măsurat | Intră semnalul analogic care trece prin protecție și buffer înainte de ADC. |
| Pin MCU | Funcție | Periferic |
|---|---|---|
| PB0 | VOLT_P (buton) | GPIO |
| PB1 | VOLT_M (buton) | GPIO |
| PB2 | CS_DAC | SPI SS |
| PB3 | MOSI | SPI |
| PB5 | SCK | SPI |
| PC0 | Semnal intrare | ADC0 |
| PC1 | Cursor de măsurare | ADC1 |
| PC4 | SDA | I²C |
| PC5 | SCL | I²C |
| PD2 | HOLD | INT0 |
| PD3 | MODE | INT1 |
| PD4 | TIME_P | GPIO/PCINT |
| PD5 | TIME_M | GPIO/PCINT |
| PD6 | DC display | GPIO |
| PD7 | CS_TFT | GPIO |
Schema este organizată în jurul plăcii ATmega328P Xplained Mini, reprezentată ca header 1×20 cu toate semnalele etichetate funcțional.
Lanțul de intrare analogică începe în mufa jack PJ-392, unde semnalul trece prin două diode 1N4148 montate ca dublu clamp (protecție 0–5V), apoi prin rezistorul R1 care limitează curentul. Op-amp-ul MCP6002 este configurat ca voltage follower pe canalul A, buffer-ând semnalul către filtrul RC anti-aliasing format din R2 și C2 (10nF). Ieșirea filtrului ajunge la pinul ADC A0 (PC0) al MCU.
Bus-ul SPI este partajat între display-ul ILI9341 și DAC-ul MCP4921. SCK și MOSI provin de la MCU, iar selectarea device-ului activ se face prin Chip Select separat (CS_DAC pe PB2, CS_TFT pe PD7). DAC-ul, alimentat la 5V, comunică direct; display-ul, cu logică 3.3V, primește semnalele prin level shifter-ul Pololu 2595 (conector 1×8 dedicat).
Bus-ul I²C conectează modulul EEPROM AT24C256 (header 1×4 cu pull-up-uri integrate pe modul) la pinii SDA/SCL ai MCU.
Interfața utilizator conține șase butoane (HOLD, MODE, TIME±, VOLT±) cu pull-up extern (R5–R10), și un potențiometru WH148 de 10kΩ (R11) conectat ca divizor între VCC și GND, cu wiper-ul ducând la ADC1 (PC1) pentru controlul cursorului de măsurare pe axa X a formei de undă.
Decuplarea alimentării este asigurată de condensatoarele C1 și C3 (100nF) montate fizic lângă pinii VDD ai op-amp-ului și DAC-ului.
Codul rulează simultan 5 module hardware care comunică între ele. DAC-ul (prin SPI) generează un semnal digital, care iese pe pinul 8 ca tensiune analogică. Această tensiune trece prin op-amp ca buffer, ajunge la pinul ADC, este eșantionată și raportată pe UART. În paralel, butoanele (GPIO) schimbă forma de undă generată, iar potențiometrul (alt canal ADC) reglează amplitudinea. Faptul că valorile DAC și ADC afișate pe Serial Monitor sunt aproape identice (diferență sub 20mV) confirmă că întregul lanț — SPI → DAC → op-amp → ADC → UART — funcționează corect.
Aceasta este “bucla de demo” a osciloscopului — DAC-ul produce semnal de test, iar restul lanțului îl procesează exact ca pentru un semnal real venit din jack. Înlocuind sursa (DAC → jack cu intrare externă), arhitectura rămâne identică. Practic, am validat hardware-ul de procesare a semnalului înainte să-l alimentez cu semnal real, ceea ce înseamnă că proiectul are deja partea cea mai critică funcțională: achiziția de date analogice prin op-amp + ADC, coordonată de MCU prin SPI și raportată prin UART.
Firmware-ul este complet funcțional și rulează pe ATmega328P Xplained Mini la 16 MHz, scris în C pur (fără Arduino HAL), compilat cu avr-gcc prin PlatformIO.
Funcționalități implementate:
Structura modulelor software:
| Modul | Fișiere | Rol |
|---|---|---|
| UART | uart.c / uart.h | Debug serial 57600 baud |
| ADC | adc.c / adc.h | Citire semnal + potențiometru |
| SPI | spi.c / spi.h | Bus comun display + DAC |
| DAC | dac.c / dac.h | Generator semnal MCP4921 |
| Display | display.c / display.h | Driver ILI9341, font 5×7, statistici |
| Timer1 | timer1.c / timer1.h | Cadență captura (CTC, prescaler 64) |
| Buttons | buttons.c / buttons.h | INT0/INT1/PCINT, flaguri volatile |
| I²C | i2c.c / i2c.h | Comunicație EEPROM AT24C256 |
| EEPROM | eeprom.c / eeprom.h | Persistență setări |
Proiectul nu folosește biblioteci externe — tot codul este scris de la zero folosind doar headere AVR-libc standard (avr/io.h, avr/interrupt.h, util/delay.h, avr/pgmspace.h)
| Concept laborator | Unde e folosit în proiect | Justificare |
|---|---|---|
| GPIO | Pini CS/DC display, butoane cu pull-up intern | Controlul chip-select pe busul SPI partajat |
| UART | Debug serial la 57600 baud | Monitorizare valori ADC în timp real |
| Întreruperi externe | INT0 (HOLD), INT1 (MODE), PCINT (VOLT/TIME) | Butoanele răspund instant indiferent de durata draw_frame |
| Timer1 CTC | Funcțiile timer1_init/reset/wait în capture_frame | Cadență precisă: OCR1A × 4µs = perioadă fixă per eșantion |
| ADC | Canal 0 (ieșire op-amp), Canal 1 (potențiometru) | Conversie analog-digital pentru ambele intrări |
| SPI | Display ILI9341 + DAC MCP4921 pe același bus | Transfer rapid 8 MHz, chip-select separat per periferic |
| I²C | EEPROM AT24C256 la adresa 0x50 | Stocare persistentă; potrivit pentru memorii externe |
Bucla principală (main.c):
while (1) { // 1. Citire potențiometru → cursor_x (mapare liniară cu calibrare hardware) // 2. Procesare flaguri ISR (mode/hold/volt/time) → update stare + EEPROM // 3. dacă !hold: // capture_frame() → warm-up 128 iter. (OCR=35) + 240 eșantioane (OCR=200) // draw_frame() → grilă + cursor + semnal coloană cu coloană // draw_cursor_label() → tensiune la cursor, lângă linie // draw_status_bar() → mod, T/div, V/div, HOLD // draw_stats_panel() → Vpp, Vavg, T/div, CUR // 4. dacă hold: // detectare schimbare cursor_x → redesenare frame din buffer (fără captură) }
Fluxul de date:
DAC (MCP4921) -> semnal analogic -> condensator cuplaj AC 10µF -> op-amp MCP6002 (buffer unitar, protecție diode) -> ADC canal 0 -> sample_buffer[240] -> calcul Vpp/Vavg/CUR -> display ILI9341
Sincronizarea DAC–ADC în capture_frame():
Validare funcționalități:
if (!pending_x) pending_x = 1Potențiometru (ADC canal 1):
Potențiometrul fizic nu acoperă întregul rang teoretic 0–1023 al ADC-ului. Valorile reale au fost măsurate prin UART în faza de debug:
Aceste valori sunt definite ca constante POT_MIN = 173 și POT_MAX = 954.
Formula de calibrare cu clamp:
pot_clamped = (pot_raw <= POT_MIN) ? 0 : (pot_raw >= POT_MAX) ? (POT_MAX - POT_MIN) : (pot_raw - POT_MIN); cursor_x = 1 + (uint32_t)pot_clamped * (SAMPLE_COUNT - 2) / (POT_MAX - POT_MIN);
Rezultat: la capătul minim fizic → cursor la x=1, la capătul maxim → cursor la x=239. Fără calibrare, ultimul sfert al cursei rămânea fără efect vizibil.
Scalarea tensiunii (ADC → Volți):
ADC 10-bit cu Vref = 5V → 1 LSB = 5V/1023 ≈ 4.89 mV. Vpp și Vavg sunt calculate direct din valorile ADC brute:
vpp_x10 = (uint32_t)(vmax - vmin) * 50 / 1023; // zecimi de volt vavg_x10 = (uint32_t)vavg * 50 / 1023;
Consistența afișaj–calcul:
Funcția adc_to_y() mapează ADC 0→fundul grilei, ADC 1023→vârful grilei,
fără margini de compresie — grila de 6 diviziuni × 36px corespunde exact cu
5V / 6 ≈ 0.83V/div, consistent cu valorile numerice afișate.
1. Font în PROGMEM (Flash, nu RAM)
Tabelul font 5×7 (59 caractere × 5 bytes = 295 bytes) este declarat cu atributul
PROGMEM și citit cu pgm_read_byte(). ATmega328P are doar 2KB SRAM —
fără această optimizare, fontul ar ocupa ~15% din SRAM-ul disponibil.
2. Warm-up cu 128 iterații (artefact triunghi)
Problema: condensatorul de cuplaj AC (10µF, RC ≈ 50ms) nu se poate stabili rapid
dacă DAC-ul face un salt brusc de la warm-up la captură. Soluția: 128 iterații de
warm-up cu OCR=35 (rapid, 140µs/pas) garantează că phase = 0 la final pentru
orice phase_step din intervalul 1–4, deoarece 128 × k mod 64 = 0 ∀k.
Astfel nu există salt DAC la tranziția warm-up → captură.
3. Desenare coloană cu coloană (fără buffer de ecran)
ATmega328P nu are suficient RAM pentru un framebuffer complet (240×320×2 = 150KB).
Soluția: draw_frame() procesează ecranul coloană cu coloană — șterge coloana,
desenează grila, desenează semnalul. Se evită flickerul global și se elimină
necesitatea unui buffer secundar.
4. ISR minim + flaguri volatile
ISR-urile de butoane nu fac nimic altceva decât să seteze un flag:
if (!pending_x) pending_x = 1. Procesarea efectivă (UART, display, EEPROM)
se face în bucla principală. Astfel ISR-ul durează < 10 cicli, minimizând
latența pentru alte întreruperi.
5. SPI bus partajat DAC + Display
Ambele periferice SPI (ILI9341 și MCP4921) partajează MOSI/SCK; chip-select separat (CS_DAC pe PB2, CS_DISPLAY pe PD7) permite comunicație fără conflicte și elimină necesitatea unui al doilea controler SPI.
Filmare care demonstreaza modul de functionare al Osciloscopului: https://youtube.com/shorts/OEblZLtn7qA