Table of Contents

Laboratorul 1: USART. Digital Debugging

Ca în toate lucrările de inginerie, bug-uri pot și vor apărea si în sisteme incorporate. În condiții de funcționare, este important să avem o modalitate de comunicare cu dispozitivul integrat. Pentru o imagine de ansamblu asupra metodelor de depanare posibile, vom face o scurtă introducere, apoi vom studia în detaliu interfața serială USART, folosită în mod uzual pentru comunicația serială dintre două dispozitive.

1. Ce este diferit fata de depanarea uzuala?

Motivul pentru care depanarea embedded este mai dificilă decât depanarea obișnuită a software-ului provine din mai multe probleme:

Cu toate acestea, principiile de debugging sunt aceleasi ca si în cazul software-ul de nivel înalt: Trebuie sa comparați ce se dorește de la sistem (cod / circuit) cu ceea ce, de fapt, face si, pentru asta, ai nevoie de vizibilitate.

2. Instrumente necesare

Vizibilitatea la nivel hardware se realizează printr-o formă de InputOutput (dacă este disponibilă):

Instrumente de măsurare:

Exemplu de flux de depanare

Un exemplu de flux de depanare ar putea fi următorul:

3. Interfața serială USART

Interfața serială este cel mai facil mod de a comunica cu microcontroller-ul vostru pentru citirea de date sau trimiterea de comenzi. Din perspectiva microcontroller-ului, comunicația serială se bazează pe doar două linii de date:

Comunicația este full-duplex, se poate transmite concomitent cu recepția.

Transmisia asincronă de date se face la nivel de cadre (frames), fiecare cadru fiind format din mai mulți biți, având formatul descris în figură.

 Transmisia serială

Se transmite un bit de start, apoi un cuvânt de date. Urmează un bit de partitate, opțional, cu rolul de a face o verificare simplă a corectitudinii datelor, și unul sau doi biți de stop.

Microcontroller-ul ATmega328p include un periferic USART (Universal Synchronous-Asynchronous Receiver/Transmitter) pentru interfața serială. În partea de inițializare a acestui periferic trebuie efectuați următorii pași:

Baud rate este numărul de simboluri/pulsuri pe secundă al semnalului. În esență, reprezintă viteza de transmisie și este foarte important ca și transmițătorul și receptorul să folosească același baud rate pentru transmisia corectă a datelor. Una dintre cele mai comune probleme cu USART configurat în mod asincron este setarea diferită a baud rate-ului pe transmițător și pe receptor. Această neconcordanță se manifestă prin recepția unor date greșite (transmițătorul trimite caracterul 'a', receptorul primește caracterul '&')

Pentru ca două dispozitive, în cazul nostru PC-ul și placa de laborator, să poată comunica între ele prin USART în mod asincron, trebuie configurate identic. Dacă placa este configurată cu baud rate 115200, 9 biți de date, 1 bit de stop și fără paritate atunci PC-ul trebuie configurat exact la fel pentru a comunica.

3.1 Registre

Descrierea completă pentru:

  • cele trei registre de control
  • registrul pentru baud rate
  • buffer-ele de transmisie/recepție

Se gaseste în Datasheet Atmega 328p în capitolul 19. Registrele au un 'n' la sfârșit care distinge între mai multe periferice USART ce pot exista pe un microcontroller (pe ATmega328P 'n' va lua doar valoarea 0 corespunzatoare USART0).

USART Data Register n (UDRn)

Registrul UDR

RXB și TXB sunt buffer-ele de recepție, respectiv transmisie. Ele folosesc aceeași adresă de I/O. Deci RXB este accesat citind din UDRn, TXB scriind în UDRn. Buffer-ul de transmisie poate fi scris numai atunci când bitul UDRE (USART Data Register Empty) din portul UCSRnA este 1. În caz contrar, scrierile vor fi ignorate.

USART Control and Status Register n A (UCSRnA)

Registrul de control UCSRnA

UCSRnA este registrul de stare al controller-ului de comunicație. Biții cei mai importanți sunt:

