Найти в Дзене
Радиотехника

Модуль BME280 (Arduino)

Ранее в статье — http://rcl-radio.ru/?p=132560 был описан датчик температуры и давления BMP280, подробно рассмотрены все регистры датчика и несколько практических примеров использования датчика с различными микроконтроллера. Эту статью можно считать продолжением, так как будет рассмотрен датчик BME280, который по сути тот же датчик BMP280, но дополнительно может измерять влажность воздуха. Как и BMP280, датчик BME280 может подключаться к микроконтроллеру используя шины SPI и I2C. Регистры датчика BME280 относящиеся к измерению температуры и давления аналогичны датчику BMP280, более подробно про них можно узнать в предыдущей статье. Характеристики В даташите (BME280-datasheet-bosch) подробно расписано назначение всех регистров, часть из них управляет работой датчика, другая содержит измеренное значение температуры и давления, есть несколько регистров которые содержать заводские калибровочные коэффициенты. Таблица регистров Основное отличие от датчика BMP280 это то, что в датчике BME280

Ранее в статье — http://rcl-radio.ru/?p=132560 был описан датчик температуры и давления BMP280, подробно рассмотрены все регистры датчика и несколько практических примеров использования датчика с различными микроконтроллера.

Эту статью можно считать продолжением, так как будет рассмотрен датчик BME280, который по сути тот же датчик BMP280, но дополнительно может измерять влажность воздуха.

Как и BMP280, датчик BME280 может подключаться к микроконтроллеру используя шины SPI и I2C. Регистры датчика BME280 относящиеся к измерению температуры и давления аналогичны датчику BMP280, более подробно про них можно узнать в предыдущей статье.

Характеристики

  • Напряжение питания модуля: 3,3 В (5 В при наличии стабилизатора)
  • Потребляемый ток: до 2 мА во время измерений (зависит от режима точности)
  • Потребляемый ток: до 0,2 мА в режиме ожидания
  • Измеряемое давление: от 30000 до 110000 Па (разрешение 0.16 Па, точность ±12 Па)
  • Измеряемая температура: от 0 до +65 °C (разрешение 0,01°C, точность ±0,5 °C)
  • Диапазон измерения влажности: 0 — 100 % (20…80 %, 25 °C, ± 3%)
  • Время преобразований: до 43,2 мс (зависит от режима точности)
  • Рабочая частота шины I2C: до 3,4 МГц
  • Подготовка к первому запуску после подачи питания: не менее 2 мс
  • Рабочая температура: -40 … +85 °C

В даташите (BME280-datasheet-bosch) подробно расписано назначение всех регистров, часть из них управляет работой датчика, другая содержит измеренное значение температуры и давления, есть несколько регистров которые содержать заводские калибровочные коэффициенты.

Таблица регистров

-2

Основное отличие от датчика BMP280 это то, что в датчике BME280 добавлено два регистра для хранения данных об измеренной влажности (hum_lsb и hum_mab). Так же добавлен регистр ctrl_hum в котором при помощи битов osrs_h[2:0] можно менять кол-во выборок, что не дает большую разрядность, но может уменьшить кол-во шумов при измерении влажности.

-3

Еще добавлены несколько регистров которые содержат заводские калибровочные коэффициенты которые используются при измерении влажности.

-4

Так же немного изменены данные регистра конфигурации (Register 0xF5 “config”), а точнее биты t_sb[2:0] которые отвечают за период перехода модуля в активное состояние с целью выполнения измерений:

-5

Все остальные регистры не имеют ни каких отличий от регистров датчика BMP280.

В даташите приводится пример код расчетов влажности:

-6

Примеры кода расчетов имеют два примера, первый пример для получения данных влажности в виде чисел float (число с плавающей точкой), как пример выводит значение температуры 47.987. Этот метод расчета более точен, но подходит для микроконтроллеров не имеющих дефицита памяти. Второй метод расчета позволяет получить значения измерения влажности в виде числа long, как пример выводит значение температуры 47987, подходит для микроконтроллеров с небольшим объемом памяти.

Ниже показан пример считывания данных в датчика BME280, для подключения используется шина I2C. Информация об измерениях выводится в монитор порта.

Скетч для Arduino Nano (Uno), LGT8F328:

#include <Wire.h>

#define ADDR 0b1110110

