Laboratorul 5: SPI (Serial Peripheral Interface)

Acest laborator acoperă noțiunea de SPI. Pentru aprofundarea acestui topic, consultați Datasheet ATmega328P și Serial Peripheral Interface.

1. SPI (Serial Peripheral Interface)

SPI este un standard sincron dezvoltat de Motorola care 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”. 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 pe magistrala care este adresată în prezent.

2. 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ă Masterul comunică cu el.

3. Conectarea mai multor dispozitive Slave cu ajutorul topologiei SPI Daisy Chain

În topologia Daisy Chain, datele sunt transferate de la Master la primul Slave, de la primul Slave la al doilea Slave, până când datele ajung la ultimul Slave care le trimite înapoi la Master. Datele primului Slave ajung la Master ultimele, în timp ce datele ultimului Slave ajung la Master primele.

4. Transmiterea datelor cu SPI

Pentru a începe comunicarea cu dispozitivul Master, ceasul Master-ului trebuie 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. Î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.

De obicei, comunicarea SPI implică două registre de șiftare (unul în Master și unul în Slave, conectate circular).

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 șiftare, Master-ul și Slave-ul au schimbat integral valorile din cele două registre de șiftare. Dacă mai există date de transmis, procesul începe din nou. Când nu 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). 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 de timp.

5. Configurări de SPI

Clock Polarity (CPOL) configurează starea IDLE a ceasului. După cum putem vedea, pentru CPOL = 0, când ceasul este inactiv valoarea semnalului SCLK este pe LOW, iar pentru CPOL = 1 ceasul inactiv are SCLK pe HIGH.

Clock Phase (CPHA) configurează momentul când datele sunt generate pe ieșire și când datele sunt citite la intrare. Pe una dintre fronturile ceasului (de întâmpinare sau de sfârșit) datele sunt generate, iar pe cealaltă datele sunt citite.

Frontul de pornire al ceasului este prima schimbare de ceas. Ex: Pentru un ceas care merge de la LOW la HIGH și înapoi la LOW, frontul de întâmpinare este frontul crescător(tranziția de la LOW la HIGH).

Pentru CPHA = 0 datele sunt generate înainte de frontul de întâmpinare (primul front al ceasului) și sunt citite pe pe frontul întâmpinare (frontul crescător pentru CPOL = 0 și descrescător pentru CPOL = 1).

Pentru CPHA = 1 datele sunt generate pe frontul de întâmpinare (primul front al ceasului) și sunt citite pe frontul de sfârșit (frontul descrescător pentru CPOL = 0 și crescător pentru CPOL = 1).

CPOL și CPHA trebuie setate în conformitate cu configurația dispozitivului SPI cu care comunicăm. De exemplu, dacă UC-ul nostru comunică cu un ADC care utilizează CPOL = 1 și CPHA = 1, este obligatoriu dispozitivul nostru să aibă aceleași configurări de SPI.

6. SPI în Atmega328p

SPI-ul inclus în microcontrolerul atmega328p poate funcționa atât ca Master, cât și ca Slave.

SPI Control Register (SPCR)

  • Bit 6 - SPE (SPI Enable)
    • 0 - SPI dezactivat
    • 1 - SPI activat
  • Bit 5 - DORD (Data Order)
    • 0 - MSB (din data) trimis primul
    • 1 - LSB (din data) trimis primul
  • Bit 4 - MSTR (Master/Slave Select)
    • 0 - Slave mode
    • 1 - Master mode
  • Bit 3 - CPOL (Clock Polarity)
    • 0 - SCK LOW pe idle (când nu comunică)
    • 1 - SCK HIGH pe idle (când nu comunică)
  • Bit 2 - CPHA (Clock Phase)
    • 0 - datele sunt citite pe frontul de întâmpinare
    • 1 - datele sunt citite pe frontul de sfârșit
  • Bit 1:0 - SPR1:SPR0 (SPI Clock Rate)
    • în modul Master: controlează frecvența SCK-ului.
    • În modul Slave: nu au efect.

SPI Status Register (SPSR)

  • Bit 7 – SPIF (SPI Interrupt Flag)
    • este automat setat pe 1 când transmisia s-a terminat.
  • Bit 0 - SPI2X (Double SPI Speed Bit)
    • în modul Master: când acest bit este setat la 1, viteza SPI (Frecvența SCK) va fi dublată. Aceasta înseamnă că perioada minimă SCK va fi de două perioade de ceas de CPU.
    • în modul Slave:SPI este doar garantat că o să funcționeze la fosc / 4 sau mai mic.

SPI Data Register (SPDR)

