Controlul portului prin registre atmega. Programare microcontrolere AVR în C. Registrul de date Port A - PORTA

Pic

Citeste, scrie

Valoarea initiala

Bit 7 - Activați toate întreruperile. Pentru a activa întreruperi, acest bit trebuie să fie setat la 1. Activarea unei anumite întreruperi este controlată de registrele de masca de întrerupere EIMSK și TIMSK. Dacă acest bit este șters (=0), atunci niciuna dintre întreruperi nu este gestionată. Bitul este șters de hardware după ce apare o întrerupere și este setat pentru a activa întreruperea mai târziu cu instrucțiunea RETI.
· Bit 6 – Salvare bit de copiere. Instrucțiunile de copiere a biților BLD și BST folosesc acest bit ca sursă și destinație pentru operațiunile pe biți. Comanda BST copiază un bit din registrul general în bitul T, comanda BLD copiază bitul T în bitul registrului general.
· Bit 5 – Half Carry Flag. Indică un transfer între tetrade atunci când se efectuează un număr de operații aritmetice.
· Bit 4 - Semnează bit. Bitul S are valoarea rezultatului operației XOR (N(+)V) pe steagurile de valoare negativă (N) și complementul a doi al indicatorului de depășire (V).

· Bit 3 – Complementul în doi al flagului de overflow. Acceptă aritmetica complementului a doi.
· Bit 2 – steag negativ. Acest indicator indică un rezultat negativ al unui număr de operații aritmetice și logice.
· Bit 1 – steag zero. Acest indicator indică rezultatul zero al unui număr de operații aritmetice și logice.
· Bit 0 – Indicatorul de transport. Acest steag indică transportul în timpul operațiilor aritmetice și logice.

Microcontrolerul AT90S8535 are 4 porturi I/O paralele A, B, C și D.
Portul A este un port bidirecțional pe 8 biți. Interacțiunea cu portul A se realizează prin trei registre din spațiul I/O al memoriei de date: registru de date - PORTA, $1B ($3B), registru de direcție a datelor - DDRA, $1A ($3A), registru de date de intrare - PINA, 19 USD (39 USD). Registrul PINA este doar citire, în timp ce registrele PORTA și DDRA sunt citire-scriere. Registrul PINA nu este un registru în sensul deplin al cuvântului. Accesarea acestuia oferă o citire a stării fizice a fiecărui pin de port. Portul A este folosit și pentru a introduce semnale analogice A/D.

Registrul de date portul A -PORTA

Pic

Citeste, scrie

Valoarea initiala

Port A Registrul de direcție a datelor -DDRA

Pic

Citeste, scrie

Valoarea initiala

Registrul de date de intrare portul A -PINA

Pic

Citeste, scrie

Valoarea initiala

Portul B este un port I/O bidirecțional pe 8 biți. Ca și în cazul portului A, comunicarea cu portul B se face prin trei registre din spațiul I/O al memoriei de date: registrul de date - PORTB, 18 USD (38 USD), registrul de direcție a datelor - DDRB, 17 USD (37 USD) și registrul de date de intrare - PINB, 16 USD (36 USD). Registrul PINB este doar pentru citire. Registrul PINB nu este un registru în sensul deplin al cuvântului. Accesarea acestuia oferă o citire a stării fizice a fiecărui pin de port. Pinii portului B pot îndeplini funcțiile alternative prezentate în Tabelul 1. 2.1.

Tabelul 2.1. Portul B Pin Funcții alternative

Pin port

Funcție alternativă

T0 - intrare temporizator/contor ceas 0

T1 - temporizator/contor 1 intrare ceas

AIN0 - terminalul pozitiv al comparatorului

AIN1 - borna negativă a comparatorului

– Intrare de selecție slave SPI

MOSI - Setare ieșire master/intrare slave SPI

Setare MISO - SPI master input/slave output

SCK - semnal de ceas SPI

Când folosiți pini pentru funcții alternative, registrele PORTB, DDRB trebuie setate în mod corespunzător.

Registrul de date portuluiBPORTB

Pic

Citeste, scrie

Valoarea initiala

Portul B Registrul de direcție a datelor –DDRB

Pic

Citeste, scrie

Valoarea initiala

Registrul de date de intrare portul B –PINB

Pic

Citeste, scrie

Valoarea initiala

Portul C este un port I/O bidirecțional pe 8 biți. La fel ca porturile A și B, interacțiunea cu portul C se realizează prin trei registre din spațiul I/O al memoriei de date: registrul de date este PORTC, 15 USD (35 USD), registrul de direcție a datelor este DDRC, 14 USD (34 USD). ) iar registrul de date de intrare este PINC, $13($33). Registrul PINC este doar citire, în timp ce registrele PORTC și DDRC sunt citire-scriere. Registrul PINC nu este un registru în sensul deplin al cuvântului. Accesarea acestuia oferă o citire a stării fizice a fiecărui pin de port.
Portul C are doar doi pini care pot îndeplini funcții alternative: pinii PC6 și PC7 realizează funcțiile TOSC1 și TOSC2 ale Timer/Counter 2.

Registrul de date portuluiCPORTC

Pic

Citeste, scrie

Valoarea initiala

Port C Registrul de direcție a datelor –DDRC

Pic

Citeste, scrie

Valoarea initiala

Registrul de date de intrare portul C -PINC

Pic

Citeste, scrie

Valoarea initiala

Portul D este un port I/O bidirecțional pe 8 biți. Ca și cu porturile A, B și C, comunicarea cu portul D se face prin trei registre din spațiul I/O al memoriei de date: registrul de date este PORTD, $12 ($32), registrul de direcție a datelor este DDRD, $11 ($31), și registrul de date de intrare – PIND, $10 ($30). Registrul PIND oferă capacitate de citire, în timp ce registrele PORTD și DDRD oferă capacitate de citire și scriere. Registrul PIND nu este un registru în sensul deplin al cuvântului. Accesarea acestuia oferă o citire a stării fizice a fiecărui pin de port.
Pinii portului D pot îndeplini funcțiile alternative prezentate în Tabelul 1. 2.2.

Tabelul 2.2. Funcții alternative ale pinului portului D

Pin port

Funcție alternativă

RxD - intrare receptor UART

TxD - Ieșire transmițător UART

