Programming the transceiver

On the reference board, an accelerometer is connected as slave to the SPI1 bus, so we can examine how to implement the master side of the communication on the microcontroller by configuring the transceiver and executing a bidirectional transaction toward the peripheral.

The SPI1 bus has its configuration registers mapped in the peripherals region:

#define SPI1 (0x40013000)

#define SPI1_CR1 (*(volatile uint32_t *)(SPI1))
#define SPI1_CR2 (*(volatile uint32_t *)(SPI1 + 0x04))
#define SPI1_SR (*(volatile uint32_t *)(SPI1 + 0x08))
#define SPI1_DR (*(volatile uint32_t *)(SPI1 + 0x0c))

The peripheral exposes a total of four registers:

  • Two bit-field configuration registers
  • One status register
  • One bidirectional data register

It is clear that the interface is similar to that of the UART transceiver, as the configuration of the communication parameters goes through the SPI_CRx registers, the status of the FIFO can be monitored by looking at SPI_SR, and SPI_DR can be used to read and write data to the serial bus.

The value for the configuration register CR1 contains the following:

  • The clock phase, zero or one, in bit 0
  • The clock polarity in bit 1
  • The SPI master mode flag in bit 2
  • The bit rate scaling factor in bits 3-5
  • The SPI enable flag in bit 6
  • Other configuration parameters, such as the word length, LSB first, and other flags that will not be used in this example, as the default will be kept for these parameters

The CR2 configuration register contains the flags to enable the interrupt events and the DMA transfers, as well as the Slave Select Output Enable (SSOE) flag, which is relevant for this example.

The SPI1_SR status register is similar to the UART status register in the previous section, as it contains flags to determine whether the transmit FIFO is empty, and when the receive FIFO is not empty, to regulate the phases of the transfer.

The bits corresponding to the flags that are used in this example are defined as follows:

#define SPI_CR1_MASTER (1 << 2)
#define SPI_CR1_SPI_EN (1 << 6)
#define SPI_CR2_SSOE (1 << 2)
#define SPI_SR_RX_NOTEMPTY (1 << 0)
#define SPI_SR_TX_EMPTY (1 << 1)

The RCC controls the clock and reset lines toward the SPI1 transceiver connected to the APB2 bus:

#define APB2_CLOCK_ER (*(volatile uint32_t *)(0x40023844))
#define APB2_CLOCK_RST (*(volatile uint32_t *)(0x40023824))
#define SPI1_APB2_CLOCK_ER_VAL (1 << 12)

The transceiver can be reset by sending a reset pulse from the RCC:

static void spi1_reset(void)
{
APB2_CLOCK_RST |= SPI1_APB2_CLOCK_ER_VAL;
APB2_CLOCK_RST &= ~SPI1_APB2_CLOCK_ER_VAL;
}

The PA5, PA6, and PA7 pins can be associated with the SPI1 transceiver by setting the appropriate alternate function:

#define SPI1_PIN_AF 5
#define SPI1_CLOCK_PIN 5
#define SPI1_MOSI_PIN 6
#define SPI1_MISO_PIN 7


static void spi1_pins_setup(void)
{
uint32_t reg;
AHB1_CLOCK_ER |= GPIOA_AHB1_CLOCK_ER;
reg = GPIOA_MODE & ~(0x03 << (SPI1_CLOCK_PIN * 2));
reg &= ~(0x03 << (SPI1_MOSI_PIN));
reg &= ~(0x03 << (SPI1_MISO_PIN));
reg
|= (2 << (SPI1_CLOCK_PIN * 2));
reg |=
(2 << (SPI1_MOSI_PIN * 2)) | (2 << (SPI1_MISO_PIN * 2))
GPIOA_MODE = reg;
reg = GPIOA_AFL &
~(0xf << ((SPI1_CLOCK_PIN) * 4));
reg &=
~(0xf << ((SPI1_MOSI_PIN) * 4));
reg &= ~(0xf << ((SPI1_MISO_PIN) * 4));
reg |=
SPI1_PIN_AF << ((SPI1_CLOCK_PIN) * 4);
reg |= SPI1_PIN_AF << ((SPI1_MOSI_PIN) * 4);
reg |= SPI1_PIN_AF << ((SPI1_MISO_PIN) * 4);
GPIOA_AFL = reg;

}

The additional pin connected to the chip select of the accelerometer is PE3, which is configured as output, with a pull-up internal resistor. The logic of this pin is active-low, so that a logical zero will turn the chip on:

#define SLAVE_PIN 3
static void slave_pin_setup(void)
{
uint32_t reg;
AHB1_CLOCK_ER |= GPIOE_AHB1_CLOCK_ER;
reg = GPIOE_MODE &
~(0x03 << (SLAVE_PIN * 2));
GPIOE_MODE = reg | (1 << (SLAVE_PIN * 2));
reg =
GPIOE_PUPD & ~(0x03 << (SLAVE_PIN * 2));
GPIOE_PUPD = reg | (0x01 << (SLAVE_PIN * 2));
reg = GPIOE_OSPD & ~(0x03 << (SLAVE_PIN * 2));
GPIOE_OSPD = reg | (0x03 << (SLAVE_PIN * 2));
}

The initialization of the transceiver begins with the configuration of the four pins involved. The clock gate is then activated, and the transceiver receives a reset through a pulse through the RCC:

void spi1_setup(int polarity, int phase)
{
spi1_pins_setup();
slave_pin_setup();
APB2_CLOCK_ER |= SPI1_APB2_CLOCK_ER_VAL;
spi1_reset();

The default parameters are left untouched: MSB first, eight bit word length. The bit rate scaling factor of this controller is expressed in powers of two, starting with 2 corresponding to bit field value 0, and doubling at each increment. A generic driver should calculate the correct scaling factor, according to the desired clock rate and the peripheral clock frequency. In this simple case, we enforce a hardcoded scaling factor of 64, corresponding to the value 5.

SPI1_CR1 is then set as:

    SPI1_CR1 = SPI_CR1_MASTER | (5 << 3) |
(polarity << 1) | (phase << 0);

Finally, we set the bit corresponding to the SSOE flag in SPI1_CR2, and the transceiver is enabled:

    SPI1_CR2 |= SPI_CR2_SSOE;
SPI1_CR1 |= SPI_CR1_SPI_EN;
}

Read and write operations can now begin, as both master and slave SPI controllers are ready to perform the transactions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.207.129