Dialog DA14531 — I2C

I2C (Inter-Integrated Circuit), pronounced I-squared-C, is a synchronous, multi-master, multi-slave, packet switched, single-ended, serial communication bus invented in 1982 by Philips Semiconductor (now NXP Semiconductors). It is widely used for attaching lower-speed peripheral ICs to processors and microcontrollers in short-distance, intra-board communication.

The DA14531 has one I2C peripheral which supports the following features:

  • Supports Standard mode (upto 100 kHZ) and Fast mode(upto 400 kHz)
  • Clock synchronization
  • 32 x 8-bit RX and 32 x 10-bit TX FIFO
  • Master and Slave operations
  • 7 or 10 bit addressing
  • 7-bit or 10-bit combined format transfers
  • Bulk transmit mode
  • Interrupt, polled-mode or DMA operation
  • Handles bit and byte waiting at both bus speeds
  • Programmable SDA hold time

Image for post

The DA14531 SDK provides an easy to use driver which abstracts over all the required register operations.

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, a breadboard and 2 4.7 kOhm resistors. If you cannot find 4.7 kOhm resistors, you can also try resistors between 1 kOhm to 10 kOhm but this might affect the working of the circuit.

Connect these components according to the below circuit diagram

Image for post

Note: VCC in the circuit refers to 3V

Code Overview

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

The WHO_AM_I register on the LSM6DS3 provides a fixed device ID. This register should be read first to determine if the circuit and the IMU is working as expected.

First, we need to configure the pins to be used for I2C. 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_I2C_SDA);
 RESERVE_GPIO(SCL, GPIO_PORT_0, GPIO_PIN_8, PID_I2C_SCL);
}void set_pad_functions(void) {
 GPIO_ConfigurePin(GPIO_PORT_0, GPIO_PIN_11, INPUT, PID_I2C_SDA, true);
 GPIO_ConfigurePin(GPIO_PORT_0, GPIO_PIN_8, OUTPUT, PID_I2C_SCL, true);
}

The below structure defines the configuration of the I2C peripheral. We set the peripheral to Master mode and the speed as Fast i.e. 400 kHz. We also se the addressing mode to 7 bits and the address of the LSM6DS3, which is 0x6A.

Note: The address of the LSM6DS3 changes to 0x6B when the pin SD0/SA0 is connected to VCC instead of GND.

static const i2c_cfg_t i2c_cfg = {
 .clock_cfg.ss_hcnt = I2C_SS_SCL_HCNT_REG_RESET,
 .clock_cfg.ss_lcnt = I2C_SS_SCL_LCNT_REG_RESET,
 .clock_cfg.fs_hcnt = I2C_FS_SCL_HCNT_REG_RESET,
 .clock_cfg.fs_lcnt = I2C_FS_SCL_LCNT_REG_RESET,
 .restart_en = I2C_RESTART_ENABLE,
 .speed = I2C_SPEED_FAST,
 .mode = I2C_MODE_MASTER,
 .addr_mode = I2C_ADDRESSING_7B,
 .address = 0x6A,
 .tx_fifo_level = 1,
 .rx_fifo_level = 1,
};

We also need to call the I2C peripheral init function.

i2c_init(&i2c_cfg);

The structure variable definition and the calling of the init function is done in the periph_init() function in the user_periph_setup.c file.

In the user_empty_peripheral_template.c file, we create the required functions to read and write data using I2C.

void i2c_write(uint8_t reg, uint8_t * bufp, uint16_t len) {i2c_abort_t abort_code;
 i2c_master_transmit_buffer_sync( &reg, 1, &abort_code, I2C_F_NONE);
 i2c_master_transmit_buffer_sync(bufp, len, &abort_code, I2C_F_ADD_STOP);
}void i2c_read(uint8_t reg, uint8_t * bufp, uint16_t len) {
 i2c_abort_t abort_code;
 i2c_master_transmit_buffer_sync( &reg, 1, &abort_code, I2C_F_NONE);
 i2c_master_receive_buffer_sync(bufp, len, &abort_code, I2C_F_ADD_STOP);
}

We also need to write the functions to read the WHO_AM_I register and to reset the IMU.

void read_who_am_i_reg() {
 uint8_t reg_val = 0;
 while (reg_val != 0x69) {
   i2c_read(0x0F, &reg_val, 1);
 }
}void imu_reset() {
 uint8_t reg_val = 0;
 i2c_read(0x12, & reg_val, 1);
 reg_val |= 0x01;
 i2c_write(0x12, &reg_val, 1);
}

As an example, I have called these functions in the custom app_on_init callback. For more details about the custom callback, take a look at my tutorial on GPIOs, buttons and LEDs.

void user_app_on_init(void) {
 read_who_am_i_reg();
 imu_reset();
 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.