INT0 - intrare de întrerupere externă 0

INT1 - intrare de întrerupere externă 1

OC1B - Pin de comparație de ieșire cronometru/contor 1

OC1A - ieșirea de comparare a ieșirii A a temporizatorului/contorului 1

ICP - Timer/Counter 1 Capture Trigger Input

OC2 - pin de comparație de ieșire cronometru/contor 2

Când folosiți pini pentru funcții alternative, registrele PORTD, DDRD trebuie setate în mod corespunzător.

Registrul de date portuluiDPORTD

Pic

Citeste, scrie

Valoarea initiala

Registrul de direcție a datelor portuluiDDDRD

Pic

Citeste, scrie

Valoarea initiala

Registrul de intrare a portuluiDPIND

Pic

Citeste, scrie

Valoarea initiala

Întrucât lucrarea luată în considerare este prima, pentru ca studenții să dobândească abilitățile de a lucra cu complexul de laborator, toți studenții fac mai întâi aceeași muncă. De la locurile lor de muncă intră în PC aceeași sarcină de scădere a numărului 3 din numărul 5, dată la paragraful 1.5.3.1. După compilarea programului, acesta este scris pe microcontrolerul locului de muncă și activitatea acestuia este demonstrată profesorului.
După o astfel de cunoaștere a complexului, studentul procedează la îndeplinirea unei sarcini individuale. Dacă este timp, profesorul poate complica sarcina individuală.

Operațiile pe biți se bazează pe operații logice, pe care le-am tratat deja mai devreme. Ele joacă un rol cheie în programarea microcontrolerelor AVR și a altor tipuri. Aproape niciun program nu poate face fără utilizarea operațiunilor pe biți. Până acum, le-am evitat în mod deliberat pentru a facilita învățarea programarii MK.

În toate articolele anterioare, am programat doar porturi I/O și nu am folosit noduri încorporate suplimentare, cum ar fi cronometre, convertoare analog-digitale, întreruperi și alte dispozitive interne fără de care MK își pierde toată puterea.

Înainte de a trece la stăpânirea dispozitivelor MK încorporate, trebuie să învățați cum să controlați sau să verificați biții individuali ai registrelor AVR MK. Anterior, am efectuat o verificare sau am setat biții întregului registru deodată. Să vedem care este diferența și apoi să continuăm.

Operații pe biți

Cel mai adesea, la programarea microcontrolerelor AVR, l-am folosit, deoarece are o claritate mai mare în comparație cu și este bine înțeles pentru programatorii MK începători. De exemplu, trebuie să setăm doar al 3-lea bit al portului D. Pentru aceasta, așa cum știm deja, putem folosi următorul cod binar:

PORTD = 0b00001000;

Cu toate acestea, cu această comandă, setăm al 3-lea bit la unu și resetam toți ceilalți (0, 1, 2, 4, 5, 6 și 7) la zero. Și acum să ne imaginăm situația în care cifrele a 6-a și a 7-a sunt folosite ca intrări ADC și în acest moment un semnal de la un dispozitiv ajunge la ieșirile corespunzătoare ale MK și resetăm aceste semnale folosind comanda de mai sus. Drept urmare, microcontrolerul nu le vede și crede că semnalele nu au venit. Prin urmare, în loc de o astfel de comandă, ar trebui să folosim alta care să seteze doar al 3-lea bit la unul, fără a afecta restul biților. Pentru aceasta, se utilizează de obicei următoarea operație pe biți:

PORT |= (1<<3);

Vom analiza mai jos sintaxa acesteia în detaliu. Și acum un alt exemplu. Să presupunem că trebuie să verificăm starea celui de-al treilea bit al registrului PIND, verificând astfel starea butonului. Dacă acest bit este resetat la zero, atunci știm că butonul este apăsat și apoi este executat codul de comandă, care corespunde stării butonului apăsat. Anterior, am fi folosit următoarea notație:

dacă (pind == 0b00000000)

(orice cod)

Cu toate acestea, cu ajutorul acestuia, verificăm nu unul singur, - al treilea, ci toți biții registrului PIND deodată. Prin urmare, chiar dacă butonul este apăsat și bitul dorit este resetat, dar în acel moment este primit un semnal pe orice alt pin portul D, bitul corespunzător va fi setat la unu, iar condiția din paranteze va fi falsă. Drept urmare, codul din acolade nu va fi executat nici măcar atunci când butonul este apăsat. Prin urmare, pentru a verifica starea unui al 3-lea bit individual al registrului PIND, trebuie utilizată o operație pe biți:

dacă (~PIND & (1<<3))

(orice cod)

Pentru a lucra cu biți individuali ai microcontrolerului, limbajul de programare C are în arsenalul său, cu ajutorul căruia puteți modifica sau verifica starea unuia sau mai multor biți individuali simultan.

Setarea unui singur bit

Pentru a seta un singur bit, cum ar fi portul D, se folosește o operație SAU pe biți. Asta am folosit la începutul articolului.

PORTD = 0b00011100; // valoarea initiala

PORTD = PORTD | (unu<<0); применяем побитовую ИЛИ

PORT |= (1<<0); // сокращенная форма записи

PORTD == 0b00011101; // rezultat

Această comandă setează bitul la zero și lasă restul neschimbat.

De exemplu, să setăm al 6-lea bit al portului D.

PORTD = 0b00011100; // starea inițială a portului

PORT |= (1<<6); //

PORTD == 0b01011100; // rezultat

Pentru a scrie de la unul la mai mulți biți separati simultan, de exemplu, zero, al șaselea și al șaptelea port B se aplică următoarea notație.

PORTB = 0b00011100; // valoarea initiala

PORTB |= (1<<0) | (1<<6) | (1<<7); //

PORTB == 0b1011101; // rezultat

Resetarea (reducerea la zero) biților individuali

Pentru a reseta un singur bit, sunt utilizate simultan trei comenzi discutate anterior: .

Să resetam al 3-lea bit al registrului PORTC și să lăsăm restul neschimbat.

PORTC = 0b00011100;

PORTC &= ~(1<<3);

PORTC == 0b00010100;

Să efectuăm acțiuni similare pentru a 2-a și a 4-a cifră:

PORTC = 0b00111110;

PORTC &= ~((1<<2) | (1<<4));

PORTC == 0b00101010;