Un registru de citire / scriere utilizat pentru transferul de date între Register File și SPI Shift Register. Scrierea în registru inițiază transmiterea datelor. Citirea registrului determină citirea bufferului Shift Register Receive.

Exemplu

Inițializare SPI ca Master.

/* set master mode */
SPCR0 |= (1 << MSTR0);

/* set prescaler 16 */
SPCR0 |=  (1 << SPR00);

/* enable SPI  */
SPCR0 |= (1 << SPE0);

Transmiterea unui byte (pe Master).

/* Start transmission */
SPDR0 = data;

/* Wait for transmission complete */
while(!(SPSR0 & (1 << SPIF0)));

Recepția unui byte (pe Slave).

/* Wait for reception complete */
while(!(SPSR0 & (1 << SPIF0)));
	
/* Return Data Register */
return SPDR0;

7. Adaptor MicroSD

Un card MicroSD este un exemplu de memorie nevolatilă. Pentru ca placa Arduino UNO să poată comunica pe SPI cu cardul, avem nevoie de un adaptor. De menționat că adaptorul din laborator nu suportă carduri de o dimensiune mai mare de 32GB.

Adaptor MicroSD în Arduino IDE

Mediul de dezvoltare Arduino IDE ne oferă bibliotecile “SPI.h” și “SD.h” pentru lucrul cu cardurile microSD. De menționat faptul că bibliotecile sunt compatibile doar cu sistemele de fișiere FAT16 și FAT32. Aceste biblioteci ne oferă posibilitatea să lucrăm cu fișiere și directoare, să le creăm, modificăm, ștergem etc. În link-ul de mai jos puteți găsi informații despre bibliotecile respective.

/* Exemplu SPI MASTER: transfer un byte către Slave */
#include "SPI.h"
 
void setup(void)
{ 
  byte masterSend = 1;
  byte masterRecv;
 
  /* Inițializarea pinului de Slave Select */
  pinMode(SS, OUTPUT);
  digitalWrite(SS, HIGH);
 
  /* Inițializarea SPI-ului */
  SPI.begin();
 
  /* Selectarea Slave-ului */
  digitalWrite(SS, LOW);
 
  /* Trimiterea byte-ului masterSend către Slave-ul selectat.
   * Salvarea byte-ului primit de la Slave-ul selectat în masterRecv. */
  masterRecv = SPI.transfer(masterSend); 
 
  /* Deselectarea Slave-ului */
  digitalWrite(SS, HIGH);
}
/* Exemplu MASTER: Creare și scriere a unui fișier pe Card MicroSD */
#include "SD.h"
 
void setup(void)
{
  File myFile;
 
  /* Inițializare comunicare cu Cardul MicroSD. Semnalul SS este pe pinul 4 */
  if (SD.begin(4))
  {
    /* Creăm fișierul dacă acesta nu există */
    if (!SD.exists("hello.txt"))
    {
      myFile = SD.open("hello.txt", FILE_WRITE);
      if (myFile)
      {
        myFile.print("Hello world");
        myFile.close();
      }
      else 
      {
        /* Fișierul nu a putut fi creat. Trebuie investigată problema. */
      }
    }
  }
  else
  {
    /* Inițializarea comunicării a eșuat. Trebuie investigată problema. */
  }
}

8. Exerciții

Tinkercad

