AVRly - AVR Development Resources
bme280.c
Go to the documentation of this file.
1/******************************************************************************
2 @copyright Copyright © 2022 by Jason Duffy.
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21******************************************************************************/
22
23/**
24 * @file bme280.c
25 * @ingroup bme280
26 * @author Jason Duffy
27 * @date 22nd June 2022
28 * @brief
29 * @bug No known bugs.
30 */
31
32#include <util/delay.h>
33
34#include "bme280.h"
35#include "atmega_i2c.h"
36#include "log_system.h"
37
38
39// 7-bit Device Address followed by read/write bit.
40#define BME280_ADDRESS_W 0xEC // 0xEE if SDO is pulled high
41#define BME280_ADDRESS_R 0xED // 0xEF if SDO is pulled high
42
43// Register addresses (See memory map P.27)
44#define CONFIG_REG 0xF5 // Read/Write
45#define CTRL_MEAS_REG 0xF4 // Read/Write
46#define STATUS_REG 0xF3 // Read
47#define CTRL_HUM_REG 0xF2 // Read/Write. Changes here only come into
48 // effect after CTRL_MEAS is written to.
49#define SOFT_RESET_REG 0xE0 // Write, value to perform reset is 0xB6
50#define CHIP_ID_REG 0xD0 // Read, value returned should be 0x60
51
52// Oversampling settings
53#define SKIPPED 0 // Skip, output set to 0x8000
54#define OVERSAMPLE_x1 1
55#define OVERSAMPLE_x2 2
56#define OVERSAMPLE_x4 3
57#define OVERSAMPLE_x8 4
58#define OVERSAMPLE_x16 5
59
60#define SLEEP_MODE 0
61#define FORCED_MODE 1
62#define NORMAL_MODE 3
63
64#define BUS_SPEED_100KHZ 100000U
65
66log_system_config_t bme280_log =
67{
68 .p_system_tag = "BME280",
69 .file_log_level = DEBUG,
70};
71
72
73static uint8_t BME280_read_buffer[33];
74
75// Temperature compensation variables (values taken from BME280 during init)
76static uint16_t dig_T1 = 0;
77static int16_t dig_T2 = 0;
78static int16_t dig_T3 = 0;
79
80// Pressure compensation values
81static uint16_t dig_P1 = 0;
82static int16_t dig_P2 = 0;
83static int16_t dig_P3 = 0;
84static int16_t dig_P4 = 0;
85static int16_t dig_P5 = 0;
86static int16_t dig_P6 = 0;
87static int16_t dig_P7 = 0;
88static int16_t dig_P8 = 0;
89static int16_t dig_P9 = 0;
90
91// Humidity compensation variables
92static uint8_t dig_H1 = 0;
93static int16_t dig_H2 = 0;
94static uint8_t dig_H3 = 1;
95static int16_t dig_H4 = 0;
96static int16_t dig_H5 = 0;
97static int16_t dig_H6 = 0;
98
99//###########################################################################//
100// Forward declarations for helper functions //
101//###########################################################################//
102
103void bme280_write_byte(uint8_t reg, uint8_t byte);
104void bme280_write_word(uint8_t reg1, uint8_t reg2, uint8_t msb, uint8_t lsb);
105uint16_t bme280_read_word(uint8_t start_address);
106uint8_t bme280_read_byte(uint8_t reg);
107void bme280_read_comp_data(void);
108uint32_t bme280_get_raw_temp_val(void);
109uint32_t bme280_get_raw_hum_val(void);
110void bme280_burst_read(uint8_t start_addr,
111 uint8_t start_position,
112 uint8_t length);
113int32_t bme280_compensate_temp(int32_t adc_T);
114int32_t bme280_compensate_pressure(int32_t adc_P);
115uint32_t bme280_compensate_humidity(int32_t adc_H);
116
117//###########################################################################//
118// Higher level functions for main to interract with chip //
119//###########################################################################//
120
121
122/*
123 * @brief Initialise the device ready for measurements to be taken.
124 */
125void init_bme280(void)
126{
127 // Setup I2C hardware fpr use.
128 init_i2c(BUS_SPEED_100KHZ);
129
130 // Perform ID check.
131 uint8_t id = bme280_read_byte(CHIP_ID_REG);
132 if (id == 0x60)
133 {
134 log_message(&bme280_log,
135 DEBUG,
136 "In init_bme280(), Chip ID check passed.");
137 }
138 else
139 {
140 log_message(&bme280_log,
141 ERROR,
142 "In init_bme280(), Chip ID check failed.");
143 }
144
145 // Read compensation data from sensor and save it.
147
148 //
149 bme280_write_byte(CTRL_HUM_REG, OVERSAMPLE_x1);
150
151 //
152 bme280_write_byte(CONFIG_REG, 0b10100000);
153
154 // oversample x1 temp, pressure off, forced mode
155 bme280_write_byte(CTRL_MEAS_REG, 0b00100001);
156}
157
158
159/*
160 * @brief Fetches the latest temperature data from the sensor, compensates and
161 * formats the data in degrees Celcius * 100.
162 * @returns eg. A return value of 5123 = 51.23˚C (degrees Celcius).
163 */
165{
166 int32_t retval;
167 uint32_t raw_temp_adc_val;
168
169 // oversample x1 temp, pressure off, forced mode.
170 bme280_write_byte(CTRL_MEAS_REG, 0b00100001);
171 raw_temp_adc_val = bme280_get_raw_temp_val();
172 retval = bme280_compensate_temp(raw_temp_adc_val);
173
174 return retval;
175}
176
177#if 0
178/*
179 * @brief Fetches the latest humidity data from the sensor, compensates and
180 * formats the data as %RH * 100.
181 * @returns eg. A return value of 2852 = 28.52 %RH (Relative Humidity).
182 */
183uint16_t bme280_get_humidity(void)
184{
185 uint32_t retval;
186 uint16_t raw_hum_adc_val;
187
188 // oversample x1 temp, pressure off, forced mode
189 bme280_write_byte(CTRL_MEAS_REG, 0b00100001);
190 raw_hum_adc_val = bme280_get_raw_hum_val();
191 retval = bme280_compensate_humidity(raw_hum_adc_val);
192 retval *= 100;
193 retval = (retval >> 10);
194
195 return retval;
196}
197#endif
198
199
200
201#if 1
202/*
203 * @brief Fetches the latest humidity data from the sensor, compensates and
204 * formats the data as %RH * 100.
205 * @returns eg. A return value of 2852 = 28.52 %RH (Relative Humidity).
206 */
208{
209 uint32_t retval;
210 uint16_t raw_hum_adc_val;
211
212 // oversample x1 temp, pressure off, forced mode
213 bme280_write_byte(CTRL_MEAS_REG, 0b00100001);
214 raw_hum_adc_val = bme280_get_raw_hum_val();
215 retval = bme280_compensate_humidity(raw_hum_adc_val);
216 retval *= 100;
217 retval = (retval >> 10);
218
219 return retval;
220}
221#endif
222
223/*
224 * @brief Fetches the latest pressure data from the sensor, compensates and
225 * formats the data as Pa * 100.
226 * @returns eg. A return value of 96386 = 963.86 Pa (Pascals).
227 */
229{
230 // Some pressure-getting code goes here.
231 return 0;
232}
233
234
235//###########################################################################//
236// ---------------------- Helper functions for I/O ctrl -------------------- //
237//###########################################################################//
238
239/**
240 * @brief Writes a single byte to a specified reguster.
241 * @param reg is the reguster to be written to.
242 * @param byte is the data to be sent.
243 */
244void bme280_write_byte(uint8_t reg, uint8_t byte)
245{
246 i2c_start();
247 i2c_send(BME280_ADDRESS_W);
248 i2c_send(reg);
249 i2c_send(byte);
250 i2c_stop();
251}
252
253// Reads a single byte from specified register
254uint8_t bme280_read_byte(uint8_t reg)
255{
256 uint8_t byte;
257
258 i2c_start();
259 i2c_send(BME280_ADDRESS_W);
260 i2c_send(reg);
261 i2c_start();
262 i2c_send(BME280_ADDRESS_R);
263 byte = i2c_read_no_ack();
264 i2c_stop();
265 return byte;
266}
267
268// Reads a whole section of memory addresses
269void bme280_burst_read(uint8_t start_address,
270 uint8_t start_position,
271 uint8_t length)
272{
273 i2c_start();
274 i2c_send(BME280_ADDRESS_W);
275 i2c_send(start_address);
276 i2c_start();
277 i2c_send(BME280_ADDRESS_R);
278
279 int i;
280 for (i = 0; i < (length - 1); ++i)
281 {
282 BME280_read_buffer[i] = i2c_read_ack();
283 }
284 BME280_read_buffer[i] = i2c_read_no_ack();
285
286 i2c_stop();
287}
288
289
290/**
291 * @brief Reads a 16bit unsigned integer from a specified register.
292 * @param start_address is the first memory location to be read from.
293 * @returns The data read from the device is returned.
294 */
295uint16_t bme280_read_word(uint8_t start_address)
296{
297 unsigned short msb;
298 unsigned short lsb;
299 unsigned short word;
300
301 i2c_start();
302 i2c_send(BME280_ADDRESS_W);
303 i2c_send(start_address);
304 i2c_start();
305 i2c_send(BME280_ADDRESS_R);
306
307 lsb = i2c_read_ack();
308 msb = i2c_read_no_ack();
309 i2c_stop();
310
311 word = (msb << 8);
312 word |= lsb;
313 return word;
314}
315
316
317/**
318 * @brief Reads in all compensation data from the device and stores it in a
319 * buffer.
320 */
321
323{
324 signed short h_lsb;
325 signed short h_msb;
326
327 // Read and store temperature compensation data.
328 dig_T1 = bme280_read_word(0x88);
329 dig_T2 = bme280_read_word(0x8A);
330 dig_T3 = bme280_read_word(0x8C);
331
332 // Read and store pressure compensation data.
333 dig_P1 = bme280_read_word(0x8E);
334 dig_P2 = bme280_read_word(0x90);
335 dig_P3 = bme280_read_word(0x92);
336 dig_P4 = bme280_read_word(0x94);
337 dig_P5 = bme280_read_word(0x96);
338 dig_P6 = bme280_read_word(0x98);
339 dig_P7 = bme280_read_word(0x9A);
340 dig_P8 = bme280_read_word(0x9C);
341 dig_P9 = bme280_read_word(0x9E);
342
343 // Read and store humidity compensation data.
344 dig_H1 = bme280_read_byte(0xA1);
345 dig_H2 = bme280_read_word(0xE1);
346 dig_H3 = bme280_read_byte(0xE3);
347
348 h_msb = bme280_read_byte(0xE4);
349 h_lsb = bme280_read_byte(0xE5);
350 h_lsb &= 0b00001111;
351 dig_H4 = h_lsb;
352 dig_H4 |= (h_msb << 4);
353
354 h_msb = bme280_read_byte(0xE6);
355 h_lsb = bme280_read_byte(0xE5);
356 h_lsb &= 0b11110000;
357 dig_H5 = (h_lsb >> 4);
358 dig_H5 |= (h_msb << 4);
359
360 dig_H6 = bme280_read_byte(0xE7);
361}
362
363
364uint32_t bme280_get_raw_temp_val(void)
365{
366 uint32_t retval;
367 uint32_t temp_msb;
368 uint32_t temp_lsb;
369 uint32_t temp_xlsb;
370
371 i2c_start();
372 i2c_send(BME280_ADDRESS_W);
373 i2c_send(0xFA);
374 i2c_start();
375 i2c_send(BME280_ADDRESS_R);
376 temp_msb = i2c_read_ack();
377 temp_lsb = i2c_read_ack();
378 temp_xlsb = i2c_read_no_ack();
379 i2c_stop();
380
381 retval = temp_msb << 12;
382 retval |= temp_lsb << 4;
383 retval |= temp_xlsb >> 4;
384
385 return retval;
386}
387
388uint32_t bme280_get_raw_hum_val(void)
389{
390 uint32_t retval;
391 uint16_t hum_msb;
392 uint16_t hum_lsb;
393
394 i2c_start();
395 i2c_send(BME280_ADDRESS_W);
396 i2c_send(0xFD);
397 i2c_start();
398 i2c_send(BME280_ADDRESS_R);
399 hum_msb = i2c_read_ack();
400 hum_lsb = i2c_read_no_ack();
401 i2c_stop();
402
403 retval = hum_msb << 8;
404 retval |= hum_lsb;
405
406 return retval;
407}
408
409//###########################################################################//
410// ---- Compensation formulae for converting raw ADC values to SI units ---- //
411//###########################################################################//
412
413// t_fine carries fine temperature value to be used in other comp formulae.
414int32_t t_fine;
415
416/**
417 * Returns temperature in DegC, resolution is 0.01 DegC. Output value of "5123"
418 * equals 51.23 DegC.
419 */
420int32_t bme280_compensate_temp(int32_t adc_T)
421{
422 int32_t var1, var2, T;
423 var1 = (((adc_T >> 3) - ((int32_t)dig_T1 << 1)) * ((int32_t)dig_T2)) >> 11;
424 var2 = (((((adc_T >> 4) - ((int32_t)dig_T1)) * ((adc_T >> 4)
425 - ((int32_t)dig_T1))) >> 12) * ((int32_t)dig_T3)) >> 14;
426 t_fine = var1 + var2;
427 T = (t_fine * 5 + 128) >> 8;
428 return T;
429}
430
431
432/**
433 * Returns pressure in Pa as unsigned 32 bit integer. Output value of "96386"
434 * equals 96386 Pa = 963.86 hPa.
435 */
436int32_t bme280_compensate_pressure(int32_t adc_P)
437{
438 int32_t var1, var2;
439 uint32_t p;
440 var1 = (t_fine >> 1) - 64000;
441 var2 = (((var1 >> 2) * (var1 >> 2)) >> 11 ) * ((int32_t)dig_P6);
442 var2 = var2 + ((var1 * ((int32_t)dig_P5)) << 1);
443 var2 = (var2 >> 2) + (((int32_t)dig_P4) << 16);
444 var1 = ((((int32_t)dig_P3 * (((var1 >> 2) * (var1 >> 2)) >> 13 )) >> 3)
445 + (((((int32_t)dig_P2) * var1) >> 1 ))) >> 18;
446 var1 = ((((32768 + var1)) * ((int32_t)dig_P1)) >> 15);
447
448 if (var1 == 0)
449 {
450 return 0; // avoid exception caused by division by zero
451 }
452
453 p = ((((1048576) - adc_P) - (var2 >> 12))) * 3125;
454
455 if (p < 0x80000000)
456 {
457 p = (p << 1) / (var1);
458 }
459
460 else
461 {
462 p = (p / var1) * 2;
463 }
464
465 var1 = ((dig_P9) * ((((p>>3) * (p >> 3)) >> 13))) >> 12;
466 var2 = (((p >> 2)) * (dig_P8)) >> 13;
467 p = (p + ((var1 + var2 + dig_P7) >> 4));
468 return p;
469}
470
471
472/**
473 * Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format (22
474 * integer and 10 fractional bits). Output value of "47445" represents
475 * 47445/1024 = 46.333 %RH.
476 */
477uint32_t bme280_compensate_humidity(int32_t adc_H)
478{
479 int32_t v_x1_u32r;
480
481 v_x1_u32r = (t_fine - (76800));
482
483 v_x1_u32r = (((((adc_H << 14) - (((int32_t)dig_H4) << 20)
484 - (((int32_t)dig_H5) * v_x1_u32r)) + (16384)) >> 15) * (((((((v_x1_u32r
485 * ((int32_t)dig_H6)) >> 10) * (((v_x1_u32r * ((int32_t)dig_H3)) >> 11)
486 + (32768))) >> 10) + (2097152)) * ((int32_t)dig_H2) + 8192) >> 14));
487
488 v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7)
489 * ((int32_t)dig_H1)) >> 4));
490 v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
491 v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
492 return (uint32_t)(v_x1_u32r>>12);
493}
494
void init_i2c(uint32_t bus_speed)
Sets pullups and initializes i2c clock to desired bus speed.
Definition: atmega_i2c.c:49
uint8_t i2c_read_no_ack(void)
Read in from slave, sending NOACK when done (no TWEA).
Definition: atmega_i2c.c:115
uint8_t i2c_read_ack(void)
Read in from slave, sending ACK when done (sets TWEA).
Definition: atmega_i2c.c:102
void i2c_stop(void)
Sends a stop condition (sets TWSTO).
Definition: atmega_i2c.c:80
void i2c_send(uint8_t data)
Loads data, sends it out, waiting for completion.
Definition: atmega_i2c.c:90
void i2c_start(void)
Sends a start condition (sets TWSTA).
Definition: atmega_i2c.c:70
uint16_t bme280_get_humidity(void)
Fetches the latest humidity data from the sensor, compensates and formats the data as RH * 100.
Definition: bme280.c:207
void bme280_read_comp_data(void)
Reads in all compensation data from the device and stores it in a buffer.
Definition: bme280.c:322
int32_t bme280_compensate_temp(int32_t adc_T)
Returns temperature in DegC, resolution is 0.01 DegC.
Definition: bme280.c:420
void init_bme280(void)
Initialise the device ready for measurements to be taken.
Definition: bme280.c:125
int16_t bme280_get_temperature(void)
Fetches the latest temperature data from the sensor, compensates and formats the data in degrees Celc...
Definition: bme280.c:164
int32_t bme280_compensate_pressure(int32_t adc_P)
Returns pressure in Pa as unsigned 32 bit integer.
Definition: bme280.c:436
uint16_t bme280_get_pressure(void)
Fetches the latest pressure data from the sensor, compensates and formats the data as Pa * 100.
Definition: bme280.c:228
uint32_t bme280_compensate_humidity(int32_t adc_H)
Returns humidity in RH as unsigned 32 bit integer in Q22.10 format (22 integer and 10 fractional bits...
Definition: bme280.c:477
uint16_t bme280_read_word(uint8_t start_address)
Reads a 16bit unsigned integer from a specified register.
Definition: bme280.c:295
void bme280_write_byte(uint8_t reg, uint8_t byte)
Writes a single byte to a specified reguster.
Definition: bme280.c:244
I2C communications driver for AVR MCU's.
Driver file providing logging functionality over USART, to print debug messages and values to a temin...
Config object, to be instantiated in each file the log system is to be used, then pass it's address i...
Definition: log_system.h:59