Start from https://www.arduino.cc/en/Reference/SPI (https://www.arduino.cc/en/Reference/SPI) and use something really dumb and simple as a target to experiment with. e.g a 74HC595 (https://www.ti.com/lit/gpn/SN74HC595) eight-bit SIPO shift register. It's not fully SPI compatible, because it doesn't tri-state its data out or ignore input data when not selected, but its enough to get started with writing code that can send a byte to a chip and get a byte back.
N.B. '595 pin names vary by manufacturer's datasheet, but their function remains unchanged. I'm using the names from the T.I. datasheet I linked.
Hook up its Vcc and Gnd to Arduino +5V and Gnd respectively, tie its /OE pin low, and its /SRCLR pin high, then connect Arduino MOSI to '595 SER, MISO to QH' (*NOT* QH), SCK to SRCLK and SS to RCLK.
Then its just a matter of:
SPI.beginTransaction(SPISettings(125000, MSBFIRST, SPI_MODE0));
setting the SS pin low, then swapping bytes with the '595 using:
receivedVal = SPI.transfer(val);
Send as many bytes as you want, you'll get each one back one byte-transfer later.
Then take the SS pin back high and the last byte you sent will be latched on the '595's QA (LSB) thru QH (MSB) outputs.
At this point you can either take SS low again and transfer another packet of bytes, or release the Arduino SPI peripheral by doing:
SPI.endTransaction();
which is strongly recommended so other libraries (or interrupt routines) can use it. Its generally undesirable to hang about waiting between SPI.beginTransaction() and SPI.endTransaction() in a complex system with several peripheral chips using the SPI interface.
You *can* get the hang of this just on 'blind trust' + eight LEDs (with series resistors) on the '595 Qn outputs, but you'll have a much easier time understanding it if you can monitor the four SPI interface signals and some of the '595 Qn outputs on a logic analyzer. The SPI clock speed is set to only 125KHz so anything that can do a minimum sample rate of 250KHz will do. If you are using a scope or other device that doesn't have enough channels, treat SCK as the master channel you reference everything else to, put a small delay in your main loop outside the code between SPI.beginTransaction() and SPI.endTransaction() so you get bursts of clock pulses while you are actively using the SPI peripheral, and examine each other signal in turn while sending the same data byte, though if you can separately trigger on SS falling edge, it will be helpful to synchronize the scope to your block of transfers even if you cant view the trigger channel.
Once you've got the hang of the basics, to use a SPI interface peripheral chip its just a matter of stuffing the bytes specified in a chip's datasheet down the SPI 'pipe' to control it, and if it returns data, saving the bytes that matter from what you get back from SPI.transfer(), then doing something useful with them (e.g. displaying them) once you've got all the ones you need.
Beware of chips with SPI-like interfaces that aren't quite SPI. e.g. anything that requires a number of bits that isn't an integer multiple of eight and wont let you 'pad' the transfer with extra clock cycles to bring it up to 8*N bits.
You'll also encounter chips with only one bidirectional data pin. Put a 1K resistor in series with Arduino MOSI to limit the drive, and connect the chip's data I/O pin to the resistor and MISO. You'll get the byte you sent back on each transfer except when the chip is outputting something as the 1K resistor makes the MOSI signal weak enough that the chip will 'win' if it has data for you.
Edit: Corrected typo '575' in first paragraph. Thanks, Benta!
Also, some ICs require pull-up resistors, some pull-down and some do not. I have added both a pull-up and pull-down resistor, and will change/remove them when I have the circuit under an osciloscope.
But how does one decide whether a resistor is needed, while still in the designing proccess ?
By carefully reading the data sheet for the part you're trying to interface. If the data sheet doesn't explicitly call for a pull-up it may say that a particular line is "open drain" or (very rarely nowadays) "open collector". Lines described as "tristate" normally don't require pull-ups. SPI doesn't normally need pull-ups, but might if you're using SPI to drive a "not strictly SPI" part like a general purpose shift register.
Note that almost all recent MCUs allow you to turn on and off pull-up and pull-down resistors that are built into the MCU IO port itself, saving you the trouble of adding a physical component. For instance, the GPIOs on STM32 MCUs have this built in as standard. In the case of the Arduino environment you can activate a pull-up by setting the pin mode:
pinMode(pin, INPUT_PULLUP);