Датчик давления BMP085

BMP085
Датчик давления BMP085 является довольно таки точным и одновременно не сложным в использовании. Был разработан товарищами из Bosch и общается, как и подобает подобного рода датчикам, по каналу I2C. Помимо давления он так же выдает температуру с точностью до десятой доли градуса. Всю остальную информацию о нем вы можете посмотреть в даташите, который без особого труда сможете найти в интернетах.

В данном посте я вкратце объясню как пользоваться датчиком и как заставить его работать с STM8L на примере kit-а STM8L-Discovery. Процесс получения «человеческих» данных из тех чисел, что дает нам датчик довольно хитер и требует загрузки целой тучи калибровочных коэффициентов из внутренней памяти датчика, но прошу не пугаться. На деле это не особо сложно.
Для начала работы настроим тактирование:
CLK_CKDIVR = 0x04;           // System clock source /16 == 1Mhz
  CLK_ICKCR_bit.HSION = 1;     // Clock ready
  CLK_PCKENR1_bit.PCKEN13 = 1; // I2C clock enable
  CLK_PCKENR1_bit.PCKEN15 = 1; // UART clock enable 

Будем тактировать от внутреннего резонатора. Поделим его на 16 для получения 1Мгц, так было легче считать коэффициенты для настройки UART и I2C. Как вы уже заметили, мы будем использовать UART. Он нам нужен будет для вывода полученных данных. Вот его настройки:
// UART init
  PC_DDR_bit.DDR3 = 0;
  PC_CR1_bit.C13 = 1;
  PC_CR2_bit.C23 = 1;

  USART1_CR1 = 0;
  USART1_CR3 = 0;
  USART1_CR4 = 0;
  USART1_CR5 = 0;
  
  USART1_BRR2 = 0x08;
  USART1_BRR1 = 0x06;
  
  USART1_CR2_bit.TEN = 1;
  USART1_CR2_bit.REN = 1;
  USART1_CR2_bit.RIEN = 1;

Предупрежу, что если вы используете микроконтроллер в связке с другими модулями, а не переходником UART-USB. То настройка порта для передачи (TX) — обязательна. Иначе же на TX пине МК не будет верхнего уровня вообще, а следовательно ни о каком UART-е речи идти и не может. Так что в любом случае указываем настройки порта.
Для работы с датчиком нам понадобится I2C. Собственно:
// I2C init
  I2C_FREQR = 0x01;            
  I2C_CCRL = 0x32;             
  I2C_TRISER = 0x02;           
  I2C_CR1_bit.PE = 1;         
  
  I2C_OARL = 0xA0;
  I2C_OARH_bit.ADDCONF = 1;

А теперь о работе по I2C. Процесс несколько сложнее чем с UART, но все это можно упростить запихнув все в отдельные функции. И так. Все происходит как по учебнику, а то есть даташиту. Начнем с самого сложного, чтения данных с датчика:
char getData(char address) {
  char result;
  
  I2C1_CR2_bit.START = 1; // Отсылаем старт бит
  while(!(I2C1_SR1_bit.SB)); // Ждем флаг об успешной отправке
  
  I2C1_DR = 0xEE; // Передаем адрес устройства с битом записи (последний бит)
  while(!(I2C1_SR1_bit.ADDR)); // Опять же ожидаем кучу флагов
  while(!(I2C1_SR1_bit.TXE));   // Очень важно соблюсти порядок чтения
  while(!(I2C1_SR3_bit.TRA));   // Иначе есть риск зависнуть в бесконечном цикле
  
  I2C1_DR = address;  // Адрес от куда хотим читать
  while(!(I2C1_SR1_bit.TXE)); // Снова флаги
  while(!(I2C1_SR1_bit.BTF));
    
  I2C1_CR2_bit.START = 1; // Повторный старт бит или "рестарт"
  while(!(I2C1_SR1_bit.SB));
  
  I2C1_DR = 0xEF; // Передаем адрес устройства, но уже с битом о чтении
  while(!(I2C1_SR1_bit.ADDR)); 
  while(I2C1_SR3_bit.TRA);
 
  while(!(I2C1_SR1_bit.RXNE));  
  result = I2C1_DR;  // Получаем результат
  
  while(!(I2C1_SR1_bit.BTF));
  
  I2C1_CR2_bit.STOP = 1; // Стоп бит
  
  return result;
}

