Laboratorul 5: SPI (Serial Peripheral Interface)

1. Introducere

În cadrul acestui laborator, vom aborda protocolul de comunicație SPI, utilizat în lumea embedded pentru transmisia datelor între 2 sau mai multe dispozitive. Scopul nostru final va fi cel de a construi pe plăcuța de laborator un player audio care să citească fișiere .wav de pe un card SD și să le redea utilizând speaker-ul integrat.

2. Protocolul SPI (Serial Peripheral Interface)

SPI este un standard sincron dezvoltat de Motorola. Acesta funcționează în modul full-duplex (transferul de date are loc în ambele direcții simultan).

Dispozitivele comunică utilizând o arhitectură Master-Slave (este permis un singur dispozitiv Master și unul sau mai multe dispozitive Slave). Dispozitivul Master este cel care inițiază comunicarea.

SPI mai este numit și four wire, deoarece în cadrul acestui protocol se transmit semnale pe 4 linii independente. Cele patru semnale utilizate sunt următoarele:

  • MOSI — Master Output Slave Input (transmiterea datelor de la Master la Slave)
  • MISO — Master Input Slave Output (transmiterea datelor de la Slave la Master)
  • SCLK — Serial Clock (sincronizarea dintre dispozitive; controlat de Master)
  • CS / SS — Chip Select / Slave Select (selectarea dispozitivului Slave de către Master; valoarea LOW pentru Slave-ul selectat)

Putem vedea că există două semnale utilizate pentru transmiterea datelor: MOSI și MISO. Transmiterea datelor pe SPI se face sincron, folosind semnalul de ceas SCLK. Semnalul CS / SS este utilizat pentru a selecta dispozitivul Slave cu care dorim să comunicăm la un moment dat.

2.1 Conectarea mai multor dispozitive Slave

Mai multe dispozitive Slave pot fi conectate în același timp la un singur dispozitiv Master. Semnalele MOSI, MISO și SCLK sunt partajate. Pentru fiecare dispozitiv Slave, dispozitivul Master are câte un semnal CS / SS. Când Masterul dorește să comunice cu un Slave, dispozitivul Master setează pe LOW semnalul CS / SS care duce către dispozitivul Slave dorit (celelalte semnale CS / SS sunt puse pe HIGH). Astfel, dispozitivul Slave cu CS / SS pe LOW știe că Master-ul comunică cu el.

3. Transmiterea datelor cu SPI

Pentru a începe comunicarea, dispozitivul Master trebuie să se asigure că ceasul său este setat la o frecvență cel mult egală cu frecvența suportată de dispozitivul Slave (de obicei, până la câțiva MHz). Apoi, Master-ul selectează dispozitivul Slave dorit, punând 0 pe linia CS / SS asociată acestuia.

În timpul unui ciclu SPI, transmisia este full-duplex:

  • Dispozitivul Master trimite un bit pe linia MOSI, iar dispozitivul Slave citește acest bit.
  • Dispozitivul Slave trimite un bit pe linia MISO, iar dispozitivul Master citește acest bit.

În general, comunicarea SPI implică doi regiștrii de shiftare (unul în Master și unul în Slave), conectați circular, la fel ca în figura de mai jos.

De obicei, primul bit mutat pe MISO / MOSI este cel mai semnificativ bit, în timp ce un bit nou este adăugat la poziția cea mai puțin semnificativă din registru. După ce întregul cuvânt a fost trimis, prin shiftare, Master-ul și Slave-ul au schimbat integral valorile din cei doi regiștrii de shiftare. Dacă mai există date de transmis, procesul începe din nou. Când nu mai există date de transmis, dispozitivul Master întrerupe generarea semnalului de ceas (SCLK) și plasează semnalul CS / SS asociat cu Slave-ul pe HIGH (1 logic).

Pe tot parcursul procesului descris mai sus, dispozitivele Slave care nu au fost selectate de semnalul SS asociat vor ignora semnalele de pe SCLK și MOSI și nu vor genera nimic pe MISO. Master-ul poate selecta un singur Slave la un moment dat.

4. SPI în ATMega324

Microcontroller-ul ATMega324 pune la dispoziția utilizatorilor săi și o interfata SPI, aceasta putând funcționa atât ca master cât și ca slave.

Dupa cum ne-am obișnuit, pentru configurarea și utilizarea interfeței vom folosi regiștrii: de control, de stare și de date.