USART Control and Status Register n B (UCSRnB)

Registrul de control UCSRnB

UCSRnB este un registru de control. Biții importanți:

USART Control and Status Register n C (UCSRnC)

Registrul de control UCSRnC

UCSRnC este tot un registru de control. Biții importanți:

 Biții UPM

 Biții USBS

Biții UCSZ

USART Baud Rate Registers (UBRRn)

Registrul UBRRn

UBRRn este registrul care selectează baud rate-ul. Are 12 biți. Primii 4 se află în UBRRnH, ceilalți 8 în UBRRnL. Valoarea pe care o scriem în UBRRn depinde de frecvența procesorului și de baud rate-ul dorit. În tabelul următor găsiți valorile pentru cele mai uzuale viteze de transmisie pentru o frecvență de ceas de 16 MHz.

Tabel baud rate

Este de dorit alegerea unui baud rate care să poată fi obținut exact din frecvența de ceas. În caz contrar se definește o toleranță (eroarea maximă a baud rate-ului) pentru care comunicația se poate realiza în condiții acceptabile. Dacă doriți să aprofundați subiectul, găsiți multe informații aici

3.2 Exemplu de utilizare

void USART0_init()
{
  /* seteaza baud rate la 9600 */
  UBRR0 = 103;
 
  /* porneste transmitatorul */
  UCSR0B = (1 << TXEN0) | (1 << RXEN0);
 
  /* seteaza formatul frame-ului: 8 biti de date, 1 bit de stop, fara paritate */
  UCSR0C &= ~(1 << USBS0);
  UCSR0C |= (3 << UCSZ00);
}
 
void USART0_transmit(unsigned char data) {
    /* așteaptă până când buffer-ul e gol */
    while(!(UCSR0A & (1<<UDRE0)));
 
    /* pune datele în buffer; transmisia va porni automat în urma scrierii */
    UDR0 = data;
}
 
char USART0_receive()
{
  /* asteapta cat timp bufferul e gol */
  while (!(UCSR0A & (1 << RXC0)));
 
  /* returneaza datele din buffer */
  return UDR0;
}

Scrieri pe 16 biți

(3 << x)

Pentru biți de configurație care se găsesc întotdeauna unul după altul se folosește și o mască cu mai mulți biți shiftați cu index-ul celui mai din dreapta: (3 << UCSZ00) înlocuiește astfel (1 << UCSZ01) | (1 << UCSZ00)

(1 << x) | (1 << y) De cele mai multe ori o să facem măști compuse, pe care le vom aplica unui registru I/O în același timp. Atenție! Pot doar să compun măști pentru aceeași operație, nu pot aplica o mască SAU în același timp cu o mască ȘI pentru că rezultatul ar fi complet eronat!

3.3 Utilizarea interfetei seriale de pe Arduino UNO

Arduino UNO se conecteaza la PC prin intermediul interfetei seriale, dar utilizeaza un convertor USB-UART integrat pe placa. Prin intermediul acestei interfete si utilizand IDE-ul dedicat Arduino se poate programa microcontrollerul, dar se poate asigura si un canal de debug. Astfel, prin mesaje simple, se poate afla starea sistemului, se pot afisa valorile variabilelor, sau chiar se pot trimite comenzi, interfata functionand bidirectional. Mai multe detalii se pot gasi aici

Configurația implicită pentru interfața serială USART folosește 8 biți de date, un bit de stop, fără paritate (8N1).

Următorul program poate trimite mesaje de la Arduino către PC, prin USB (sau folosind emulatorul din Tinkercad)

void setup()
{
  Serial.begin(9600);
  Serial.println("in function setup");
}
 
void loop()
{
  Serial.println("in function loop");
  delay(1000);
}

Următorul program poate primi mesaje trimise de PC, prin USB (sau folosind emulatorul din Tinkercad)

void setup()
{
  Serial.begin(9600);
  Serial.println("astept comenzi");
}
 
void loop()
{
  if (Serial.available()) {
    char a = Serial.read();
    char buf[20];
    sprintf(buf, "%s: %c", "primit caracter", a);
    Serial.println(buf);
  }
}

