AVR Embedded Tutorial - SPI Shiftregister: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: AVR Embedded Tutorial - SPI Shiftregister
| Old | New | Line | |
|---|---|---|---|
| 1 | + | # Controlling a 74HC595 shift register via SPI | |
| 2 | + | ||
| 3 | + | While other shift registers can also be controlled, the SPI compatible [74HC595](https://www.ti.com/product/SN74HC595) has a big advantage over a normal shift register in that it is about 10 times faster (ATmega328). This is noticeable with a larger multiplex. | |
| 4 | + | ||
| 5 | + | The following functions are described in other tutorials: | |
| 6 | + | ||
| 7 | + | - [[AVR Embedded Tutorial - Simple GPIO on and off output|GPIO - Output / Input]] - How do I make a GPIO access on the AVR. | |
| 8 | + | - [[AVR Embedded Tutorial - SPI|SPI]] - use of the hardware SPI interface. | |
| 9 | + | ||
| 10 | + | ## Atmega328 / Arduino Uno / Nano example | |
| 11 | + | ||
| 12 | + | ### Constants | |
| 13 | + | ||
| 14 | + | Pins which are required at PORTB for SPI. | |
| 15 | + | ||
| 16 | + | ``` pascal | |
| 17 | + | const | |
| 18 | + | SPI_SlaveSelect = 2; | |
| 19 | + | SPI_MOSI = 3; | |
| 20 | + | SPI_Clock = 5; | |
| 21 | + | ``` | |
| 22 | + | ||
| 23 | + | ### Write to a pin | |
| 24 | + | ||
| 25 | + | Writing a PORTB pin is used for SlaveSelect and for the software version. | |
| 26 | + | ||
| 27 | + | ``` pascal | |
| 28 | + | procedure WritePortB(Pin : byte; Value : boolean); | |
| 29 | + | begin | |
| 30 | + | if Value then | |
| 31 | + | begin | |
| 32 | + | PORTB := PORTB or (1 shl pin); | |
| 33 | + | end | |
| 34 | + | else | |
| 35 | + | begin | |
| 36 | + | PORTB := PORTB and not (1 shl pin); | |
| 37 | + | end; | |
| 38 | + | end; | |
| 39 | + | ``` | |
| 40 | + | ||
| 41 | + | ### Write character string to the SPI port | |
| 42 | + | ||
| 43 | + | Writing the string | |
| 44 | + | ||
| 45 | + | ``` pascal | |
| 46 | + | procedure WriteDataHardSPI(p : PByte; len : byte); | |
| 47 | + | var | |
| 48 | + | i : byte; | |
| 49 | + | begin | |
| 50 | + | WritePortB(SPI_SlaveSelect, False); | |
| 51 | + | ||
| 52 | + | for i := len - 1 downto 0 do | |
| 53 | + | begin | |
| 54 | + | SPDR := p[i]; | |
| 55 | + | ||
| 56 | + | while(SPSR and (1 shl SPIF)) = 0 do | |
| 57 | + | begin | |
| 58 | + | end; | |
| 59 | + | end; | |
| 60 | + | ||
| 61 | + | WritePortB(SPI_SlaveSelect, True); | |
| 62 | + | end; | |
| 63 | + | ``` | |
| 64 | + | ||
| 65 | + | ### Main program | |
| 66 | + | ||
| 67 | + | The SPI register is set to the highest possible speed. | |
| 68 | + | ||
| 69 | + | ``` pascal | |
| 70 | + | var | |
| 71 | + | z : uint16; | |
| 72 | + | begin | |
| 73 | + | // All pins set to output | |
| 74 | + | DDRB := (1 shl SPI_MOSI) or (1 shl SPI_Clock) or (1 shl SPI_SlaveSelect); | |
| 75 | + | ||
| 76 | + | // Set the SPI register | |
| 77 | + | SPCR := (1 shl SPE) or (1 shl MSTR) or (%00 shl SPR); | |
| 78 | + | SPSR := (1 shl SPI2X); | |
| 79 | + | ||
| 80 | + | repeat | |
| 81 | + | // Increment the counter, return to 0 if overflow occurs | |
| 82 | + | Inc(z); | |
| 83 | + | ||
| 84 | + | // SPI port described | |
| 85 | + | WriteDataHardSPI(@z, 2); // write 2 bytes. | |
| 86 | + | until 1 = 2; | |
| 87 | + | end. | |
| 88 | + | ``` | |
| 89 | + | ||
| 90 | + | ## ATTiny2313 example | |
| 91 | + | ||
| 92 | + | The ATTiny2313 has no direct SPI interface, but there is the universal USI interface, which is used here for SPI . This is a little more complicated when writing than with the direct SPI interface of the Atmega. | |
| 93 | + | ||
| 94 | + | Note: The ATTiny2313 also has MOSI and MISO connections, but these are only used for the programmer. There are DO and DI for this, but they work exactly in the reverse order. | |
| 95 | + | ||
| 96 | + | ### Declarations | |
| 97 | + | ||
| 98 | + | Pins which are required at PORTB for SPI. | |
| 99 | + | ||
| 100 | + | A bitpacked record is used for this, so you can access the pins directly. | |
| 101 | + | ||
| 102 | + | ``` pascal | |
| 103 | + | type | |
| 104 | + | TSPIGPIO = bitpacked record | |
| 105 | + | p0, p1, p2, p3, SlaveSelect, DataInt, DataOut, Clock : boolean; | |
| 106 | + | end; | |
| 107 | + | ||
| 108 | + | var | |
| 109 | + | SPI_PORT : TSPIGPIO absolute PORTB; | |
| 110 | + | SPI_DDR : TSPIGPIO absolute DDRB; | |
| 111 | + | ``` | |
| 112 | + | ||
| 113 | + | ### Write a character string to the SPI port | |
| 114 | + | ||
| 115 | + | Writing the string | |
| 116 | + | ||
| 117 | + | ``` pascal | |
| 118 | + | procedure SPIWriteData(p : PByte; len : byte); | |
| 119 | + | var | |
| 120 | + | i : byte; | |
| 121 | + | begin | |
| 122 | + | SPI_PORT.SlaveSelect := False; | |
| 123 | + | ||
| 124 | + | for i : = len - 1 downto 0 do | |
| 125 | + | begin | |
| 126 | + | USIDR := p[i]; | |
| 127 | + | USISR := 1 shl USIOIF; | |
| 128 | + | ||
| 129 | + | repeat | |
| 130 | + | USICR := (%01 shl USIWM) or (%10 shl USICS) or (1 shl USICLK) or (1 shl USITC); | |
| 131 | + | until (USISR and (1 shl USIOIF)) <> 0; | |
| 132 | + | end; | |
| 133 | + | ||
| 134 | + | SPI_PORT.SlaveSelect := True; | |
| 135 | + | end; | |
| 136 | + | ``` | |
| 137 | + | ||
| 138 | + | ### Main program - Hardware version | |
| 139 | + | ||
| 140 | + | This example shows how to write 2 cascaded shift registers. | |
| 141 | + | ||
| 142 | + | An integer value is simply output. | |
| 143 | + | ||
| 144 | + | You can also see that no configuration of USI is necessary at the beginning, except that you have to set the ports to output. | |
| 145 | + | ||
| 146 | + | ``` pascal | |
| 147 | + | var | |
| 148 | + | z : uint16 = 0; | |
| 149 | + | begin | |
| 150 | + | SPI_DDR.DataOut := True; | |
| 151 | + | SPI_DDR.Clock := True; | |
| 152 | + | SPI_DDR.SlaveSelect := True; | |
| 153 | + | ||
| 154 | + | repeat | |
| 155 | + | Inc(z); | |
| 156 | + | SPIWriteData(@z, 2); // output integer | |
| 157 | + | until 1 = 2; | |
| 158 | + | end. | |
| 159 | + | ``` | |
| 160 | + | ||
| 161 | + | ### Main program - Software version | |
| 162 | + | ||
| 163 | + | I can show that you can also do software emulation very well here. | |
| 164 | + | ||
| 165 | + | However, this is about a factor of 10 slower than the hardware version of the Atmega328, you can use any ports for this. | |
| 166 | + | ||
| 167 | + | The software version has the advantage that it runs on every AVR and can also be used if you need multiple SPI interfaces. | |
| 168 | + | ||
| 169 | + | ``` pascal | |
| 170 | + | procedure WriteDataSoftSPI(p : PByte; len : byte); | |
| 171 | + | var | |
| 172 | + | i , j : byte; | |
| 173 | + | begin | |
| 174 | + | WritePortB(SPI_SlaveSelect, False); | |
| 175 | + | ||
| 176 | + | for j := 0 to len - 1 do | |
| 177 | + | begin | |
| 178 | + | for i := 7 downto 0 do | |
| 179 | + | begin | |
| 180 | + | if(p[j] and (1 shl i)) <> 0 then | |
| 181 | + | begin | |
| 182 | + | WritePortB(SPI_MOSI, True); | |
| 183 | + | end | |
| 184 | + | else | |
| 185 | + | begin | |
| 186 | + | WritePortB(SPI_MOSI, False); | |
| 187 | + | end; | |
| 188 | + | ||
| 189 | + | WritePortB(SPI_Clock, True); | |
| 190 | + | WritePortB(SPI_Clock, False); | |
| 191 | + | end; | |
| 192 | + | ||
| 193 | + | WritePortB(SPI_SlaveSelect, True); | |
| 194 | + | end; | |
| 195 | + | end; | |
| 196 | + | ``` | |
| 197 | + | ||
| 198 | + | # See also | |
| 199 | + | ||
| 200 | + | - Overview page - [[AVR Embedded Tutorials]]. | |
| 201 | + | - [[AVR Embedded Tutorial - Shiftregister|Shift registers]] - How do I control shift registers? | |
| 202 | + | - [[AVR Embedded Tutorial - SPI|SPI]] - Use of the hardware SPI interface with an ATmega328 / Arduino. | |
| 203 | + | ||
| 204 | + | --- | |
| 205 | + | *From the Free Pascal wiki, page "AVR Embedded Tutorial - SPI Shiftregister", as it stood on 2020-03-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).* |