Вот такой нехитрой функцией мы получили один байт по необходимому нам адресу. Есть режимы для чтения сразу нескольких байтов. Но по какой то причине они у меня не заработали. Посему читаем по байту. 
В начале поста я говорил о калибровочных коэффициентах которые нам понадобятся для перевода чисел с датчика в паскали. Табличку с ними мы можем увидеть из даташита к датчику:
 Калибровочные коэфициенты
Чтение всех этих коэффициентов является частью инициализации нашей программы. Посему сделаем функцию для чтения двух байт и прочтем все эти коэффициенты в свои переменные
unsigned short getTwoByte(char address) {
  char MSB = getData(address);
  char LSB = getData(address+1);   
  return (MSB<<8) + LSB;
}

 
AC1 = getTwoByte(0xAA);
  AC2 = getTwoByte(0xAC);        
  AC3 = getTwoByte(0xAE);     
  AC4 = getTwoByte(0xB0);
  AC5 = getTwoByte(0xB2);
  AC6 = getTwoByte(0xB4);
  B1 = getTwoByte(0xB6); 
  B2 = getTwoByte(0xB8);
  MB = getTwoByte(0xBA);
  MC= getTwoByte(0xBC);
  MD = getTwoByte(0xBE);

Ну и напоследок. Разрешаем все прерывания и уходим в бесконечный цикл:
asm("RIM");
  while (1);

С начальной инициализацией мы закончили. Теперь приступим к измерениям. Датчик может производить измерения в двух режимах. Быстрый — меньшая точность измерений и более долгий, соответственно точность измерений выше. Мы будем реализовывать «долгий» режим. Куда нам торопиться :)
Функция для отправки команды для старта подсчета давления:
void startMeasurement(char action) {
  I2C1_CR2_bit.START = 1;       
  while(!(I2C1_SR1_bit.SB));
  
  I2C1_DR = 0xEE;
  while(I2C1_SR1_bit.ADDR);
  while(!(I2C1_SR1_bit.TXE));
  while(!(I2C1_SR3_bit.TRA));
  
  I2C1_DR = 0xF4;
  while(!(I2C1_SR1_bit.TXE));
  
  I2C1_DR = action;
  while(!(I2C1_SR1_bit.TXE));
  while(!(I2C1_SR1_bit.BTF));
  
  I2C1_CR2_bit.STOP = 1;
}

Для управления измерениями я использую UART. Когда на UART приходит символ единички '1', то следует начать измерять данные и отправлять результат обратно. Все реализовано прямо в прерывании. Посему не забудем выставить приоритеты, если это будет необходимо.
#pragma vector=USART_R_OR_vector
__interrupt void USART_RXNE(void)
{ 
  char recive = USART1_DR;
  
  if (recive == '1') {
    clear();  // Очищает все промежуточные данные
    // Start temperature measurement
    startMeasurement(0x2E); // Отправляем команду на старт вычислений температуры
    delay(5); // ждем...
    // Get data    
    UT = getTwoByte(0xF6); // Берем два байта температуры
        
    // Start pressure measurement 
    startMeasurement(0xF4); // Теперь требуем давление
    delay(5); 
    // Get data
    UP = (getTwoByte(0xF6)<<8) + getData(0xF8); // Тут слегка похитрее, так как нам необходимо взять три байта
    
    // Calculate true temperature
    X1 = (UT-AC6)*AC5/pow(2,15); // Все как по даташиту. Ничено сложного
    X2 = MC*pow(2,11)/(X1+MD);
    B5 = X1 + X2;
    T = (B5+8)/pow(2,4);
    
    outputLong(T);
    
    // Calculate true pressure
    B6 = B5 - 4000;
    X1 = (B2 * (B6*B6/pow(2,12)))/pow(2,11);
    X2 = AC2*B6/pow(2,11);
    X3 = X1 + X2;
    B3 = ((AC1*4+X3)+2)/4;
    X1 = AC3*B6/pow(2,13);
    X2 = (B1*(B6*B6/pow(2,12)))/pow(2,16);
    X3 = ((X1+X2) + 2)/4;
    B4 = AC4 * (unsigned long)(X3 + 32768)/pow(2,15);
    B7 = ((unsigned long)UP - B3)*(50000);
      if (B7 < 0x80000000) {
        p = (B7*2)/B4;
      } else {
        p = (B7/B4)*2;
      }
    X1 = (p/pow(2,8))*(p/pow(2,8));
    X1 = (X1*3038)/pow(2,16);
    X2 = (-7357*p)/pow(2,16);
    p = p + (X1+X2+3791)/pow(2,4);
    
    outputLong(p);
  }
}