4. Exerciții

Task 1 (3p)

Folosind interfața serială și biblioteca Arduino, trimiteți următoarele comenzi dintr-un terminal serial:

Fiecare comandă va fi urmată de caracterul de control: “\n” (newline). Configurați terminalul serial să trimită (doar) acest caracter după fiecare mesaj. Implementați un program în Arduino care să recunoască aceste comenzi și să realizeze acțiunile corespunzătoare pentru fiecare dintre ele. Folosiți pin-ul 13 (PB5) pentru LED și pin-ul 2 (PD2) pentru buton.

  • pentru a primi un șir de caractere de la interfața serială, puteți folosi un vector de caractere pe post de buffer
  • pentru a verifica dacă șirul de caractere primit corespunde cu comanda, puteți folosi funcția strcmp(), ex. strcmp(a, b) == 0

Pentru a putea primi comenzi pe serială, este de dorit să nu existe delay-uri sau funcții blocante în programul principal (în loop). Funcția blink ar trebui să fie non-blocantă. Vom folosi funcția millis() pentru a măsura durata de timp (în loc să ținem programul blocat până expiră delay-ul) astfel:

long ts = 0; // global variable is your friend
void loop() {
 // my other code here
 if ((millis() - ts) >= 100) {
    ts = millis();
    // my non-blocking timed loop here
 }
 // my other code here
}

Schema Task 1

Task 2 (3p)

Rulați exemplul pentru USART pe bază de registre. Pentru configurările serialei, urmăriți fisierul usart.c din schelet.

Schelet

Capitole utile din Datasheet Atmega 328p

  • 1. Pin Configurations - pag. 3
  • 19. USART - pag. 143
    • secțiunile 19.1 - 19.2 pentru overview
    • secțiunea 19.3 pentru generarea ceasului
    • secțiunea 19.4 pentru formatul frame-ului
    • secțiunile 19.5 - 19.8 pentru exemple de cod - inițializare și funcționare
    • secțiunea 19.10 este referința pentru registrele I/O

Configurați USART0 cu următorii parametri: baud rate 19200, 8 biți de date, 1 bit de stop, fara paritate. Transmiteți către PC câte un mesaj pentru fiecare eveniment de apăsare/lăsare a unui buton (ex: se apasă PD2, se transmite “PD2 apăsat”, se lasă PD2, se transmite “PD2 lăsat”, câte o singură dată pe apăsare).

Terminalul serial din Arduino este configurat implicit în mod 8N1, doar baud rate-ul fiind configurabil. Pentru a avea control mai precis asupra formatului se poate folosi un terminal serial dedicat, precum Putty.

Task 3 (4p)

Colaborați cu colegii de lângă voi pentru a conecta două plăci Arduino între ele și trimiteți mesaje, dinspre primul Arduino către al doilea, și invers.

Schema Task 3

Până acum am folosit interfața serială pentru a realiza comunicația dintre Arduino și PC, prin intermediul adaptorului USB-UART disponibil pe placă. Există însă situații în care se dorește conectarea unui dispozitiv extern la Arduino (de ex. un modul de comunicație radio, bluetooth, GPS, sau la modul general un alt microcontroller).

În cazul în care nu avem nevoie de conexiunea la PC, putem conecta un al doilea dispozitiv pe liniile RX și TX. Dacă însă am vrea să facem debug sau să trimitem mesaje de la PC, conexiunea nu mai funcționează corect, deoarece interfața serială nu permite conectarea a mai mult de 2 dispozitive (cel puțin pe linia TX).

O altă constrângere este că avem o singură interfață USART pe ATmega328p, fapt pentru care vom avea nevoie de simularea unei a doua interfețe seriale în software.

Găsiți un exemplu în Arduino IDE: Files > Examples > SoftwareSerial > SoftwareSerialExample

Bonus (2p) Realizați implementarea Task 1 pe bază de registre.

5. Resurse

pinout Arduino UNO

Rezolvare