LABORATORUL 01

Introducere - limbajul C, mediul Arduino și primul program

Durată: 2 ore Suport: Capitolul 1 Platformă: Arduino Uno / ATmega328P PDF îndrumar EN English version

Prima ședință fixează bazele: cum arată un program care rulează la nesfârșit pe un microcontroler, ce resurse are efectiv ATmega328P, ce face fiecare pin al plăcii și cum se comandă prima ieșire digitală.

1Obiectivele lucrării

  • Instalarea mediului Arduino IDE și configurarea plăcii și a portului de încărcare
  • Înțelegerea structurii setup() / loop() și a motivului pentru care există
  • Identificarea funcțiilor fiecărui pin al plăcii Arduino Uno
  • Alegerea tipurilor de date potrivite pentru un sistem cu 2 KB de RAM
  • Comanda unui pin de ieșire și verificarea funcționării prin LED

2Materiale necesare

  • 1 Placă Arduino Uno (ATmega328P)
  • 1 Cablu USB tip B
  • 1 Breadboard
  • 1 LED de 5 mm
  • 1 Rezistor 220 Ω
  • 2 Fire jumper

3Arhitectura plăcii

Placa Arduino Uno este construită în jurul microcontrolerului ATmega328P, un procesor pe 8 biți cu arhitectură Harvard modificată. Resursele lui sunt modeste - și tocmai de aceea fiecare decizie de programare contează.

ResursăValoareObservații
Frecvență de ceas16 MHzcristal extern; un ciclu durează 62,5 ns
Memorie Flash (program)32 KBdin care 0,5 KB ocupați de bootloader
Memorie SRAM (date)2 KBaici trăiesc variabilele - resursa cea mai critică
Memorie EEPROM1 KBpăstrează datele după oprirea alimentării
Pini digitali14 (din care 6 cu PWM)D0–D13
Intrări analogice6A0–A5, convertor pe 10 biți
Curent maxim pe pin40 mAabsolut maxim; recomandat sub 20 mA
Curent total pe toți pinii200 mAlimita microcontrolerului
2 KB de RAM înseamnă foarte puținUn singur șir de caractere de 100 de octeți consumă 5% din memoria totală. Un tablou de 500 de int nu încape deloc. La microcontrolere, alegerea tipului de date nu este o chestiune de stil, ci de funcționare.

4Explorator de pini

Apăsați pe orice pin pentru a vedea ce funcții are. Observați că mulți pini au funcții multiple - de exemplu D13 este simultan ceasul SPI și LED-ul de pe placă, iar D0 și D1 sunt ocupate de comunicația serială.

Arduino Uno - funcțiile pinilor
Pini de evitat la începutD0 și D1 sunt folosiți de Serial și de încărcarea programului. Dacă legați ceva la ei, încărcarea sketch-ului poate eșua. D13 are un LED și un rezistor deja montate pe placă, deci nu este ideal ca intrare.
Configurația completă a pinilor plăcii Arduino Uno
Fig. 1 - Configurația completă a pinilor plăcii Arduino Uno, cu toate funcțiile alternative marcate pe culori. Păstrați-o la îndemână pe tot parcursul semestrului. Fig. 1.3 din îndrumar

5Structura programului

Un program obișnuit pornește, își face treaba și se termină. Un program pentru microcontroler nu se termină niciodată: cât timp placa este alimentată, procesorul trebuie să execute ceva. Mediul Arduino impune de aceea două funcții:

FuncțieCând se executăRol tipic
setup()o singură dată, la pornire sau după resetconfigurarea pinilor, pornirea comunicației, inițializarea perifericelor
loop()continuu, imediat ce setup() s-a terminatcitirea senzorilor, decizii, comanda ieșirilor
ce se întâmplă în spate
/* Cele doua functii pe care le scrieti dumneavoastra in schita: */
void setup() { /* codul VOSTRU de initializare */ }
void loop()  { /* codul VOSTRU, repetat la nesfarsit */ }

/* ...iar mediul Arduino adauga singur, in spate, exact atat: */
int main(void)
{
    init();          // configureaza timerele si ADC-ul, apelata automat

    setup();         // o singura data, la pornire

    for (;;) {       // bucla infinita
        loop();      // la nesfarsit
    }
    return 0;        // nu se ajunge niciodata aici
}
Consecință practicăDacă puneți o buclă infinită proprie în loop(), restul programului nu se mai execută niciodată. Iar dacă loop() este gol, procesorul tot rulează - consumând curent degeaba.

6Simulator de execuție

Urmăriți pas cu pas ordinea reală a execuției: setup() rulează o singură dată, apoi loop() se reia la infinit. Observați valoarea contorului static, care demonstrează că funcția chiar este apelată repetat.

setup() și loop() - ordinea apelurilor

7Tipuri de date pe AVR

