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