#define OSRS_T 0b101
// 000 Skipped (output set to 0x80000)
// 001 ×1 16 bit / 0.0050 °C
// 010 ×2 17 bit / 0.0025 °C
// 011 ×4 18 bit / 0.0012 °C
// 100 ×8 19 bit / 0.0006 °C
// 101, 110, 111 ×16 20 bit / 0.0003 °C
#define OSRS_P 0b110
// 000 Skipped (output set to 0x80000)
// 001 ×1 16 bit / 2.62 Pa
// 010 ×2 17 bit / 1.31 Pa
// 011 ×4 18 bit / 0.66 Pa
// 100 ×8 19 bit / 0.33 Pa
// 101, 110, 111 ×16 20 bit / 0.16 Pa
#define MODE 0b11
// 00 Sleep mode
// 01 and 10 Forced mode
// 11 Normal mode
#define FILTER 0b001
// 000 Filter off Full
// 001 2 0.223 × ODR
// 010 4 0.092 × ODR
// 011 8 0.042 × ODR
// 100, others 16 0.021 × ODR
#define STANDBY 0b101
// 000 0.5 ms
// 001 62.5 ms
// 010 125 ms
// 011 250 ms
// 100 500 ms
// 101 1000 ms
// 110 10 ms
// 111 20 ms
#define OSRS_H 0b001
// 000 Skipped (output set to 0x80000)
// 001 ×1
// 010 ×2
// 011 ×4
// 100 ×8
// 101, 110, 111 ×16
int32_t  temp_dig,press_dig,h_dig;
uint32_t t1,p1,h1,h3;
int32_t t2,t3,p2,p3,p4,p5,p6,p7,p8,p9,h2,h4,h5,h6;

void setup() {
Serial.begin(9600);
Wire.begin();
 I2C_write(0xE0, 0xB6);// reset
 I2C_write(0xF5, (STANDBY<<5) | (FILTER<<2));
 I2C_write(0xF2, OSRS_H);
 I2C_write(0xF4, (OSRS_T<<5)|(OSRS_P<<2)|MODE); // settings
 delay(200);
 t1 = (uint32_t)I2C_read(0x89) << 8 | (uint32_t)I2C_read(0x88);
 t2 = I2C_read(0x8B) << 8 | I2C_read(0x8A);
 t3 = I2C_read(0x8D) << 8 | I2C_read(0x8C);
 p1 = (uint32_t)I2C_read(0x8F) << 8 | (uint32_t)I2C_read(0x8E);
 p2 = I2C_read(0x91) << 8 | I2C_read(0x90);
 p3 = I2C_read(0x93) << 8 | I2C_read(0x92);
 p4 = I2C_read(0x95) << 8 | I2C_read(0x94);
 p5 = I2C_read(0x97) << 8 | I2C_read(0x96);
 p6 = I2C_read(0x99) << 8 | I2C_read(0x98);
 p7 = I2C_read(0x9B) << 8 | I2C_read(0x9A);
 p8 = I2C_read(0x9D) << 8 | I2C_read(0x9C);
 p9 = I2C_read(0x9F) << 8 | I2C_read(0x9E);
 h1 = (uint32_t)I2C_read(0xA0);
 h2 = I2C_read(0xE2) << 8 | I2C_read(0xE1);
 h3 = (uint32_t)I2C_read(0xE3);
 h4 = I2C_read(0xE4) << 4 | (I2C_read(0xE5)& 0x0F);
 h5 = I2C_read(0xE6) << 4 | ((I2C_read(0xE5)& 0xF0)>>4);
 h6 = I2C_read(0xE7);
Serial.print("ID = 0x");Serial.println(I2C_read(0xD0), HEX); // ID 58
Serial.print("t1 = ");Serial.println(t1);
Serial.print("t2 = ");Serial.println(t2);
Serial.print("t3 = ");Serial.println(t3);
Serial.print("p1 = ");Serial.println(p1);
Serial.print("p2 = ");Serial.println(p2);
Serial.print("p3 = ");Serial.println(p3);
Serial.print("p4 = ");Serial.println(p4);
Serial.print("p5 = ");Serial.println(p5);
Serial.print("p6 = ");Serial.println(p6);
Serial.print("p7 = ");Serial.println(p7);
Serial.print("p8 = ");Serial.println(p8);
Serial.print("p9 = ");Serial.println(p9);
Serial.print("h1 = ");Serial.println(h1);
Serial.print("h2 = ");Serial.println(h2);
Serial.print("h3 = ");Serial.println(h3);
Serial.print("h4 = ");Serial.println(h4);
Serial.print("h5 = ");Serial.println(h5);  
Serial.print("h6 = ");Serial.println(h6);  
Serial.println();
}

