User Tools

Site Tools


Sidebar

Laboratorul 4: SPI, FAT Filesystem, Player Audio

Capitole utile din Datasheet ATmega324

  • Spi- Serial Peripheral Interface – 164
    • Overview – 164
    • Data modes – 169
    • Register description – 171

În laboratoarele precedente am implementat comunicația serială folosind standardul RS-232. În acest laborator ne vom familiariza cu un alt standard serial: SPI.

1. SPI

SPI (Serial Peripheral Interface Bus) este un standard sincron dezvoltat de Motorola ce operează în mod full-duplex (transferul de date are loc în ambele direcții simultan). Dispozitivele comunică folosind o relație de tipul master/multi-slave (nu sunt suportate mai multe dispozitive master) master-ul fiind cel care inițiază cadrele de date. SPI se mai numește și “four wire” serial bus pentru a-l deosebi de celelalte standarde ce folosesc 1, 2 sau 3 legături. Cele patru semnale utilizate sunt următoarele:

  • SCK — Serial Clock (output de la master către slave)
  • MOSI — Master Output, Slave Input (output de la master către slave)
  • MISO — Master Input, Slave Output (output de la slave către master)
  • SS — Slave Select (activ pe 0; output de la master către slave)

Observăm (figure 1) că avem două semnale pe care se transmit date: MOSI și MISO. Al treilea semnal este folosit pentru transmiterea unui semnal de ceas, SPI folosind o comunicație sincronă. Ultimul semnal este folosit pentru activarea dispozitivului slave.

 Diagramă conectare SPIFig. 1: Liniile de date și control SPI

Mai multe dispozitive slave pot fi conectate la un singur master (figure 2), activarea unui anumit slave făcându-se cu semnalul SS asociat slave-ului. Celelalte 3 semnale sunt partajate.

 Diagramă conectare SPI multi-slaveFig. 2: Conectarea prin SPI a mai multor componente

1.1. Mod de funcționare

Pentru a porni comunicația dispozitivul master trebuie să seteze ceasul la o frecvență cel mult egală cu frecvența suportată de slave (de regulă până la câțiva MHz). Master-ul selectează apoi dispozitivul slave dorit, punând 0 pe linia SS spre acesta. În timpul unui ciclu SPI, transmisia este full-duplex:

  • master-ul trimite un bit pe linia MOSI, iar slave-ul îl citește
  • slave-ul trimite un bit pe linia MISO, iar master-ul îl citește

Comunicația pe SPI implică de obicei existența a două registre de shiftare ( Shift Registers), unul în master și unul în slave, conectate circular (figure 3).

 Diagramă comunicare SPIFig. 3: Modul de transmisie a unui octet între slave și master în comunicația SPI

De obicei primul bit shiftat pe liniile de MISO/MOSI este bitul cel mai semnificativ, în timp ce un nou bit este adăugat pe poziția cea mai puțin semnificativă din registru. După ce întregul cuvant a fost trimis, prin shiftare, master-ul și slave-ul au interschimbat valorile din cele două registre de shiftare. Dacă mai există date de transmis, procesul este reluat. Când nu mai există date de transmis, masterul întrerupe generarea ceasului și, în general, pune 1 pe linia de SS asociata slave-ului. Dispozitivele slave care nu au fost selectate prin semnalul SS asociat lor vor ignora semnalele de pe SCK și MOSI și nu vor genera nimic pe MISO (care se va afla într-o stare neutră - stare de impedanță mărită). Master-ul poate selecta doar un singur slave la un moment dat.

Exemplu de transmisie simultană de date între master și slave:

 Exemplu comunicare SPIFig. 4: Transmisia unui byte între master și slave.

1.2. SPI vs I2C

Microcontroller-ul Atmega324 suportă și comunicația serială prin I2C. Acest standard necesită doar două linii de comunicație, unul pentru semnalul de ceas și altul pentru date, deci comunicația este half-duplex, funcționând cu până la 400Kbps. De obicei este preferat SPI-ului atunci cand avem nevoie să interconectăm mulți slaves. Unul dintre motivele pentru care I2C este preferat fiind că selecția slave-ului se face prin adresare, nu printr-un semnal adițional de chip-select.

Ambele standarde sunt folosite cu succes pentru comunicația cu periferice lente ce sunt accesate intermitent (EEPROM-urile si ceasurile de timp real sunt exemple de astfel de device-uri). Totuși, SPI se mulează mai bine ca I2C pe aplicațiile care folosesc stream-uri de date (spre deosebire de aplicațiile unde se citesc/scriu diverse locații din slave device). Un exemplu de aplicație ce folosește stream-uri este comunicația dintre micropocesoare sau DSP-uri (digital signal processors). De asemenea SPI poate atinge rate de transfer semnificativ mai mari decat I2C, interfețele SPI putând funcționa la zeci de MHz și este eficient mai ales în aplicații ce îi folosesc capacitatea de a realiza conexiuni full duplex, ca de exemplu comunicarea dintre un “codec” (coder-decoder) și un DSP, ce presupune trimiterea de sample-uri în ambele direcții.