Schimbând ritmul

Pe lângă setare și resetare, se folosește și o comandă utilă care comută un singur bit în starea opusă: unu la zero și invers. Această operațiune logică este utilizată pe scară largă în construirea diferitelor efecte de iluminare, de exemplu, cum ar fi o ghirlandă de Anul Nou. Luați în considerare exemplul PORTA

PORTA = 0b00011111;

PORTA ^= (1<<2);

PORTA == 0b00011011;

Schimbați starea biților zero, al doilea și al șaselea:

PORTA = 0b00011111;

PORTA ^= (1<<0) | (1<<2) | (1<<6);

PORTA == 0b01011010;

Verificarea stării unui bit individual. Permiteți-mi să vă reamintesc că verificarea (spre deosebire de scriere) unui port I/O se realizează prin citirea datelor din registrul PIN.

Cel mai frecvent test este efectuat de una dintre cele două instrucțiuni de buclă: if și while. Suntem deja familiarizați cu acești operatori mai devreme.

Verificarea descărcării pentru prezența unui zero logic (resetare) cu dacă

dacă (0==(PIND & (1<<3)))

Dacă al treilea bit al portului D este șters, Code1 este executat. În caz contrar, Code2 este executat.

Acțiuni similare sunt efectuate cu și sub această formă de înregistrare:

dacă (~PIND & (1<<3))

Verificarea descărcării pentru prezența unei unități logice (setare) cu dacă

dacă (0 != (PIND & (1<<3)))

dacă (PIND și (1<<3))

Cele două bucle de mai sus funcționează în mod similar, dar datorită flexibilității limbajului de programare C, pot fi scrise diferit. Operația != înseamnă nu este egal. Dacă al treilea bit al portului PD I/O este setat (unul), atunci Code1 este executat, dacă nu, Code2.

Aștept un pic de resetare cu in timp ce

în timp ce (PIND & (1<<5))

Code1 va fi executat atâta timp cât al 5-lea bit al registrului PIND este setat. Resetarea acestuia va începe executarea Code2.

Se așteaptă ca bitul să fie setat in timp ce

Aici, sintaxa limbajului C vă permite să scrieți cod în două dintre cele mai comune moduri. În practică, se folosesc ambele tipuri de înregistrare.

Multă vreme am plecat fără atenție Microcontrolere AVR, iar acum este timpul să corectăm această neînțelegere! În ceea ce privește alte controlere, vom lua în considerare treptat diverse periferice AVR, mai întâi teoria, tot felul de registre și, în final, mici exemple.

Eu folosesc ca IDE AVR Studio 5, a șasea versiune AVR Studio Nici nu l-am încercat, nu am întâlnit sarcini pentru AVR atât de des în ultima vreme) În general, nu e rău să ai instalat și AVR Studio 4, pentru că uneori se întâmplă să fie nevoie să programezi controlerul de la AVR Studio 5 nu pare posibil. Recent, am vrut să flash ATMega2560 folosind programatorul STK500, iar acest lucru s-a dovedit a fi imposibil după Studio 5) A rămas bine din vremurile vechi ale AVR Studio 4, iar problema a fost rezolvată în câteva minute.

Ce să mai zic? .. Da, în principiu, atât, poți să te apuci de treabă;)

Desigur, vom începe cu GPIO - porturi de intrare-ieșire, pentru că fără ele nu există nicăieri) Și înainte de a descrie registrele care controlează funcționarea porturilor, voi nota câteva momente „electrice”.

La intrarea fiecărui picior al microcontrolerului, dezvoltatorii grijulii pun câteva diode care ar trebui să salveze microcontrolerul în cazul depășirii tensiunii permise. DAR! În realitate, totul nu este atât de roz și dacă, de exemplu, se aplică 7,5 volți la intrarea microcontrolerului, atunci nimeni și nimic nu va ajuta controlerul, a fost verificat din propria noastră experiență. Prin urmare, toate experimentele trebuie efectuate cu atenție)

Acum la înregistrări. Toate lucrările cu porturile I/O din AVR-uri sunt concentrate în trei registre - DDRx, PORTx, PINx. Caracterul „x” este înlocuit cu numele portului corespunzător (A,B…). Adică dacă vrem să lucrăm cu portul A al microcontrolerului, atunci avem nevoie de registrele DDRA, PORTA, PINA. Dacă vrem să lucrăm cu al cincilea pin al portului A (PA5), atunci suntem interesați de al cincilea bit al registrelor menționate mai sus. După cum puteți vedea, totul este destul de simplu) Rămâne doar să ne dăm seama ce și unde să scrieți, de ce este responsabil fiecare dintre aceste registre. Așa că am început...

Registrul DDRx.

DDRx este responsabil pentru direcția de funcționare a pinilor corespunzători ai microcontrolerului. Fiecare pin poate fi fie o intrare, fie o ieșire, nu există un al treilea. Pentru a configura ieșirea să funcționeze în modul de intrare a registrului DDR, trebuie să scrieți 0 pentru acest port și 1 pentru ieșire. Să presupunem că trebuie să configuram PA6 ca intrare și PA3 ca ieșire. Ce facem? Așa este, setăm al treilea bit al registrului DDRA la 1 și scriem 0 în al 6-lea bit al aceluiași registru DDRA. Gata!

Registrul PINx.

Nu putem scrie nimic în acest registru, este destinat exclusiv citirii datelor. Acest registru conține informații despre nivelul semnalului pe portul corespunzător. După cum ne amintim, microcontrolerul este un dispozitiv digital, iar semnalele de pe picioarele sale pot fi fie ridicate (1 logic) fie scăzute (0 logic). Dacă vrem să știm ce avem acolo la intrarea PB4, atunci ne interesează al patrulea bit al registrului PINB.

Înregistrați PORTx.

Acest registru este puțin mai complicat decât precedentul. Funcționalitatea sa depinde de direcția în care funcționează pinii microcontrolerului. Dacă pinul este folosit ca intrare, atunci registrul PORTx specifică tipul de intrare. Există două opțiuni posibile aici:

PORTx = 1 - cu această configurație, obținem o intrare cu un pull up (PullUp)

PORTX = 0 - intrare de impedanță mare (Hi-Z) - asta înseamnă că rezistența portului este atât de mare încât poate fi considerată infinită)

