Difference between revisions of "AVR Embedded Tutorial - SPI/de"
Timm Thaler (talk | contribs) (Eine SPI-Unit für Embedded AVR / Arduino Controller) |
Timm Thaler (talk | contribs) |
||
Line 15: | Line 15: | ||
Zuerst kommt der | Zuerst kommt der | ||
− | ==Header der Unit== | + | ===Header der Unit=== |
welcher die Pin- und Portkonfiguration enthält: | welcher die Pin- und Portkonfiguration enthält: | ||
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
Line 48: | Line 48: | ||
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. | 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== | + | ===Initialisierung=== |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
Line 77: | Line 77: | ||
Die Register entsprechend der Unit ATmega328P aus den Embedded AVR Sources. Für andere Controller unbedingt die Register anhand des Datenblattes überprüfen. | 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== | + | ===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. | 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. | ||
Line 102: | Line 102: | ||
Für dout, din darf auch das gleiche Array angegeben werden, dann werden die ursprünglichen Daten durch die empfangenen Daten ersetzt. | Für dout, din darf auch das gleiche Array angegeben werden, dann werden die ursprünglichen Daten durch die empfangenen Daten ersetzt. | ||
− | ==Daten nur Schreiben== | + | ===Daten nur Schreiben=== |
Wenn nur Daten geschrieben werden soll, aber keine Antwort vom Device erwartet wird, kann man die Routine vereinfachen. | Wenn nur Daten geschrieben werden soll, aber keine Antwort vom Device erwartet wird, kann man die Routine vereinfachen. | ||
Line 122: | Line 122: | ||
Hier gilt das Gleiche wie für die Transfer-Routine. | Hier gilt das Gleiche wie für die Transfer-Routine. | ||
− | == Einzelnes Byte senden und empfangen== | + | === Einzelnes Byte senden und empfangen=== |
Mitunter muß nur ein einzelnes Byte übertragen werden, zum Beispiel zum Abfragen eines Status. | Mitunter muß nur ein einzelnes Byte übertragen werden, zum Beispiel zum Abfragen eines Status. | ||
Line 143: | Line 143: | ||
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> | ||
end. | end. | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ==Anmendung 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. | ||
+ | |||
+ | ===Konfig schreiben=== | ||
+ | |||
+ | <syntaxhighlight lang="pascal"> | ||
+ | // 1 Byte in Konfig senden | ||
+ | |||
+ | procedure nrf_set_config(reg, data : uint8); | ||
+ | begin | ||
+ | PORTnrf := PORTnrf and not(Pnrf_csn); | ||
+ | spi_fast_shift(cW_REGISTER or (cREGISTER_MASK and reg)); | ||
+ | spi_fast_shift(data); | ||
+ | PORTnrf := PORTnrf or Pnrf_csn; | ||
+ | end; | ||
</syntaxhighlight> | </syntaxhighlight> |
Revision as of 21:27, 21 October 2017
Nutzung der Hardware-SPI-Schnittstelle bei einem ATmega328 / Arduino
Voraussetzung ist ein eingerichteter Crosscompiler für AVR Controller wie hier beschrieben: http://wiki.freepascal.org/Arduino_und_Lazarus
Zu den Grundlagen von SPI, Master-Slave, Verdrahtung siehe Wikipedia: https://de.wikipedia.org/wiki/Serial_Peripheral_Interface
Das ist eine ganz einfache SPI-Unit, bei der die Einstellungen für
- Datenformat
- Mode
- ChipSelect
- Speed
von Hand vorgenommen werden müssen. Zur Funktion der einzelnen Flags siehe Datenblatt zum ATmega328.
SPI-Master
Zuerst kommt der
Header der Unit
welcher die Pin- und Portkonfiguration enthält:
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
// 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
- SPE = 1: SPI enable
- SPIE = 0: kein Interrupt
- DORD = 0: MSB first, 1: LSB first
- MSTR = 1: als Master, kann aber durch Pin SS von Außen überschrieben werden, 0: als Slave
- CPOL = 0: SCK Polarität normal, 1: SCK invertiert
- CPHA = 0: SCK Phase normal, 1: SCK verschoben
- SPR = 01: fcpu / 16
- SPI2X = 1: fcpu * 2, ergibt 1MHz Clock bei 8Mhz Controllertakt, siehe Datenblatt für andere Clockraten.
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.
// 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.
Daten nur Schreiben
Wenn nur Daten geschrieben werden soll, aber keine Antwort vom Device erwartet wird, kann man die Routine vereinfachen.
// 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.
// 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.
end.
Anmendung 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.
Konfig schreiben
// 1 Byte in Konfig senden
procedure nrf_set_config(reg, data : uint8);
begin
PORTnrf := PORTnrf and not(Pnrf_csn);
spi_fast_shift(cW_REGISTER or (cREGISTER_MASK and reg));
spi_fast_shift(data);
PORTnrf := PORTnrf or Pnrf_csn;
end;