Dialog DA14531 — Interrupts

Interrupts are an essential part of every microcontroller and microprocessor. If interrupts didn’t exist, then the CPU will have to keep checking if the data or peripheral it is waiting on is ready for some operation. An interrupt instead notifies the CPU that the specific peripheral has something of use which requires it’s attention.

All ARM Cortex cores have a peripheral called the Nested Vector Interrupt Controller(NVIC).

Image for post

The NVIC allows a specific number of interrupts to be defined and it also allows the assigning of priorities for these interrupts. The NVIC ensures that a lower priority interrupt cannot preempt a higher priority interrupt. Whereas, if a higher priority interrupt is triggered after a lower priority interrupt, the higher priority interrupt is serviced and then CPU returns to the non interrupt state without completing the execution of the lower priority interrupt.

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 hardware setup is similar to the Dialog DA14531 I2C blog with one addition to the circuit. 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.

The LSM6DS3 has 2 configurable interrupt pins which can be triggered by a variety of functions in the LSM6DS3. We will use the INT1 pin to find when new data is available to read.

Connect these components according to the below circuit diagram:

Image for post

Code Overview

In this tutorial, we will use interrupts to obtain Accelerometer data from the LSM6DS3. The LSM6DS3 will configured to set the INT1 pin when new accelerometer and gyroscope data is available. We will use ST’s standard C drivers to interface with the LSM6DS3. The repository for the drivers can be found here.

The driver has an device structure which requires the user to provide 3 inputs: A read function, a write function and a handle. The read and write functions can be in I2C or in SPI. The handle is the handle structure for the peripheral used to communicate with the LSM6DS3(It can be null too).

Below are the I2C read and write functions:

int32_t platform_i2c_write(void * handle, 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);
 return 0;
}int32_t platform_i2c_read(void * handle, 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);return 0;
}

In the periph_setup() function in the user_periph_setup.c file, we initialize the I2C peripheral and LSM6DS3 driver.

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,
};i2c_init(&i2c_cfg);

// LSM6DS3 driver init
dev_ctx.write_reg = platform_i2c_write;
dev_ctx.read_reg = platform_i2c_read;
dev_ctx.handle = NULL;

We initialize the IMU in the user_empty_peripheral_template.c file.

void imu_init(void) {
 uint8_t raw_data_buffer[14] = {0};uint8_t I2C_Response = 0xFF;
 lsm6ds3_int1_route_t int_1_reg;
 uint8_t who_am_i = 0;while (who_am_i != LSM6DS3_ID) {
   lsm6ds3_device_id_get( &dev_ctx, &who_am_i);
 }
 /* Reset IMU */
 lsm6ds3_reset_set( &dev_ctx, PROPERTY_ENABLE);
 do {
   lsm6ds3_reset_get( & dev_ctx, &I2C_Response);
 }
 while (I2C_Response);/* Enable Block Data Update */
 lsm6ds3_block_data_update_set( &dev_ctx, PROPERTY_ENABLE);/* Set full scale */
 lsm6ds3_xl_full_scale_set( &dev_ctx, LSM6DS3_2g);
 lsm6ds3_gy_full_scale_set( &dev_ctx, LSM6DS3_2000dps);/* Set Output Data Rate */
 lsm6ds3_xl_data_rate_set( &dev_ctx, LSM6DS3_XL_ODR_12Hz5);
 lsm6ds3_gy_data_rate_set( &dev_ctx, LSM6DS3_GY_ODR_12Hz5);lsm6ds3_xl_power_mode_set( &dev_ctx, LSM6DS3_XL_NORMAL);
 lsm6ds3_gy_power_mode_set( &dev_ctx, LSM6DS3_GY_NORMAL);/* Set Interrupt Pin Active High */
 lsm6ds3_pin_polarity_set( &dev_ctx, LSM6DS3_ACTIVE_LOW);lsm6ds3_int_notification_set( &dev_ctx, LSM6DS3_INT_LATCHED);/* Set Accelerometer Data-Ready Interrupt */
 lsm6ds3_pin_int1_route_get( &dev_ctx, &int_1_reg);
 int_1_reg.int1_drdy_xl = PROPERTY_ENABLE;
 lsm6ds3_pin_int1_route_set( &dev_ctx, &int_1_reg);/* Synchronise Interrupt Signal */
 lsm6ds3_xl_flag_data_ready_get( &dev_ctx, &I2C_Response);
 do {
   lsm6ds3_xl_g_raw_get( &dev_ctx, raw_data_buffer);
   lsm6ds3_xl_flag_data_ready_get( &dev_ctx, &I2C_Response);
 }
 while (I2C_Response);
}

The above function performs the following actions:

  • Read the WHO_AM_I register and waits for it to give the correct value.
  • Reset the IMU.
  • Enables the block data update setting which ensures that the interrupt will be fired only after all the 3 axis data for the accelerometer and gyroscope have been updated in the registers.
  • Set the scale, output data rate and the power mode for the Accelerometer and Gyroscope.
  • Set the INT1 pin to active high and latched. Latching it ensures that the interrupt line does not go low till the data is read.
  • Configure the INT1 pin to be mapped with the Data ready signal.

The GPIO interrupt is configured in the custom callback user_app_on_init() function. For more details about the custom callback and GPIO interrupt, take a look at my tutorial on GPIOs, buttons and LEDs.

void user_app_on_init(void)
{
   imu_init();
   GPIO_EnableIRQ(GPIO_PORT_0, GPIO_PIN_7, GPIO0_IRQn, true, false, 0);
   GPIO_RegisterCallback(GPIO0_IRQn, interrupt_handler);
   default_app_on_init();
}
The interrupt_handler() is called every time the interrupt is detected. The function also reads the accelerometer and gyroscope data from the LSM6DS3.
uint8_t accel_data_buffer[12] = {0};void interrupt_handler()
{
   lsm6ds3_xl_g_raw_get(&dev_ctx, accel_data_buffer);
   NVIC_ClearPendingIRQ(GPIO0_IRQn);
}

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. Add the accel_data_buffer variable to the expressions window. Take a look at my DA14531 ADC tutorial to check how to view the variable values by adding them to the expressions window. Pause and resume the execution multiple times to view the values change.