В программе задействованы еще несколько функций. Они не особо сложны и не думаю что нуждаются в комментариях:
// Возводит число в степень
long pow(int number, int power) { 
  long result = 1;
  for (int i=0; i<power; i++) {
      result *= number;
  }
  return result;
}

// Задержка
void delay (int time) {
  for (int i=0; i<time*1000; i++);
}

// Выводит 4 байта по UART
void outputLong(long value) {
  while(!(USART1_SR_bit.TXE));
  USART1_DR = value>>24;
  while(!(USART1_SR_bit.TXE));
  USART1_DR = value>>16;
  while(!(USART1_SR_bit.TXE));
  USART1_DR = value>>8;
  while(!(USART1_SR_bit.TXE));
  USART1_DR = value;
}

В всю прошивку можно увидеть на гитхабе.
Теперь набросаем небольшой скрипт для отображения данных с датчика:
import serial
import struct
import time

tempx = [];
pressx = [];
ser = serial.Serial('/dev/ttyUSB0', 9600)

while(1):
    try:
        ser.write('1'); 
        temp = ser.read(8)
	press = temp[-4:]
	temp = temp[:4]
        temp = float(struct.unpack('>i', temp)[0])/10
        press = float(struct.unpack('>i', press)[0])/1000
	print str(temp)+" C"
	print str(press)+" kPa"
        print "----------"
	tempx.append(temp)
    except:
        print "Error"
        print "----------"
    time.sleep(1)

В результате должно получиться что то вроде этого:

И напоследок еще раз ссылка на репозиторий на GitHub: Тык! 
  • +6
  • 01 февраля 2014, 11:17
  • ftp27

Комментарии (41)

RSS свернуть / развернуть
Калибровочные коэффициенты индивидуальны для каждого конкретного датчика?
0
  • avatar
  • Bonio
  • 01 февраля 2014, 13:09
Думаю да. Иначе смысла в подсчетах на стороне МК не было бы.
0
Да, на то они и калибровочные, на заводе их пишут в каждый датчик свои, этакая замена механической подгонки )
0
Я работал с MS5611-01BA01 — он очень похож на Ваш датчик (по интерфейсу и логике обработке данных).

Хинт по оптимизации:

X1 = (B2 * (B6*B6/pow(2,12)))/pow(2,11);
X2 = AC2*B6/pow(2,11);


Если присмотреться, то в формулах сначала возводят 2 в некую степень, а потом делят на получившийся результат. А деление на степень двойки можно заменить сдвигом.

X1 = (B2 * ((B6*B6) >> 12)) >> 11;
X2 = (AC2*B6) >> 11;


Главное следить за разрядностью вычислений.
+1
  • avatar
  • e_mc2
  • 01 февраля 2014, 14:16
Да. В моей реализации как раз в таком порядке и производиться съем/подсчет
0
Прошу прощения. Промахнулся…
0
Отличная статья! Как раз жду с ебея этот датчик. Кстати, а как насчет идеи-считать калибровочные коэффициенты и занести их сразу в EEPROM, чтобы лишний раз их не считывать? И да, насколько я знаю, на борту этого датчика есть еще и термометр и я видел где-то рекомендацию, что температуру лучше тоже считывать)) Причем, сначала считать температуру, а потом давление.
0
Кстати, а как насчет идеи-считать калибровочные коэффициенты и занести их сразу в EEPROM, чтобы лишний раз их не считывать?
Зачем? Из EEPROM их все равно читать. К тому же, тогда при замене датчика придется обновлять EEPROM.
Из оптимизацией — лучше вынести в инициализацию расчеты, которые не зависят от считанных температуры и давления.
0
Да. В моей реализации как раз в таком порядке и производиться съем/подсчет
0
не вдаваясь в подробности расчетов хочу спросить
а что за давление получилось в итоге
нормальное атмосферное примерно 101,325 кПА
0
101325 * 75 / 10000 = 760 мм. рт. ст.
0
а на скриншоте — 237 с копейками кПа — это типа на Венере?
+1
Да странные показания, если учесть, что диапазон датчика от 30 до 110 kPa.
Сам второй день бьюсь с PIC18. Не хочет XC8 считать формулы из даташита.
На EFM32 пошло все сразу. У меня BMP180, но расчет тот же.
0
Согласен сильно наворотили, у меня 085 пашет без нареканий:



Мои наброски + исходник на Си: ziblog.ru/2013/03/15/bmp085-datchik-davleniya.html
0
Так на 32-х разрядах вопросов нет. Вот 8-ми разрядные компиляторы некорректно считают. На STM8 не пробовали запустить?
0
та вроде математика везде одна, нет не пробовал. уверен на 100%, что результат будет таким же…
0
Вот мой быдлокод расчетов температуры и давления, должен быть рабочий, когда-то писал для STM8L с радиоканалом на RFM70, 5 строк с rfm70_tx_buf можно заремить, это преобразование в символы для вывода на экран.