TipOctețiDomeniuCând se folosește
bool1true / falseindicatori; ocupă tot un octet, nu un bit
char1−128 … 127caractere ASCII
uint8_t / byte10 … 255numere de pini, contoare mici, valori de registru
int2−32 768 … 32 767atenție: pe AVR are 16 biți, nu 32!
unsigned int20 … 65 535valori ADC, contoare medii
long4±2,1 · 10⁹millis(), micros()
float4±3,4 · 10³⁸, ~7 cifreevitați: nu există unitate hardware, calculele sunt lente
Capcana clasică pe AVR int t = 1000 * 60; dă rezultat greșit, pentru că 60 000 depășește domeniul lui int pe 16 biți. Corect: long t = 1000L * 60; - sufixul L forțează calculul pe 32 de biți.
De ce se evită floatATmega328P nu are unitate de virgulă mobilă. O simplă înmulțire de tip float durează de zeci de ori mai mult decât una întreagă și ocupă aproape 1 KB de Flash doar pentru bibliotecă. Acolo unde este posibil, lucrați cu întregi scalați (de exemplu temperatura în zecimi de grad, ca int).

Calificatori importanți

CalificatorEfectCând este obligatoriu
constvaloare care nu se modificănumere de pini, praguri
staticpăstrează valoarea între apeluricontoare locale în loop()
volatileinterzice optimizarea accesuluiorice variabilă modificată într-un ISR
PROGMEMstochează datele în Flash, nu în RAMtabele constante mari, texte fixe

8Schema de montaj

Arduino Uno D8 GND 220Ω LED I = (5 − 2) / 220 ≈ 13,6 mA sub limita de 20 mA ✓
Fig. 1 - LED conectat la pinul D8 prin rezistor de limitare. Rezistorul este obligatoriu: fără el, curentul ar depăși 40 mA și ar distruge atât LED-ul, cât și pinul microcontrolerului.
Calculul rezistorului
R = (V_alimentare − V_LED) / I_dorit
Pentru un LED roșu (V_LED ≈ 2 V) alimentat la 5 V, cu 15 mA dorit: R = (5 − 2) / 0,015 = 200 Ω → se alege valoarea standard imediat superioară, 220 Ω.

9Cod sursă

Programul minimal - Blink

blink.ino
const uint8_t LED_PIN = 8;    // const + uint8_t: nu ocupa RAM inutil

void setup() {
  pinMode(LED_PIN, OUTPUT);   // pinul devine iesire
}

void loop() {
  digitalWrite(LED_PIN, HIGH);  // aprinde
  delay(500);                   // asteapta 500 ms
  digitalWrite(LED_PIN, LOW);   // stinge
  delay(500);
}

Aceeași funcționalitate, la nivel de registru

Funcțiile pinMode() și digitalWrite() există doar în mediul Arduino. Aceleași operații scrise direct pe regiștri funcționează pe orice compilator AVR și se execută de aproximativ 50 de ori mai rapid:

blink_registri.ino
void setup() {
  DDRB |= (1 << PB0);           // pinul 8 devine iesire  (echiv. pinMode(8, OUTPUT))
}

void loop() {
  PORTB |=  (1 << PB0);         // pinul 8 pe HIGH        (echiv. digitalWrite(8, HIGH))
  delay(500);
  PORTB &= ~(1 << PB0);         // pinul 8 pe LOW         (echiv. digitalWrite(8, LOW))
  delay(500);
}
OperațieFuncție ArduinoEchivalent pe registru
Pin ca ieșirepinMode(8, OUTPUT)DDRB |= (1 << PB0);
Pin ca intrarepinMode(2, INPUT)DDRD &= ~(1 << PD2);
Intrare cu pull-uppinMode(2, INPUT_PULLUP)DDRD &= ~(1<<PD2); PORTD |= (1<<PD2);
Scrie HIGHdigitalWrite(8, HIGH)PORTB |= (1 << PB0);
Scrie LOWdigitalWrite(8, LOW)PORTB &= ~(1 << PB0);
Citește pindigitalRead(2)(PIND & (1 << PD2))
Continuare obligatorieNotația (1 << PB0) se numește mască de biți și este fundamentul întregului curs. Dacă nu vă este încă limpede de ce funcționează, parcurgeți Laboratorul 1B înainte de a trece la lucrarea următoare - acolo este explicată pas cu pas, cu simulator interactiv.

Depanare fără ecran: LED-ul ca instrument

Microcontrolerul nu are ecran. Până la lucrarea a șasea, când veți învăța comunicația serială și veți putea trimite text către calculator, singurul instrument de depanare pe care îl aveți este chiar LED-ul: îl folosiți ca să arătați că programul a ajuns într-un anumit punct sau ca să „afișați” un număr mic, prin clipiri numărate.

depanare_led.ino
void setup() {
  pinMode(13, OUTPUT);
}