Descrierea completă a regiștrilor asociați interfeței SPI o găsiți în datasheet, capitolul SPI – Serial Peripheral Interface (pagina 214). În continuare, vom face o scurtă trecere în revistă a regiștrilor și a câmpurilor relevante din aceștia.

SPI Control Register (SPCR)

  • SPE - SPI Enable - setat pe 1 pentru activarea interfeței SPI
  • DORD - Data Order - când este, 1 bitul cel mai puțin semnificativ (LSB) de date este transmis primul, invers pentru 0
  • MSTR - Master/Slave Select - când este 1, interfața funcționează în modul Master, când este 0 aceasta funcționează în modul Slave
  • CPOL, CPHA, SPR1, SPR0 - setările pentru linia SCLK

 Registrul SPCR

SPI Status Register (SPSR)

  • SPIF - SPI Interrupt Flag - este pus pe 1 când s-a terminat o transmisie

 Registrul SPSR

SPI Data Register (SPDR)

Acesta este registrul din care citim datele pe care le-am primit / în care scriem datele pe care vrem să le transmitem. Scrierile în acest registru inițiază o nouă transmisie atunci când interfața funcționează în module Master. Datele citite din acestu registru vin din registrul de shiftare.

 Registrul SPDR

5. FAT SD Card

Plăcuța de laborator este dotată cu un cititor de carduri SD care poate comunica cu microprocesorul nostru prin protocolul SPI, acesta fiind și usecase-ul la care vom lucra astăzi.

Figura de mai jos ilustrează modul de conectare al cititorului SD la microprocesor. După cum se poate observa, pentru a seta semnalele descrise în secțiunea 2 a laboratorului, ne putem folosi de o parte din pinii cu care am lucrat și în laboratoarele trecute.  Schemă conectare card SD cu ATmega324

Cardurile SD cu care vom lucra astăzi sunt formatate cu sistemul de fișiere FAT32. Pentru a ușura lucrul cu acest sistem de fișiere, ne vom baza pe biblioteca Petit FAT Filesystem, atât datorită dimensiunii mici (2-4KB), cât și faptului că folosește un minim de memorie RAM (46 octeți plus stivă).

API-ul expus de bibliotecă oferă următoarele funcții:

FRESULT pf_mount (FATFS*);			// Mount / Unmount pentru un volum
FRESULT pf_open (const char*);			// Deschide un fișier
FRESULT pf_read (void*, WORD, WORD*);		// Citește dintr-un fișier
FRESULT pf_write (const void*, WORD, WORD*);	// Scrie într-un fișier
FRESULT pf_lseek (DWORD);			// Mută pointer-ul de citire/scriere
FRESULT pf_opendir (DIR*, const char*);		// Deschide un director
FRESULT pf_readdir (DIR*, FILINFO*);		// Citește un director

6. Exerciții

