Notes About Working with Various Arduino & Netduino Microcontroller Boards

Friday, May 29, 2015

C Code for the Arduino Galileo to Read Barometric Pressure Using a BMP085

Below is a sample program written in C for the Galileo that reads the barometric pressure from an Adafruit BMP085.  This code just prints out the barometric pressure in inches of mercury (adjusted to sea level) until the user ends the program using ctrl-C.

You will need to install the gcc compiler. See my previous post, "Installing the gcc Compiler on an Arduino Galileo Running Clanton" for further information.

This example also uses the smbus I2C library compiled as a shared library. See "Building a Shared C Library Using gcc on the Arduino Galileo (Gen. 1)" for my notes on this.

For information on the Galileo's GPIO pins and how they are configured under Linux, see Sergey Kisilev's very useful post.

Connections


Galileo   BMP085
VIN       3.3V
GND       GND
SCL       SCL (or A5)
SDA       SDA (or A4)

Source Code


Header File bmp085.h

 #include <inttypes.h>

// Macro to flip bytes in short integer
#define flipBytes(val) ((val<<8) & 0xFF00) | ((val>>8) & 0xFF)

int deviceHandle; // Handle for accessing i2c. Global so sigaction can access it
int run = 1;      // Controls loop - set to run when 1, 0 to quit

// Variables to hold calibration values
int16_t ac1;
int16_t ac2;
int16_t ac3;
uint16_t ac4;
uint16_t ac5;
uint16_t ac6;
int16_t b1;
int16_t b2;
int16_t mb;
int16_t mc;
int16_t md;
int32_t b5;
int16_t t;
int32_t p;

const uint8_t OSS = 0;  // Oversampling Setting

// Function declarations
void configPins();
void openI2C();
void calibrate();
short getTemperature(uint16_t);
int32_t getPressure(uint32_t);                        
uint16_t getUncompensatedTemperature(uint16_t);
uint32_t getUncompensatedPressure(uint16_t);
void handleTermination(int);

C Source Code (bmp085.c)


/* This code uses smbus to access the i2c bus. You can find the source code for smbus at 
   https://www.john.geek.nz/2012/12/update-reading-data-from-a-bosch-bmp085-with-a-raspberry-pi/.  
   This version of the smbus code compiles on and works with the Galileo running Clanton 
   without modification */

#include "bmp085.h"
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <math.h>   // needed for trunc()
#include <signal.h> // needed for signal()
#include <smbus.h>  // smbus.h copied to /usr/include
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>

/* This program prints out the current barometric pressure in inches
   of mercury every 10 seconds. This code adjusts the pressure to
   sea-level based on the elevation here in Minneapolis. See comment
   below about how to change this adjustment for other elevations.

   The program runs until the user presses ctrl-C. This code uses 
   signal() to close the device handle for the I2C bus before
   exiting. */

int main() {
int32_t p; // 32-bit integer to hold pressure returned by sensor

// Set up handler for ctrl-C. End program by pressing ctrl-C
signal(SIGINT, handleTermination);
configPins();
openI2C();
calibrate(deviceHandle);
// loop runs while run is not 0
while(run) {
// Need to call function to read temperature to compensate pressure 
getTemperature(getUncompensatedTemperature(deviceHandle));
p = getPressure(getUncompensatedPressure(deviceHandle));
  double pInHg = p * 0.000295333727; // convert to inches of mercury
  // Now adjust to sea level: Add 1200 Pa / 100 meters in elev.
  // Minneapolis is 264m above sea level. This is 0.935" mercury
  pInHg += 0.935;
  pInHg = trunc(pInHg * 100)/100;
time_t t = time(NULL);
struct tm tm = *localtime(&t);
printf("%02d/%02d/%d %02d:%02d:%02d ", tm.tm_mon + 1, tm.tm_mday, 
               tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec);
  printf("Pressure: %.02f\n", pInHg);
// sleep for about 10 seconds
sleep(10);
}
return 0;
}

// Configure multiplexing of Galileo's GPIO pins for I2C
void configPins() {
   // Set multiplexing for i2c pins via gpio29. Start by exporting gpio 29 so
   // it can be accessed via sysfs.
   int fd = open("/sys/class/gpio/export", O_WRONLY);
   if(fd == -1) {
      fprintf(stderr, "Failed to open gpio export path.\n");
      exit(1);
   }
   write(fd, "29", 2);
   close(fd);
   // Set gpio29's direction to out
   fd = open("/sys/class/gpio/gpio29/direction", O_WRONLY);
   if(fd == -1) {
      fprintf(stderr, "Failed to open gpio direction path.\n");
      exit(2);
   }
   write(fd, "out", 3);
   close(fd);

   // Set gpio29's value to 0 to enable i2c via Quark chip
   fd = open("/sys/class/gpio/gpio29/value", O_WRONLY);
   if(fd == -1) {
      fprintf(stderr, "Failed to open gpio value path.\n");
      exit(3);
   }
   // Note that 0 is passed as a string/char.
   write(fd, "0", 1);
close(fd);
}

void openI2C() {
  // On Galileo (Clanton), the I2C bus is /dev/i2c-0
   deviceHandle = open("/dev/i2c-0", O_RDWR);
   if(deviceHandle < 0) {
      fprintf(stderr, "Failed to open /dev/i2c-0.\n");
      exit(4);
   }
// The BMP085 uses the device address 0x77
   if(ioctl(deviceHandle, I2C_SLAVE, 0x77) < 0) {
      fprintf(stderr, "Failed to open I2C device as slave.\n");
      exit(5);
   }
}