Deci, să continuăm cu registrul PORTx. Dacă ieșirea funcționează ca o ieșire, iar registrul PORTx este unul, atunci ieșirea va fi un nivel de semnal ridicat, în mod similar, PORTx \u003d 0 - un nivel scăzut.

Să luăm un mic exemplu pentru a ilustra 😉

Să configuram pinul PC4 să funcționeze în modul de intrare pull-up. Pentru a face acest lucru, scrieți 0 (modul de intrare) în al patrulea bit al registrului DDRC și setați al patrulea bit din registrul PORTC la 1 (tragere în sus). Asta e tot.

În principiu, asta este tot ceea ce privește teorie, nu vom aprofunda mai departe. Rămâne să respectați tradițiile și să vă jucați cu dioda. Lăsați dioda să fie conectată la pinul PV5. Să-l facem să clipească! Și, în primul rând, să creăm un proiect. Eu, așa cum am spus deja, folosesc AVR Studio 5, iar ca controler îmi voi alege ATMega88 preferat)

// Conectați fișierul necesar#include /*******************************************************************/ // O funcție simplă pentru a genera o întârziere void delay(unsigned int time) (unsigned int i = 0; pentru (i = 0; i)< time ; i++ ) ; } /*******************************************************************/ // Inițializam ieșirea noastră, funcționează la ieșire void initAll() ( DDRB = 0b00100000 ; ) /*******************************************************************/ // Corpul actual al funcției main(). int main(void ) ( initAll() ; while (1 ) ( // Porniți dioda PORTB = 0b00100000 ; // Rest delay(50000 ) ; // Opriți dioda PORTB = 0b00000000 ; delay(50000000 ) ) ) /*******************************************************************/

Așa se face prima noastră comunicare cu Microcontrolere AVR. În curând vom lua în considerare, dacă este posibil, întreaga lor periferie, iar apoi vom putea stârni ceva mai interesant 😉

Acum citești asta și te gândești - memoria, registrele, stiva și așa mai departe sunt bune. Dar nu poți simți, nu poți vedea. Cu excepția cazului în simulator, dar pot codifica pe Delphi cu aceeași condiție. Unde este carnea!!!

În alte cursuri de acolo, aproape de la primele rânduri, fac ceva semnificativ - clipesc cu o diodă și spun că aceasta este Hello World. Si aici? Gide???

Da, da, da, te înțeleg. Mai mult decât atât, probabil că ai fugit deja la concurenții tăi și le-ai clipit cu o diodă;)))) Nimic, de iertare.

Pur și simplu nu am vrut să mă opresc la aceleași didodik-uri intermitente, iar progresul necesită o înțelegere clară a elementelor de bază și a principiilor - o bază teoretică puternică. Dar acum este timpul pentru antrenament.

Am vorbit deja despre porturi, aveți deja un șablon de program, așa că să începem imediat.

Instrumente
Lucrul cu porturi înseamnă, de obicei, lucrul cu biți. Acesta este să pună puțin, să resetați puțin, să inversați puțin. Da, desigur, există comenzi convenabile în assembler

cbi/sbi, dar funcționează doar într-un interval mic de adrese (de la 0 la 1F, deci să scriem mai întâi macrocomenzi universale, astfel încât să le putem folosi în viitor și să nu ne facem griji pentru spațiul de adrese.

Macro-urile vor fi numite:

  • SETB octet, bit, temp
  • CLRB octet, bit, temp
  • INVB octet, bit, temp, temp2

Mai mult, atunci când lucrați cu biți ai RVV inferioară (adresă 0-1F), este posibil ca valoarea parametrului TEMP să nu fie specificată - oricum nu va fi înlocuită. Cu excepția comenzilor de inversare, acolo vor fi necesare registre intermediare.

De asemenea, este util să aveți un grup de macrocomenzi care nu folosesc registre. Mai exact, vor folosi registre, dar după ce le-au salvat pe stivă. Pot fi împinși fără minte ca echipele obișnuite. Dar rularea lor va dura mai mult și va necesita RAM.

  • SETBM octet, biți
  • CLRBM octet, biți
  • INVBM octet, biți

Iată codul lor sursă. După cum puteți vedea, condițiile de limbaj macro sunt utilizate în mod activ, ceea ce face posibilă crearea de macrocomenzi universale. Compilatorul își va da seama ce versiune să pună unde să o pună :)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 < 0x20 SBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ORI R17,1<<@1 OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ORI R17,1<<@1 STS @0,R17 POP R17 .endif .endif .ENDM ;SET BIT with REG .MACRO SETB .if @0 < 0x20 ; Low IO SBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ORI @2,1<<@1 OUT @0,@2 .else ; Memory LDS @2,@0 ORI @2,1<<@1 STS @0,@2 .endif .endif .ENDM ;............................................................. ;Clear BIT with REG .MACRO CLRB .if @0 < 0x20 ; Low IO CBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ANDI @2,~(1<<@1) OUT @0,@2 .else ; Memory LDS @2,@0 ANDI @2,~(1<<@1) STS @0,@2 .endif .endif .ENDM ;Clear BIT with STACK .MACRO CLRBM .if @0 < 0x20 CBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ANDI R17,~(1<<@1) OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ANDI R17,~(1<<@1) STS @0,R17 POP R17 .endif .endif .ENDM ;............................................................. .MACRO INVB .if @0 < 0x40 IN @2,@0 LDI @3,1<<@1 EOR @3,@2 OUT @0,@3 .else LDS @2,@0 LDI @3,1<<@1 EOR @2,@3 STS @0,@2 .endif .ENDM .MACRO INVBM .if @0 < 0x40 PUSH R16 PUSH R17 IN R16,@0 LDI R17,1<<@1 EOR R17,R16 OUT @0,R17 POP R17 POP R16 .else PUSH R16 PUSH R17 LDS R16,@0 LDI R17,1<<@1 EOR R17,R16 STS @0,R17 POP R17 POP R16 .endif .ENDM ;= End macro.inc ========================================