void loop() {
  static uint8_t contor = 0;    // static: isi pastreaza valoarea intre apeluri
  contor++;

  // "afisam" contorul: cate o clipire pentru fiecare unitate
  for (uint8_t i = 0; i < contor; i++) {
    digitalWrite(13, HIGH);
    delay(150);
    digitalWrite(13, LOW);
    delay(250);
  }

  delay(1500);                  // pauza lunga = s-a terminat "afisarea"
  if (contor >= 5) contor = 0;  // luam de la capat
}
Numărați clipirileLa fiecare reluare a lui loop() vedeți cu o clipire mai mult: 1, 2, 3, 4, 5, apoi iar 1. Aceasta este dovada că static chiar păstrează valoarea între apeluri - fără el, contorul ar reveni la 0 de fiecare dată și ați vedea mereu o singură clipire.

Verificarea memoriei disponibile

Dimensiunile tipurilor le găsiți în tabelul de la capitolul 7. Cât RAM a mai rămas liber se poate afla însă doar în timpul execuției, cu funcția de mai jos. Neavând încă monitorul serial, programul nu tipărește valoarea, ci o semnalizează: dacă memoria liberă scade sub un prag, LED-ul rămâne aprins continuu.

memorie.ino
// Returneaza numarul de octeti liberi intre heap si stiva
int memorieLibera() {
  extern int __heap_start, *__brkval;
  int v;
  return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval);
}

void setup() {
  pinMode(13, OUTPUT);
}

void loop() {
  if (memorieLibera() < 256) {
    digitalWrite(13, HIGH);       // alarma: memorie aproape epuizata
  } else {
    digitalWrite(13, LOW);
  }
  delay(500);
}
Cum vedeți numărul exactLa lucrarea a șasea, după ce studiați comunicația serială, veți putea înlocui LED-ul de alarmă cu o linie care trimite valoarea către calculator și veți citi direct câți octeți au mai rămas din cei 2048.

10Montaj interactiv

Montajele de mai jos rulează codul chiar în pagină, pe un ATmega328P simulat. Nu este o animație: PORTB chiar comandă pinul, LED-ul se aprinde pentru că bitul 5 a devenit 1, iar ceasul de 16 MHz curge la fel ca pe placa reală. Apăsați Pornește, apoi schimbați codul și porniți din nou.

Primul program - LED-ul care clipește
void setup() {
  pinMode(13, OUTPUT);      // pinul 13 devine iesire
}

void loop() {
  digitalWrite(13, HIGH);   // aprinde
  delay(500);               // asteapta 500 ms
  digitalWrite(13, LOW);    // stinge
  delay(500);
}
De încercatPriviți panoul de regiștri de sub montaj în timp ce programul rulează: pinMode(13, OUTPUT) pune pe 1 bitul DDB5, iar fiecare digitalWrite comută bitul PORTB5. Funcțiile Arduino nu fac nimic magic - scriu în aceiași regiștri pe care îi veți folosi direct la lucrarea următoare. Încercați delay(100) și priviți cum se îndesesc impulsurile pe osciloscop.
Exercițiu - semnalul SOS în cod Morse
/* Punct = 200 ms aprins, linie = 600 ms aprins.
   Intre semnale: 200 ms pauza. Intre litere: 600 ms pauza. */

void punct() {
  digitalWrite(13, HIGH);
  delay(200);
  digitalWrite(13, LOW);
  delay(200);
}

void linie() {
  /* Completati: la fel ca punct(), dar aprins 600 ms */
}

void setup() {
  pinMode(13, OUTPUT);
}

void loop() {
  punct(); punct(); punct();     // S
  delay(400);
  /* Completati: trei linii pentru O, apoi trei puncte pentru S,
     apoi o pauza lunga de 2000 ms inainte de a relua. */
}
De încercatUrmăriți traseul de pe osciloscop: trebuie să vedeți clar trei impulsuri scurte, trei lungi și încă trei scurte. Este exact ce ar arăta un osciloscop real legat la pinul 13.

11Sarcini de lucru

  • Instalați Arduino IDE, selectați placa Arduino Uno și portul serial corect.
  • Încărcați programul Blink și verificați funcționarea LED-ului de pe placă (D13).
  • Montați un LED extern cu rezistor de 220 Ω pe pinul D8 și modificați programul.
  • Calculați curentul prin LED și verificați că se încadrează sub 20 mA.
  • Modificați intervalele astfel încât LED-ul să fie aprins 100 ms și stins 900 ms.
  • Rulați programul de verificare a dimensiunilor și notați valorile pentru int și long.
  • Verificați experimental capcana int t = 1000 * 60; și corectați-o cu sufixul L.
  • Afișați memoria RAM liberă și observați cum scade după declararea unui tablou de 200 de int.

12Aplicație de aprofundare

ExtindereScrieți un program care clipește LED-ul într-un ritm progresiv: pornește cu o pauză de 1000 ms și scade cu 50 ms la fiecare clipire, până ajunge la 50 ms, apoi o ia de la capăt. Afișați pe serial pauza curentă și numărul ciclului.

Întrebare de reflecție: ce tip de date este potrivit pentru variabila care ține pauza și de ce un uint8_t nu ar fi suficient?

13Întrebări de verificare

14Resurse