In my previous posts, I’ve shown how to read the ID value from a BME280 sensor using an RC2014 computer. That’s not very useful in itself, but it is a good building block for getting practical information from the sensor. In this post, I’m going to read the current temperature.
The temperature isn’t returned as a sensible, human-understandable value directly from the sensor. It needs to be calculated using calibration data that the BME280 also supplies. The BME280 datasheet gives us an algorithm to do this.

I also need to tell the BME280 to continually sense; otherwise, it is in sleep mode.
The BME280 is a very well-known sensor, and there are some libraries written for other systems that I can use to verify my work. Adafruit has a BME280 library for the Arduino that is written in C++. I am going to use some of the code from the Adafruit library where possible.
Writing and reading data
My previous post had a transfer routine to handle the bit-banging of SPI data, so I am going to build upon this.
The first routine we need is one to send and receive data of different lengths. I pass in address locations and lengths. The routine will then bit-bang that amount of data.
void write_then_read(const uint8_t *write_data, uint8_t write_length, uint8_t *read_data, uint8_t read_length) {
// Deselect the BME280 to ensure it's not selected before starting communication.
io_spi = SELECT | CLK | MOSI;
// Write data
for (uint8_t i = 0; i < write_length; i++) {
transfer(write_data[i]);
}
// Read data
for (uint8_t i = 0; i < read_length; i++) {
read_data[i] = transfer(0x00); // Send dummy byte to read
}
// Deselect the BME280 after communication is done.
io_spi = SELECT | CLK | MOSI;
}
I need to be able to read in 8 bit, 16 bit, and 24 bit values in little-endian format, both signed and unsigned, as well as to be able to write an 8 bit value. These utility routines can help with that.
void write8(uint8_t reg, uint8_t value) {
uint8_t write_data[2] = { reg & 0x7F, value }; // Clear MSB
write_then_read(write_data, 2, NULL, 0);
}
uint8_t read8(uint8_t reg) {
uint8_t write_data[1] = { reg };
uint8_t read_data[1] = { 0 };
write_then_read(write_data, 1, read_data, 1);
return read_data[0];
}
uint16_t read16_LE(uint8_t reg) {
uint8_t write_data[1] = { reg };
uint8_t read_data[2] = { 0 };
write_then_read(write_data, 1, read_data, 2);
return (read_data[1] << 8) | read_data[0];
}
int16_t readS16_LE(uint8_t reg) {
return (int16_t)read16_LE(reg);
}
uint32_t read24(uint8_t reg) {
uint8_t write_data[1] = { reg };
uint8_t read_data[3] = { 0 };
write_then_read(write_data, 1, read_data, 3);
uint32_t value = 0;
value |= read_data[0];
value <<= 8;
value |= read_data[1];
value <<= 8;
value |= read_data[2];
return value;
}
BME280 registers
The BME280 has numerous registers we can read and write to. The ID we retrieve in the previous blog post uses register 0xd0. Adafruit has defined all of these in their library, so I will reuse their code for this.
enum {
BME280_REGISTER_DIG_T1 = 0x88,
BME280_REGISTER_DIG_T2 = 0x8A,
BME280_REGISTER_DIG_T3 = 0x8C,
BME280_REGISTER_DIG_P1 = 0x8E,
BME280_REGISTER_DIG_P2 = 0x90,
BME280_REGISTER_DIG_P3 = 0x92,
BME280_REGISTER_DIG_P4 = 0x94,
BME280_REGISTER_DIG_P5 = 0x96,
BME280_REGISTER_DIG_P6 = 0x98,
BME280_REGISTER_DIG_P7 = 0x9A,
BME280_REGISTER_DIG_P8 = 0x9C,
BME280_REGISTER_DIG_P9 = 0x9E,
BME280_REGISTER_DIG_H1 = 0xA1,
BME280_REGISTER_DIG_H2 = 0xE1,
BME280_REGISTER_DIG_H3 = 0xE3,
BME280_REGISTER_DIG_H4 = 0xE4,
BME280_REGISTER_DIG_H5 = 0xE5,
BME280_REGISTER_DIG_H6 = 0xE7,
BME280_REGISTER_CHIPID = 0xD0,
BME280_REGISTER_VERSION = 0xD1,
BME280_REGISTER_SOFTRESET = 0xE0,
BME280_REGISTER_CAL26 = 0xE1, // R calibration stored in 0xE1-0xF0
BME280_REGISTER_CONTROLHUMID = 0xF2,
BME280_REGISTER_STATUS = 0XF3,
BME280_REGISTER_CONTROL = 0xF4,
BME280_REGISTER_CONFIG = 0xF5,
BME280_REGISTER_PRESSUREDATA = 0xF7,
BME280_REGISTER_TEMPDATA = 0xFA,
BME280_REGISTER_HUMIDDATA = 0xFD
};
Setting the sampling rate
The BME280 doesn’t do much unless it’s told to. So we need a routine to turn it on and get it sampling data. There are various configurations defined in the BME280 documentation, but I’m going to lift these values from the Adafruit library, as they have done the hard work of turning them into enum values already.
enum sensor_mode {
MODE_SLEEP = 0b00,
MODE_FORCED = 0b01,
MODE_NORMAL = 0b11
};
enum sensor_filter {
FILTER_OFF = 0b000,
FILTER_X2 = 0b001,
FILTER_X4 = 0b010,
FILTER_X8 = 0b011,
FILTER_X16 = 0b100
};
enum sensor_sampling {
SAMPLING_NONE = 0b000,
SAMPLING_X1 = 0b001,
SAMPLING_X2 = 0b010,
SAMPLING_X4 = 0b011,
SAMPLING_X8 = 0b100,
SAMPLING_X16 = 0b101
};
To set the sampling rate, I need to perform several operations. I first put the BME280 to sleep. This allows me to safely update the configuration and control registers.
void setSampling(enum sensor_mode mode, enum sensor_filter filter, enum standby_duration duration) {
// Set the sensor to sleep mode before changing settings
write8(BME280_REGISTER_CONTROL, MODE_SLEEP);
// Set the humidity oversampling to x1 (0b001)
write8(BME280_REGISTER_CONTROLHUMID, 0b001);
// Set the filter and standby duration in the config register
uint8_t config = (duration << 5) | (filter << 2);
write8(BME280_REGISTER_CONFIG, config);
// Set the temperature and pressure oversampling to x1 (0b001)
// and the mode
uint8_t control = (0b001 << 5) | (0b001 << 2) | mode;
write8(BME280_REGISTER_CONTROL, control);
}
Reading the calibration coefficients
I mentioned earlier the need to use calibration data to calculate the temperature. We need to get these values and store them.
typedef struct {
uint16_t dig_T1; ///< temperature compensation value
int16_t dig_T2; ///< temperature compensation value
int16_t dig_T3; ///< temperature compensation value
uint16_t dig_P1; ///< pressure compensation value
int16_t dig_P2; ///< pressure compensation value
int16_t dig_P3; ///< pressure compensation value
int16_t dig_P4; ///< pressure compensation value
int16_t dig_P5; ///< pressure compensation value
int16_t dig_P6; ///< pressure compensation value
int16_t dig_P7; ///< pressure compensation value
int16_t dig_P8; ///< pressure compensation value
int16_t dig_P9; ///< pressure compensation value
uint8_t dig_H1; ///< humidity compensation value
int16_t dig_H2; ///< humidity compensation value
uint8_t dig_H3; ///< humidity compensation value
int16_t dig_H4; ///< humidity compensation value
int16_t dig_H5; ///< humidity compensation value
int8_t dig_H6; ///< humidity compensation value
} bme280_calib_data;
bme280_calib_data _bme280_calib;
The calibration data is a mix of signed and unsigned integers of various lengths. I can use the various read functions I defined earlier to read and save these.
void readCoefficients(void) {
_bme280_calib.dig_T1 = read16_LE(BME280_REGISTER_DIG_T1);
_bme280_calib.dig_T2 = readS16_LE(BME280_REGISTER_DIG_T2);
_bme280_calib.dig_T3 = readS16_LE(BME280_REGISTER_DIG_T3);
_bme280_calib.dig_P1 = read16_LE(BME280_REGISTER_DIG_P1);
_bme280_calib.dig_P2 = readS16_LE(BME280_REGISTER_DIG_P2);
_bme280_calib.dig_P3 = readS16_LE(BME280_REGISTER_DIG_P3);
_bme280_calib.dig_P4 = readS16_LE(BME280_REGISTER_DIG_P4);
_bme280_calib.dig_P5 = readS16_LE(BME280_REGISTER_DIG_P5);
_bme280_calib.dig_P6 = readS16_LE(BME280_REGISTER_DIG_P6);
_bme280_calib.dig_P7 = readS16_LE(BME280_REGISTER_DIG_P7);
_bme280_calib.dig_P8 = readS16_LE(BME280_REGISTER_DIG_P8);
_bme280_calib.dig_P9 = readS16_LE(BME280_REGISTER_DIG_P9);
_bme280_calib.dig_H1 = read8(BME280_REGISTER_DIG_H1);
_bme280_calib.dig_H2 = readS16_LE(BME280_REGISTER_DIG_H2);
_bme280_calib.dig_H3 = read8(BME280_REGISTER_DIG_H3);
_bme280_calib.dig_H4 = ((int8_t)read8(BME280_REGISTER_DIG_H4) << 4) |
(read8(BME280_REGISTER_DIG_H4 + 1) & 0xF);
_bme280_calib.dig_H5 = ((int8_t)read8(BME280_REGISTER_DIG_H5 + 1) << 4) |
(read8(BME280_REGISTER_DIG_H5) >> 4);
_bme280_calib.dig_H6 = (int8_t)read8(BME280_REGISTER_DIG_H6);
}
Reading the temperature
The example code given in the BME280 datasheet returned the temperature as an integer, but for ease of use, I will want to use a floating-point value. This is simply the value from the datasheet divided by 100 to give us a value in Celsius. This routine makes extensive use of the calibration data from the BME280.
int32_t t_fine;
int32_t t_fine_adjust = 0;
float readTemperature(void) {
int32_t var1, var2;
int32_t adc_T = read24(BME280_REGISTER_TEMPDATA);
adc_T >>= 4;
var1 = (int32_t)((adc_T / 8) - ((int32_t)_bme280_calib.dig_T1 * 2));
var1 = (var1 * ((int32_t)_bme280_calib.dig_T2)) / 2048;
var2 = (int32_t)((adc_T / 16) - ((int32_t)_bme280_calib.dig_T1));
var2 = (((var2 * var2) / 4096) * ((int32_t)_bme280_calib.dig_T3)) / 16384;
t_fine = var1 + var2 + t_fine_adjust;
int32_t T = (t_fine * 5 + 128) / 256;
return (float)T / 100;
}
Putting it all together
I have enough code to be able to read the temperature from the BME280, so it’s time to put it all together.
The first thing I need to do is reset the BME280. I can do this by writing to the soft reset register. Secondly, I turn on the BME280 and get it sampling. Next, I read the calibration data. Finally, in a loop, I can continually read the temperature and display it to my serial terminal.
int main(void) {
write8(BME280_REGISTER_SOFTRESET, 0xB6);
setSampling(MODE_NORMAL, FILTER_OFF, SAMPLING_X16);
readCoefficients();
while(1) {
float temperature = readTemperature();
printf("Temperature: %.2f°C\n", temperature);
}
}
Compiling the code
Because I am using floating-point numbers, I need to include the maths library when compiling. I do this using the -lm flag.
The printf command doesn’t handle floating-point numbers by default, so I also need to enable this. This is done using a C pragma.
#pragma printf %f
When I compile the application using z88dk zcc, my command line looks like this.
zcc +rc2014 -subtype=basic -clib=sdcc_iy -lm -v -m -SO3 --max-allocs-per-node200000 bme280.c -o bme280 -create-app
Running the code
It was very satisfying to see real temperatures on screen when I ran the code. The thermostat in my room was set to 18C, so this seems to be working really well.











