Модуль BMP280 представляет из себя высокоточный цифровой измеритель атмосферного давления на базе микро-чипа BMP280 от фирмы BOSH. После изготовления каждый датчик проходит индивидуальную калибровку в заводских условиях.
Малые размеры, низкое энергопотребление и высокая измерительная способность модуля сделали его популярным среди множества разработчиков Arduino-проектов.
Модуль BMP280 был разработан фирмой как более технологичная модель своего предшественника BMP180.
BMP280 может подключаться к микроконтроллеру используя шины SPI и I2C.
Характеристики
- Напряжение питания модуля: 3,3 В
- Потребляемый ток: до 2 мА во время измерений (зависит от режима точности)
- Потребляемый ток: до 0,2 мА в режиме ожидания
- Измеряемое давление: от 30000 до 110000 Па (разрешение 0.16 Па, точность ±12 Па)
- Измеряемая температура: от 0 до +65 °C (разрешение 0,01°C, точность ±0,5 °C)
- Время преобразований: до 43,2 мс (зависит от режима точности)
- Рабочая частота шины I2C: до 3,4 МГц
- Подготовка к первому запуску после подачи питания: не менее 2 мс
- Рабочая температура: -40 … +85 °C
На сайте rcl-radio.ru достаточно много проектов в которых используется данный модуль и все они используют уже готовые библиотеки для запуска BMP280. Использование библиотек значительно упрощает разработку Arduino-проектов, поэтому практически любой начинающий радиолюбитель может без особого труда использовать среду программирования Arduino IDE.
Но мне было интересно изучить более подробно как управляется данный модуль, какие есть регистры в модуле и для чего они предназначены. Поэтому статья будет посвящена не очередному проекту на базе модуля BMP280, а изучению регистров и их назначению.
В даташите (BST-BMP280) подробно расписано назначение всех регистров, часть из них управляет работой датчика, другая содержит измеренное значение температуры и давления, есть несколько регистров которые содержать заводские калибровочные коэффициенты.
Таблица регистров
Первые три регистра являются тремя частями 20-и битного байта который содержит информацию (данные) об измеренной температуре. Используя данные этих трех регистров, а так же несколько формул и калибровочные коэффициенты можно получить значение температуры в виде чиcла float (23.25) или long (2325).
- 0xFA temp_msb[7:0]
- 0xFB temp_lsb[7:0]
- 0xFC (bit 7, 6, 5, 4) temp_xlsb[3:0]
Как ранее отмечалось, регистры температуры являются тремя частями 20-и битного байта, старший байт temp_msb и младший temp_lsb имеют длину по 8 бит, а так как разрешение измерения температуры может менять от 16 до 20 бит, то используется третий дополнительный регистр temp_xlsb, данные в котором появляются только тогда, когда установленное разрешение измерения температуры превышает 16 бит.
Регистры давления
- 0xF7 press_msb[7:0]
- 0xF8 press_lsb[7:0]
- 0xF9 (bit 7, 6, 5, 4) press_xlsb[3:0]
Регистры давления работают аналогично регистрам температуры, так же для получения значения давления необходимо несколько формул и несколько калибровочных коэффициентов. Так же для хранения информации о давлении используется байт длиной 20 бит.
Следующий регистр это регистр конфигурации (Register 0xF5 “config”):
Биты t_sb[2:0] отвечает за период перехода модуля в активное состояние с целью выполнения измерений:
Этот параметр отвечает как часто датчик будет проводить измерения.
Биты filter[2:0] отвечает за уровень фильтрации измеренных данных:
Этот параметр дает устранить влияние на измерения резких изменений температуры и давления.
Бит spi3w_en указывает тип шины подключения для SPI нужно указать 1, для I2C 0.
Следующий регистр ctrl_meas определяет режимы измерения и режим работы датчика BMP280.
Биты osrs_t определяют разрешение измерения температуры от 16 до 20 бит
Биты osrs_p определяют разрешение измерения давления от 16 до 20 бит
Биты mode определяют основной режим работы:
- NORMAL – в данном режиме модуль просыпается с определённой периодичностью, выполняет необходимые измерения и снова засыпает. Частота измерений задаётся программным путём, а результат считывается при необходимости.
- SLEEP – режим максимально пониженного энергопотребления.
- FORCED – этот режим позволяет будить модуль подачей внешнего управляющего сигнала. После выполнения измерений, модуль автоматически переходит в режим пониженного энергопотребления.
- бит measuring автоматически устанавливается на «1« всякий раз, когда выполняется преобразование , и возвращается на «0«, когда результаты передаются в регистры данных.
- Бит im_update автоматически устанавливается в значение «1«, когда происходит копирование данных из энергонезависимой памяти (считывание коэффициентов) и устанавливается на «0», когда копирование завершено.
Регистр reset — запись в него значения 0xB6 приводит к перезагрузке датчика.
Регистр id содержит номер ID датчика, по даташиту это 0х58.
Последние регистры 0xA1…0x88 это регистры калибровочных коэффициентов:
Каждое значение коэффициента состоит из 2-х байт.
Вот примерные значение коэффициентов указанные в даташите и данные регистров измерения температуры и давления:
Вот значения полученные из моего датчика
В даташите приводится пример кода расчетов температуры и давления:
Примеры кода расчетов имеют по два примера, первый пример для получения данных от температуре и давлении в виде чисел float (число с плавающей точкой), как пример выводит значение температуры 25.56. Этот метод расчета более точен, но подходит для микроконтроллеров не имеющих дефицита памяти. Второй метод расчета позволяет получить значения измерения температуры и давления в виде числа long, как пример выводит значение температуры 2556, подходит для микроконтроллеров с небольшим объемом памяти.
Ниже показан пример считывания данных в датчика BMP280, для подключения используется шина 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 0b110 // 000 0.5 ms // 001 62.5 ms // 010 125 ms // 011 250 ms // 100 500 ms // 101 1000 ms // 110 2000 ms // 111 4000 ms uint32_t t1,p1, temp_dig,press_dig; int32_t t2,t3,p2,p3,p4,p5,p6,p7,p8,p9; void setup() { Serial.begin(9600); Wire.begin(); I2C_write(0xE0, 0xB6);// reset I2C_write(0xF5, (STANDBY<<5) | (FILTER<<2)); I2C_write(0xF4, (OSRS_T<<5)|(OSRS_P<<2)|MODE); // osrs_t settings delay(200); t1 = (int32_t)I2C_read(0x89) << 8 | I2C_read(0x88); t2 = I2C_read(0x8B) << 8 | I2C_read(0x8A); t3 = I2C_read(0x8D) << 8 | I2C_read(0x8C); p1 = (int32_t)I2C_read(0x8F) << 8 | 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); 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.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)); 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.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(); }
Как видно на скриншоте монитора порта, расчет производится по обеим методикам (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(0xF5, (STANDBY<<5) | (FILTER<<2)); I2C_write(0xF4, (OSRS_T<<5)|(OSRS_P<<2)|MODE);
Далее сразу считываются все калибровочные коэффициенты
t1 = (int32_t)I2C_read(0x89) << 8 | I2C_read(0x88); t2 = I2C_read(0x8B) << 8 | I2C_read(0x8A); t3 = I2C_read(0x8D) << 8 | I2C_read(0x8C); p1 = (int32_t)I2C_read(0x8F) << 8 | 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);
В основном цикле loop производится считывание данных температуры и давления в виде 20-и битных байтов, вот пример считывания данных температуры:
temp_dig = (int32_t)I2C_read(0xFA)<<12 | (int32_t)I2C_read(0xFB)<<4 | (I2C_read(0xFC) & 0xF0)>>4;
Данные давления считываются аналогично:
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;