;= Porniți macro.inc ======================================= ;SETARE BIT cu stiva .MACRO SETBM .dacă @0< 0x20 SBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ORI R17,1<<@1 OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ORI R17,1<<@1 STS @0,R17 POP R17 .endif .endif .ENDM ;SET BIT with REG .MACRO SETB .if @0 < 0x20 ; Low IO SBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ORI @2,1<<@1 OUT @0,@2 .else ; Memory LDS @2,@0 ORI @2,1<<@1 STS @0,@2 .endif .endif .ENDM ;............................................................. ;Clear BIT with REG .MACRO CLRB .if @0 < 0x20 ; Low IO CBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ANDI @2,~(1<<@1) OUT @0,@2 .else ; Memory LDS @2,@0 ANDI @2,~(1<<@1) STS @0,@2 .endif .endif .ENDM ;Clear BIT with STACK .MACRO CLRBM .if @0 < 0x20 CBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ANDI R17,~(1<<@1) OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ANDI R17,~(1<<@1) STS @0,R17 POP R17 .endif .endif .ENDM ;............................................................. .MACRO INVB .if @0 < 0x40 IN @2,@0 LDI @3,1<<@1 EOR @3,@2 OUT @0,@3 .else LDS @2,@0 LDI @3,1<<@1 EOR @2,@3 STS @0,@2 .endif .ENDM .MACRO INVBM .if @0 < 0x40 PUSH R16 PUSH R17 IN R16,@0 LDI R17,1<<@1 EOR R17,R16 OUT @0,R17 POP R17 POP R16 .else PUSH R16 PUSH R17 LDS R16,@0 LDI R17,1<<@1 EOR R17,R16 STS @0,R17 POP R17 POP R16 .endif .ENDM ;= End macro.inc ========================================

De-a lungul timpului, când scrieți în assembler, există o mulțime de astfel de macrocomenzi. Acestea sunt scoase într-un fișier separat și pur și simplu conectate la oricare dintre proiectele dvs., iar scrierea codului devine ușoară și plăcută.

Dar revenind la cod,
Să clipim un LED, atunci, în sfârșit?

Sigur, nu este o problemă. LED-urile sunt deja montate pe placa demo, de ce să nu le folosiți? Se atârnă de pinii portului PD4, PD5, PD7. Trebuie doar să purtați pulovere.

; Internal Hardware Init ===================================== SETB DDRD,4,R16; DDRD.4 = 1 SETB DDRD,5,R16 ; DDRD.5 = 1 SETB DDRD,7,R16 ; DDRD.7 = 1; Încheierea inițierii hardware-ului intern ====================================

Rămâne să ne aprindem diodele. Acestea sunt aprinse prin scrierea de biți în registrul PORT. Acest lucru este deja făcut în secțiunea principală a programului.

; Principal =================================================== ======= Principal: SETB PORTD,4,R16 ; Aprins LED1 SETB PORTD,7,R16 ; Aprins LED3 JMP Principal ; Sfârșit principal ================================================== ====

Compilăm, îl puteți rula în tracer, veți vedea imediat cum se schimbă biții. Clipim... și după ce faceți clic pe RESET și descărcați bootloader-ul (cu excepția cazului în care, desigur, aveți) veți vedea această imagine:


Și pentru versiunea a II-a


În! E cam plictisitor. Să le facem cu ochiul.

Să înlocuim macrocomenzile noastre.

; Principal =================================================== ======= Principal: SETB PORTD,4,R16 ; Aprins LED1 INVB PORTD,7,R16,R17 ; Inversează LED3 JMP Principal ; Sfârșit principal ================================================== ====

Aprins, aprins...

Dar smochine - ambele sunt aprinse, dar unul este puțin slab. De fapt pâlpâie, dar foarte foarte repede. Dacă introduceți un osciloscop în ieșirea lui PD7, veți vedea că nivelul se schimbă acolo cu o frecvență frenetică:


Ce sa fac? În mod evident, încetinește. Cum? Cel mai simplu mod, care se practică în marea majoritate a tutorialelor și a pornirilor rapide, este o întârziere stupidă. Acestea. obține cod ca:

; Principal =================================================== ======= Principal: SETB PORTD,4,R16 ; Aprins LED1 INVB PORTD,7,R16,R17 ; Invert LED3 RCALL Delay JMP Main ; Sfârșit principal ================================================== ==== ; = Procedura================================================== == .equ LowByte = 255 .equ MedByte = 255 .equ HighByte = 255 Întârziere: LDI R16,LowByte ; Încărcați trei octeți LDI R17,MedByte; Extrasul nostru LDI R18,HighByte bucla: SUBI R16,1; Scăderea 1 SBCI R17.0; Scădeți doar C SBCI R18.0 ; Scădeți numai C BRCC Loop ; Dacă nu există transfer-tranziție RET; Încheierea procedurii =================================================

L-am lansat, l-am lansat... Da, acum clipitul va fi vizibil.

Cu parametrii 255.255.255, viteza obturatorului la 8 MHz va fi de aproximativ 2,1 secunde. Puteți mări lungimea întârzierii cu încă câțiva octeți. Apoi puteți încărca cel puțin o oră.

Dar această metodă este greșită, acum vă voi arăta de ce.

Să adăugăm un buton. Lăsați LED-ul 3 să clipească în inversare. Și vom face astfel încât atunci când butonul este apăsat, LED1 este aprins, iar când este eliberat, LED2 este aprins.

Să luăm ceasul A ca buton, să-l conectăm la portul PD6.


Pentru versiunea II este similar. Doar luați butonul din grupul de butoane și conectați ieșirea PD6 a controlerului la pinul COL1 de pe tastatură.

Atenție doar la jumperii care se află pe jumperii câmpului de butoane. Cele albastre sunt. Și, de asemenea, un jumper negru discret, care este indicat de săgeata dreapta. Conectează coloana din stânga de butoane la pământ. Se năpustește pe pinii GND și ROW1. Totul este semnat pe tablă.

Verificarea butonului se face prin comanda SBIC, dar mai întâi trebuie inițializat. Faceți DDR=0, PORT=1 - intrare pull-up.

Adăugați aceste linii la secțiunea de inițializare (Internal Hardware Init):

; Principal =================================================== ======= Principal: SBIS PIND,6 ; Dacă butonul este apăsat - tranziție RJMP BT_Push SETB PORTD,5 ; Aprinde LED2 CLRB PORTD,4 ; Opriți LED1 Următorul: INVB PORTD,7,R16,R17; Invertire LED3 RCALL Delay JMP Main BT_Push: SETB PORTD,4 ; Aprinde LED1 CLRB PORTD,5 ; Opriți LED2 RJMP Next; Sfârșit principal ================================================== ====

