Sign in Join
Wiki copytemporary

Raspberry Pi - SPI/de: what changed

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

Title: Raspberry Pi - SPI/de

OldNewLine
1+## Nutzung der SPI-Schnittstelle am Raspberry Pi mit Freepascal
2+
3+Gleich vorweg: Es ist eine gute Idee, für die Hardwarekomponenten wie SPI, I2C und GPIOs des Raspberry Pi die pascalio Lib (https://github.com/SAmeis/pascalio) zu verwenden.
4+
5+Allerdings ist die pascalio auch sehr umfangreich und wer verstehen möchte, was da passiert, ist vielleicht mit dieser Anleitung ganz gut bedient.
6+
7+## SPI unter Linux
8+
9+Auch für die SPI-Schnittstelle gilt das Linux-Prinzip "Alles ist eine Datei". Den Vorteil, dass man mit einfachen Dateizugriffen auf die Schnittstelle zugreifen kann, erkauft man sich allerdings mit geringerer Geschwindigkeit - meist unkritisch - und damit, dass auch andere Programme auf die gleiche Schnittstelle zugreifen können.
10+
11+Auf dem Raspberry Pi (wir reden hier von Version 3 mit Raspbian Stretch, Stand Okt 2017) gibt es eine SPI-Schnittstelle, die 2 Devices ansprechen kann: */dev/spidev0.0* und */dev/spidev0.1* (Also genaugenommen gibt es mehrere, aber nur eine ist nach außen geführt.)
12+
13+Zu den Grundlagen von SPI, Master-Slave, Verdrahtung siehe Wikipedia: <https://de.wikipedia.org/wiki/Serial_Peripheral_Interface>
14+
15+### Header
16+
17+``` pascal
18+unit test_spi;
19+
20+{$mode objfpc}{$H+}
21+
22+interface
23+
24+uses
25+ Classes, SysUtils, BaseUnix;
26+
27+type
28+ Tspi = class
29+
30+ procedure Init();
31+ procedure Close();
32+
33+ function DataIn(var din; cnt : byte) : integer;
34+ function DataOut(const dout; cnt : byte) : integer;
35+
36+ procedure TransferSync(const dout; var din; len : byte);
37+ procedure TransmitSync(const dout; len : byte);
38+ function FastShift(dout : byte) : byte;
39+
40+ private
41+ { private declarations }
42+ public
43+ { public declarations }
44+ end;
45+
46+const
47+ // SPI Mode Flags
48+
49+ cSPI_CPHA = $01;
50+ cSPI_CPOL = $02;
51+
52+ cSPI_MODE_0 = (0 or 0);
53+ cSPI_MODE_1 = (0 or SPI_CPHA);
54+ cSPI_MODE_2 = (SPI_CPOL or 0);
55+ cSPI_MODE_3 = (SPI_CPOL or SPI_CPHA);
56+
57+ cSPI_CS_HIGH = $04;
58+ cSPI_LSB_FIRST = $08;
59+ cSPI_3WIRE = $10;
60+ cSPI_LOOP = $20;
61+ cSPI_NO_CS = $40;
62+ cSPI_READY = $80;
63+
64+ cSPI_CTRL = $6B; // ist das "Magic Byte"
65+
66+ // Controlregister
67+ // Read/Write + Size + MagicByte + Register
68+
69+ cSPI_RD_MODE : uint32 = $80016B01;
70+ cSPI_WR_MODE : uint32 = $40016B01;
71+ cSPI_RD_LSB_FIRST : uint32 = $80016B02;
72+ cSPI_WR_LSB_FIRST : uint32 = $40016B02;
73+ cSPI_RD_BITS_PER_WORD : uint32 = $80016B03;
74+ cSPI_WR_BITS_PER_WORD : uint32 = $40016B03;
75+ cSPI_RD_MAX_SPEED_HZ : uint32 = $80046B04;
76+ cSPI_WR_MAX_SPEED_HZ : uint32 = $40046B04;
77+
78+ cSPI_DEVICE = '/dev/spidev0.0'; // Device 0, ChipSelect 0
79+// cSPI_DEVICE = '/dev/spidev0.1'; // Device 0, ChipSelect 1
80+ cSPI_SPEED = 1000000; // Datenrate in Hz
81+ cSPI_BITS = 8; // Datenbits
82+ cSPI_LSBF = 0; // LSB first = -1
83+ cSPI_MODE = cSPI_MODE_0;
84+
85+type
86+ spi_ioc_transfer = record // Controlregister für FileIO benötigt
87+ tx_buf : uint64; //pointer;
88+ rx_buf : uint64; //pointer; // immer 64-bit
89+ len : uint32; // Anzahl Zeichen
90+ speed : uint32; // Datenrate in Hz
91+ delay : uint16; // Verzögerung CS in usec
92+ bpw : uint8; // Bits per Word
93+ csc : uint8; // CS change
94+ txn : uint8;
95+ rxn : uint8;
96+ pad : uint16;
97+ end; // ingesamt 32 Byte
98+
99+var
100+ spi : Tspi;
101+ spihnd : longint; // der filehandle für die Schnittstelle
102+
103+implementation
104+```
105+
106+### Initialisierung
107+
108+``` pascal
109+ // SPI initialisieren
110+
111+procedure Tspi.Init();
112+var
113+ val8 : byte;
114+ val32 : longword;
115+begin
116+ try
117+ spihnd := FpOpen(cSPI_DEVICE, O_RdWr); // Schnittstelle für Read/Write öffnen
118+
119+ if spihnd <> -1 then begin
120+ val8 := cSPI_MODE;
121+ FpIOCtl(spihnd, cSPI_WR_MODE, @val8); // Mode setzen
122+ val8 := cSPI_BITS;
123+ FpIOCtl(spihnd, cSPI_WR_BITS_PER_WORD, @val8); // Datenbits per Byte setzen
124+ val8 := cSPI_LSBF; //-1
125+ FpIOCtl(spihnd, cSPI_WR_LSB_FIRST, @val8); // MSB oder LSB first setzen
126+ val32 := cSPI_SPEED;
127+ FpIOCtl(spihnd, cSPI_WR_MAX_SPEED_HZ, @val32); // Speed setzen
128+ end;
129+ finally
130+
131+ end;
132+end;
133+```
134+
135+Die Schnittstelle wird beim Programmstart geöffnet und die Parameter eingestellt. Die Parameter können auch später noch geändert werden. Zum Beispiel kann die Clockrate erst langsam gesetzt und dann erhöht werden, was für einige SD-Karten nötig ist.
136+
137+**Achtung!** Die Clockrate wird aus dem momentanen Systemtakt abgeleitet. Ändert sich der Takt, ändert sich die Taktrate. So geht eine Clockrate von 1Mhz bei 600MHz Systemtakt bei höherer Prozessorlast und 1200MHz Systemtakt auf 2MHz hoch.
138+
139+Und: Die Clockrate wird durch einen Teiler 2^n aus dem Systemtakt abgeleitet und stimmt nie genau. Wenn man 1MHz angibt, liegt die resultierende Rate irgendwo bei 800kHz.
140+
141+### Beenden
142+
143+``` pascal
144+// SPI beenden
145+
146+procedure Tspi.Close();
147+begin
148+ if spihnd <> -1 then begin
149+ FpClose(spihnd);
150+ end;
151+end;
152+```
153+
154+Ja, am Programmende machen wir die Schnittstelle brav wieder zu.
155+
156+### Daten auslesen
157+
158+``` pascal
159+// SPI Buffer Read
160+
161+function Tspi.DataIn(var din; cnt : byte) : integer;
162+begin
163+ if spihnd <> -1 then begin
164+ DataIn := FpRead(spihnd, din, cnt);
165+ end;
166+end;
167+```
168+
169+Da "Alles eine Datei" ist, können wir ein einfaches Read auf die SPI-Schnittstelle anwenden. Das gibt den Takt für die angegebene Anzahl Bytes aus und liest die Antwort des Devices in ein Array *din*.
170+
171+### Daten senden
172+
173+``` pascal
174+// SPI Buffer Write
175+
176+function Tspi.DataOut(const dout; cnt : byte) : integer;
177+begin
178+ if spihnd <> -1 then begin
179+ DataOut := FpWrite(spihnd, dout, cnt);
180+ end;
181+end;
182+```
183+
184+Genauso können wir Daten mit Write senden. Dabei wird die angegebene Anzahl Bytes aus dem Array *dout* ausgegeben.
185+
186+### SPI Transfer
187+
188+Allerdings verlangen die meisten SPI-Devices eine etwas komplexere Interaktion als einfaches Senden und Empfangen. Oft werden gleichzeitig Daten gesendet und empfangen, und das überfordert die Möglichkeiten eines Read und Write. Hier brauchen wir eine Transfer-Funktion.
189+
190+``` pascal
191+// SPI Daten senden, empfangen
192+
193+procedure Tspi.TransferSync(const dout; var din; len : byte);
194+var
195+ outbuf : array of byte;
196+ inbuf : array of byte;
197+ transfer : spi_ioc_transfer;
198+begin
199+ if len > 0 then begin
200+ SetLength(outbuf, len);
201+ FillByte(outbuf[0], len, 0);
202+ Move(dout, outbuf[0], len);
203+
204+ SetLength(inbuf, len);
205+ FillByte(inbuf[0], len, 0);
206+
207+ FillByte(transfer, SizeOf(transfer), 0);
208+ transfer.tx_buf := uint64(@outbuf[0]);
209+ transfer.rx_buf := uint64(@inbuf[0]);
210+ transfer.len := len;
211+ transfer.delay := 0;
212+ transfer.speed := cSPI_SPEED;
213+ transfer.bpw := cSPI_BITS;
214+ transfer.csc := 0;
215+
216+ try
217+ FpIOCtl(spihnd, $40206B00, @transfer);
218+ finally
219+ Move(inbuf[0], din, len);
220+ end;
221+ end;
222+end;
223+```
224+
225+Das ist jetzt schwierig. Um gleichzeitig Senden und Empfangen zu können, brauchen wir zwei Buffer *outbuf* und *inbuf*, in die wir die Daten spiegeln. Deren Startadressen übergeben wir - unbedingt als 64-bit-Werte, auch bei einem 32bit-OS - in das Transfer-Register. Außerdem bekommt das Register noch die zu übertragende Anzahl Bytes und ein paar Einstellungen übergeben.
226+
227+Witzigerweise kann man hier jedesmal die Einstellungen ändern, also zum Beispiel die Clockrate anpassen.
228+
229+Anschließen übergeben wir das Transfer-Register mit den Einstellungen dem Dateihandler, der brav die Arrays abklappert und die Daten rausschickt und einliest. Das mystische *\$40206B00* ist dabei wieder ein Write (\$40), gefolgt von der Anzahl der Bytes im Transfer-Register (\$20 = 32 Bytes), dem Magic Byte (\$6B, siehe oben) und dem Controlregister (\$00) der Schnittstelle. Einfach so lassen und **nicht dran rumfummeln**.
230+
231+**Disclaimer:** Das funktioniert hier und jetzt auf dem Raspberry Pi 3 unter Raspbian. Das kann auf anderen Linux-OS und Hardware-Plattformen anders aussehen, vor allem wenn die Byteorder der Adressen eine andere ist.
232+
233+Und wenn ich nur Daten empfangen will? Dann übergebe ich einfach ein leeres Array. Ich kann auch für *dout* und *din* das gleiche Array angeben, dann werden die alten Arraydaten mit den neuen Daten überschrieben.
234+
235+### SPI Transmit
236+
237+``` pascal
238+// SPI Daten senden ohne Empfang
239+
240+procedure Tspi.TransmitSync(const dout; len : byte);
241+var
242+ outbuf : array of byte;
243+ transfer : spi_ioc_transfer;
244+begin
245+ if len > 0 then begin
246+ SetLength(outbuf, len);
247+ FillByte(outbuf[0], len, 0);
248+ Move(dout, outbuf[0], len);
249+
250+ FillByte(transfer, SizeOf(transfer), 0);
251+ transfer.tx_buf := uint64(@outbuf[0]);
252+ transfer.rx_buf := uint64(@outbuf[0]); // gleicher Buffer
253+ transfer.len := len;
254+ transfer.delay := 0;
255+ transfer.speed := cSPI_SPEED;
256+ transfer.bpw := cSPI_BITS;
257+ transfer.csc := 0;
258+
259+ try
260+ FpIOCtl(spihnd, $40206B00, @transfer);
261+ finally
262+
263+ end;
264+ end;
265+end;
266+```
267+
268+Sollen Daten nur geschrieben werden, kann man obiges DataOut nehmen, oder diese abgespeckte Variante der Tranfer-Routine.
269+
270+### SPI FastShift
271+
272+``` pascal
273+// SPI einzelnes Byte senden, empfangen
274+
275+function Tspi.FastShift(dout : byte) : byte;
276+var
277+ din : byte;
278+ transfer : spi_ioc_transfer;
279+begin
280+ din := 0;
281+ FillByte(transfer, SizeOf(transfer), 0);
282+ transfer.tx_buf := uint64(@dout);
283+ transfer.rx_buf := uint64(@din);
284+ transfer.len := 1;
285+ transfer.delay := 0;
286+ transfer.speed := cSPI_SPEED;
287+ transfer.bpw := cSPI_BITS;
288+ transfer.csc := 0;
289+
290+ try
291+ FpIOCtl(spihnd, $40206B00, @transfer);
292+ finally
293+ FastShift := din;
294+ end;
295+end;
296+```
297+
298+Wird nur ein einzelnes Byte gesendet und empfangen, zum Beispiel für eine Statusabfrage, bietet sich diese Version an. Hier steht das Ergebnis nicht in einem Buffer, sondern wird als Funktionswert zurückgegeben.
299+
300+### Fertsch
301+
302+``` pascal
303+end.
304+```
305+
306+---
307+*From the Free Pascal wiki, page "Raspberry Pi - SPI/de", as it stood on 2019-12-17 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).*
308+Category: Deutsch
309+Category: Main/de

Back to history