Wiki-KopieMirror

Dies ist ein Mirror des Wikis von pascal.chat. Bearbeiten, Verlauf und Anmeldung finden dort statt.

AVR Embedded Tutorial - SPI/de

Deutsch · English

Zur Übersichtseite AVR Embedded Tutorial.

Nutzung der Hardware-SPI-Schnittstelle bei einem ATmega328 / Arduino

Voraussetzung ist ein eingerichteter Crosscompiler für AVR Controller wie hier beschrieben:

Zu den Grundlagen von SPI, Master-Slave, Verdrahtung siehe Wikipedia:

Das ist eine ganz einfache SPI-Unit, bei der die Einstellungen für

von Hand vorgenommen werden müssen. Zur Funktion der einzelnen Register und Flags siehe Datenblatt zum ATmega328.

SPI-Master

Zuerst kommt der

Header der Unit

welcher die Pin- und Portkonfiguration enthält:

pascal
unit gh_spi;

{$mode objfpc}{$H+}
{$goto on}

interface

uses
  ; 

procedure spi_init();
procedure spi_transfer_sync(var dout : array of uint8; var din : array of uint8; len : uint8);
procedure spi_transmit_sync(var dout : array of uint8; len : uint8);
function spi_fast_shift(data : uint8) : uint8;

const
  Psck      = 1 shl 5;  // PB5
  Pmiso     = 1 shl 4;  // PB4
  Pmosi     = 1 shl 3;  // PB3
  Pss       = 1 shl 2;  // PB2
  Pcs       = 1 shl 1;  // PB1

var
  DDRspi   : byte absolute DDRB;  // SPI an Port B
  PORTspi  : byte absolute PORTB;
  PINspi   : byte absolute PINB;

implementation

Wenn man mehrere SPI-Devices ansprechen will, kann man sich dafür mehrere CS (ChipSelect) Pins definieren. Das SS Pin muß auf Ausgang stehen. Steht es auf Eingang, kann darüber das SPI-Interface in den Slave-Mode geschaltet werden - und nix geht mehr. Und wenn es einmal auf Ausgang steht, kann man es auch gleich noch für CE (ChipEnable) oder CS (ChipSelect) verwenden.

Initialisierung

pascal
// SPI initialisieren

procedure spi_init();
begin
    DDRspi := DDRspi and not(Pmiso);  // MISO Eingang
    DDRspi := DDRspi or (Pmosi or Psck or Pss);  // MOSI, SCK, SS Ausgang
    // SS muß Ausgang sein, sonst Umschalten auf Slave von außen möglich

    SPCR := ((1 shl SPE) or (0 shl SPIE) or (0 shl DORD) or (1 shl MSTR) or (0 shl CPOL) or (0 shl CPHA) or (%01 shl SPR));
    // SPI enable, MSB first, Master select, SCK 1MHz = XTAL / 16 x 2
    SPSR := (1 shl SPI2X);  // SCK x 2 auf 1 MHZ
end;

Eingestellt werden hier

Die Register entsprechend der Unit ATmega328P aus den Embedded AVR Sources. Für andere Controller unbedingt die Register anhand des Datenblattes überprüfen.

Daten übertragen

Um Daten von einem Device zu lesen, müssen gleichzeitig auch Daten geschrieben werden. Das können aber auch Dummydaten sein. Die Anzahl geschriebener und gelesener Bytes ist gleich.

pascal
// SPI Daten senden, empfangen

procedure spi_transfer_sync(var dout : array of uint8; var din : array of uint8; len : uint8);
var
  cnt : uint8;
begin
    for cnt := 0 to len - 1 do begin
        SPDR := dout[cnt];  // zu sendendes Byte ausgeben
        while (SPSR and (1 shl SPIF)) = 0 do;  // warten bis fertig
        din[cnt] := SPDR;  // empfangenes Byte einlesen
    end;
end;

Das Schöne bei Pascal ist, dass man mit richtigen Arrays arbeiten kann und nicht wie bei C mit Pointern rummachen muß. Wichtig ist hier, dass die Arrays per var übergeben werden. Dann arbeitet Pascal mit dem ursprünglichen Array, andernfalls wird eine Kopie erstellt, was unnötige Zeit und unnötigen Speicherbedarf erfordert.

Die Routine wartet, bis alle Bytes aus dout rausgeschrieben und gleichzeitig die empfangenen Daten in din eingelesen sind, was bei 1Mhz etwa 10µsec pro Byte dauert. Da das real nur wenige Systemtakte pro Byte sind lohnt sich hier ein Interrupt nicht. Die Routine kann durch andere Interrupts (Timer, Uart) unterbrochen werden, was das Ausgeben des nächsten Bytes verzögert, aber unproblematisch ist.

Für dout, din darf auch das gleiche Array angegeben werden, dann werden die ursprünglichen Daten durch die empfangenen Daten ersetzt.