Datorită faptului că nu exista suport built-in pentru adresarea device-urilor, SPI necesită mai mult efort și mai multe resurse hardware decat I2C când avem mai mulți slave. Pe de altă parte SPI este mai simplu și mai eficient în aplicații point-to-point (single master, single slave) din aceleași motive: lipsa adresării device-urilor presupune mai puțin overhead.

Sumarizând, putem enumera următoarele avantaje ale SPI:

  • Comunicație full-duplex
  • Throughput semnificativ mai mare
  • Flexibilitate pentru biții transferați: se pot trimite mesaje de orice lungime
  • Se interfațează usor
  • Usurință în folosire
  • Mai eficient în comunicarea punct-la-punct

Dintre dezavantaje amintim:

  • Folosește mai multe linii de comunicație
  • Nu are suport pentru adresarea device-urilor; este necesar câte un semnal de Slave Select pentru fiecare slave
  • Nu există flow control și slave acknowledgement (master poate “vorbi in gol” fară să știe).
  • Suportă numai un singur master
  • Funcționează pe distanțe mai mici decât RS-232

1.3. SPI - 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 deja ne-am obișnuit, pentru configurarea și utilizarea interfeței lucrăm cu registre: de control, de stare și de date.

 Schema bloc a perifericului SPI din ATmega324Fig. 5: Schema bloc a interfaței SPI din interiorul controller-ului ATmega324

Descrierea completă a registrelor o găsiți în datasheet în capitolul SPI - Serial Peripheral Interface.

SPI Control Register (SPCR)

  • SPE - SPI Enable - când este 1 interfața SPI este pornită
  • 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, și în modul slave când este 0
  • CPOL, CPHA, SPR1, SPR0 - setările pentru linia SCLK

 Registrul SPCRFig. 6: Registrul SPCR

SPI Status Register (SPSR)

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

 Registrul SPSRFig. 7: Registrul SPSR

SPI Data Register (SPDR)

Acesta este registrul din care citim datele pe care le-am primit sau în care scriem datele pe care vrem să le transmitem. Scrierile în acest registru inițiază o nouă transmisie iar citirile se fac din registrul de shiftare.

 Registrul SPDRFig. 8: Registrul SPDR

2. FAT Filesystem

Interfața SPI este folosită ca standard de comunicație în toate cardurile SD și MMC. Din această cauză este foarte ușor să interfațăm un card de memorie la microcontroller-ul nostru și să câștigăm câțiva gigabytes de memorie pentru datele pe care vrem să le salvăm sau să le citim în aplicația noastră. Conectarea la microcontroller se face ca în figure 9:

 Schemă conectare card SD cu ATmega324Fig. 9: Interfațarea cardului SD la interfața SPI de la ATmega324.

Pentru a ușura accesul la datele de pe card, este foarte util să nu scriem direct în spațiul de memorie ci să folosim un modul filesystem care să permită microcontroller-ului să scrie și să editeze fișiere formatate FAT32. Există mai multe implementări și biblioteci disponibile pe internet dar am ales să folosim Petit FAT Filesystem din cauza dimensiunii mici (2-4KB) și a faptului că folosește un minim de memorie RAM (46 octeți plus stivă).

API-ul pentru filesystem conține 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

3. WAV Player

Având atât spațiu la dispoziție, putem sa implementăm un player audio foarte simplu care să citească fișiere WAV de pe card și să le redea prin PWM pe difuzorul de pe placă. Folosim fișiere de tip WAV pentru că acestea conțin stream-ul audio necomprimat (decodificarea unui fișier MP3 este peste putințele unui AVR și necesită un circuit decoder extern gen STA013 sau VS1011).

Teoria care stă în spatele redării fișierului este simplă: pentru a înregistra o melodie se eșantionează cu o anumită frecvență semnalul analogic de la intrare (microfon). De frecvența de eșantionare depinde calitatea semnalului redat la playback: cu cât eșantionăm mai repede, cu atât stocăm mai multă informație și, conform teoremei lui Nyquist, putem reproduce o bandă de frecvențe mai mare. Banda de frecvență pentru o înregistrare audio telefonică (sau radio AM) este de 0-4000Hz și o putem obține dacă eșantionăm semnalul de intrare la 8000Hz, sau de 8000 de ori pe secundă. Dacă vrem sa mărim calitatea înregistrării astfel încât să reproducă mai fidel întregul spectru audibil (pentru om este aproximativ 20Hz-20000Hz) va trebui să eșantionăm cu o viteza de 40000Hz. Aceste rate de eșantionare sunt standardizate în industria muzicala și au următoarele valori: 8000Hz, 11025Hz, 16000Hz, 22050Hz, 32000Hz, 44100Hz, 48000Hz, 88200Hz, 96000Hz, 176400Hz, 192000Hz.

Calitatea înregistrării este dată și de numărul de biți pe care este stocat fiecare eșantion în parte. Formatul WAV poate stoca date pe 8, 16, 24 sau 32 de biți.

