Bosch BME280


In today’s lesson, we will be connecting to the Bosch BME280. The sensor development board used in this example is the Adafruit 2652, purchased from Digi-Key (PN 11528-1359-ND). You can download the datasheet from Bosch.

The BME280 is an environmental sensor that has temperature, barometric pressure, and relative humidity in a single piece of silicon. It operates over -40C to 85C.





I2C Communications

The BME280 can communicate over I2C (IIC, or Inter-integrated Circuit) or SPI (Serial Peripheral Bus). In today’s lesson we will be using I2C. Sparkfun has a good tutorial on the basics of I2C. You can search the internet for more details if needed.

The BME280 uses a 7-bit address mode (there are 8 bits to the address though. The LSB indicates read or write mode). The 7-bit address is 0x76 (0b1110110x) or 0x77 (0b1110111x). The address select is determinded by the logical value on the SDO pin. If SDO is logical LOW, the BME280 I2C address is 0x76. If SDO is logical HIGH, the BME280 I2C address is 0x77.

This tutorial connects SDO to 3.3V, setting the address to 0x77.

Why two addresses? It’s so you can have two sensors sharing a single I2C bus. If you want more than two sensors, then you have three options. First, you can use multiple I2C busses. Each bus can have two BME280’s on them.

Second, you can use some kind of switch, relay, or transistor to ‘disable’ the chips you don’t want to communicate with (it’s better to disable the I2C clock line instead of powering the chip off. Since the chip must be configured on power-up, you don’t want the communications overhead of re-configuring the chip every time you want to communicate with it).

Third, instead of I2C, you can use SPI. The largest drawback of SPI is it requires 3 wires for the bus and a single wire per device. Three of those wires are on a common bus (clock, master out/slave in, and master in/slave out). The fourth wire is a chip select wire.

Project Setup

The BME280 project requires several drivers on the common thread. Bolded items are changed from the default.

These are:

  • I/O Port Driver
  • ELC Driver
  • CGC Driver
  • UART Driver
  • I2C Driver
  • Timer Driver

Please use the following settings for each driver:

  • I/O Port Driver
    • Common
      • Parameter Checking: Default (BSP)
    • Module
      • Name: g_ioport
  • ELC Driver
    • Common
      • Parameter Checking: Default (BSP)
    • ICU
      • ELC SOFTARE EVENT 0: Disabled
      • ELC SOFTARE EVENT 1: Disabled
    • Module
      • Name: g_elc
  • CGC Driver
    • Common
      • Parameter Checking: Default (BSP)
      • Main Oscillator Wait Time: 8163 cycles
      • Main Oscillator Clock Source: Crystal or Resonator
      • Oscillator Stop Detect: Enabled
      • Subclock Driver: Standard (12.5pf)
    • Module:
      • Name: g_cgc
  • UART Driver
    • Common
      • External RTS Operation: Disable
      • Reception: Enable
      • Transmission: Enable
      • Parameter Checking: Default (BSP)
    • ICU (You must set the Module: Channel first!)
      • SCI8 RXI: Priority 14
      • SCI8 TXI: Priority 14
      • SCI8 TEI: Priority 14
      • SCI8 ERI: Priority 14
    • Module
      • Name: g_uart
      • Channel: 8
      • Baud Rate: 9600
      • Data Bits: 8bits
      • Parity: None
      • Stop Bits: 1bit
      • CTS/RTS Selection: RTS (CTS is disabled)
      • Name of UART callback function to be defined by user: UartCallback
      • Name of UART callback function for the RTS external pin control to be defined by user: NULL
      • Clock Source: Internal Click
      • Baudrate Clock Output from SCK pin: Disable
      • Start bit detection: Falling Edge
      • Noise Cancel: Disable
  • UART Driver=>SCI Common
    • Common
      • Asychronous Mode (r_sci_uart): Enabled
      • Simple SPI Mode (R_sci_spi): Disabled
      • Simple I2C Mode (r_sci_i2c): Enabled
  • UART Driver=>DTC
    • Remove DTC from both Transmission and Reception
  • I2C Driver
    • Common
      • Parameter Checking: Default (BSP)
    • ICU (You must set the Module: Channel first!)
      • SCI4 RXI: Priority 14
      • SCI4 TXI: Priority 14
      • SCI4 TEI: Priority 14
      • SCI4 ERI: Priority 14
    • Module:
      • Name: g_i2c
      • Channel: 4
      • Rate: Fast-mode
      • Slave Address: 0x00
      • Address Mode: 7-Bit
      • SDA Output Delay (nano seconds): 300
      • Callback: NULL
  • Timer Driver
    • Common
      • Parameter Checking: Default (BSP)
    • ICU
      • GPT0 COUNTER OVERFLOW: Disabled
    • Module
      • Name: g_timer
      • Channel: 0
      • Mode: Periodic
      • Period Value: 0
      • Period Unit: Milliseconds
      • Duty Cycle Value: 50
      • Duty Cycle Unit: Unit Raw Counts
      • Auto Start: True
      • GTIOCA Output Enabled: False
      • GTIOCA Stop Level: Pin Level Low
      • GTIOCB Output Enabled: False
      • GTIOCB Stop Level: Pin Level Low
      • Callback: NULL

The Timer Driver has a period value of 0. Since we are not using the overflow feature, the period value is never used by the SSP code.