Ei bine, funcționează. Este apăsat butonul - diodele se schimbă. Al treilea face cu ochiul vesel. Dar există o captură:

Programul încetinește! Am apăsat butonul, dar imaginea nu s-a schimbat, trebuie să așteptați, să o țineți apăsat... De ce? Și asta din cauza bydlokoding-ului nostru.

Îți amintești că ți-am spus că întârzierile stupide în care MK nu face nimic sunt un rău infernal? Aici! Acum ai văzut-o singur. Ei bine, răul trebuie luptat. Cum? Ei bine, am mai spus și asta - să faci un ciclu continuu cu steaguri. Măcar adăugați ceva lucru util la întârzierea noastră.

flaşnetă
Acum vă voi arăta cum puteți face o orgă digitală. Îți amintești cum se face?

Există un tambur cu cuie proeminente și arcuri în diferite tonuri. Unghiile se învârt, arcurile se zvâcnesc — zgâiâie. Se pare raskolbasny Mouzon. Și dacă ghirona noastră este extinsă într-o panglică. Nu este adevărat că unghiile arată ca unități? ;))))

Banda va fi un numărător care numără de la zero, să zicem, până la FF.FF.FF.FF și apoi din nou până la zero sau o altă valoare, atât cât avem nevoie și vom face. Și subrutinele noastre vor juca rolul de arcuri, agățându-se de locurile potrivite - comparând constanta lor cu valoarea actuală a contorului.

A coincis? Hai să facem un rahat!

Rămâne doar să prescriem pe ciclul de timp al ghifei noastre unde și ce ar trebui lansat. Și aici există o caracteristică foarte convenabilă - pentru a construi secvențe ciclice, este suficient să prindem un bit.

Să presupunem că orga cu butoi numără de la 0 la 1000 și trebuie să clipim dioda de 10 ori. Nu este necesar să lipiți 10 handlere cu valori diferite. Unul este suficient, dar pentru ca acesta să prindă valoarea **10. Orice altceva nu este important pentru noi. Și va funcționa pe 0010, 0110, 0210, 0310, 0410, 0510, 0610, 0710, 0810, 0910. Se împart și intervale mai dese pe măsură ce avem nevoie, este suficient să intrăm în altă categorie. Aici este necesar doar să nu uitați să tăiați cifrele senior nafig pentru a nu interfera.

Să începem. Mai întâi, să creăm contorul nostru în segmentul de date:

LDS R16,CCNT LDS R17,CCNT+1 LDS R18,CCNT+2 LDS R19,CCNT+3

Totul, acum în R16 cel mai tânăr octet al contorului nostru, iar în R19 cel mai vechi.

Registrele pot fi pre-împinse pe stivă, dar vă voi da un sfat mai bun - atunci când scrieți un program, gândiți-vă la algoritm în așa fel încât să utilizați registrele ca un TEMP continuu ale cărui date sunt relevante doar aici și acum. Și ce se va întâmpla cu ei în următoarea procedură nu mai este important - tot ceea ce trebuie stocat în RAM.

O poți face astfel:

LDI R20.1; Avem nevoie de un CLR R15; Și, de asemenea, un zero. Adăugați R16,R20; Adăugăm 1 dacă registrul este 255, atunci va fi C ADC R17, R15; Se adaugă 0+C ADC R18,R15; Se adaugă 0+С ADC R19,R15; Adăugați 0+C

A trebuit să cheltuim încă două registre pentru a ne stoca constantele de adunare. Totul de la faptul că AVR-ul nu este capabil să adauge registre cu un număr direct. Dar știe să citească.

Am arătat deja că R-(-1)=R+1, dar nimeni nu ne interzice să aranjam același truc aici - să facem adunarea prin scădere.

1 2 3 4 SUBI R16,(-1) SBCI R17,(-1) SBCI R18,(-1) SBCI R19,(-1)

SUBI R16,(-1) SBCI R17,(-1) SBCI R18,(-1) SBCI R19,(-1)

Ne va oferi o creștere a unui număr de patru octeți R19:R18:R17:R16

Și acum vă voi arăta niște magie întregi.
De ce va funcționa? Aruncă o privire și tu:

SUBI R16, (-1) este, de fapt, R16 - 255 și în aproape toate cazurile ne va acorda un împrumut din următoarea categorie - C. Și numărul cu care vor rămâne mai mulți în registru.

Acestea. vedeți cum funcționează această matematică, amintiți-vă despre numărul din codurile suplimentare. Voi afișa un exemplu zecimal cu patru cifre. Avem DOAR PATRU DIFERITE, nici mai mult, nici mai puțin. Un număr negativ este 0-1, nu? BINE.

1 C 0000-1=9999+C

Acestea. noi, parcă, am luat 1 din 1 C 0000 de cinci cifre, dar avem doar patru cifre! Am primit codul suplimentar 9999 și indicatorul de împrumut C (indicând că a existat un împrumut)

Acestea. în matematica noastră întreg 9999=-1 :) Este ușor să verificați -1+1 = 0 Nu?

9999+1 = 1 C 0000 Corect! :))) Și cea mai semnificativă cifră 1 pur și simplu nu s-a încadrat în capacitate și a intrat în steagul de transport C, care semnalează și o depășire.

Okie, acum hai să luăm și să facem R-(-1). Fie R=4

1 C 0004-9999 = 0005+C

Așa că au luat-o și au adăugat-o prin scădere. Doar magie, nu? ;)

Gluma asamblatorului este că acestea sunt doar comenzi, și nu o doctrină și nu o regulă. Iar comenzile care implică calcule de semne pot fi folosite oriunde, atâta timp cât dau rezultatul de care avem nevoie!

Aici și aici - contorul nostru este și el nesemnat, dar folosim caracteristicile calculului semnat pentru că ne este mai convenabil.

Steagul C nu va apărea doar când bifăm până la 255 (9999 în exemplul zecimal), atunci va fi 255-255 = 0 și va apărea doar Z, dar nu avem nevoie de el.

STS CCNT,R16 STS CCNT+1,R17 STS CCNT+2,R18 STS CCNT+3,R19

