In this exercise, we will use a BME280 Sensor to provide temperature, pressure, and humidity (T, P, H) readings. The sensor supports both I2C and SPI communication. We choose a breakout board that supports SPI communication. The figure below shows the BME280 module and the breakout board.
To use the sensor, please follow the specifications given in the datasheet.
The sensor (breakout board) provides six pins for hardware connection as below:
VCC GND SCL (SCK) SDA (SDI) CSB SDO
Where SDA is the serial data in (SDI) and SDO is the serial data out pin connection. The SPI interface pins should not be at a logical high level when VDD is not connected or switched off as such a configuration can permanently damage the device. As mentioned earlier, when configuring a slave on an SPI bus, make sure that pins are connected correctly, connections are solid, and the device is functional and properly powered.
The BME280 sensor offers three sensor modes: sleep mode, forced mode, and normal mode. Sleep mode is the one selected after the powering up of the sensor. So, we should configure the mode of the sensor to go to normal or forced mode after powering up. That is to say that we need to configure the mode bits of the sensor before going to read the values.
The entire communication with the BME280 is performed by reading and writing to registers that are 8-bit wide. The memory map of BME280 is shown below:
Some registers are reserved and should not be changed. The calibration data is read-only, as these values are fixed at the time of production. Control registers are read-write capable and are used to control the settings. Data registers are the values returned or stored by the sensor after performing the sensing operation and, hence, are read-only. Status and chip-id are also read-only, while reset is write-only.
Data registers provide a 20-bit pressure value, a 20-bit temperature value, and a 16-bit humidity value. As all registers are 8-bit wide, we will perform a multiple-byte read operation and then construct the value by putting bits/bytes at their proper locations. To read out the data values after conversion, it is recommended to use a burst mode and not address every register individually. Data readout is done by starting a burst read from 0xF7 to 0xFC (for temperature and pressure) or from 0xF7 to 0xFE (for temperature, pressure, and humidity). The data is read out in an unsigned 20-bit format both for temperature and pressure and an unsigned 16-bit format for humidity. These readout values represent the uncompensated measurements. The actual temperature, pressure, and humidity values are then calculated using the compensation parameters. These compensation parameters are stored in the non-volatile memory of the device at the time of production. The register address, the content, and the datatype for these compensation parameters are shown in the table below.
Most of the compensation parameters are 2-byte values. After reading from register locations, we will put bits/bytes in their proper locations to form compensation parameter values. For example, first, we read the 1-byte value from 0x88 and store it in variable a, and then we read the 1-byte value from 0x89 and store it in variable b. In the exercise, we read both bytes in one go by specifying how many bytes we want to read from a register. Now we put these bytes (a and b) at their proper location to get the value of dig_T1 (which is the temperature compensation parameter) as below:
Copy
dig_T1 = (b<<8) | a;
C
These compensation parameter values are then to be used by the compensation routines along with uncompensated environmental readings to make the correct / compensated outputs. Compensation formulas and routines are provided in the datasheet. Refer to the datasheet for more information. We use those routines as is with small modifications (in the prototype and variable datatypes).
The BME280 SPI interface is compatible with the SPI mode 0 (CPOL=0, CPHA=0) and SPI mode 3 (CPOL=1, CPHA=1).
Here are some of the important things to consider while working on this exercise:
Make sure you have your sensor datasheet and consult it whenever you feel the need.
If purchasing a BME280 sensor board, make sure you get the one with the correct sensor. If you have received a BMP280 instead, you should consult the datasheet and make the necessary changes. For example, some register addresses might be different. Also, BMP280 does not support humidity measurements.
Another important thing is to ensure that your sensor board is correctly connected to the DK (pins are correct as per the overlay) and that connections are solid.
Make sure you understand the overlay, and there are no conflicts.
Exercise steps
Open the code base of the exercise by navigating to Create a new application in the nRF Connect for VS Code extension, select Copy a sample, and search for Lesson 5 – Exercise 1.
Add the following configs in the prj.conf file that is present in the project folder.
Copy
CONFIG_GPIO=yCONFIG_SPI=y
Kconfig
Important
If you are building for a build target that includes TF-M (nrfxxxxdk_nrfxxxx_ns), you will need to disable logging for TF-M. Depending on your SoC/SiP, the UART peripheral used for TF-M could share the same base address as the SPIM 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
1.2 Include the header files for SPI, GPIO, and the devicetree.
Add SPI, GPIO and other device-related header files by including the following lines at the top of main.c.
In the boards directory of the application, inter_less5_exer1/boards, rename the overlay file to the name of the build target for the board you are using, for example nrf52840dk_nrf52840.overlay.
Important
If using a DK with a multi-core chip (nRF7002 DK, nRF9160 DK, nRF91x1 DK, and nRF5340 DK), make sure to build with Trusted Firmware-M (TF-M).
2.1 Add the SPI slave device to &spi1.
Firstly, we are disabling the peripherals that are not being used, so intentionally disabling i2c0, spi0, and i2c1 controllers and enabling the spi1 controller. When using an nRF peripheral, consult the product specification of the SoC to see if there is more than one peripheral with the same ID and ensure that only one of those is active.
Define the compatible property of &spi1 to "nordic, nrf-spim" (with the exception of nRF52 DK, due to Errata 58), status to “okay“, set the pin control states and names, and configure the chip-select bar, cs-gpio.
Then we add our sensor as a child node on the spi1 controller, name it bme280, and set the compatible property to “bosch,bme280“, which is defined in bosch,bme280-spi.yaml to indicate what kind of device it is. Since the bme280 node describes hardware on an SPI bus, then the bus type is considered when matching the node to a binding. We have also set the max frequency to 125KHz conservatively.
We will be using the last four pins of the Arduino Header as SPI pins on different DKs. These pins are located near the RESET button on all Nordic Boards.
3. Retrieve the API-specific device structure.
Use SPI_DT_SPEC_GET() to get the device structure for the BME280 node.
Here, we are getting the spi_dt_spec from the devicetree as generated through the overlay and board files. We will use spispec to use the SPI-specific functions for communication with the sensor. For the spispec, SPI_DT_SPEC_GET() will be using the SPIOP (which is defined as SPI_WORD_SET(8) | SPI_TRANSFER_MSB) to fill in the spi_config structure. SPIOP represents operation flags, which here is to say that the spi-word-size is 8-bit and the MSB should be transferred first (that is how the data over SPI lines should be interpreted).
After initialization, spispec.config will look like this:
4. Complete the code for the function bme_read_reg() that reads data from a register
The bme_read_reg() function takes in a register address reg, a data pointer *data and a number size, and reads size number of bytes from that register location and stores the values at the location pointed by data pointer.
This is done in two easy steps:
Declare and set the transmit and receive buffers
Communicate with sensor using spi_transceive_dtfunction and utilizing spispec
4.1 Declare and set the transmit and receive buffers
First, tx_spi_buf is pointing to the register address, reg, and its length is 1 byte
Next, tx_spi_buf_set is pointing to tx_spi_buf, and we are using 1 such buffer (.count = 1)
Similarly, for the reception, rx_spi_bufs is pointing to the location pointed by data, and the length of this buffer is size. Lastly, rx_spi_buf_set is pointing to rx_spi_bufs and we have 1 such buffer.
4.2 Call the transceive function
To write the register address and read the size bytes (as set by the buffers in 4.1), we will use the spi_transceive_dt() function from the SPI API with our spispec.
An important point note is that spi_transceive_dt() will first write and then read from the register into data pointer. Therefore, the first byte read will be the dummy byte, as the data will be sampled but not coming from the actual register. Therefore, to read N bytes, we need to send size = N+1, and ignore the first byte, i.e. data[0].
5. Complete the function bme_write_reg() that writes the data byte to the sensor register
In this step, we will complete the function bme_write_reg(), which takes in a register number, reg, and a data-byte value, and writes the data-byte to the sensor register.
5.1 Set the transmit buffer to point to the register address and the value we want to write.
MSB for write command is 0, so setting it using AND operation. Declare and set the transmit buffers as previously.
6. Go through bme_calibrationdata() and see how we are using the bme_read_reg() function to read different amounts of data from register locations.
The rest of the application’s functions utilize the ones we just completed. In this step, go through bme_calibrationdata() and see how we are using our defined bme_read_reg() function to read different amounts of data from register locations. As explained earlier, using the transceive function we need to set size to N+1 to read N bytes and ignore the first byte.
We are starting from 0x88, which is our calibration register number 0, and we are reading 2 bytes from that location (we have set size=3) and then we are putting the bytes in correct order to construct the dig_T1 value (that is a compensation parameter). Similarly, all of the other compensation parameter values are calculated. We store these values in a bme280_data struct variable (bmedata) which is a global data structure. Lastly, we print all of the compensation parameters. For most of the registers we need to read 2 bytes (size=3) and for few of them we need to read only 1 byte (size=2).
7. Go through bme_print_registers() and see that it uses bme_read_reg() to read and print different registers (1-byte each)
bme_print_registers() reads and prints different BME280 registers. It sets the register addresses as per the data sheet and calls bme_read_reg() to read registers one by one and print the contents to the console. We start with the device ID register, and then read more calibration registers, and then other registers, reading 1 byte from each (size=2), and printing on the screen. As we are not reading in the burst more, but rather one by one, we intentionally put a small delay of between consecutive reads.
8. Go through the compensation functions and note that they use the compensation parameters from the bme280_data structure and then store back the compensated value
The compensation code, bme280_compensate_temp(), bme280_compensate_press(), and bme280_compensate_humidity(), is taken from bme280 datasheet. These functions take in the bme280_data structure and the uncompensated value of respective parameter. These functions use the compensation parameters from the bme280_data structure to calculate the compensated value and store that back in the bme280_data structure, in the comp_temp, comp_press and comp_humidity field respectively. We will use these functions to compensate the sample values obtained from sensor.
9. Complete the function bme_read_sample() to get data samples
Now is the time to do the burst read, compensate and print the values for the temperature, pressure and humidity samples.
9.1 Store register addresses to do burst read.
As per the datasheet, to read sample values we have to read all registers from 0xF7 to 0xFE to do the burst read (to read all the registers using one go).
Set the register addresses with the following code snippet.
The rest of the function manipulates the data received, calls the compensation routines and lastly prints the data to the console. Before printing, suitable conversions are done as per the data sheet (as below):
Compensated temperature value is divided by 100, i.e: 2462 equals 24.62 degrees C
Compensated pressure value is divided by 256, i.e: 25634778 equals 100,135 Pa (or 1001 hPa)
Compensated humidity value is divided by 1024, i.e: 2023 equals 1.97%RH
10. Complete the main function
10.1 In the main function, check if the SPI device is ready.
Add the following code:
Copy
err = spi_is_ready_dt(&spispec);if (!err) {LOG_ERR("Error: SPI device is not ready, err: %d", err);return0;}
C
10.2 Call bme_calibrationdata() to read calibration data.
Call bme_calibrationdata() to fill in the bme280_data structure by reading the calibration parameters from the sensor. Add following line under step 10.2:
Copy
bme_calibrationdata();
C
10.3 Write sampling parameters and read and print the registers.
Here, we will use the bme_write_reg to write to the CTRLHUM and CTRLMEAS registers. As per the datasheet, we have to put register into active/normal mode by setting MODE bits to b’11. And to set the oversampling parameters to b’100 (i.e. 8x oversampling), we have to write 0x04 and 0x93 to the CTRLHUM and CTRLMEAS register respectively. These registers are defined at the top of the main.c. Add the following code to set sampling parameters and then print bme280 registers:
10.4 Continuously read sensor samples and toggle LED.
Now, we can use the while loop to continuously get and display the samples from our sensor. In the loop, we will call bme_read_sample() to get and print sample (T,P,H) values and toggle the LED0 using gpio_pin_toggle_dt() a visual clue that a new sample from the sensor has been read. We will put the device to sleep for DELAY_VALUES ms and then continue to sample.
To test the application, we need to connect our sensor to the DK. We are performing this test with the nRF52840 DK. From the respective overlay, recall that we have used P0.28, P0.29, P0.30 and P0.31 for the SPI SCLK, MOSI, CS and MISO pins, respectively. Therefore, we have to connect the pins of the sensor breakout board to the respective pins on the DK.
In the images below, we have made the necessary pin connections. On the sensor side, we have the VCC and GND pins required to power the sensor.
SCL (orange) – P0.28
MOSI (yellow) – P0.29
CSB (green) – P0.30
MISO (blue) – P0.31
The consideration here is that the device should not be faulty and is functional. Also make sure that the connections to the device are solid. Moreover, the connections should be as per defined in the overlay.
12. Build and flash the application to your board.
After successfully connecting the sensor to the DK, build and flash the code to your device.
You should see a similar output on your terminal (sensor values and parameters will be different, and timestamps have been omitted for clarity)
We can first see all the compensation parameters printed at the top, then 1-byte data from several registers, and then continuous data printing, with a delay between the successive readings.
You should get different readings based on environmental conditions. You may also like to check and verify the current environmental reading at your place.
Below, you can see the signal diagram using a logic analyzer on the SPI pins, which is optional but useful from a hardware perspective and also a good debugging tool. We have highlighted a single write and read transaction where we are sending the value of ID register (0xD0) on the MOSI line and then reading data from the sensor from the MISO line, that is 0x60, which is the chip-id of the sensor.
The solution for this exercise can be found in the GitHub repository, lesson5/inter_less5_exer1_solution.
Nordic Developer Academy Privacy Policy
1. Introduction
In this Privacy Policy you will find information on Nordic Semiconductor ASA (“Nordic Semiconductor”) processes your personal data when you use the Nordic Developer Academy.
References to “we” and “us” in this document refers to Nordic Semiconductor.
2. Our processing of personal data when you use the Nordic Developer Academy
2.1 Nordic Developer Academy
Nordic Semiconductor processes personal data in order to provide you with the features and functionality of the Nordic Developer Academy. Creating a user account is optional, but required if you want to track you progress and view your completed courses and obtained certificates. If you choose to create a user account, we will process the following categories of personal data:
Email
Name
Password (encrypted)
Course progression (e.g. which course you have completely or partly completed)
Certificate information, which consists of name of completed course and the validity of the certificate
Course results
During your use of the Nordic Developer Academy, you may also be asked if you want to provide feedback. If you choose to respond to any such surveys, we will also process the personal data in your responses in that survey.
The legal basis for this processing is GDPR article 6 (1) b. The processing is necessary for Nordic Semiconductor to provide the Nordic Developer Academy under the Terms of Service.
2.2 Analytics
If you consent to analytics, Nordic Semiconductor will use Google Analytics to obtain statistics about how the Nordic Developer Academy is used. This includes collecting information on for example what pages are viewed, the duration of the visit, the way in which the pages are maneuvered, what links are clicked, technical information about your equipment. The information is used to learn how Nordic Developer Academy is used and how the user experience can be further developed.
2.2 Newsletter
You can consent to receive newsletters from Nordic from within the Nordic Developer Academy. How your personal data is processed when you sign up for our newsletters is described in the Nordic Semiconductor Privacy Policy.
3. Retention period
We will store your personal data for as long you use the Nordic Developer Academy. If our systems register that you have not used your account for 36 months, your account will be deleted.
4. Additional information
Additional information on how we process personal data can be found in the Nordic Semiconductor Privacy Policy and Cookie Policy.
Nordic Developer Academy Terms of Service
1. Introduction
These terms and conditions (“Terms of Use”) apply to the use of the Nordic Developer Academy, provided by Nordic Semiconductor ASA, org. nr. 966 011 726, a public limited liability company registered in Norway (“Nordic Semiconductor”).
Nordic Developer Academy allows the user to take technical courses related to Nordic Semiconductor products, software and services, and obtain a certificate certifying completion of these courses. By completing the registration process for the Nordic Developer Academy, you are agreeing to be bound by these Terms of Use.
These Terms of Use are applicable as long as you have a user account giving you access to Nordic Developer Academy.
2. Access to and use of Nordic Developer Academy
Upon acceptance of these Terms of Use you are granted a non-exclusive right of access to, and use of Nordic Developer Academy, as it is provided to you at any time. Nordic Semiconductor provides Nordic Developer Academy to you free of charge, subject to the provisions of these Terms of Use and the Nordic Developer Academy Privacy Policy.
To access select features of Nordic Developer Academy, you need to create a user account. You are solely responsible for the security associated with your user account, including always keeping your login details safe.
You will able to receive an electronic certificate from Nordic Developer Academy upon completion of courses. By issuing you such a certificate, Nordic Semiconductor certifies that you have completed the applicable course, but does not provide any further warrants or endorsements for any particular skills or professional qualifications.
Nordic Semiconductor will continuously develop Nordic Developer Academy with new features and functionality, but reserves the right to remove or alter any existing functions without notice.
3. Acceptable use
You undertake that you will use Nordic Developer Academy in accordance with applicable law and regulations, and in accordance with these Terms of Use. You must not modify, adapt, or hack Nordic Developer Academy or modify another website so as to falsely imply that it is associated with Nordic Developer Academy, Nordic Semiconductor, or any other Nordic Semiconductor product, software or service.
You agree not to reproduce, duplicate, copy, sell, resell or in any other way exploit any portion of Nordic Developer Academy, use of Nordic Developer Academy, or access to Nordic Developer Academy without the express written permission by Nordic Semiconductor. You must not upload, post, host, or transmit unsolicited email, SMS, or \”spam\” messages.
You are responsible for ensuring that the information you post and the content you share does not;
contain false, misleading or otherwise erroneous information
infringe someone else’s copyrights or other intellectual property rights
contain sensitive personal data or
contain information that might be received as offensive or insulting.
Such information may be removed without prior notice.
Nordic Semiconductor reserves the right to at any time determine whether a use of Nordic Developer Academy is in violation of its requirements for acceptable use.
Violation of the at any time applicable requirements for acceptable use may result in termination of your account. We will take reasonable steps to notify you and state the reason for termination in such cases.
4. Routines for planned maintenance
Certain types of maintenance may imply a stop or reduction in availability of Nordic Developer Academy. Nordic Semiconductor does not warrant any level of service availability but will provide its best effort to limit the impact of any planned maintenance on the availability of Nordic Developer Academy.
5. Intellectual property rights
Nordic Semiconductor retains all rights to all elements of Nordic Developer Academy. This includes, but is not limited to, the concept, design, trademarks, know-how, trade secrets, copyrights and all other intellectual property rights.
Nordic Semiconductor receives all rights to all content uploaded or created in Nordic Developer Academy. You do not receive any license or usage rights to Nordic Developer Academy beyond what is explicitly stated in this Agreement.
6. Liability and damages
Nothing within these Terms of Use is intended to limit your statutory data privacy rights as a data subject, as described in the Nordic Developer Academy Privacy Policy. You acknowledge that errors might occur from time to time and waive any right to claim for compensation as a result of errors in Nordic Developer Academy. When an error occurs, you shall notify Nordic Semiconductor of the error and provide a description of the error situation.
You agree to indemnify Nordic Semiconductor for any loss, including indirect loss, arising out of or in connection with your use of Nordic Developer Academy or violations of these Terms of Use. Nordic Semiconductor shall not be held liable for, and does not warrant that (i) Nordic Developer Academy will meet your specific requirements, (ii) Nordic Developer Academy will be uninterrupted, timely, secure, or error-free, (iii) the results that may be obtained from the use of Nordic Developer Academy will be accurate or reliable, (iv) the quality of any products, services, information, or other material purchased or obtained by you through Nordic Developer Academy will meet your expectations, or that (v) any errors in Nordic Developer Academy will be corrected.
You accept that this is a service provided to you without any payment and hence you accept that Nordic Semiconductor will not be held responsible, or liable, for any breaches of these Terms of Use or any loss connected to your use of Nordic Developer Academy. Unless otherwise follows from mandatory law, Nordic Semiconductor will not accept any such responsibility or liability.
7. Change of terms
Nordic Semiconductor may update and change the Terms of Use from time to time. Nordic Semiconductor will seek to notify you about significant changes before such changes come into force and give you a possibility to evaluate the effects of proposed changes. Continued use of Nordic Developer Academy after any such changes shall constitute your acceptance of such changes. You can review the current version of the Terms of Use at any time at https://academy.nordicsemi.com/terms-of-service/
8. Transfer of rights
Nordic Semiconductor is entitled to transfer its rights and obligation pursuant to these Terms of Use to a third party as part of a merger or acquisition process, or as a result of other organizational changes.
9. Third Party Services
To the extent Nordic Developer Academy facilitates access to services provided by a third party, you agree to comply with the terms governing such third party services. Nordic Semiconductor shall not be held liable for any errors, omissions, inaccuracies, etc. related to such third party services.
10. Dispute resolution
The Terms of Use and any other legally binding agreement between yourself and Nordic Semiconductor shall be subject to Norwegian law and Norwegian courts’ exclusive jurisdiction.