La redare datele conținute în fișierul WAV trebuie să fie redate cu aceeași rată cu care au fost înregistrate, pentru a produce un playback inteligibil. Să presupunem că avem un fișier WAV care a fost înregistrat cu o rata de 44100Hz și 8 biți per eșantion, MONO. Pentru a-l reda corect trebuie să avem un timer care să genereze o întrerupere cu frecvența de 44100Hz. În fiecare întrerupere trebuie să citim un sample din fișier (un octet) și să-l punem pe ieșirea unui al doilea timer care a fost configurat în modul PWM cu rezoluția de 8 biti.

4. Exerciții

  1. (3p) Listați conținutul directorului music de pe cardul SD pe prima linie la LCD-ului, la apăsarea simultană a butoanelor PB2 și BTN1 (pinul PA5).
    • Daca microcontroller-ul rămâne în starea mounting… mai mult de 3-4 secunde, încercați sa resetați placa.
    • Cand butonul BTN1 este apasat, pinul PA5 are nivel logic HIGH.
    • Folosiți funcția pf_opendir pentru a deschide un director pentru iterație.
    • Folosiți funcția pf_readdir pentru a itera prin conținutul directorului.
    • :!: Funcția pf_readdir nu returnează un cod de eroare când ajunge la sfârșitul directorului, ci returnează un fișier cu nume vid (stringul ””).
    • Definiția structurii FILINFO se găsește în fișierul pff.h la liniile 96-102.
    • Prima linie a LCD-ului are adresa 0x00.
    • Folosiți un delay de 1000ms după fiecare iterație.
  2. (1p) Redați primul fișier audio (extensia .WAV) din directorul music la apăsarea butonului PD6. Afișați numele fișierului redat pe prima linie a LCD-ului.
    • :!: LCD-ul afișează caracterul ~ ca .
    • Folosiți funcția play, care primește ca parametru calea absolută a fișierului.
    • Funcția play este blocantă. Pentru oprire apăsați simultan PB2 și PD6.
  3. (3p) Implementați redarea tuturor fișierelor audio (extensia .WAV) din directorul music. Butonul PB2 va cicla prin fișierele audio, afișând numele fișierului selectat pe prima linie a LCD-ului. Butonul PD6 va începe redarea fișierului selectat.
    • Pentru simplitate implementați găsirea celui de-al N-lea fișier audio în funcția get_music.
    • La iterarea unui director, fișierele se deosebesc prin faptul că nu au biții AM_DIR și AM_HID setați în câmpul fattrib.
  4. (1p) Până în acest punct funcțiile SD_init, SD_receive și SD_transmit au fost implementate în fișierul sd.o. Va trebui să ștergeți sd.o și să implementați cele 3 funcții în fișierul sd.c. Modificați Makefile-ul în concordanță, prin ștergerea lui sd.o din comanda de compilare și adăugarea lui sd.c în lista de dependențe.
    • Folosiți funcțiile SPI_init și SPI_exchange din spi.h.
    • Cardul SD are semnalul SS conectat la PA2. Nu uitați că acest pin funcționează în mod GPIO, și nu este controlat de SPI.
    • Ca să recepționați de la slave, trebuie să trimiteți o valoare dummy într-un transfer SPI. :!: Singura valoare dummy acceptată de cadrul SD este 0xFF.
  5. (2p) Până în acest punct funcțiile SPI_init și SPI_exchange au fost implementate în fișierul spi.o. Va trebui să ștergeți spi.o și să implementați cele 2 funcții în fișierul spi.c. Modificați Makefile-ul în concordanță, prin ștergerea lui spi.o din comanda de compilare și adăugarea lui spi.c în lista de dependențe.
    • Parametrii necesari pentru o comunicație corectă sunt: frecvență SCK - f_osc/4, mod SPI - 0 (tabelul 18-2, SPI modes), ordine biți - MSB first.
    • În datasheet aveți exemple de cod pentru inițializarea SPI-ului în modul master și pentru transmiterea/recepția unui byte.
    • În funcție de versiunea de compilator, este posibil să fie necesară adăugarea sufixului 0 la fiecare registru și bit de configurare SPI (SPCR vs. SPCR0).
    • :!: Nu uitați să configurați direcția pinilor SCK, MISO și MOSI.
    • :!: Pinul de /SS de pe microcontroller (PB4) trebuie configurat ca ieșire si pus pe HIGH.
  6. (2p + bragging rights) Implementați un ceas vorbitor. La apăsarea butonului BTN1 (pin-ul PA5) redați ora curentă sub forma: The time now is HH hours MM minutes and SS seconds
    • Directorul speech conține fișiere care redau unele numere și denumiri. Revedeți exercițiul 1.
    • Pentru simplitate implementați funcția say_number care redă corect un număr între 0 și 59.
    • Pentru eleganță implementați funcția say_time care redă ora completă primită ca parametru.
    • Studiați secțiunea Ceas pentru a vedea cum puteți afla ora curentă.

5. Resurse

lab/lab4.txt · Last modified: 2019/04/06 16:55 by andrei [dot] voinescu [at] upb [dot] ro