Feedback
Feedback

If you are having issues with the exercises, please create a ticket on DevZone: devzone.nordicsemi.com
Click or drag files to this area to upload. You can upload up to 2 files.

Exercise 1

v2.x.x

Connecting an STTS751 temperature sensor

For this exercise, we will use the very simple STTS751 temperature sensor, which is hosted on an X-NUCLEO-IKS01A3 expansion board. This will help us illustrate the use of the I2C API with ease.

Note

For the time being, this exercise is not supported by the nRF7002 DK

One advantage of the X-NUCLEO-IKS01A3 is that it can easily be attached to any of our development kits (with the exception of the nRF7002 DK) through the Arduino UNO R3 Connector.

X-NUCLEO-IKS01A3 expansion board
UNO R3 connector on the nRF52833 DK

When placing the board on top of the DK in the UNO R3 connector, the sensors on the expansion board connect to the nRF52833 DK through the I2C SDA and SCL lines which are wired to pins P0.26 and P0.27. This is shown in the GPIO pin mapping obtained from the nRF52833 DK schematics.

UNO R3 connector pin mapping on the nRF52833 DK

The STTS751 is a digital temperature sensor that communicates over a two-wire interface that is I2C compatible. The temperature is measured with a user-configurable resolution between 9 and 12 bits. At 9 bits, the smallest step size is 0.5 °C and at 12 bits, it is 0.0625 °C. The sensor supports different conversion rates starting from 0.0625 conversions per second up to 32. We will use the default of 1 conversion/sec.

The STTS751 has several internal registers as shown in the figure below, taken from its datasheet.

SSTS751 register map

There are three registers that are important to us, the configuration register (0x03), the temperature value high byte (0x00), and the temperature value low byte (0x02).

To read the temperature from the SSTS751 sensor, you must set up the sensor by writing the desired settings to the configuration register. Read the temperature value high byte, then read the temperature value low byte. Concatenate the two raw bytes and convert them to a temperature reading in either Celsius or Fahrenheit.

Exercise steps

1. In the GitHub repository for this course, open the base code for this exercise, found in lesson6/fund_less6_exer1 of whichever version directory you are using (v2.x.x or v1.6.0-v1.9.1).

Make sure to mount the X-NUCLEO-IKS01A3 expansion board on your development kit.

2. Let’s enable the I2C driver by adding the following line into the application configuration file prj.conf.

3. In main.c , include the header file of the I2C API.

#include <zephyr/drivers/i2c.h>

4. To display the sensor readings on the console, we will use the simple printk().

4.1 Include the header file <sys/printk.h> to use printk().

#include <zephyr/sys/printk.h>

4.2 Add the following configuration opinion to prj.conf to enable support for floating-point format specifiers, so we can print the temperature readings as floats. The reason why this option is not enabled by default is to save memory space, as enabling this option would increase code size by at least 1Kbytes.

5. Devicetree preparations

5.1 Create a folder called “boards” and place it in this exercise’s directory as shown below.

5.2 Since the sensor is external to the board, we must add an overlay file to specify this external sensor is a child to which i2c node. In the same overlay file, we also specify the sensor’s address.

If you are using any nRF52 Series DK (nRF52 DK , nRF52840 DK, nRF52833 DK), the external sensor is a child of the i2c0 node. If you are using nRF5340 DK , the external sensor is a child of the i2c1 node.

As we explained in the previous topic (I2C Driver), create an overlay file, name it nrf52833dk_nrf52833.overlay , and add the following code in the overlay file. How we got the 0x4a is explained in step 5.3

Note

When adding an overlay file to an application, a pristine build must be run before flashing. In VS Code, a warning will pop-up asking if you would like to run the pristine build now.

Important

Depending on your board, the I2C controller connected to the UNO R3 Connector can be i2c0 , i2c1 i2c2 or i2c3. Check the schematic of your board to know which I2C controller the UNO R3 Connector is connected to, like we did at the beginning of this exercise.

