Porturile digitale sunt interfața microcontrolerului cu lumea exterioară. Fiecare port este controlat de exact trei regiștri, iar înțelegerea rolului lor explică tot ce se întâmplă la nivel de pin: de la aprinderea unui LED până la citirea unui buton fără componente externe.
1Obiectivele lucrării
- Înțelegerea rolului regiștrilor
DDRx,PORTxșiPINx - Configurarea pinilor prin acces direct la regiștri, cu operații pe biți
- Utilizarea rezistoarelor de pull-up interne pentru citirea butoanelor
- Implementarea eliminării vibrațiilor contactelor
- Compararea vitezei între
digitalWrite()și scrierea directă în registru
2Materiale necesare
- 1 Arduino Uno
- 1 Breadboard
- 4 LED-uri
- 4 Rezistoare 220 Ω
- 2 Butoane
- 10 Fire jumper
3Cei trei regiștri ai unui port
ATmega328P are trei porturi: B (pinii digitali 8–13), C (pinii analogici A0–A5) și D (pinii digitali 0–7). Fiecare este controlat de trei regiștri de câte 8 biți:
| Registru | Denumire completă | Rol | Acces |
|---|---|---|---|
DDRx | Data Direction Register | stabilește direcția: 0 = intrare, 1 = ieșire | citire / scriere |
PORTx | Data Register | la ieșire: nivelul logic la intrare: activează pull-up-ul intern | citire / scriere |
PINx | Port Input Pins | reflectă starea reală a pinilor | doar citire |
Tabela de configurare completă
| DDRxn | PORTxn | Configurație | Rezistență echivalentă | Utilizare |
|---|---|---|---|---|
| 0 | 0 | intrare, flotantă | foarte mare (Z înalt) | de evitat - citiri aleatoare |
| 0 | 1 | intrare cu pull-up | 20–50 kΩ spre 5 V | citirea butoanelor, fără componente externe |
| 1 | 0 | ieșire LOW | ~25 Ω spre GND | stinge LED-ul / atrage sarcina la masă |
| 1 | 1 | ieșire HIGH | ~25 Ω spre 5 V | aprinde LED-ul |
4Structura internă a unui pin
5Simulator de regiștri
Apăsați pe biți pentru a modifica regiștrii. Starea pinilor din partea de jos se actualizează exact după tabela de configurare - la fel ca pe microcontrolerul real.
6Laborator de măști
Nu se modifică niciodată tot registrul dacă se dorește schimbarea unui singur pin - s-ar afecta ceilalți șapte. Se folosesc măști de biți, construite cu operatorul de deplasare:
| Operație | Sintaxă | Efect |
|---|---|---|
| Setare bit (1) | PORTB |= (1 << PB5); | doar bitul 5 devine 1 |
| Ștergere bit (0) | PORTB &= ~(1 << PB5); | doar bitul 5 devine 0 |
| Comutare (toggle) | PORTB ^= (1 << PB5); | bitul 5 își schimbă starea |
| Testare bit | if (PINB & (1 << PB0)) | adevărat dacă bitul 0 este 1 |
| Setare grup | DDRB |= 0b00001111; | pinii 8–11 devin ieșiri |
Verificați mai jos, bit cu bit, ce face fiecare operație. Alegeți operatorul
& cu operandul B pe 11011111 ca să vedeți cum se șterge exact bitul 5.
~(1 << n)
1 << 5 produce 00100000. Negarea dă 11011111 - toți biții 1,
mai puțin cel de pe poziția 5. ȘI cu această mască lasă neatinși toți ceilalți biți și forțează pe 0
doar bitul dorit.7Scheme de montaj
PORTD = 0b00000001, cu DDRD având bitul 0 pe 0 (intrare).
Fig. 2.7 din îndrumar8Vibrațiile contactului
Un buton mecanic nu comută curat. Lamelele metalice vibrează câteva milisecunde la contact, generând zeci de tranziții false. Fără filtrare, o singură apăsare fizică este văzută de program ca mai multe apăsări.
9Simulator de debounce
Urmăriți pas cu pas cum algoritmul respinge tranzițiile false și acceptă doar
starea stabilă. Observați că vibrațiile de la t = 102 și t = 107 nu produc
nicio incrementare.
10Cod sursă
Funcții Arduino vs. acces direct la regiștri
void setup() {
// --- varianta Arduino: lizibila, dar lenta ---
pinMode(13, OUTPUT);
pinMode(2, INPUT_PULLUP);
// --- varianta echivalenta cu registri: rapida ---
DDRB |= (1 << PB5); // pinul 13 = iesire
DDRD &= ~(1 << PD2); // pinul 2 = intrare
PORTD |= (1 << PD2); // activeaza pull-up pe pinul 2
}
void loop() {
// citirea butonului: 0 = apasat, din cauza pull-up-ului
if (!(PIND & (1 << PD2))) {
PORTB |= (1 << PB5); // aprinde
} else {
PORTB &= ~(1 << PB5); // stinge
}
}
digitalWrite() are nevoie de
aproximativ 50 de cicluri de ceas: verifică validitatea pinului, caută portul într-un tabel din
Flash și dezactivează PWM-ul dacă era activ. PORTB |= (1 << PB5) se compilează
într-o singură instrucțiune. La 16 MHz, diferența este între aproximativ 3 µs și 62 ns -
de circa 50 de ori.Măsurarea diferenței de viteză
Nu avem încă monitorul serial (lucrarea 6), așa că nu tipărim durata: o facem vizibilă. Fiecare metodă comută pinul într-o buclă strânsă, iar pinul comandă un LED separat. Cu cât metoda este mai rapidă, cu atât bucla se termină mai devreme și LED-ul de „ocupat” stă aprins mai puțin. Diferența se vede cu ochiul liber, iar pe osciloscop se poate și măsura.
const uint16_t N = 20000;
void setup() {
DDRB |= (1 << PB0); // pinul 8: pinul care se comuta
DDRB |= (1 << PB4); // pinul 12: martor "ruleaza digitalWrite"
DDRB |= (1 << PB3); // pinul 11: martor "ruleaza varianta cu registru"
}
void loop() {
// ---- varianta cu functie: martorul de pe pinul 12 sta aprins cat dureaza
PORTB |= (1 << PB4);
for (uint16_t i = 0; i < N; i++) {
digitalWrite(8, HIGH);
digitalWrite(8, LOW);
}
PORTB &= ~(1 << PB4);
delay(400);
// ---- varianta cu registru: martorul de pe pinul 11
PORTB |= (1 << PB3);
for (uint16_t i = 0; i < N; i++) {
PORTB |= (1 << PB0);
PORTB &= ~(1 << PB0);
}
PORTB &= ~(1 << PB3);
delay(400);
}
digitalWrite stă
aprins vizibil - zeci de milisecunde. Martorul variantei cu registru clipește atât de scurt încât
abia se observă. Raportul dintre cele două durate este chiar factorul de aproximativ 50 din caseta
de mai sus.Debounce complet
const uint8_t PIN_BUTON = 2;
const uint8_t PIN_LED = 13;
const uint16_t INTERVAL_DEBOUNCE = 40; // ms
bool stareLed = false;
bool ultimaCitire = HIGH;
bool stareStabila = HIGH;
unsigned long ultimulMoment = 0;
void setup() {
pinMode(PIN_BUTON, INPUT_PULLUP);
pinMode(PIN_LED, OUTPUT);
}
void loop() {
bool citire = digitalRead(PIN_BUTON);
if (citire != ultimaCitire) { // orice schimbare reporneste cronometrul
ultimulMoment = millis();
ultimaCitire = citire;
}
if (millis() - ultimulMoment > INTERVAL_DEBOUNCE) {
if (citire != stareStabila) { // starea s-a stabilizat
stareStabila = citire;
if (stareStabila == LOW) { // front descendent = apasare reala
stareLed = !stareLed;
digitalWrite(PIN_LED, stareLed); // o singura comutare pe apasare
}
}
}
}
Efect de lumini pe patru LED-uri
void setup() {
DDRB |= 0b00001111; // pinii 8, 9, 10, 11 ca iesiri, dintr-o singura operatie
}
void loop() {
// deplasare la stanga
for (uint8_t i = 0; i < 4; i++) {
PORTB = (PORTB & 0xF0) | (1 << i); // pastreaza bitii 4-7, seteaza doar unul din 0-3
delay(120);
}
// deplasare la dreapta, fara a repeta capetele
for (int8_t i = 2; i > 0; i--) {
PORTB = (PORTB & 0xF0) | (1 << i);
delay(120);
}
}
(PORTB & 0xF0)Masca păstrează neatinși biții 4–7
(printre care și D13, LED-ul de pe placă) și lasă doar biții 0–3 să fie rescriși. Fără ea,
atribuirea directă ar stinge tot ce se afla pe ceilalți pini ai portului.11Montaj interactiv
Un pin de intrare se citește din PINx, nu din PORTx - iar când folosiți rezistența internă de ridicare, logica se inversează: butonul apăsat înseamnă 0. Montajele de mai jos vă lasă să apăsați butonul cu mouse-ul și să vedeți bitul din PIND schimbându-se în timp real.
PORTD |= (1 << PD2); - fără rezistența de ridicare, intrarea rămâne „în aer” și citirea devine imprevizibilă.if (0), adică mereu falsă, deci corpul ei nu se execută niciodată. Aveți patru linii de completat, marcate în cod. Ideea întregului exercițiu: nu ne interesează dacă butonul este apăsat, ci clipa în care a fost apăsat. Aceasta se numește front căzător și se prinde comparând citirea de acum cu cea de la rotirea anterioară a buclei - de aceea avem nevoie de variabila stareAnterioara și de pasul 4, care o ține la zi. Fără cei 20 ms de la pasul 3, un singur clic poate fi văzut ca zeci de apăsări, pentru că lamelele contactului vibrează câteva milisecunde. Verificați pe osciloscop: LED-ul trebuie să comute exact o dată la fiecare apăsare. Dacă vă blocați, butonul 💡 vezi soluția încarcă o variantă corectă în editor.12Sarcini de lucru
- Montați 4 LED-uri pe pinii 8–11 și aprindeți-le secvențial, folosind doar operații pe regiștri.
- Conectați un buton pe pinul 2 cu pull-up intern; afișați pe serial valoarea în repaus și la apăsare.
- Verificați ce se citește pe un pin de intrare lăsat flotant, fără pull-up.
- Implementați debounce software și numărați apăsările; verificați că fiecare apăsare fizică produce exact o incrementare.
- Modificați programul astfel încât fiecare apăsare să schimbe direcția efectului de lumini.
- Rulați benchmark-ul și notați raportul dintre
digitalWrite()și accesul direct. - Realizați un numărător binar pe 4 LED-uri, incrementat la fiecare apăsare de buton.
- Încercați
PORTB = 0b00000001;fără mască și observați ce se întâmplă cu LED-ul de pe D13.
13Aplicație de aprofundare
randomSeed(millis());
Extindere suplimentară: detectați apăsarea lungă (peste 1 secundă) și folosiți-o pentru a reseta un contor al aruncărilor, afișat tot în binar pe LED-uri.