Dialog DA14531 — SPI

Serial Peripheral Interface (SPI) is a synchronous serial communication interface specification used for short-distance communication, primarily in embedded systems. The interface was developed by Motorola in the mid-1980s and has become a de facto standard.

SPI is a full duplex protocol which uses 4 pins. One might think that I2C seems like a better protocol as it uses only 2 pins but SPI has a higher clock frequency than I2C. It also uses Push Pull to control the lines rather than an Open collector system, which removes the need for pull up resistors.

SPI can be thought of as 2 shift registers which are connected by wires.

Image for post

The DA14531 supports the following parameters for SPI:

  • Master and Slave mode
  • 4 bit to 32 bit operation
  • Master clock line upto 32 MHz, Slave clock line upto 16 MHz
  • 1 byte x 4 byte individual FIFO s for RX and TX
  • Blocking, Interrupt and DMA modes

Project setup

The project with the code for this tutorial is available on Github at https://github.com/vicara-hq/da14531-tutorials

Download the project and copy it. The project has to be placed inside the Dialog SDK6 folder. Navigate to <SDK6_ROOT>/projects/target_apps/template and paste it in this folder. The project is a modified version of the empty_peripheral_template project provided by Dialog. But to keep this tutorial series as open source as possible, all the following steps will use the SmartSnippets Studio.

Hardware overview

We will use a DA145xx Pro motherboard with a DA14531 Tiny module daughterboard with a STEVAL-MKI160V1. The STEVAL-MKI160V1 is an evaluation kit for the LSM6DS3, which is a 6-axis IMU. Other than this, we will also need a few jumpers and a breadboard.

Image for post

Note: VCC in the circuit refers to 3V


Code Overview

The aim of this tutorial is to use SPI to read the WHO_AM_I register and reset the IMU.

You can take a look at my blog on the I2C peripheral on the DA14531 as I have used the LSM6DS3 in it.

First, we need to configure the pins to be used for SPI in the user_periph_setup.c file. I would suggest that you use the same pins as mentioned in the below code as other pins are connected to other components like buttons, LEDs on the daughterboard.

void GPIO_reservations(void) {
 RESERVE_GPIO(SDA, GPIO_PORT_0, GPIO_PIN_11, PID_SPI_DO);
 RESERVE_GPIO(SCL, GPIO_PORT_0, GPIO_PIN_6, PID_SPI_DI);
 RESERVE_GPIO(SCL, GPIO_PORT_0, GPIO_PIN_8, PID_SPI_CLK);
 RESERVE_GPIO(SCL, GPIO_PORT_0, GPIO_PIN_7, PID_SPI_EN);
}void set_pad_functions(void) {
 GPIO_ConfigurePin(GPIO_PORT_0, GPIO_PIN_11, OUTPUT, PID_SPI_DO, true);
 GPIO_ConfigurePin(GPIO_PORT_0, GPIO_PIN_6, INPUT, PID_SPI_DI, true);
 GPIO_ConfigurePin(GPIO_PORT_0, GPIO_PIN_8, OUTPUT, PID_SPI_CLK, true);
 GPIO_ConfigurePin(GPIO_PORT_0, GPIO_PIN_7, OUTPUT, PID_SPI_EN, true);
}

Next, in the periph_init() function, we need to initialize the SPI peripheral.

// Configuration struct for SPI
static const spi_cfg_t spi_cfg = {
 .spi_ms = SPI_MS_MODE_MASTER,
 .spi_cp = SPI_CP_MODE_0,
 .spi_speed = SPI_SPEED_MODE_2MHz,
 .spi_wsz = SPI_MODE_8BIT,
 .spi_cs = SPI_CS_0,
 .cs_pad.port = GPIO_PORT_0,
 .cs_pad.pin = GPIO_PIN_7,
 #if defined(CFG_SPI_DMA_SUPPORT)
 .spi_dma_channel = SPI_DMA_CHANNEL_01,
 .spi_dma_priority = DMA_PRIO_0,
 #endif
};spi_initialize(&spi_cfg);

The above structure configures the SPI peripheral in Master mode with a clock frequency of 2 MHz, word size of 8 bits and Mode 0. SPI has 4 modes which decide when the signal lines need to be sampled. In the DA14531, the SPI Peripheral provides the option to use 2 CS(Chip Select) lines or to use any GPIO as a CS line or to not use a CS line.

In the user_empty_peripheral_template.c, we will add a read_who_am_i_reg() function which has a different implementation when compared to it’s I2C counterpart.

void read_who_am_i_reg() {
 uint8_t reg_val = 0;
 while (reg_val != 0x69) {
   uint8_t reg_addr = 0x80 | 0x0F;
   spi_cs_low();
   spi_send( & reg_addr, 1, SPI_OP_BLOCKING);
   spi_receive( & reg_val, 1, SPI_OP_BLOCKING);
   spi_cs_high();
 }
}
As done in my DA14531 I2C blog, the read_who_am_i_reg() is called in the user_app_on_init() function.
void user_app_on_init(void) {
 read_who_am_i_reg();
 default_app_on_init();
}

Testing

Build the project and start the debugger with the DA14531 connected to your computer. If there are any issues with the working of the circuit, the execution of the program will stop. You can also place a breakpoint in the read_who_am_i_reg() function and step through it to view the value read from the LSM6DS3.