Sign in Join
Wiki copytemporary

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

OldNewLine
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).*

Back to history