5.3 The 0x4a address was obtained from the datasheet of the shield indirectly, as shown below. The provided address in the datasheet is an 8-bit address 0x94 (1001 0100); therefore, it must be logically shifted to the right by 1 bit to get the 7-bit address. (1001 0100 -> 100 1010), which yields 0x4a.

I2C addresses of the sensors on the X-NUCLEO-IKS01A3 (Provided as 8-bit addresses). Source: UM2559

Some sensor vendors offer 8-bit addresses that include the read/write bit. To identify this, they usually provide separate addresses for writing and reading. In such cases, only the top seven bits of the address should be used (That is why we logically shifted the provided address in the datasheet to the right by 1). Another way to determine if a vendor is using 8-bit addresses instead of 7-bit addresses, you can also verify the address range. All 7-bit addresses should fall between the range of 0x08 to 0x77 (decimal 8 to 119). The first three bits of the address are fixed, and the remaining four bits can be programmed to any value. If your target address is beyond this range, it is likely that the sensor/shield vendor has indicated an 8-bit address.

6. Get the node identifier of the sensor. This was explained in detail in step 4 of the I2C Driver section.

#define I2C0_NODE DT_NODELABEL(mysensor)

7. Retrieve the API-specific device structure and make sure that the device is ready to use:

static const struct i2c_dt_spec dev_i2c = I2C_DT_SPEC_GET(I2C0_NODE);
if (!device_is_ready(dev_i2c.bus)) {
	printk("I2C bus %s is not ready!\n\r",dev_i2c.bus->name);
	return -1;
}

With this, we have the pointer to the device structure of the I2C controller and the sensor address (target device address) and can start using the I2C driver API to configure the sensor connected to the I2C controller.

8. Define the addresses of the relevant registers (from the sensor datasheet). Typically this information goes into a separate header file (.h). However, for the sake of keeping this demonstration simple, we will add them in main.c.

#define STTS751_TEMP_HIGH_REG            0x00
#define STTS751_TEMP_LOW_REG             0x02
#define STTS751_CONFIG_REG               0x03

9. Now we need to configure the sensor by writing to the configuration register. Recall from the beginning of this exercise that the configuration register has the address 0x03.

SSTS751 configuration register
  • We will disable the EVENT pin.
  • We want the device running in continuous conversion mode.
  • We select the bit combination that will give us the highest temperature resolution to get the most accurate reading from the sensor.

Notice that bits 0, 1, 4 and 5 are not used so they will all be 0.

b7b6b5b4b3b2b1b0
MASK1RUN/STOP0RFUTres1Tres0RFURFU
10001100
Sending 1001100 (0x8C) to configuration register

Using the table below, the bit combination we need to use is 10001100 which is 0x8C if converted to hex.

Note

Since binary literals are not natively supported in the C language, we commonly convert the value to hexadecimal and pass it as a hexadecimal literal by adding 0x as a prefix with the number.

This is the value we need to write to the configuration register, see below

	uint8_t config[2] = {STTS751_CONFIG_REG,0x8C};
	ret = i2c_write_dt(&dev_i2c, config, sizeof(config));
	if(ret != 0){
		printk("Failed to write to I2C device address %x at Reg. %x \n", dev_i2c.addr,config[0]);
		return -1;
	}

Notice how we are actually writing two bytes in the above code snippet, STTS751_CONFIG_REG and 0x8C. The reason for this is that we first need to write the address of the register that we wish to write to, i.e the configuration register (0x03). Then we write the value which will be written to the configuration register, i.e 0x8C. This is a very common operation in I2C.

10. Once the sensor is configured, reading the temperature is a straightforward task of reading the two registers temperature value high byte and temperature value low byte.

SSTS751 temperature register

In order to read the registers, we first need to write the address and then issue a read. We will use the i2c_write_read_dt() API to do that in one shot as shown below:

uint8_t temp_reading[2]= {0};
uint8_t sensor_regs[2] ={STTS751_TEMP_LOW_REG,STTS751_TEMP_HIGH_REG};
ret = i2c_write_read_dt(&dev_i2c,&sensor_regs[0],1,&temp_reading[0],1);
if(ret != 0){
	printk("Failed to write/read I2C device address %x at Reg. %x \r\n", dev_i2c.addr,sensor_regs[0]);
}
ret = i2c_write_read_dt(&dev_i2c,&sensor_regs[1],1,&temp_reading[1],1);
if(ret != 0){
	printk("Failed to write/read I2C device address %x at Reg. %x \r\n", dev_i2c.addr,sensor_regs[1]);
}

11. Now that we have the temperature reading in raw bytes, the next step to do is to convert them to Celsius and Fahrenheit.

int temp = ((int)temp_reading[1] * 256 + ((int)temp_reading[0] & 0xF0)) / 16;
if(temp > 2047)
{
	temp -= 4096;
}

// Convert to engineering units 
double cTemp = temp * 0.0625;
double fTemp = cTemp * 1.8 + 32;

//Print reading to console  
printk("Temperature in Celsius : %.2f C \n", cTemp);
printk("Temperature in Fahrenheit : %.2f F \n", fTemp);

12. Build the application and flash it on your development kit. Using a serial terminal you should see the below output:

Try blowing on the sensor, and notice an immediate change in the readings.

The solution for this exercise can be found in the GitHub repository, lesson6/fund_less6_exer1_solution of whichever version directory you are using (v2.x.x or v1.6.0-v1.9.1).

v1.6.0 – v1.9.1

Connecting an STTS751 temperature sensor

For this exercise, we will use the very simple STTS751 temperature sensor, which is hosted on an X-NUCLEO-IKS01A3 expansion board. This will help us illustrate the use of the I2C driver API with ease.

One advantage of the X-NUCLEO-IKS01A3 is that it can easily be attached to any of our development kits through the Arduino UNO R3 Connector.

X-NUCLEO-IKS01A3 expansion board
UNO R3 connector on the nRF52833 DK

When placing the board on top of the DK in the UNO R3 connector, the sensors on the expansion board connect to the nRF52833 DK through the I2C SDA and SCL lines which are wired to pins P0.26 and P0.27. This is shown in the GPIO pin mapping obtained from the nRF52833 DK schematics.

UNO R3 connector pin mapping on the nRF52833 DK

The STTS751 is a digital temperature sensor that communicates over a two-wire interface that is I2C compatible. The temperature is measured with a user-configurable resolution between 9 and 12 bits. At 9 bits, the smallest step size is 0.5 °C and at 12 bits, it is 0.0625 °C. The sensor supports different conversion rates starting from 0.0625 conversions per second up to 32. We will use the default of 1 conversion/sec.

The STTS751 has several internal registers as shown in the figure below, taken from its datasheet.

SSTS751 register map

There are three registers that are important to us, the configuration register (0x03), the temperature value high byte (0x00), and the temperature value low byte (0x02).

To read the temperature from the SSTS751 sensor, you must set up the sensor by writing the desired settings to the configuration register. Read the temperature value high byte, then read the temperature value low byte. Concatenate the two raw bytes and convert them to a temperature reading in either Celsius or Fahrenheit.

Exercise steps

1. In the GitHub repository for this course, open the base code for this exercise, found in lesson6/fund_less6_exer1 of whichever version directory you are using (v2.x.x or v1.6.0-v1.9.1).

Make sure to mount the X-NUCLEO-IKS01A3 expansion board on your development kit.

2. Let’s enable the I2C driver by adding the following line into the application configuration file prj.conf.

3. In main.c , include the header file of the I2C API.

#include <drivers/i2c.h>

4. To display the sensor readings on the console, we will use the simple printk().

4.1 Include the header file <sys/printk.h> to use printk().