Tinkercad nu oferă suport pentru SPI. Din acest motiv, pentru înțelegerea mai bună a funcționării SPI-ului, în cadrul exercițiilor vom implementa o comunicare SPI cu ajutorul pinilor GPIO. Abordarea în care în loc să folosim componenta propriu-zisă (ex: SPI) al microcontrolerului, simulăm componenta respectivă în software se numește Bit Banging.

  • Scopul exercițiului:
    • Dorim să asigurăm o comunicare pe bază de SPI dintre 2 plăci de tip Arduino Uno.
    • Placa din stânga va fi Master-ul, iar placa din dreapta va fi Slave-ul.
    • Master-ul va trimite caractere de la “A” la “Z”, iar Slave-ul caractere de la “0” la “9”.
    • Exercițiile trebuie rezolvate obligatoriu în ordine.
    • Un exemplu de transmitere pe SPI a unui byte este ilustrat în imaginea de mai jos.
    • Ghidați-vă după imaginea de mai jos pentru a înțelege ce semnale trebuie activate.
  • Setup:
    • Copiați în workspace-ul vostru din Tinkercad proiectul cu scheletul de laborator de la acest link:
    • Apăsați “Start Simulation” și asigurați-vă că nu aveți erori de compilare.
    • Asigurați-vă că :
      • consola Master afișează: “Hello, I am SPI Master”
      • consola Slave afișează: “Hello, I am SPI Slave”
  1. Configurați pinii pentru SPI Master și SPI Slave. Efectuați atât pe Master cât și pe Slave:
    1. Setați #define TASK 1 (pe linia 3 din cod)
    2. Completați toate “TODO1” (din setup())
    3. SCK, MOSI și MISO trebuie să fie LOW în idle
    4. CS / SS trebuie să fie HIGH în idle
    5. Asigurați-vă că:
      • ambele console afișează: “SPI <device> pins are initialized”
      • semnalul CS/SS este HIGH (LED-ul roșu este aprins) - niciun Slave nu este selectat
  2. Configurați selecția Slave-ului cu ajutorul semnalului CS/SS. Efectuați atât pe Master cât și pe Slave:
    1. Setați #define TASK 2
    2. Completați toate “TODO2” (din loop())
    3. Master-ul trebuie să selecteze Slave-ul prin setarea semnalului CS/SS pe LOW
    4. Asigurați-vă că:
      • ambele console afișează un număr egal de selectări/deselectări
      • semnalul CS/SS este LOW când Slave-ul este selectat (LED-ul roșu se stinge)
  3. Configurați generarea semnalului de ceas SCK. Efectuați atât pe Master cât și pe Slave:
    1. Setați #define TASK 3
    2. Completați toate “TODO3” (din resetClock(), getClockTransition())
    3. Cât timp niciun Slave nu este selectat, ceasul nu este folosit și trebuie să fie LOW
    4. Când un Slave este selectat, ceasul trebuie să facă Toggle (16 ori pentru 8 biți)
    5. În următorul exercițiu, cat timp ceasul face toggle se vor trimite biții de date
    6. Asigurați-vă că:
      • Ambele console afișează fronturile ceasului: “CLK signal <action> HIGH”
      • semnalul SCK face toggle când Slave-ul este selectat (LED-ul verde face toggle)
  4. Configurați transmiterea datelor pe MOSI și MISO. Efectuați atât pe Master cât și pe Slave:
    1. Setați #define TASK 4
    2. Completați toate “TODO4” (din sendRecvByte())
    3. Pe front crescător trebuie citite datele celuilalt dispozitiv (Master citește pe MISO, Slave citește pe MOSI)
    4. Pe front descrescător trebuie pregătite datele (Master scrie pe MOSI, Slave scrie pe MISO)
    5. Asigurați-vă că:
      • Ambele console afișează aceleași mesaje trimise și recepționate (Master trimite “A” - “Z”, Slave trimite “0” - “9”).
      • semnalele MISO și MOSI sunt HIGH când se trimit biți de 1 (LED-urile albastru și maro se aprind)
  5. Ajustați configurările la Master (pentru a crește throughput-ul):
    1. #define SPI_SCK_T 100
    2. #define START_DELAY 500
    3. #define SEND_DELAY 500

Arduino fizic

În acest laborator vom folosi un card MicroSD (memorie nevolatilă) pentru a stoca de câte ori placa Arduino UNO a fost resetată (butonul roșu de reset sau deconectare/conectare la sursa de alimentare).

  • Scopul exercițiului:
    • La fiecare boot, citim fișierul contor.txt de pe card.
    • Incrementăm valoarea citită.
    • Suprascriem fișierul cu valoarea actualizată.
  • Setup:
    • Introduceți cardul în adaptor
    • Realizați conexiunile fizice dintre adaptor și Arduino UNO
      • Adaptor GND - GND Arduino Uno
      • Adaptor VCC - VCC Arduino Uno
      • Adaptor MISO - 12 Arduino Uno
      • Adaptor MOSI - 11 Arduino Uno
      • Adaptor SCK - 13 Arduino Uno
      • Adaptor CS - 4 Arduino Uno
    • Rulați exemplul File → Examples → SD → CardInfo
    • Verificați că nu aveți erori
  • Implementarea contorului pentru reset
    • Creați un proiect nou în Arduino IDE
    • Suprascrieți codul proiectului cu cel de mai jos
    • Analizați codul
    • Uploadați proiectul pe placă
  • Testare:
    • Deschideți Tools → Serial Monitor
    • Resetați placa apăsând pe butonul roșu (reset)
    • Resetați placa prin deconectarea sursei de alimentare
    • Observați că valoarea de reset-uri citită din fișier se incrementează

9. Resurse

pinout Arduino UNO

10. Linkuri Utile

pm/lab/lab5-2021.txt · Last modified: 2021/04/17 16:05 by jan.vaduva
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