void TEMPER_and_PRESS_Calculate(void)
{  
  long int x1,x2,x3,b3,b5,b6;    
  unsigned long int b4, b7;
  
// Калибровочные коэффициенты BMP085
short int BMP085_AC1 = (short int)BMP085_COEFF[0];
short int BMP085_AC2 = (short int)BMP085_COEFF[1];
short int BMP085_AC3 = (short int)BMP085_COEFF[2];
unsigned short int BMP085_AC4 = (unsigned short int)BMP085_COEFF[3];
unsigned short int BMP085_AC5 = (unsigned short int)BMP085_COEFF[4];
unsigned short int BMP085_AC6 = (unsigned short int)BMP085_COEFF[5];
short int BMP085_B1 = (short int)BMP085_COEFF[6];
short int BMP085_B2 = (short int)BMP085_COEFF[7];
short int BMP085_MB = (short int)BMP085_COEFF[8];
short int BMP085_MC = (short int)BMP085_COEFF[9];
short int BMP085_MD = (short int)BMP085_COEFF[10];  
  
uint16_t temperature;
long pressure;  
  
  x1 = (( (unsigned long int)BMP085_TEMPER - ((long int) BMP085_AC6)) * ((long int) BMP085_AC5)) >> 15;
  x2 = (((long int) BMP085_MC) << 11) / (x1 + ((long int)BMP085_MD));
  b5 = x1 + x2;
  temperature = (short int)((b5 + 8) >> 4);  // temperature in 0.1°C
   
  rfm70_tx_buf[1] = temperature / 100 + '0';
  rfm70_tx_buf[2] = (temperature  % 100) / 10 + '0';
  rfm70_tx_buf[4] = temperature % 10 + '0'; 
  b6 = b5 - 4000;
  //*****calculate B3************
  x1 = (b6 * b6) >> 12;	 	 
  x1 *= BMP085_B2;
  x1 >>=11;

  x2 = (BMP085_AC2 * b6);
  x2 >>=11;

  x3 = x1 + x2;

  b3 = ((((unsigned long int)BMP085_AC1 )*4 + x3) + 2) >> 2;

  //*****calculate B4************
  x1 = (BMP085_AC3* b6) >> 13;
  x2 = (BMP085_B1 * ((b6 * b6) >> 12) ) >> 16;
  x3 = ((x1 + x2) + 2) >> 2;
  b4 = (BMP085_AC4 * (unsigned long int) (x3 + 32768)) >> 15;
    
  b7 = ((unsigned long int)((long int)BMP085_PRESS - b3) * 50000);   
  if (b7 < 0x80000000)
  {
    pressure = (b7 << 1) / b4;
  }
  else
  { 
    pressure = (b7 / b4) << 1;
  }
   
  x1 = pressure >> 8;
  x1 *= x1;
  x1 = (x1 * 3038) >> 16;
  x2 = (-7357 * pressure) >> 16;
  pressure += (x1 + x2 + 3791) >> 4;	// pressure in Pa  
  pressure = pressure * 3 / 400; 
  rfm70_tx_buf[6] = pressure / 100;
  pressure = pressure - (rfm70_tx_buf[6] * 100);
  rfm70_tx_buf[6] += '0';
  rfm70_tx_buf[7] = pressure / 10 + '0';
  rfm70_tx_buf[8] = pressure % 10 + '0';
  
  return;
}
+1
Да, забыл сказать, на всяк случай: BMP085_COEFF[], BMP085_TEMPER и BMP085_PRESS — 16 битные, считываются из BMP085 побайтно, первым читается старший байт.
0
Спасибо, немного помогло. Оказалось запутался в приведении типов. Температура пошла, застрял на В7, почти в 2 раза завышено.
0
Вроде бы у меня все типы приведены как надо, исходные типы были:
uint16_t BMP085_COEFF, BMP085_TEMPER, BMP085_PRESS;
0
У меня были попутаны.
0
Теперь срослось? Рад, если помогло. ))
0
Я пробовал, полет нормальный
0
отлично на STM8 работает. Компилятор IAR.
0
Да, какое-то странное давление, если даже грубо пересчитать 239134/133 = 1798 мм.рт.ст
0
Да. Он выдавал мне как раз порядка 100 кПа какое то время. А потом сбился. Думаю, это из за того что я его в тот момент пальцем прижимал, чтобы датчик температуры проверить
0
Ему должно быть пофиг, температура участвует в расчёте давления и оно должно оставаться стабильным
0
Я свой датчик феном грел, давление не изменялось.
0
Я вечер промучался с давлением, цифры были в 10 раз больше, чем нужно. Думал датчик битый, проверил на другом. Проблемы оказались с математикой в МК — что-то типа «от перестановки слагаемых сумма меняется», плюс типы данных.
Помогла отладка — подставил на вход данные из даташита и добивался верных результатов по шагам.
0
Ага, в не которых компиляторах (-7357 * р) и (р * -7357) разные результаты, при чем во втором случае правильный. На XC8 так и не добился правильных показаний.
0
знал что есть такой датчик, но не знал что он и температуру измерять умеет. буду иметь ввиду. есть как раз парочка в запасе
0
  • avatar
  • woddy
  • 02 февраля 2014, 10:55