// Read registers with factory-set calibration values.  
void calibrate() {
   ac1 = i2c_smbus_read_word_data(deviceHandle, 0xAA);
// flipBytes is a macro defined in bmp085.h
   ac1 = flipBytes(ac1);
   ac2 = i2c_smbus_read_word_data(deviceHandle, 0xAC);
   ac2 = flipBytes(ac2);
   ac3 = i2c_smbus_read_word_data(deviceHandle, 0xAE);
   ac3 = flipBytes(ac3);
   ac4 = i2c_smbus_read_word_data(deviceHandle, 0xB0);
   ac4 = flipBytes(ac4);
   ac5 = i2c_smbus_read_word_data(deviceHandle, 0xB2);
   ac5 = flipBytes(ac5);
   ac6 = i2c_smbus_read_word_data(deviceHandle, 0xB4);
   ac6 = flipBytes(ac6);
   b1 = i2c_smbus_read_word_data(deviceHandle, 0xB6);
   b1 = flipBytes(b1);
   b2 = i2c_smbus_read_word_data(deviceHandle, 0xB8);
   b2 = flipBytes(b2);
   mb = i2c_smbus_read_word_data(deviceHandle, 0xBA);
   mb = flipBytes(mb);
   mc = i2c_smbus_read_word_data(deviceHandle, 0xBC);
   mc = flipBytes(mc);
   md = i2c_smbus_read_word_data(deviceHandle, 0xBE);
   md = flipBytes(md);
}

// Read the uncompensated temperature value
uint16_t getUncompensatedTemperature(uint16_t deviceHandle) {
   uint16_t ut;  
  // Write 0x2E into Register 0xF4
  // This requests a temperature reading
i2c_smbus_write_byte_data(deviceHandle, 0xF4, 0x2E);
// Wait at least 4.5ms (4500 microseconds for usleep() call)
usleep(4500);
  // Read two bytes from registers 0xF6 and 0xF7
  ut = i2c_smbus_read_word_data(deviceHandle, 0xF6);
  return flipBytes(ut);
}

// Read the uncompensated pressure value
uint32_t getUncompensatedPressure(uint16_t deviceHandle) {
  unsigned char msb, lsb, xlsb;
unsigned long up = 0;
  
// Write 0x34 + (OSS << 6) into register 0xF4
// Request a pressure reading w/ oversampling setting
i2c_smbus_write_byte_data(deviceHandle, 0xF4, 0x34 + (OSS << 6));
  // Wait for conversion. 5 milliseconds = 5000 microseconds
  usleep(5000);
  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
msb = i2c_smbus_read_byte_data(deviceHandle, 0xF6);
lsb = i2c_smbus_read_byte_data(deviceHandle, 0xF7);
xlsb = i2c_smbus_read_byte_data(deviceHandle, 0xF8);
up = (((uint32_t) msb << 16) | ((uint32_t) lsb << 8) | (uint32_t) xlsb) >> (8 - OSS);
  return up;
}

// Calculate temperature given unadjusted temp ut.
// Value returned will be in units of 0.1 deg C
int16_t getTemperature(uint16_t ut) {
  int32_t x1, x2;
  x1 = (((int32_t)ut - (int32_t)ac6) * (int32_t)ac5) >> 15;
  x2 = ((int32_t)mc << 11) / (x1 + md);
  b5 = x1 + x2;
  return ((b5 + 8)>>4);  
}

// Calculate pressure given unadjusted pressure up
// calibration values must be known
// b5 is also required so bmp085GetTemperature(...) must be called first.
// Value returned will be pressure in units of Pa.
int32_t getPressure(uint32_t up) {
  int32_t x1, x2, x3, b3, b6, p;
uint32_t b4, b7;
  
b6 = b5 - 4000;
// Calculate B3
x1 = (b2 * (b6 * b6)>>12) >> 11;
x2 = (ac2 * b6) >> 11;
x3 = x1 + x2;
b3 = (((((int32_t)ac1) * 4 + x3) << OSS) + 2) >> 2;
  
// Calculate B4
x1 = (ac3 * b6) >> 13;
x2 = (b1 * ((b6 * b6) >> 12)) >> 16;
x3 = ((x1 + x2) + 2) >> 2;
b4 = (ac4 * (uint32_t)(x3 + 32768)) >> 15;
  
b7 = ((uint32_t)(up - b3) * (50000 >> OSS));
if (b7 < 0x80000000) {
p = (b7 << 1) / b4;
}
  else {
    p = (b7 / b4) << 1;
}
    
x1 = (p >> 8) * (p >> 8);
x1 = (x1 * 3038) >> 16;
x2 = (-7357 * p) >> 16;
p += (x1 + x2 + 3791) >> 4;
  
return p;
}

// Function that handles SIGINT (ctrl-C) and closes i2c
void handleTermination(int signum) {
  fprintf(stderr, "\nProgram exiting...\n");
   // Not possible to pass arguments to this function
   // so deviceHandle needs to be a global variable
   // (in this case, set in bmp085.h header file.
  close(deviceHandle);
// Setting run to 0 ends main program loop
  run = 0;
}

Compiling the Code


Assuming that the files bmp085.c and bmp085.h are in the current directory and that you've installed smbus as a shared library, the following command will compile the program:

gcc -Wall -lm -lsmbus -o bmp085 bmp085.c

-Wall turns on all warnings. -lm links in the math library and -lsmbus links in the smbus library.  As usual, the -o option identifies the executable file to be produced and the last file is the C source code file. 

No comments:

Post a Comment