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.
This exercise is not supported by the nRF7002 DK, due to the development kit not having 3V GPIO voltage to power the shield.
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 nRF54L15 DK) through the Arduino UNO R3 Connector.
The nRF54L15 DK does not have the Arduino Uno Rev3 form factor, so we will connect the shield to the development kit using jumper wires. For this you will need four jumper cables, socket to socket.
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.
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.
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.
1. In the GitHub repository for this course, open the base code for this exercise, found in l6/l6_e1
of whichever version directory you are using.
Make sure to mount the X-NUCLEO-IKS01A3 expansion board on your development kit. (See Step 12 – If you are using the nRF54L15 DK)
2. Let’s enable the I2C driver by adding the following line into the application configuration file prj.conf
.
CONFIG_I2C=y
KconfigIf you are building with TF-M (EX: nrf5340dk/nrf5340/ns , nrf9160dk/nrf9160/ns , etc ), you will need to disable logging for TF-M. The UART peripheral used for TF-M shares the same base address as the TWIM peripheral used in this exercise, and it’s enabled by default in nRF Connect SDK 2.6.0 and above. To disable it, simply add these two Kconfig symbols in prj.conf
.
CONFIG_TFM_SECURE_UART=n
CONFIG_TFM_LOG_LEVEL_SILENCE=y
3. In main.c
, include the header file of the I2C API.
#include <zephyr/drivers/i2c.h>
C4. 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>
C4.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.
CONFIG_CBPRINTF_FP_SUPPORT=y
Kconfig5. Devicetree preparations
5.1 Create a folder called “boards” and place it in this exercise’s directory as shown below.
5.2 Add an overlay file for the board
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
&i2c0 {
mysensor: mysensor@4a{
compatible = "i2c-device";
status = "okay";
reg = < 0x4a >;
};
};
DevicetreeDepending 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.
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, and specify the sensor’s address. We also need to enable the i2c
node and configure the pins.
As we explained in the previous topic (I2C Driver), create an overlay file, name it nrf54l15dk_nrf54l15_cpuapp_ns
, and add the following code in the overlay file. How we got the 0x4a
is explained in step 5.3.
The following code snippet enables the i2c22
node, and sets the SCL line to pin P1.11, and the SDA line to pin P1.12.
&i2c22 {
status = "okay";
pinctrl-0 = <&i2c22_default>;
pinctrl-1 = <&i2c22_sleep>;
pinctrl-names = "default", "sleep";
mysensor: mysensor@4a{
compatible = "i2c-device";
status = "okay";
reg = < 0x4a >;
};
};
&pinctrl {
/omit-if-no-ref/ i2c22_default: i2c22_default {
group1 {
psels = <NRF_PSEL(TWIM_SCL, 1, 11)>,
<NRF_PSEL(TWIM_SDA, 1, 12)>;
};
};
/omit-if-no-ref/ i2c22_sleep: i2c22_sleep {
group1 {
psels = <NRF_PSEL(TWIM_SCL, 1, 11)>,
<NRF_PSEL(TWIM_SDA, 1, 12)>;
low-power-enable;
};
};
};
DevicetreeWhen 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.
5.3 The 0x
4a 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.
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)
C7. 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;
}
CWith 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
C9. 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.
Notice that bits 0, 1, 4 and 5 are not used so they will all be 0.
b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
MASK1 | RUN/STOP | 0 | RFU | Tres1 | Tres0 | RFU | RFU |
1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
Using the table below, the bit combination we need to use is 10001100 which is 0x8C if converted to hex.
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;
}
CNotice 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.
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]);
}
C11. 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);
C12. (Only for nRF54L15 DK) Set up and configure the nRF54L15 DK.
12.1 Connect the shield to the board using jumper cables.
Wire color | Shield | DK |
Red | Vio on J2 | Vbus by Port P1 |
Brown | GND on J2 | GND by Port P1 |
Green | SDA on J2 | Port P1 12 |
Blue | SCL on J2 | Port P1 11 |
12.2 Configure the board to output the correct voltage.
In nRF Connect for Desktop, install and launch the Board Configurator. The Board Configurator is a desktop app that lets you adjust the settings of the “board controller” on Nordic Development Kits (DKs).
The board controller is firmware running on the DK’s Interface MCU, which controls the DK’s operation. Using the Board Configurator, you can easily change the DK’s configuration.
In the upper left-hand corner, click Select Device, and a drown-down menu will appear with all connected devices. Select the nRF54L15 DK.
Under VDD (nPM VOUT1) make sure 3.3V is selected. Then select Write config, to write the current configuration to the board. Now, the board will give the correct output voltage to power the shield.
13. Build the application and flash it on your development kit.
Using a serial terminal you should see the below output:
*** Booting nRF Connect SDK v2.8.0-preview1-11645184a54d ***
*** Using Zephyr OS v3.7.99-adcffa835a8e ***
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.47 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.47 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.47 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.36 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.36 F
TerminalTry blowing on the sensor, and notice an immediate change in the readings.
The solution for this exercise can be found in the GitHub repository, l6/l6_e1_sol
of whichever version directory you are using.
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.
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.
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.
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.
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.
1. In the GitHub repository for this course, open the base code for this exercise, found in l6/l6_e1
of whichever version directory you are using.
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
.
CONFIG_I2C=y
KconfigIf you are building with TF-M (EX: nrf5340dk_nrf5340_ns , nrf9160dk_nrf9160_ns , etc ), you will need to disable logging for TF-M. The UART peripheral used for TF-M shares the same base address as the TWIM peripheral used in this exercise, and it’s enabled by default in nRF Connect SDK 2.6.0 and above. To disable it, simply add these two Kconfig symbols in prj.conf
.
CONFIG_TFM_SECURE_UART=n
CONFIG_TFM_LOG_LEVEL_SILENCE=y
3. In main.c
, include the header file of the I2C API.
#include <zephyr/drivers/i2c.h>
C4. 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>
C4.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.
CONFIG_CBPRINTF_FP_SUPPORT=y
Kconfig5. 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
&i2c0 {
mysensor: mysensor@4a{
compatible = "i2c-device";
status = "okay";
reg = < 0x4a >;
};
};
DevicetreeWhen 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.
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 0x
4a 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.
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)
C7. 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;
}
CWith 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
C9. 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.
Notice that bits 0, 1, 4 and 5 are not used so they will all be 0.
b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
MASK1 | RUN/STOP | 0 | RFU | Tres1 | Tres0 | RFU | RFU |
1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
Using the table below, the bit combination we need to use is 10001100 which is 0x8C if converted to hex.
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;
}
CNotice 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.
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]);
}
C11. 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);
C12. Build the application and flash it on your development kit. Using a serial terminal you should see the below output:
*** Booting nRF Connect SDK 2.6.1-3758bcbfa5cd ***
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.47 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.47 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.47 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.36 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.36 F
TerminalTry blowing on the sensor, and notice an immediate change in the readings.
The solution for this exercise can be found in the GitHub repository, l6/l6_e1_sol
of whichever version directory you are using.
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.
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.
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.
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.
1. In the GitHub repository for this course, open the base code for this exercise, found in l6/l6_e1
of whichever version directory you are using.
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
.
CONFIG_I2C=y
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.
CONFIG_CBPRINTF_FP_SUPPORT=y
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
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Euismod in pellentesque massa placerat duis. Consectetur purus ut faucibus pulvinar elementum integer. Tempor nec feugiat nisl pretium fusce id velit. Sed sed risus pretium quam vulputate. Ultrices vitae auctor eu augue ut lectus arcu bibendum.
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.
Notice that bits 0, 1, 4 and 5 are not used so they will all be 0.
b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
MASK1 | RUN/STOP | 0 | RFU | Tres1 | Tres0 | RFU | RFU |
1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
Using the table below, the bit combination we need to use is 10001100 which is 0x8C if converted to hex.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Euismod in pellentesque massa placerat duis. Consectetur purus ut faucibus pulvinar elementum integer. Tempor nec feugiat nisl pretium fusce id velit. Sed sed risus pretium quam vulputate. Ultrices vitae auctor eu augue ut lectus arcu bibendum.
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.
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:
*** Booting Zephyr OS build v2.7.0-ncs1 ***
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.47 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.47 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.47 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.36 F
Temperature in Celsius : 26.37 C
Temperature in Fahrenheit : 79.36 F
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, l6/l6_e1_sol
of whichever version directory you are using.