Я же писал в своем каменте, что температуру тоже меряет) И даже, если она не нужна, то лучше ее хотя бы просто считывать с датчика))) На всякий случай)
0
Температуру не считывать не получится. Она используется для расчета давления.
0
Хм, точно, спасибо, сейчас глянул в даташит и убедился)) Просто в железе я еще не начинал с ним работать, ибо датчик с ебея еще не пришел(
0
Да и на скриншоте автора температура присутствует)
0
Я когда работал с этим датчиком вообще пошел путем для ленивых: прикрутил готовый бошевский API: zh.bosch-sensortec.com/content/language1/downloads/API_BMP085.zip
Там все функции пересчета уже реализованы. Нужна только своя прослойка для IIC и все. Но либа тяжелая, мне на STM8 для экономии флеша пришлось выпилить оттуда поддержку SMD500 (т.к. универсальность мне не упала в виде SMD500).
0
  • avatar
  • N1X
  • 02 февраля 2014, 14:27
Кстати, коллеги, есть вопрос по переводу давления в барометрическую высоту.

Ели верить Вики то зависимость давления от высоты выражается как

Вычисление давления на высоте h по давлению на уровне моря Po и температуре воздуха T:


где Po — давление Па на уровне моря [Па]; M — молярная масса сухого воздуха 0,029 [кг/моль]; g — ускорение свободного падения 9,81 [м/с²]; R- универсальная газовая постоянная 8,31 [Дж/моль К]; T — абсолютная температура воздуха [К], T = t + 273, где t — температура в °C; h — высота [м].
Но, в нескольких проектах, найденных на просторах интернета (например здесь), для вычислений высоты используется формула

h = 44330.0f * (1.0f - pow((float) Pressure / 101325.0f, 0.190295f));

И эта формула (похоже) работает на практике. По крайней мере, относительные изменения высоты выдает корректно. Абсолютной высоты своей комнаты над уровнем моря я не знаю :) (по данной формуле получается, ЕМНИП, около 170 метров).

Дык вот, может я туплю, но я не понимаю откуда взялась вторя формула. Может кто-то подскажет как ее получили?
0
  • avatar
  • e_mc2
  • 04 февраля 2014, 00:21
Так они особо не мучались, открыли DS на микруху и скопировали формулу оттуда, подставив где надо константы.
0
Дык вот, может я туплю, но я не понимаю откуда взялась вторя формула. Может кто-то подскажет как ее получили?
Отсюда
Абсолютной высоты своей комнаты над уровнем моря я не знаю :)
Вариант 1. Найти генштабовскую карту, на ней есть высота подстилающей поверхности.
Вариант 2. Вроде в ГуглМапе тоже имеется высота.
Далее прибавляешь высоту комнаты относительно земли.
Учти, что давление изменяется в зависимости от погоды. :) За две предыдущие недели «высота» моей комнаты изменялась от примерно 600 метров до минус 90 метров (по давлению 760 мм.рт.ст.).

PS. Вроде уже как год объявили о снятии этих датчиков с производства.
Прибавить
0
Саму барометрическую формулу я знаю, ее я выше и привел.

Я не понимаю, как из нее получили вторую формулу (из даташита)

В барометрической формуле высота в показателе степени. Если из этой формулы выразить высоту, то там будет натуральный логарифм от отношения давлений. Вот куда они логарифм дели?

PS. Вроде уже как год объявили о снятии этих датчиков с производства.
Я использую MS5611-01BA01. Он похож на данный датчик но, ЕМНИП, точнее.
0
Что-то у этого 5611 ценник не гуманный…
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.