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