Achtung! Hier und für die folgenden Routinen gilt: Wird die Routine aufgerufen, ohne dass SPE im SPCR gesetzt wurde (siehe Init), bleibt der Controller in der Warteschleife hängen, da das Datenbyte nicht gesendet und folglich das SPIF-Flag nicht gesetzt wird.

Daten nur Schreiben

Wenn nur Daten geschrieben werden soll, aber keine Antwort vom Device erwartet wird, kann man die Routine vereinfachen.

pascal
// SPI Daten senden ohne Empfang

procedure spi_transmit_sync(var dout : array of uint8; len : uint8);
var
  cnt : uint8;
begin
    for cnt := 0 to len - 1 do begin
        SPDR := dout[cnt];  // zu sendendes Byte ausgeben
        while (SPSR and (1 shl SPIF)) = 0 do;  // warten bis fertig
    end;
end;

Hier gilt das Gleiche wie für die Transfer-Routine.

Einzelnes Byte senden und empfangen

Mitunter muß nur ein einzelnes Byte übertragen werden, zum Beispiel zum Abfragen eines Status.

pascal
// SPI einzelnes Byte senden, empfangen

function spi_fast_shift(data : uint8) : uint8;
begin
    SPDR := data;       // zu sendendes Byte ausgeben
    while (SPSR and (1 shl SPIF)) = 0 do;  // warten bis fertig
    spi_fast_shift := SPDR;  // empfangenes Byte einlesen
end;

Raus, warten, rein. Eigentlich ganz einfach.

Und fertig ist die Unit.

pascal
end.

Anwendung als Master

Die beispielhafte Anwendung der Unit als Master für ein NRF-Modul.

Am Programmanfang muss natürlich spi_init aufgerufen werden. Außerdem sind die Register des NRF-Moduls als Konstanten deklariert und ein Sende- und ein Empfangsbuffer als array of uint8 eingerichtet.

Um das CS müssen wir uns selbst kümmern. Beim NRF-Modul ist das CS low-aktiv, der Ruhepegel ist also high und wird zur Aktivierung auf low gezogen.

Konfig schreiben

pascal
procedure nrf_set_config(reg, data : uint8);
begin
    PORTspi := PORTspi and not(Pspi_cs);  // CS setzen, ist beim Modul low-aktiv
    spi_fast_shift(cW_REGISTER or (cREGISTER_MASK and reg));  // Controlbyte senden
    spi_fast_shift(data);  // Datenbyte senden
    PORTspi := PORTspi or Pspi_cs;  // CS löschen, ist beim Modul low-aktiv
end;

Es wird ein Byte zur Adressierung des Registers und ein Byte mit dem Wert gesendet, auf den das Register gesetzt werden soll.

Konfig auslesen

pascal
function nrf_get_config(reg : uint8) : uint8;
begin
    PORTspi := PORTspi and not(Pspi_cs);  // CS setzen, ist beim Modul low-aktiv
    spi_fast_shift(cR_REGISTER or (cREGISTER_MASK and reg));  // Controlbyte senden
    nrf_get_config := spi_fast_shift(0);  // Dummy senden und Datenbyte lesen
    PORTspi := PORTspi or Pspi_cs;  // CS löschen, ist beim Modul low-aktiv
end;

Es wird ein Byte zur Adressierung des Registers gesendet, dann wird ein Dummybyte gesendet. Der empfangene Registerinhalt wird übergeben.

Register schreiben

pascal
procedure nrf_write_register(reg : uint8; len : uint8);
begin
    PORTspi := PORTspi and not(Pspi_cs);  // CS setzen, ist beim Modul low-aktiv
    spi_fast_shift(cW_REGISTER or (cREGISTER_MASK and reg));  // Controlbyte senden
    spi_transmit_sync(nrftxbuf, len);  // Daten aus Tx-Buffer senden
    PORTspi := PORTspi or Pspi_cs;  // CS löschen, ist beim Modul low-aktiv
end;

Es wird ein Controlbyte gesendet, gefolgt von den zu schreibenden Daten. Während des gesamten Vorgangs bleibt CS low.

Register auslesen

pascal
procedure nrf_read_register(reg : uint8; len : uint8);
begin
    PORTspi := PORTspi and not(Pspi_cs);  // CS setzen, ist beim Modul low-aktiv
    spi_fast_shift(cR_REGISTER or (cREGISTER_MASK and reg));  // Controlbyte senden
    spi_transfer_sync(nrftxbuf, nrfrxbuf, len);  // Daten in Rx-Buffer lesen, Tx-Buffer ist Dummy
    PORTspi := PORTspi or Pspi_cs;  // CS löschen, ist beim Modul low-aktiv
end;

Es wird ein Controlbyte gesendet, gefolgt von Dummydaten. Die empfangenen Daten stehen im Rx-Buffer.

Eine umfangreiche Anleitung für die nRF24L01+ Funkmodule folgt demnächst.

Siehe auch


From the Free Pascal wiki, page "AVR Embedded Tutorial - SPI/de", as it stood on 2020-03-06 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Version 3, zuletzt geändert von WikiArchive .