#include <sys/printk.h>

4.2 Add the following configuration opinion to prj.conf to enable support for floating-point format specifiers, so we can print the temperature readings as floats. The reason why this option is not enabled by default is to save memory space, as enabling this option would increase code size by at least 1Kbytes.

5. Get the label of the I2C controller connected to your sensor. This was explained in detail in step 3 of the I2C Driver section.

/* 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

Important

Depending on your board, the I2C controller connected to the UNO R3 Connector can be i2c0 , i2c1 i2c2 or i2c3. Check the schematic of your board to know which I2C controller the UNO R3 Connector is connected to, like we did at the beginning of this exercise.

6. Now that we have the label of the I2C controller devicetree node, we can simply get the binding through the function device_get_binding() as shown below:

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

With this, we have the pointer to the device structure of the I2C controller, and can start using the I2C driver API to configure the sensor connected to the I2C controller.

7. Define the I2C slave device address (from the shield board datasheet) and the addresses of the relevant registers (from the sensor datasheet). Typically this information goes into a separate header file (.h). However, for the sake of keeping this demonstration simple, we will add them in main.c.

#define STTS751_TEMP_HIGH_REG            0x00
#define STTS751_TEMP_LOW_REG             0x02
#define STTS751_CONFIG_REG               0x03
#define STTS751_I2C_ADDRESS              0x4A

8. Now we need to configure the sensor by writing to the configuration register. Recall from the beginning of this exercise that the configuration register has the address 0x03.

SSTS751 configuration register
  1. We will disable the EVENT pin.
  2. We want the device running in continuous conversion mode.
  3. We select the bit combination that will give us the highest temperature resolution to get the most accurate reading from the sensor.

Notice that bits 0, 1, 4 and 5 are not used so they will all be 0.

b7b6b5b4b3b2b1b0
MASK1RUN/STOP0RFUTres1Tres0RFURFU
10001100
Sending 1001100 (0x8C) to configuration register

Using the table below, the bit combination we need to use is 10001100 which is 0x8C if converted to hex.

Note

Since binary literals are not natively supported in the C language, we commonly convert the value to hexadecimal and pass it as a hexadecimal literal by adding 0x as a prefix with the number.

This is the value we need to write to the configuration register, see below

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

Notice how we are actually writing two bytes in the above code snippet, STTS751_CONFIG_REG and 0x8C. The reason for this is that we first need to write the address of the register that we wish to write to, i.e the configuration register (0x03). Then we write the value which will be written to the configuration register, i.e 0x8C. This is a very common operation in I2C.

9. Once the sensor is configured, reading the temperature is a straightforward task of reading the two registers temperature value high byte and temperature value high byte.

SSTS751 temperature register

In order to read the registers, we first need to write the address and then issue a read. We will use the i2c_write_read() API to do that in one shot as shown below:


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

10. Now that we have the temperature reading in raw bytes, the next step to do is to convert them to Celsius and Fahrenheit.

int temp = ((int)temp_reading[1] * 256 + ((int)temp_reading[0] & 0xF0)) / 16;
if(temp > 2047)
{
	temp -= 4096;
}

// Convert to engineering units 
double cTemp = temp * 0.0625;
double fTemp = cTemp * 1.8 + 32;

//Print reading to console  
printk("Temperature in Celsius : %.2f C \n", cTemp);
printk("Temperature in Fahrenheit : %.2f F \n", fTemp);

11. Build the application and flash it on your development kit. Using a serial terminal you should see the below output:

Try blowing on the sensor, and notice an immediate change in the readings.

The solution for this exercise can be found in the GitHub repository, lesson6/fund_less6_exer1_solution of whichever version directory you are using (v2.x.x or v1.6.0-v1.9.1).

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

  • 8 or more characters
  • Upper and lower case letters
  • At least one number or special character

Forgot your password?
Enter the email associated with your account, and we will send you a link to reset your password.