AVR Embedded Tutorial - SPI: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: AVR Embedded Tutorial - SPI
| Old | New | Line | |
|---|---|---|---|
| 1 | + | # Use of the hardware SPI interface with an ATmega328 / Arduino | |
| 2 | + | ||
| 3 | + | A prerequisite is an established crosscompiler for AVR microcontrollers as described here: [[AVR Embedded Tutorial - Entry Lazarus and Arduino|Getting started with Lazarus and Arduino]]. | |
| 4 | + | ||
| 5 | + | For the basics of SPI, master-slave, wiring see Wikipedia: <https://de.wikipedia.org/wiki/Serial_Peripheral_Interface> | |
| 6 | + | ||
| 7 | + | This is a very simple SPI unit in which the settings for: | |
| 8 | + | ||
| 9 | + | - Data format | |
| 10 | + | - Mode | |
| 11 | + | - ChipSelect | |
| 12 | + | - Speed | |
| 13 | + | ||
| 14 | + | must be done by hand. For the function of the individual registers and flags, see the data sheet for the ATmega328. | |
| 15 | + | ||
| 16 | + | ## SPI master | |
| 17 | + | ||
| 18 | + | ### Unit header | |
| 19 | + | ||
| 20 | + | First comes the Unit header which contains the pin and port configuration: | |
| 21 | + | ||
| 22 | + | ``` pascal | |
| 23 | + | unit gh_spi; | |
| 24 | + | ||
| 25 | + | {$mode objfpc} {$H+} | |
| 26 | + | {$goto on} | |
| 27 | + | ||
| 28 | + | interface | |
| 29 | + | ||
| 30 | + | uses | |
| 31 | + | ; | |
| 32 | + | ||
| 33 | + | procedure spi_init(); | |
| 34 | + | procedure spi_transfer_sync(var dout : array of uint8; var din : array of uint8; len : uint8); | |
| 35 | + | procedure spi_transmit_sync(var dout : array of uint8; len : uint8); | |
| 36 | + | function spi_fast_shift(data : uint8) : uint8; | |
| 37 | + | ||
| 38 | + | const | |
| 39 | + | Psck = 1 shl 5; // PB5 | |
| 40 | + | Pmiso = 1 shl 4; // PB4 | |
| 41 | + | Pmosi = 1 shl 3; // PB3 | |
| 42 | + | Pss = 1 shl 2; // PB2 | |
| 43 | + | Pcs = 1 shl 1; // PB1 | |
| 44 | + | ||
| 45 | + | var | |
| 46 | + | DDRspi : byte absolute DDRB; // SPI on port B | |
| 47 | + | PORTspi : byte absolute PORTB; | |
| 48 | + | PINspi : byte absolute PINB; | |
| 49 | + | ||
| 50 | + | implementation | |
| 51 | + | ``` | |
| 52 | + | ||
| 53 | + | If you want to address multiple SPI devices, you can define multiple CS (ChipSelect) pins. The SS pin must be an output. If it is an input, the SPI interface can be switched to slave mode - and nothing works. And once it is an output, you can also use it for CE (ChipEnable) or CS (ChipSelect). | |
| 54 | + | ||
| 55 | + | ### Initialization | |
| 56 | + | ||
| 57 | + | ``` pascal | |
| 58 | + | // initialize SPI | |
| 59 | + | ||
| 60 | + | procedure spi_init(); | |
| 61 | + | begin | |
| 62 | + | DDRspi := DDRspi and not (Pmiso); // MISO entrance | |
| 63 | + | DDRspi := DDRspi or (Pmosi or Psck or Pss); // MOSI, SCK, SS output | |
| 64 | + | // SS must be output, otherwise switching to slave from outside is possible | |
| 65 | + | ||
| 66 | + | SPCR := ((1 shl SPE) or (0 shl SPIE) or (0 shl SPORD) or (1 shl MSTR) or (0 shl CPOL) or (0 shl CPHA) or (%01 shl SPR)); | |
| 67 | + | // SPI enable, MSB first, Master select, SCK 1MHz = XTAL / 16 x 2 | |
| 68 | + | SPSR := (1 shl SPI2X); // SCK x 2 on 1 MHz | |
| 69 | + | end ; | |
| 70 | + | ``` | |
| 71 | + | ||
| 72 | + | Register settings: | |
| 73 | + | ||
| 74 | + | - SPE = 1: SPI enable | |
| 75 | + | - SPIE = 0: no interrupt | |
| 76 | + | - DORD = 0: MSB first, 1: LSB first | |
| 77 | + | - MSTR = 1: as master, but can be overwritten by Pin SS from outside, 0: as slave | |
| 78 | + | - CPOL = 0: SCK polarity normal, 1: SCK inverted | |
| 79 | + | - CPHA = 0: SCK phase normal, 1: SCK shifted | |
| 80 | + | - SPR = 01: fcpu / 16 | |
| 81 | + | - SPI2X = 1: fcpu * 2, gives 1MHz clock at 8Mhz controller clock, see data sheet for other clock rates. | |
| 82 | + | ||
| 83 | + | The registers corresponding to the ATmega328P unit from the Embedded AVR Sources. For other controllers, check the registers using the data sheet. | |
| 84 | + | ||
| 85 | + | ### Data Transfer | |
| 86 | + | ||
| 87 | + | To read data from a device, data must also be written at the same time. But that can also be dummy data. The number of bytes written and read is the same. | |
| 88 | + | ||
| 89 | + | ``` pascal | |
| 90 | + | // Send, receive SPI data | |
| 91 | + | ||
| 92 | + | procedure spi_transfer_sync(var dout : array of uint8; var din : array of uint8; len : uint8); | |
| 93 | + | var | |
| 94 | + | cnt : uint8; | |
| 95 | + | begin | |
| 96 | + | for cnt := 0 to len - 1 do | |
| 97 | + | begin | |
| 98 | + | SPDR := dout[cnt]; // output byte to be sent | |
| 99 | + | while (SPSR and (1 shl SPIF)) = 0 do ; // wait until done | |
| 100 | + | din[cnt] := SPDR; // read the received byte | |
| 101 | + | end; | |
| 102 | + | end; | |
| 103 | + | ``` | |
| 104 | + | ||
| 105 | + | The nice thing about Pascal is that you can work with the right arrays and not have to mess around with pointers like C does. It is important here that the arrays are passed via var. Then Pascal works with the original array, otherwise a copy is made, which takes up unnecessary time and memory. | |
| 106 | + | ||
| 107 | + | The routine waits until all bytes have been written out of dout and the received data has been read into din at the same time, which takes about 10µsec per byte at 1Mhz. Since these are really only a few system clocks per byte, an interrupt is not worthwhile here. The routine can be interrupted by other interrupts (timer, UART), which delays the output of the next byte, but is not a problem. | |
| 108 | + | ||
| 109 | + | The same array may also be used for dout and din in which case the original data will be replaced by the received data. | |
| 110 | + | ||
| 111 | + | **Caution!** The following applies here and for the following routines: If the routine is called without SPE being set in the SPCR (see Init), the controller remains in the queue because the data byte is not sent and consequently the SPIF flag is not set. | |
| 112 | + | ||
| 113 | + | ### Write data only | |
| 114 | + | ||
| 115 | + | If only data is to be written but no response is expected from the device, the routine can be simplified as follows: | |
| 116 | + | ||
| 117 | + | ``` pascal | |
| 118 | + | // Send SPI data without receiving | |
| 119 | + | ||
| 120 | + | procedure spi_transmit_sync(var dout : array of uint8; len : uint8); | |
| 121 | + | var | |
| 122 | + | cnt : uint8; | |
| 123 | + | begin | |
| 124 | + | for cnt := 0 to len - 1 do | |
| 125 | + | begin | |
| 126 | + | SPDR := dout[cnt]; // output byte to be sent | |
| 127 | + | while(SPSR and (1 shl SPIF)) = 0 do ; // wait until done | |
| 128 | + | end; | |
| 129 | + | end; | |
| 130 | + | ``` | |
| 131 | + | ||
| 132 | + | The same applies here as for the transfer routine. | |
| 133 | + | ||
| 134 | + | ### Send and receive a single byte | |
| 135 | + | ||
| 136 | + | Sometimes only a single byte has to be transmitted, for example to query a status. | |
| 137 | + | ||
| 138 | + | ``` pascal | |
| 139 | + | // Send, receive SPI single byte | |
| 140 | + | ||
| 141 | + | function spi_fast_shift(data : uint8) : uint8; | |
| 142 | + | begin | |
| 143 | + | SPDR := data; // output byte to be sent | |
| 144 | + | while(SPSR and (1 shl SPIF)) = 0 do ; // wait until done | |
| 145 | + | spi_fast_shift := SPDR; // read the received byte | |
| 146 | + | end; | |
| 147 | + | ``` | |
| 148 | + | ||
| 149 | + | Out, wait, in. Quite simple, actually. | |
| 150 | + | ||
| 151 | + | ### The unit is finished | |
| 152 | + | ||
| 153 | + | ``` pascal | |
| 154 | + | end. | |
| 155 | + | ``` | |
| 156 | + | ||
| 157 | + | ## Use as a master | |
| 158 | + | ||
| 159 | + | An example application of the unit as a master for an NRF module. | |
| 160 | + | ||
| 161 | + | Of course, **spi_init** must be called at the beginning of the program. In addition, the registers of the NRF module are declared as constants and a send and a receive buffer are set up as an array of uint8. | |
| 162 | + | ||
| 163 | + | We have to take care of CS ourselves. In the NRF module, the CS is low-active, so the idle level is high and is pulled to low for activation. | |
| 164 | + | ||
| 165 | + | ### Write config | |
| 166 | + | ||
| 167 | + | ``` pascal | |
| 168 | + | procedure nrf_set_config(reg, data : uint8); | |
| 169 | + | begin | |
| 170 | + | PORTspi := PORTspi and not (Pspi_cs); // set CS low active for the module | |
| 171 | + | spi_fast_shift(cW_REGISTER or (cREGISTER_MASK and reg)); // send control byte | |
| 172 | + | spi_fast_shift(data); // send data byte | |
| 173 | + | PORTspi := PORTspi or Pspi_cs; // unset CS | |
| 174 | + | end; | |
| 175 | + | ``` | |
| 176 | + | ||
| 177 | + | One byte is sent to address the register and one byte with the value to which the register is to be set. | |
| 178 | + | ||
| 179 | + | ### Read out config | |
| 180 | + | ||
| 181 | + | ``` pascal | |
| 182 | + | function nrf_get_config(reg : uint8) : uint8; | |
| 183 | + | begin | |
| 184 | + | PORTspi := PORTspi and not (Pspi_cs); // set CS low active for the module | |
| 185 | + | spi_fast_shift(cR_REGISTER or (cREGISTER_MASK and reg)); // send control byte | |
| 186 | + | nrf_get_config := spi_fast_shift(0); // send dummy and read data byte | |
| 187 | + | PORTspi := PORTspi or Pspi_cs; // unset CS | |
| 188 | + | end; | |
| 189 | + | ``` | |
| 190 | + | ||
| 191 | + | A byte is sent to address the register, then a dummy byte is sent. The received register content is transferred. | |
| 192 | + | ||
| 193 | + | ### Write register | |
| 194 | + | ||
| 195 | + | ``` pascal | |
| 196 | + | procedure nrf_write_register(reg : uint8; len : uint8); | |
| 197 | + | begin | |
| 198 | + | PORTspi := PORTspi and not (Pspi_cs); // set CS low active for the module | |
| 199 | + | spi_fast_shift(cW_REGISTER or (cREGISTER_MASK and reg)); // send control byte | |
| 200 | + | spi_transmit_sync(nrftxbuf, len); // send data from the Tx buffer | |
| 201 | + | PORTspi := PORTspi or Pspi_cs; // unset CS | |
| 202 | + | end; | |
| 203 | + | ``` | |
| 204 | + | ||
| 205 | + | A control byte is sent, followed by the data to be written. CS remains low throughout the process. | |
| 206 | + | ||
| 207 | + | ### Read register | |
| 208 | + | ||
| 209 | + | ``` pascal | |
| 210 | + | procedure nrf_read_register(reg : uint8; len : uint8); | |
| 211 | + | begin | |
| 212 | + | PORTspi := PORTspi and not (Pspi_cs); // set CS is low active for the module | |
| 213 | + | spi_fast_shift(cR_REGISTER or (cREGISTER_MASK and reg)); // send control byte | |
| 214 | + | spi_transfer_sync(nrftxbuf, nrfrxbuf, len); // read data in Rx buffer, Tx buffer is dummy | |
| 215 | + | PORTspi := PORTspi or Pspi_cs; // unset CS | |
| 216 | + | end; | |
| 217 | + | ``` | |
| 218 | + | ||
| 219 | + | A control byte is sent, followed by dummy data. The received data is in the Rx buffer. | |
| 220 | + | ||
| 221 | + | Extensive instructions for the nRF24L01 + radio modules will follow shortly. | |
| 222 | + | ||
| 223 | + | # See also | |
| 224 | + | ||
| 225 | + | - Overview page - [[AVR Embedded Tutorials]] | |
| 226 | + | ||
| 227 | + | --- | |
| 228 | + | *From the Free Pascal wiki, page "AVR Embedded Tutorial - SPI", as it stood on 2020-08-08 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).* |