Descărcați arhiva cu scheletul de cod. În cadrul laboratorului de astăzi, nu veți scrie foarte multe linii de cod, însă va fi important să înțelegeți modul în care codul vostru va interacționa cu restul scheletului și modul de funcționare al funcțiilor de bibliotecă pe care le veți apela.

  1. SPI Control. Prima voastră sarcină pentru a implementa player-ul audio este să implementați funcțiile de bază care vor asigura comunicarea microprocesorului nostru prin SPI: funcțiile SPI_init() și SPI_exchange() din fișierul spi.c. Urmăriți comentariile marcate cu TODO1 . De asemenea, țineți cont de următoarele hint-uri:
    • PIN-ul SS de pe microcontroller (PB4) trebuie configurat ca output și trecut în starea HIGH – acest pin, dacă e INPUT, e folosit ca sursă de întrerupere SPIF și trebuie dezactivat!
    • Pentru implementarea celor 2 funcții, porniți de la exemplele de cod de la pagina 217 din datasheet.
    • Folosiți schema din secțiunea 5 pentru a identifica pinii asociați semnalelor MOSI și SCLK.
    • Interfața SPI trebuie configurată ca Master. Vom folosi pentru semnalul SCLK o frecvență de 16 ori mai mică decât frecvența de ceas a microprocesorului (fosc/16) deoarece, după cum am spus și în secțiunea 3, trebuie să ne adaptăm la frecvența suportată de dispozitivul Slave (cititorul SD, în cazul nostru).
    • Va trebui să adăugați un 0 la finalul numelui regiștrilor descriși în secțiunea 4.
    • Ca rezultat al modului de comunicare descris în secțiunea 3, registrul SPDR0 va conține, la începutul unei transmisii, datele pe care dorim să le transmitem către Slave, iar la finalul transmisiei unui octet, va conține datele primite de la Slave. Acest lucru ne va permite să implementăm comunicarea într-o singură funcție, SPI_exchange(), în loc să avem 2 funcții (e.g. SPI_read() și SPI_write()).
  2. SD Card Control. În continuare, vom implementa funcțiile de bază pentru comunicarea cu cardul SD (practic, o abstractizare peste funcțiile SPI de la exercițiul anterior). Sarcina voastră este să implementați funcțiile SD_init(), SD_receive() și SD_transmit() din fișierul sd.c. Urmăriți comentariile marcate cu TODO2 și țineți cont de următoarele hint-uri:
    • PA2 este pin-ul ce corespunde semnalului SS pentru cardul SD. Acesta trebuie setat ca output, însă nu trebuie setat pe starea HIGH sau LOW (acest lucru va fi gestionat de scheletul de cod).
    • Comunicarea, atât la recepție, cât și la transmisie, va fi asigurată de funcția SPI_exchange(). În cazul recepției, nu ne va interesa să transmitem date utile, ci doar să citim date de pe cardul SD. Cu toate acestea, din cauza faptului că microprocesorul nostru acționează ca dispozitiv Master, el este cel care trebuie să inițieze comunicația și care trebuie să transmită mai întâi un octet pentru a citi ulterior un octet de la cardul SD. Pentru asta, va trebui să trimitem în cazul funcției SD_receive() un octet dummy, cu valoarea 0xff.
    • CHECKPOINT: După implementarea acestui task ar trebui ca, la câteva secunde după upload-ul programului, textul afișat pe LCD să se schimbe din Mounting… în Mounted!. Dacă nu se întâmplă asta, încercați să reporniți programul folosind butonul de RESET de pe plăcuță (dacă nu merge nici așa, faceți debugging împreună cu asistentul :) ).
  3. First File Display. Acum că am reușit să montăm filesytem-ul de pe cardul SD, ne dorim să afișăm numele primului fișier audio din directorul /music. Urmăriți comentariile marcate cu TODO3 în fișerul lab5.c și implementați astfel funcția init_directory().
    • Folosiți funcția pf_opendir() pentru a deschide directorul și funcția pf_readdir() pentru a naviga prin acesta.
    • La parcurgerea directorului, e posibil să găsiți și niște fișiere “poluate”, al căror nume începe cu underscore. Filtrați aceste fișiere. Practic, va trebui să parcurgeți fișerele și să vă opriți la primul fișier al cărui nume nu începe cu underscore.
    • Pentru afișarea pe ecranul LCD-ului, puteți să vă inspirați din apelurile LCD_clear_top_line() și LDC_printAt() utilizate în main pentru afișarea mesajului Mounted!
  4. File Cycling. La apăsarea butonului PB2, afișați pe ecranul LCD-ului următorul fișier din directorul /music. Când ajungeți la ultimul fișier, săriți din noul la primul fișier. Urmăriți comentariile marcate cu TODO4 în fișierul lab5.c. Din nou, va trebui să ignorați fișierele al căror nume începe cu underscore.
    • Funcția pf_readdir() va completa parametrul file cu un fișier cu numele ”” (string-ul vid) atunci când s-a ajuns la finalul directorului.
    • Cum apăsarea unui buton nu durează un timp infinit de mic, dacă folosim un simplu if pentru schimbarea fișerului afișat în acest moment, e posibil sa ciclăm de mai multe ori prin toate fișierele la o singură apăsare de buton. Puteți rezolva această problemă în mai multe moduri, cel mai facil fiind introducerea unui delay de 200-300ms la finalul if-ului care verifică starea butonului, astfel încât utilizatorul să aibă timp să îl elibereze.
  5. Play Music. Party time! La apăsarea butonului PD6, folosiți funcția play() din fișierul lab5.c pentru a reda pe speaker fișierul afișat la momentul curent pe LCD. Urmăriți comentariile marcate cu TODO5 în scheletul de laborator.
    • Funcția play() este blocantă. Pentru a opri redarea fișierului curent, apăsați simultan butoanele PD6 și PB2. Acest feature este deja implementat în scheletul de cod și poate fi folosit direct.

4. Linkuri utile

5. Responsabili laborator

pm/lab/lab5-2023-2024.txt · Last modified: 2024/04/10 11:02 by irina.bradu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0