void loop() {
 temp_dig = (int32_t)I2C_read(0xFA)<<12 | (int32_t)I2C_read(0xFB)<<4 | (I2C_read(0xFC) & 0xF0)>>4;

 double var1, var2, T;
 var1 = (((double)temp_dig)/16384.0 - ((double)t1)/1024.0) * ((double)t2);
 var2 = ((((double)temp_dig)/131072.0 - ((double)t1)/8192.0) *(((double)temp_dig)/131072.0 - ((double) t1)/8192.0)) * ((double)t3);
 int32_t t_fine = (int32_t)(var1 + var2);
 T = (var1 + var2) / 5120.0;

 int32_t qT;
 var1 = ((((temp_dig>>3) - ((uint32_t)t1<<1))) * ((uint32_t)t2)) >> 11;
 var2 = (((((temp_dig>>4) - ((uint32_t)t1)) * ((temp_dig>>4) - ((uint32_t)t1))) >> 12) *((int32_t)abs(t3))) >> 14;
 uint32_t qt_fine = var1 + var2;
 qT = (qt_fine * 5 + 128) >> 8;

 press_dig = (int32_t)I2C_read(0xF7)<<12 | (int32_t)I2C_read(0xF8)<<4 | (I2C_read(0xF9)&0xF0)>>4;

 double p;
 var1 = ((double)t_fine/2.0) - 64000.0;
 var2 = var1 * var1 * ((double)p6) / 32768.0;
 var2 = var2 + var1 * ((double)p5) * 2.0;
 var2 = (var2/4.0)+(((double)p4) * 65536.0);
 var1 = (((double)p3) * var1 * var1 / 524288.0 + ((double)p2) * var1) / 524288.0;
 var1 = (1.0 + var1 / 32768.0)*((double)p1);
 if (var1 == 0.0){return 0;}
 p = 1048576.0 - (double)press_dig;
 p = (p - (var2 / 4096.0)) * 6250.0 / var1;
 var1 = ((double)p9) * p * p / 2147483648.0;
 var2 = p * ((double)p8) / 32768.0;
 p = p + (var1 + var2 + ((double)p7)) / 16.0;

 int32_t zvar1, zvar2;
 uint32_t zp;
 zvar1 = (((int32_t)t_fine)>>1)-(int32_t)64000;
 zvar2 = (((zvar1>>2) * (zvar1>>2)) >> 11 ) * ((int32_t)p6);
 zvar2 = zvar2 + ((zvar1*((int32_t)p5))<<1);
 zvar2 = (zvar2>>2)+(((int32_t)p4)<<16);
 zvar1 = (((p3 * (((zvar1>>2) * (zvar1>>2)) >> 13 )) >> 3) + ((((int32_t)p2) * zvar1)>>1))>>18;
 zvar1 =((((32768+zvar1))*((int32_t)p1))>>15);
 if (zvar1 == 0){return 0; }
 zp = (((uint32_t)(((int32_t)1048576)-press_dig)-(zvar2>>12)))*3125;
 if (zp < 0x80000000){zp = (zp << 1) / ((uint32_t)zvar1);}
 else{zp = (zp / (uint32_t)zvar1) * 2;}
 zvar1 = (((int32_t)p9) * ((int32_t)(((zp>>3) * (zp>>3))>>13)))>>12;
 zvar2 = (((int32_t)(zp>>2)) * ((int32_t)p8))>>13;
 zp = (uint32_t)((int32_t)zp + ((zvar1 + zvar2 + p7) >> 4));

 h_dig = (int32_t)I2C_read(0xFD)<<8 | I2C_read(0xFE);

 double var_H;
 var_H = (((double)t_fine) - 76800.0);
 var_H = (h_dig - (((double)h4) * 64.0 + ((double)h5) / 16384.0 * var_H)) *(((double)h2) / 65536.0 * (1.0 + ((double)h6) / 67108864.0 * var_H *(1.0 + ((double)h3) / 67108864.0 * var_H)));
 var_H = var_H * (1.0 - ((double)h1) * var_H / 524288.0);
 if (var_H > 100.0){var_H = 100.0;}
 else if (var_H < 0.0){var_H = 0.0;}

 int32_t v_x1_u32r;
 v_x1_u32r = (t_fine - ((int32_t)76800));
 v_x1_u32r = (((((h_dig << 14)-(((int32_t)h4) << 20)-(((int32_t)h5) * v_x1_u32r))+((int32_t)16384)) >> 15) * (((((((v_x1_u32r * ((int32_t)h6)) >> 10) * (((v_x1_u32r *((int32_t)h3)) >> 11) + ((int32_t)32768))) >> 10) + ((int32_t)2097152))*((int32_t)h2) + 8192) >> 14));
 v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((int32_t)h1)) >> 4));
 v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
 v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
 long var_H_long = (uint32_t)(v_x1_u32r>>12);