Codul de creștere al unei constante de patru octeți din memorie poate fi pliat într-o macro pentru a nu aglomera codul

; Principal =================================================== ======= Principal: SETB PORTD,4 ; Aprins LED1 INVB PORTD,7,R16,R17 ; LED3 inversat Următorul: INCM CCNT JMP Principal

Porniți modul de depanare și puneți un punct de întrerupere (F9) pe eticheta Main și conduceți cursorul la primul punct de întrerupere.

0xB6(CCNT) 0x9F(CCNT+1) 0x04(CCNT+2) 0x00(CCNT+3)

Rămâne acum doar să comparăm numărul cu această distribuție.

Cum se compară? Da, este destul de simplu. Totul depinde de ceea ce vrem să obținem. Dacă există UN SINGUR eveniment pentru TOATA perioadă a contorului global al ghifei noastre, atunci prostește, octet cu octet. În acest caz, dioda dvs. va clipi o secundă după pornire, apoi veți aștepta o jumătate de oră până când întregul contor de patru octeți va depăși.

Pentru ca acesta să clipească în fiecare secundă, trebuie să mascați biții înalți ai contorului global atunci când faceți o comparație, de parcă ar avea o lățime de biți nu de 32 de biți, ci mai puțin (și depășește mai des).

Octeții mai mici sunt comparați așa cum sunt, iar cel mai vechi este doar până la cifra maximă, restul trebuie tăiat.

Acestea. cel mai semnificativ bit pentru acest caz este CCNT+2=0x04 dacă în reprezentare binară, atunci 0x04 = 00000 100 deci, avem un numărător de patru cifre, ceea ce înseamnă un eveniment cu mască

00 04 9F B6 ( 00000000 00000 100 10011111 10110110)

inainte de debordare va fi un numar de dofiga de ori. Vedeți, am evidențiat zerourile cu caractere aldine. Nu îl vom compara deloc pe cel mai vechi, dar înainte de cel mai vechi, este necesar să trecem prin AND pe masca 00000111 pentru a tăia biții înalți.

Ele trebuie încă umplute înainte de preaplin și de a pune la zero contorul. Dar dacă îi deghăm, atunci soarta lor ulterioară nu ne deranjează. Lasă-l să bifeze până la a doua venire, nu ne pasă.

LDS R16,CCNT; Încărcăm numere în registrele LDS R17,CCNT+1 LDS R18,CCNT+2 ANDI R18,0x07 ; Impunem o masca CPI R16,0xB6; Compara octet cu octet BRNE NoMatch CPI R17.0x9F BRNE NoMatch CPI R18.0x04 BRNE NoMatch ; Dacă se potrivește, atunci facem acțiunea Match: INVB PORTD,7,R16,R17 ; LED3 inversat; Nu s-a potrivit - nu o face :) NoMatch: Următorul: INCM CCNT ; Întoarcerea orgii cu butoi JMP Main

În, descărcat acum clipește. Nu există tocituri, nimic nu atârnă nicăieri, iar ciclul principal zboară cu un fluier, doar ai timp să răsuci toba ghiurbei :)

Doar clipește vizibil mai lent decât ne-am fi dorit. Nu 1 secundă, ci 8. Ei bine, ce ai vrut - adăugând procedura de comparație, am prelungit ciclul cu încă câteva comenzi. Și acum se efectuează nu pentru 25 de cicluri, ci pentru 36. Recalculați din nou toate numerele :))))))

Dar acesta nu este cel mai tsimes! Gluma este că o parte din cod rulează, iar altele nu - comenzi de comparare și tranziție. Prin urmare, este mai ușor să calculați cu exactitate întârzierea pe cicluri - trebuie să calculați pentru fiecare iterație când și câte tranziții veți avea, câte cicluri vor dura ...

Și dacă codul este și mai mare, atunci în sfârșit conducta și eroarea se acumulează cu fiecare iterație!

Dar dacă adăugați codul de procesare a butonului:

; Principal =================================================== ======= Principal: SBIS PIND,6 ; Dacă butonul este apăsat - tranziție RJMP BT_Push SETB PORTD,5 ; Aprinde LED2 CLRB PORTD,4 ; Opriți LED1 Următorul: LDS R16,CCNT ; Încărcați numere în registre LDS R17,CCNT+1 LDS R18,CCNT+2 ANDI R18.0x07 CPI R16.0xB6 ; Compara octet cu octet BRNE NoMatch CPI R17.0x9F BRNE NoMatch CPI R18.0x04 BRNE NoMatch ; Dacă se potrivește, atunci facem acțiunea Match: INVB PORTD,7,R16,R17 ; LED3 inversat; Nu s-a potrivit - nu o face :) NoMatch: NOP INCM CCNT JMP Main BT_Push: SETB PORTD,4 ; Aprinde LED1 CLRB PORTD,5 ; Opriți LED2 RJMP Next; Sfârșit principal ================================================== ====

Apoi vom vedea că nu au mai rămas urme ale frânelor. Butoanele răspund instantaneu la apăsare, iar LED-ul clipește singur. Multifunctional! :)

În general, ghirona nu este potrivită acolo unde sunt necesare calcule precise. Dar dacă sarcina este din seria „trebuie să te smuci periodic și nu contează cât de exact”, atunci asta este. pentru că nu ocupă resurse hardware ca un cronometru.

De exemplu, scanați periodic tastatura, să zicem, la fiecare 2048 de rotații ale buclei principale. Estimați-vă ce număr trebuie să încărcați pentru comparație și ce mască să aplicați :)

Puteți descărca și arunca o privire, urmăriți-l pentru a vedea cum se învârte totul.

Și pentru calcule precise ale timpului, există cronometre. Dar există o discuție separată despre ele.

  • tutorial

Operarea porturilor I/O

După ce ați studiat acest material, în care totul este descris în detaliu și în detaliu cu un număr mare de exemple, puteți stăpâni și programa cu ușurință porturile de intrare/ieșire ale microcontrolerelor AVR.

Un exemplu va fi luat în considerare pe un microcontroler ATMega8 .

Vom scrie programul în Atmel Studio 6.0 .

Vom emula circuitul în Proteus 7 Professional .

Microcontrolerul comunică cu lumea exterioară prin porturi de intrare/ieșire. Schema portului I/O este indicată în fișa de date:

Dar este destul de dificil pentru un începător să înțeleagă schema. Deci, să simplificăm diagrama:

Fiecare port pentru microcontroler AVR (numit de obicei A, B și uneori C sau chiar D) are 8 biți, fiecare dintre care este legat de un pin specific al șasiului. Fiecare port are trei registre speciale DDRx, PORTxȘi PINx(unde x este litera portului A, B, C sau D). Scopul registrelor:

DDRx– Setați biți ai portului x la intrare sau la ieșire.

PORTx– Controlul stării ieșirilor portului x (dacă bitul corespunzător este configurat ca ieșire), sau prin conectarea unui rezistor de pull-up intern (dacă bitul corespunzător este configurat ca intrare).

PINx– Citiți nivelurile de biți logici ale portului x.

PINхn este registrul de citire. Se poate citi doar din. În registru PINxn conține informații despre nivelul logic actual actual pe pinii portului. Indiferent de setările portului. Deci, dacă vrem să știm ce avem la intrare, citim bitul corespunzător din registru PINxn. Mai mult, există două granițe: limita zeroului garantat și limita unuia garantat - pragurile dincolo de care putem determina clar, fără ambiguitate, nivelul logic actual. Pentru o sursă de cinci volți, acestea sunt de 1,4 și, respectiv, 1,8 volți. Adică atunci când tensiunea scade de la maxim la minim, biții din registru PINx se va comuta de la 1 la 0 doar când tensiunea scade sub 1,4 volți, dar când tensiunea crește de la minim la maxim, bitul va comuta de la 0 la 1 doar când tensiunea ajunge la 1,8 volți. Adică, există o histerezis de comutare de la 0 la 1, care elimină comutarea haotică sub influența interferențelor și interferențelor și, de asemenea, elimină citirea eronată a nivelului logic între pragurile de comutare.

Odată cu scăderea tensiunii de alimentare, desigur, scad și aceste praguri.

DDRxn este registrul de direcție portului. Portul la un anumit moment poate fi fie o intrare, fie o ieșire (dar pentru starea biților PINxn, acest lucru nu contează. Citiți din PINxn valoarea reală este întotdeauna posibilă).

DDRxy= 0 - ieșirea funcționează ca INTRARE.

DDRxy= 1 - ieșirea funcționează ca IEȘIRE.

PORTxn– modul de control al stării ieșirii. Când setăm ieșirea la intrare, apoi de la PORTx depinde de tipul de intrare (Hi-Z sau PullUp, mai multe despre asta mai jos).

Când pinul este setat la ieșire, valoarea bitului corespunzător din registru PORTx definește starea de ieșire. Dacă PORTxn=1 atunci ieșirea este log.1, dacă PORTxn=0 atunci rezultatul este log.0.

Când piciorul este configurat pentru intrare, atunci dacă PORTxn=0, atunci ieșirea este în modul Hi-Z. Dacă PORTxn\u003d 1, atunci ieșirea este în modul PullUp cu un rezistor pull-up de 100k la putere.

Masa. Configurație pin port.

DDRxn PORTxn I/O Comentariu
0 0 I (Intrare) Intrare Intrare de mare impedanţă. (Nu recomand folosirea acestuia, deoarece pot fi induse interferențe de la sursa de alimentare)
0 1 I (Intrare) Intrare Rezistența internă trasă în sus.
1 0 O (Ieșire) Ieșire Ieșirea este scăzută.
1 1 O (Ieșire) Ieșire Ieșirea este ridicată.

Imaginea generală a funcționării portului este prezentată în figuri:

Orez. DDRxn=0 PORTxn=0 – Mod: HI-Z– intrare cu impedanță mare.

Orez. DDRxn=0 PORTxn=1 - Mod: trage– intrare cu un pull-up la log.1.

Orez. DDRxn=1 PORTxn=0 – Mod: Ieșire– log.0 ieşire. (aproape GND)

Orez. DDRxn=1 PORTxn=1 - Mod: Ieșire- la jurnalul de ieşire.1. (aproape VCC)

Intrare Salut-Z- mod de intrare de înaltă impedanță.
Acest mod este activat implicit. Toate comutatoarele sunt deschise și rezistența porturilor este foarte mare. În principiu, în comparație cu alte moduri, poate fi considerat infinit. Adică, din punct de vedere electric, ieșirea, așa cum ar fi, nu este conectată nicăieri și nu afectează nimic. Dar! În același timp, își citește constant starea în registru PINnși putem afla întotdeauna ce avem la intrare - unu sau zero. Acest mod este bun pentru a asculta orice magistrală de date, deoarece. nu are niciun efect asupra autobuzului. Ce se întâmplă dacă intrarea este sus în aer? Și în acest caz, tensiunea va sări pe el în funcție de pickup-uri externe, interferențe electromagnetice și, în general, de faza lunii și vremea de pe Marte (o modalitate ideală de a tăia numere aleatorii!). Foarte des, în acest caz, un sinus instabil de 50 Hz pe port este un pickup din rețeaua de 220V, iar în registru PINn va schimba 0 și 1 cu o frecvență de aproximativ 50Hz

Intrare trage- Intrare de tragere.
La DDRxn=0 și PORTxn=1, comutatorul pull-up se închide și un rezistor de 100 kΩ este conectat la linie, care aduce instantaneu linia care nu este conectată nicăieri la starea de log.1. Scopul pull-up-ului este evident - pentru a preveni o schimbare haotică a stării la intrare sub influența pickup-urilor. Dar dacă la intrare apare un zero logic (linia este închisă la masă printr-un buton sau alt microcontroler / microcircuit), atunci un rezistor slab de 100kΩ nu va putea menține tensiunea pe linie la nivelul log.1 și intrarea va fi log.0.

ieșire din modul.
Aici, cred, totul este clar - dacă trebuie să emitem log.1 la port, pornim portul de ieșire ( DDRxn=1) și jurnalul de ieșire.1 ( PORTxn\u003d 1) - în același timp, comutatorul superior se închide și la ieșire apare o tensiune apropiată de putere. Și dacă aveți nevoie de log.0, atunci porniți portul de ieșire ( DDRxn=1) și jurnalul de ieșire.0 ( PORTxn\u003d 1) - în același timp, supapa inferioară se deschide, ceea ce dă aproximativ zero volți la ieșire.