In this example, the SPI is configured to have communication in master mode, full-duplex with an eeprom memory, enable writing and reading of data in it. Since the SPI communication always will transmit a data when receive and vice-versa, there is a special function to take care of the information send and received, HAL_SPI_TransmitReceive it is necessary to specify both buffers for Tx and Rx and the number of transactions to perform.

#include "bsp.h"

SPI_HandleTypeDef SpiHandle;        /*Structure to handle SPI*/
GPIO_InitTypeDef GPIO_InitStruct;   /*Structure to init GPIOs*/
uint8_t RxBuffer[5];
uint8_t TxBuffer[4];

int main(void)
{
    HAL_Init();           /*Library Initialization*/

    __GPIOD_CLK_ENABLE(); /*Enable port D clock*/
    
    GPIO_InitStruct.Pin   = GPIO_PIN_9;                    /*CS pin to start/finish SPI transmition */
    GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;           /*Output push-pull Type                  */
    GPIO_InitStruct.Pull  = GPIO_NOPULL;                   /*Pin without pull-up or pull-down       */
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;           /*Pin in low speed                       */
    /*Initialize pins with the previous parameters*/
    HAL_GPIO_Init( GPIOD, &GPIO_InitStruct );
    /*Ensures slave is disabled pin D9 high*/
    HAL_GPIO_WritePin( GPIOD, GPIO_PIN_9, SET );

    /*Configuration of SPI in master mode, Full-Duplex communication, Clock polarity in Low,
    clock phase in rising edge and CLK frecc of 4 MHz*/
    SpiHandle.Instance            = SPI1;
    SpiHandle.Init.Mode           = SPI_MODE_MASTER;
    SpiHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;  
    SpiHandle.Init.Direction      = SPI_DIRECTION_2LINES;
    SpiHandle.Init.CLKPhase       = SPI_PHASE_1EDGE;             
    SpiHandle.Init.CLKPolarity    = SPI_POLARITY_LOW;            
    SpiHandle.Init.DataSize       = SPI_DATASIZE_8BIT;
    SpiHandle.Init.FirstBit       = SPI_FIRSTBIT_MSB;
    SpiHandle.Init.NSS            = SPI_NSS_SOFT;
    SpiHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
    SpiHandle.Init.TIMode         = SPI_TIMODE_DISABLED;
    /*Apply configuration to SPI1*/
    HAL_SPI_Init( &SpiHandle );

    /*Enable writing instructions in the eeprom memory by sending a 0x06*/
    HAL_GPIO_WritePin( GPIOD, GPIO_PIN_9, RESET );
    TxBuffer[0] = 0x06;                                 /*WREN Instruction value*/
    HAL_SPI_Transmit( &SpiHandle, TxBuffer, 1, 500 );
    HAL_GPIO_WritePin( GPIOD, GPIO_PIN_9, SET );
    
    /*Send to write the value 0x35 in the address 0x0000 of the memory*/
    HAL_GPIO_WritePin( GPIOD, GPIO_PIN_9, RESET );
    TxBuffer[0] = 0x02;                                 /*Write instruction */
    TxBuffer[1] = 0x00;                                 /*16 bit - address  */
    TxBuffer[2] = 0x00;                                 /*16 bit - address  */
    TxBuffer[3] = 0x35;                                 /*Data byte         */
    HAL_SPI_Transmit( &SpiHandle, TxBuffer, 4, 500 );
    HAL_GPIO_WritePin( GPIOD, GPIO_PIN_9, SET );

    /*Wait for the data to be recorded in memory, it is not the best way to
    do it, so it's just for demonstration purposes*/
    HAL_Delay( 5 );

    /*Read a byte from direccion 0 of eeprom memory, TransmitReceive() Function needs as parameters structure
    to handle SPI , buffer where data to transmit is stored, buffer where data received is stored, total amount
    of bytes to transmit and receive, value of timeout to accomplish transmision*/
    HAL_GPIO_WritePin( GPIOD, GPIO_PIN_9, RESET );
    TxBuffer[0] = 0x03;         /*Read instruction*/
    TxBuffer[1] = 0x00;         /*16 bit - address  */
    TxBuffer[2] = 0x00;         /*16 bit - address  */
    /*Instead of calling Transmit and then Receive functions we can use only one function to perform
    both operations*/
    HAL_SPI_TransmitReceive( &SpiHandle, TxBuffer, RxBuffer, 4, 500 );
    HAL_GPIO_WritePin( GPIOD, GPIO_PIN_9, SET );

    while (1)
    {
    }
}

msps.c

/*This function will be called inside the HAL_SPI_Init function*/
void HAL_SPI_MspInit( SPI_HandleTypeDef *hspi )
{
    /* pins D8, D6 and D5 in alternate function spi1 for CLK, MOSI and MISO*/
    GPIO_InitTypeDef GPIO_InitStruct;
    __GPIOD_CLK_ENABLE();
    __SPI1_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_6 | GPIO_PIN_5; /*Function of pin: CLK , MOSI , MISO */ 
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;                     /*Alternate Function mode for pins*/
    GPIO_InitStruct.Pull = GPIO_PULLUP;                         /*Pull-Up activation for selected pins*/
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;               /*High speed selected for pins*/
    GPIO_InitStruct.Alternate = GPIO_AF1_SPI1;                  /*Alternate function for selected pins*/
    HAL_GPIO_Init( GPIOD, &GPIO_InitStruct );
}