Serial.print("T(float) = ");Serial.print(T,2);Serial.println(" °C");
Serial.print("T(int32_t) = ");Serial.println(qT);
Serial.print("P(float) = ");Serial.print(p,2); Serial.println(" Pa");
Serial.print("P(int32_t) = ");Serial.print(zp); Serial.println(" Pa");
Serial.print("P = ");Serial.print(p/133.3224,2); Serial.println(" mmHg");
Serial.print("H(float) = ");Serial.print(var_H,3); Serial.println(" %");
Serial.print("H(int32_t) = ");Serial.println(var_H_long);
Serial.println();
 delay(2000);
}

byte I2C_read(byte reg){
Wire.beginTransmission(ADDR);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(ADDR,1);
 while(
Wire.available()<1);
 byte value =
Wire.read();
 return value;
 }

void I2C_write(byte reg, byte data){  
Wire.beginTransmission(ADDR);
Wire.write(reg);
Wire.write(data);
Wire.endTransmission();
 }

Калибровочные коэффициенты моего датчика:

-7

Измеренное значение температуры, давления и влажности:

-8

Как видно на скриншоте монитора порта, расчет производится по обеим методикам (float и long).

В скетче для поддержки шины I2C используется библиотека Wire (входит в состав Arduino IDE), при помощи двух функций осуществляется запись и чтение регистров датчика:

byte I2C_read(byte reg){
Wire.beginTransmission(ADDR);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(ADDR,1);
 while(
Wire.available()<1);
 byte value =
Wire.read();
 return value;
 }

void I2C_write(byte reg, byte data){  
Wire.beginTransmission(ADDR);
Wire.write(reg);
Wire.write(data);
Wire.endTransmission();
 }

Далее происходит конфигурирование датчика, задается режим работы, состояние фильтра, разрядность измерений:

I2C_write(0xE0, 0xB6);// reset
 I2C_write(0xF5, (STANDBY<<5) | (FILTER<<2));
 I2C_write(0xF2, OSRS_H);
 I2C_write(0xF4, (OSRS_T<<5)|(OSRS_P<<2)|MODE); // settings

Далее сразу считываются все калибровочные коэффициенты

t1 = (uint32_t)I2C_read(0x89) << 8 | (uint32_t)I2C_read(0x88);
 t2 = I2C_read(0x8B) << 8 | I2C_read(0x8A);
 t3 = I2C_read(0x8D) << 8 | I2C_read(0x8C);
 p1 = (uint32_t)I2C_read(0x8F) << 8 | (uint32_t)I2C_read(0x8E);
 p2 = I2C_read(0x91) << 8 | I2C_read(0x90);
 p3 = I2C_read(0x93) << 8 | I2C_read(0x92);
 p4 = I2C_read(0x95) << 8 | I2C_read(0x94);
 p5 = I2C_read(0x97) << 8 | I2C_read(0x96);
 p6 = I2C_read(0x99) << 8 | I2C_read(0x98);
 p7 = I2C_read(0x9B) << 8 | I2C_read(0x9A);
 p8 = I2C_read(0x9D) << 8 | I2C_read(0x9C);
 p9 = I2C_read(0x9F) << 8 | I2C_read(0x9E);
 h1 = (uint32_t)I2C_read(0xA0);
 h2 = I2C_read(0xE2) << 8 | I2C_read(0xE1);
 h3 = (uint32_t)I2C_read(0xE3);
 h4 = I2C_read(0xE4) << 4 | (I2C_read(0xE5)& 0x0F);
 h5 = I2C_read(0xE6) << 4 | ((I2C_read(0xE5)& 0xF0)>>4);
 h6 = I2C_read(0xE7);

В основном цикле loop производится считывание данных влажности в виде 16-и битного байта, вот пример считывания данных:

h_dig = (int32_t)I2C_read(0xFD)<<8 | I2C_read(0xFE);

После получения данных можно произвести расчеты используя калибровочные коэффициенты, для примера получение числа float:

double var_H;
 var_H = (((double)t_fine) - 76800.0);
 var_H = (h_dig - (((double)h4) * 64.0 + ((double)h5) / 16384.0 * var_H)) *(((double)h2) / 65536.0 * (1.0 + ((double)h6) / 67108864.0 * var_H *(1.0 + ((double)h3) / 67108864.0 * var_H)));
 var_H = var_H * (1.0 - ((double)h1) * var_H / 524288.0);
 if (var_H > 100.0){var_H = 100.0;}
 else if (var_H < 0.0){var_H = 0.0;}

Как ранее отмечалось, считывание данных и расчет по поправочным коэффициентам давления и температуры в датчике BME280 осуществляется аналогично как в датчике BMP280, примеры можно посмотреть в http://rcl-radio.ru/?p=132560.