You are currently not logged in and your progress will not be saved. Register or Log in

I2C Driver

To learn how to set up the I2C driver in nRF Connect SDK, we will focus on the I2C master driver. In order to use the I2C master driver we need to perform the following steps by order.

Enabling driver

1. Enable the I2C driver by adding the following line into the application configuration file prj.conf.

CONFIG_I2C=y

2. Include the header file of the I2C API in your source code file.

#include <drivers/i2c.h>

3. Get the label property of the I2C controller devicetree node and make sure that the controller is enabled. The label is an important parameter that we will pass to generate the binding of the device driver through the device_get_binding() function.

Note

Depending on the Nordic chip used, there can be more than one I2C controller, so make sure to select the controller that is connected to your sensor.

This can be confirmed by checking the schematic of your board or development kit to locate the pins connected to SDA and SCL. We will examine this in the exercise section of this lesson. You can use the DeviceTree viewer in VS Code to display the devicetree nodes for the available I2C controllers, which will tell you which pins are used by the controller as shown in the screenshot below.

DeviceTree View in nRF Connect for VS Code
SDA and SCL pins for i2c0 node

3.1 Get a node identifier for the I2C controller devicetree node. This is done using the macro DT_NODELABEL().

#define I2C0_NODE DT_NODELABEL(i2c0)

The line above creates the node identifier symbol I2C0_NODE from the devicetree node i2c0 . The I2C0_NODE is now representing the I2C hardware controller. I2C0_NODE contains information about the pins used for SDA and SCL, a memory map of the controller, the default I2C frequency, and the label property associated with the node.

Note

The reason for extracting this information in a #define instead of passing it directly to the device_get_binding() function like we did in Lesson 5 is so we can check that the node is enabled, like we do in the next step.

This is good practice when using a peripheral that is not always enabled by default.

3.2 Make sure that the status of the node is set to okay, meaning that the device is enabled. This is done through the macro DT_NODE_HAS_STATUS().

DT_NODE_HAS_STATUS(I2C0_NODE, okay)

3.3 Get the label property of the node. The label is an important parameter that we will pass to the device_get_binding() function. This is done through the micro DT_LABEL().

#define I2C0	DT_LABEL(I2C0_NODE)

It is recommended to place the code that checks for the status of the node inside a conditional compilation, as shown below:

/* The devicetree node identifier for the "i2c0" */
#define I2C0_NODE DT_NODELABEL(i2c0)

#if DT_NODE_HAS_STATUS(I2C0_NODE, okay)
#define I2C0	DT_LABEL(I2C0_NODE)
#else
/* A build error here means your board does not have I2C enabled. */
#error "i2c0 devicetree node is disabled"
#define I2C0	""
#endif

An error will be generated (i2c0 devicetree node is disabled) and the code will not compile if i2c0 node’s status is not set to "okay" .

4. Now that we have the label property of the I2C controller devicetree node, we can simply get the binding through the function device_get_binding():

const struct device *dev_i2c = device_get_binding(I2C0);
if (dev_i2c == NULL) {
	printk("Could not find  %s!\n\r",I2C0);
	return;
}

We now have a pointer dev_i2c of type struct device that we can pass to the I2C generic API interface to perform read/write operations.

I2C Write

The simplest way to write to a slave device is through the function i2c_write(), which has the following signature:

For example, the following code snippet writes 2 bytes to an I2C slave device at the address 0x4A.

uint8_t config[2] = {0x03,0x8C};
ret = i2c_write(dev_i2c, config, sizeof(config), 0x4A);
if(ret != 0){
	printk("Failed to write to I2C device address %x at Reg. %x \n", 0x4A,config[0]);
}

I2C Read

The simplest way to read from an I2C slave device is through the function i2c_read(), which has the following signature:

For example, the following code snippet reads 1 byte from an I2C slave device at the address 0x4A.

uint8_t data;
ret = i2c_read(dev_i2c, &data, sizeof(data), 0x4A);
if(ret != 0){
	printk("Failed to read from I2C device address %x at Reg. %x \n", 0x4A,config[0]);
}

I2C Write/Read

With I2C devices, it is very common to perform a write and a read data back to back by using the function i2c_write_read(), which has the following signature:

A common scenario for this is to first write the address of an internal register to be read, then directly follow with a read to get the content of that register. We will demonstrate this in more detail in the exercise section of this lesson.

For example, the following code snippet writes the value sensor_regs[0] = 0x02 to the I2C device at the address 0x4A and then reads 1 byte from that same device and saves it in the variable temp_reading[0].

uint8_t sensor_regs[2] ={0x02,0x00};
uint8_t temp_reading[2]= {0};	
int ret = i2c_write_read(dev_i2c,0x4A,&sensor_regs[0],1,&temp_reading[0],1);
if(ret != 0){
	printk("Failed to write/read I2C device address %x at Reg. %x \n", 0x4A,sensor_regs[0]);
}