At the time of this writing, there is a bug in the e2Studio (v5.2.0.020 and SSP v1.1.3). When you create a UART Driver and set the channel, it automatically sets the pins to the correct mode (Peripherial Controlled). This does not happen when you add an I2C Driver.

Switch the Synergy Configuration to the Pins tab. Expand the Peripherals, then SCI0_2_4_6_8, and click on SCI4.

Ensure that Operation Mode is set to Simple I2C (SDA/SCL) and the Input/Output pins for RXD4_SCL4_MISO4 is P511 and TXD4_SDA4_MOSI4 is P512.

If you used a different channel on your board (other than channel 4) you will need to update the pins for that channel.

Hardware Hookup

On the DK-S7G2, the I2C SCI 4 has the clock pin on P511 and the data pin on P512. The BME280 development board requires a 5.0 VCC (there is a 3.3V pin, but this is the output from the onboard 3.3V regulator. Due to the level shifting used on the board, the 5.0V supply is required…I spent about two hours trying to figure out why things weren’t working with a 3.3V supply voltage…).

The BME280 development board has onboard pull-up resistors so there is no need for extra hardware there.

Connect the following pins:

BME280 Dev Board DK-S7G2
Pin 1 (VIN)  J9: 5V
Pin 2 (3Vo)  Not Connected
Pin 3 (GND)  J9: GND
Pin 4 (SCK)  J9: P511
Pin 5 (SDO)  J9: GND
Pin 6 (SDI)  J9: P512
Pin 7 (CS)  J9: 3.3V

The Code

The primary code follows a similar flow as the previous lessons. First we open the UART device, then open each peripherial while sending updates to the UART console. At the end, we connect to the BME280, retrieve the Chip ID (a good way to verify that you are connected correctly to the chip), retrieve the calibration factors, then enter a loop where we continously update and output the sensor values.

The full code is on GitHub. Below I will hilight the more important parts.


I finally dug though the SSP API to find out how to get the clock speed of a peripherial clock.

The g_cgc global variable has the relevent call:

uint32_t clockFrequency;
g_cgc_on_cgc.systemClockFreqGet (CGC_SYSTEM_CLOCKS_PCLKA, &clockFrequency);

uint32_t ticksPerMillisecond;
ticksPerMillisecond = clockFrequency / 1000;

These four lines retrieve the clock value for PCLKA which the GPT uses for its clock source. We then divide the clock rate by 1000 to get the tick rate per milliseconds (remember, we set the Timer Driver=>Period Unit to milliseconds.

It’s important to note that a call to TimerSleepMs() is a blocking call.


The I2C file wraps lower level SSP calls into helper functions. These incude functions to read and write single bytes, and read and write multiple bytes.

An important note about the I2C functions. The SSP gets the slave address from the g_i2c data structure. In order to talk to different devices, you have to set the slave address first.

This is handled in the I2C functions with a call to g_i2c.p_ctrl->info.slave.


Bme280Open() verifies the Chip ID is a match (indicating that we have successfully connected to the chip.

It then configures the main configuration register (address 0xF5) and configures the measurement options (addresses 0xF2 and 0xF4).

The config register is set for a 0.5ms standby, a filter coefficient of 16, and SPI disabled. (value 0b00010000).

t_sb = 0b000 (0.5ms)
filter = 0b100 (16)
Bit 1: Not Used, set to 0b0
spi3w_en = 0b0 (SPI disable)


We next set the humidity control to oversampling x 16 (value 0b00000101).

Bits 7-3: Not Used, set to 0b0
osrs_h: 0b101 (oversampling x 16)

It’s important to note that writes to ctrl_hum do not take effect until a write to ctrl_meas. This means if you write to ctrl_meas, then ctrl_hum, the humidity control settings will not go into effect.


Lastly we configure the measurement options for pressure and temperature. We set both oversampling to 16x and put the measurement mode to normal mode (value 0b10110111).

osrs_t: 0b101
osrs_p: 0b101
mode: 0b11


Normal mode puts the sensor in continuous measurement with a sleep cycle of 0.5ms.


After configuring the BME280, we retrieve the calibration factors. This is performed in two separate read cycles. The first cycle retrieves 25 bytes starting at address 0x88. The second memory read is 7 bytes and starts at memory address 0xE1.

Mapping the raw data to individual calibration factors is pretty straight forward until you reach H4 and H5. Bosch split a single byte into two and assigned them to different parts of H4 and H5.

To read any sensor measurement, a call to Bme280Get<Sensor Name> is required. You pass the BME280 address, calibration factors, and a pointer to a floating point which will hold the result.

Each function reads the raw ADC values and uses the calibration factors to determine the measurement. The math is fairly complicated and it’s best to either use the API Bosch provides (posted on GitHub) or to copy the code from the datasheet. Since the Bosch API would require quite a bit of modification to get it to use the SSP I2C driver, I opted to use the datasheet code.

An important note about the BME280 code: As the code is written, each measurement is read one at a time. The datasheet recommends to read the registers in bulk and convert each result after the bulk read.

It would be a good exercise for you to make the necessary changes. One way to go about it would to be to create an GetMeasurements function, pass a pointer to a byte array, and modify the Get<Sensor Name> functions to use the ADC data passed to them instead of performing the reads.

That’s it for this lesson! You can download the code from GitHub.



Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s