Работа с датчиком 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: Тык!