Sign in Join
Wiki copytemporary

AVR Embedded Tutorial - SPI/de: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: AVR Embedded Tutorial - SPI/de

OldNewLine
1+Zur Übersichtseite [[AVR Embedded Tutorial/de|AVR Embedded Tutorial]].
2+
3+# Nutzung der Hardware-SPI-Schnittstelle bei einem ATmega328 / Arduino
4+
5+Voraussetzung ist ein eingerichteter Crosscompiler für AVR Controller wie hier beschrieben:
6+
7+- [[AVR Embedded Tutorial - Entry Lazarus and Arduino/de|Einstieg Lazarus und Arduino]]
8+
9+Zu den Grundlagen von SPI, Master-Slave, Verdrahtung siehe Wikipedia:
10+
11+- <https://de.wikipedia.org/wiki/Serial_Peripheral_Interface>
12+
13+Das ist eine ganz einfache SPI-Unit, bei der die Einstellungen für
14+
15+- Datenformat
16+- Mode
17+- ChipSelect
18+- Speed
19+
20+von Hand vorgenommen werden müssen. Zur Funktion der einzelnen Register und Flags siehe Datenblatt zum ATmega328.
21+
22+## SPI-Master
23+
24+Zuerst kommt der
25+
26+### Header der Unit
27+
28+welcher die Pin- und Portkonfiguration enthält:
29+
30+``` pascal
31+unit gh_spi;
32+
33+{$mode objfpc}{$H+}
34+{$goto on}
35+
36+interface
37+
38+uses
39+ ;
40+
41+procedure spi_init();
42+procedure spi_transfer_sync(var dout : array of uint8; var din : array of uint8; len : uint8);
43+procedure spi_transmit_sync(var dout : array of uint8; len : uint8);
44+function spi_fast_shift(data : uint8) : uint8;
45+
46+const
47+ Psck = 1 shl 5; // PB5
48+ Pmiso = 1 shl 4; // PB4
49+ Pmosi = 1 shl 3; // PB3
50+ Pss = 1 shl 2; // PB2
51+ Pcs = 1 shl 1; // PB1
52+
53+var
54+ DDRspi : byte absolute DDRB; // SPI an Port B
55+ PORTspi : byte absolute PORTB;
56+ PINspi : byte absolute PINB;
57+
58+implementation
59+```
60+
61+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.
62+
63+### Initialisierung
64+
65+``` pascal
66+// SPI initialisieren
67+
68+procedure spi_init();
69+begin
70+ DDRspi := DDRspi and not(Pmiso); // MISO Eingang
71+ DDRspi := DDRspi or (Pmosi or Psck or Pss); // MOSI, SCK, SS Ausgang
72+ // SS muß Ausgang sein, sonst Umschalten auf Slave von außen möglich
73+
74+ 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));
75+ // SPI enable, MSB first, Master select, SCK 1MHz = XTAL / 16 x 2
76+ SPSR := (1 shl SPI2X); // SCK x 2 auf 1 MHZ
77+end;
78+```
79+
80+Eingestellt werden hier
81+
82+- SPE = 1: SPI enable
83+- SPIE = 0: kein Interrupt
84+- DORD = 0: MSB first, 1: LSB first
85+- MSTR = 1: als Master, kann aber durch Pin SS von Außen überschrieben werden, 0: als Slave
86+- CPOL = 0: SCK Polarität normal, 1: SCK invertiert
87+- CPHA = 0: SCK Phase normal, 1: SCK verschoben
88+- SPR = 01: fcpu / 16
89+- SPI2X = 1: fcpu * 2, ergibt 1MHz Clock bei 8Mhz Controllertakt, siehe Datenblatt für andere Clockraten.
90+
91+Die Register entsprechend der Unit ATmega328P aus den Embedded AVR Sources. Für andere Controller unbedingt die Register anhand des Datenblattes überprüfen.
92+
93+### Daten übertragen
94+
95+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.
96+
97+``` pascal
98+// SPI Daten senden, empfangen
99+
100+procedure spi_transfer_sync(var dout : array of uint8; var din : array of uint8; len : uint8);
101+var
102+ cnt : uint8;
103+begin
104+ for cnt := 0 to len - 1 do begin
105+ SPDR := dout[cnt]; // zu sendendes Byte ausgeben
106+ while (SPSR and (1 shl SPIF)) = 0 do; // warten bis fertig
107+ din[cnt] := SPDR; // empfangenes Byte einlesen
108+ end;
109+end;
110+```
111+
112+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.
113+
114+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.
115+
116+Für *dout*, *din* darf auch das gleiche Array angegeben werden, dann werden die ursprünglichen Daten durch die empfangenen Daten ersetzt.
117+
118+**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.
119+
120+### Daten nur Schreiben
121+
122+Wenn nur Daten geschrieben werden soll, aber keine Antwort vom Device erwartet wird, kann man die Routine vereinfachen.
123+
124+``` pascal
125+// SPI Daten senden ohne Empfang
126+
127+procedure spi_transmit_sync(var dout : array of uint8; len : uint8);
128+var
129+ cnt : uint8;
130+begin
131+ for cnt := 0 to len - 1 do begin
132+ SPDR := dout[cnt]; // zu sendendes Byte ausgeben
133+ while (SPSR and (1 shl SPIF)) = 0 do; // warten bis fertig
134+ end;
135+end;
136+```
137+
138+Hier gilt das Gleiche wie für die Transfer-Routine.
139+
140+### Einzelnes Byte senden und empfangen
141+
142+Mitunter muß nur ein einzelnes Byte übertragen werden, zum Beispiel zum Abfragen eines Status.
143+
144+``` pascal
145+// SPI einzelnes Byte senden, empfangen
146+
147+function spi_fast_shift(data : uint8) : uint8;
148+begin
149+ SPDR := data; // zu sendendes Byte ausgeben
150+ while (SPSR and (1 shl SPIF)) = 0 do; // warten bis fertig
151+ spi_fast_shift := SPDR; // empfangenes Byte einlesen
152+end;
153+```
154+
155+Raus, warten, rein. Eigentlich ganz einfach.
156+
157+Und fertig ist die Unit.
158+
159+``` pascal
160+end.
161+```
162+
163+## Anwendung als Master
164+
165+Die beispielhafte Anwendung der Unit als Master für ein NRF-Modul.
166+
167+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.
168+
169+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.
170+
171+### Konfig schreiben
172+
173+``` pascal
174+procedure nrf_set_config(reg, data : uint8);
175+begin
176+ PORTspi := PORTspi and not(Pspi_cs); // CS setzen, ist beim Modul low-aktiv
177+ spi_fast_shift(cW_REGISTER or (cREGISTER_MASK and reg)); // Controlbyte senden
178+ spi_fast_shift(data); // Datenbyte senden
179+ PORTspi := PORTspi or Pspi_cs; // CS löschen, ist beim Modul low-aktiv
180+end;
181+```
182+
183+Es wird ein Byte zur Adressierung des Registers und ein Byte mit dem Wert gesendet, auf den das Register gesetzt werden soll.
184+
185+### Konfig auslesen
186+
187+``` pascal
188+function nrf_get_config(reg : uint8) : uint8;
189+begin
190+ PORTspi := PORTspi and not(Pspi_cs); // CS setzen, ist beim Modul low-aktiv
191+ spi_fast_shift(cR_REGISTER or (cREGISTER_MASK and reg)); // Controlbyte senden
192+ nrf_get_config := spi_fast_shift(0); // Dummy senden und Datenbyte lesen
193+ PORTspi := PORTspi or Pspi_cs; // CS löschen, ist beim Modul low-aktiv
194+end;
195+```
196+
197+Es wird ein Byte zur Adressierung des Registers gesendet, dann wird ein Dummybyte gesendet. Der empfangene Registerinhalt wird übergeben.
198+
199+### Register schreiben
200+
201+``` pascal
202+procedure nrf_write_register(reg : uint8; len : uint8);
203+begin
204+ PORTspi := PORTspi and not(Pspi_cs); // CS setzen, ist beim Modul low-aktiv
205+ spi_fast_shift(cW_REGISTER or (cREGISTER_MASK and reg)); // Controlbyte senden
206+ spi_transmit_sync(nrftxbuf, len); // Daten aus Tx-Buffer senden
207+ PORTspi := PORTspi or Pspi_cs; // CS löschen, ist beim Modul low-aktiv
208+end;
209+```
210+
211+Es wird ein Controlbyte gesendet, gefolgt von den zu schreibenden Daten. Während des gesamten Vorgangs bleibt CS low.
212+
213+### Register auslesen
214+
215+``` pascal
216+procedure nrf_read_register(reg : uint8; len : uint8);
217+begin
218+ PORTspi := PORTspi and not(Pspi_cs); // CS setzen, ist beim Modul low-aktiv
219+ spi_fast_shift(cR_REGISTER or (cREGISTER_MASK and reg)); // Controlbyte senden
220+ spi_transfer_sync(nrftxbuf, nrfrxbuf, len); // Daten in Rx-Buffer lesen, Tx-Buffer ist Dummy
221+ PORTspi := PORTspi or Pspi_cs; // CS löschen, ist beim Modul low-aktiv
222+end;
223+```
224+
225+Es wird ein Controlbyte gesendet, gefolgt von Dummydaten. Die empfangenen Daten stehen im Rx-Buffer.
226+
227+Eine umfangreiche Anleitung für die nRF24L01+ Funkmodule folgt demnächst.
228+
229+## Siehe auch
230+
231+- Übersichtseite [[AVR Embedded Tutorial/de|AVR Embedded Tutorial]]
232+
233+---
234+*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](/wiki-contact).*

Back to history