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. 

Building a Shared C Library Using gcc on the Arduino Galileo (Gen. 1)

I posted a couple days ago about installing the gcc compiler on an Arduino Galileo.  In the course of writing C code to use the Adafruit BMP085 barometric pressure sensor, I found this smbus code (in  a post about I2C on the Raspberry Pi) that works well with I2C devices connected to the Galileo without any modification.  I'll have other projects that use I2C, so I wanted to build smbus as a shared library.  Below are my brief notes on the process of using gcc to build the library.

The steps below assume that you are working at the Galileo's command prompt (not the Arduino IDE).

To build the shared library:

  1. gcc -c -Wall -fpic smbus.c   (create smbus.o object file in PIC format, show all warnings)
  2. gcc -shared -o libsmbus.so smbus.o (create shared library .so file. Name of .so file must start with lib)
Put the library where gcc can find it:
  1. cp libsmbus.so /usr/lib/
  2. chmod 0755 /usr/lib/libsmbus.so
  3. ldconfig
  4. cp smbus.h /usr/include
To use this library with gcc, include -lsmbus in the gcc commmand line, like this -

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

Note how the library name after -l matches the name of the library file minus the lib prefix and the .so file extension. -lm is the switch for including the standard math library that my program uses.

I'll post my C code for reading barometric pressure using the BMP085 separately.


Thursday, May 28, 2015

Python Script for an Arduino Galileo to Read Barometric Pressure from an Adafruit BMP085 via I2C

This example shows how to read barometric pressure from an Adafruit BMP085 via I2C using a Python script on an Arduino Galileo (Gen. 1 running Clanton).

I want to rewrite this code in C, but using this Python script that I had on hand for another board helped me work through how to set the multiplexing for the Galileo's pins to get I2C working.  For information on the Galileo's GPIO pins and how to access them from outside of an Arduino sketch, see Sergey Kiselev's blog post.  I also found this post (and answer by rgb)  in an Intel discussion forum very helpful in getting this right.

Prerequisites 


The python-smbus package provides access to the I2C bus, so be sure that it is installed using opkg  before running this script.  You may also want to the i2c-tools package that provides the i2cdetect utility.  I installed these after configuring opkg as described in AlexT's Galileo and Edison Pages, so that configuration of opkg may be a requirement, too.

Connections

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

Code 


Here is the Python code.  When copying from the code to this block, the tabs have been converted to a single space indentation.  This makes it a bit harder to see the indentation but won't offend the Python interpreter if the code is copied and pasted.

import time
import smbus

# Galileo i2c is on bus 0
bus = smbus.SMBus(0)
mode = 3
i2c_addr = 0x77 #Addr for bmp085

#=================================================================
# Fix port mux. Export gpio29 for access via sysfs.
# sysfsgpio29 controls the multiplexing of pins A4/A5 (and SCL/SDA pins)
with open("/sys/class/gpio/export","w") as fh:
 fh.write("29")
# Set gpio29 for output
with open("/sys/class/gpio/gpio29/direction","w") as fh:
 fh.write("out")
# Set value to 0 for i2c via the Quark chip
with open("/sys/class/gpio/gpio29/value","w") as fh:
 fh.write("0")
#=================================================================

def read_word_signed(addr, reg):
 msb = bus.read_byte_data(addr, reg)
 if msb > 127: msb -= 256
 lsb = bus.read_byte_data(addr, reg+1)
 return (msb << 8) + lsb

def read_word_unsigned(addr, reg):
 msb = bus.read_byte_data(addr, reg)
 lsb = bus.read_byte_data(addr, reg+1)
 return (msb << 8) + lsb

# Read factory programmed parameters
# These are used for calculations 
AC1 = read_word_signed(i2c_addr, 0xAA)
AC2 = read_word_signed(i2c_addr, 0xAC)
AC3 = read_word_signed(i2c_addr, 0xAE)
AC4 = read_word_unsigned(i2c_addr, 0xB0)
AC5 = read_word_unsigned(i2c_addr, 0xB2)
AC6 = read_word_unsigned(i2c_addr, 0xB4)
B1 = read_word_signed(i2c_addr, 0xB6)
B2 = read_word_signed(i2c_addr, 0xB8)
MB = read_word_signed(i2c_addr, 0xBA)
MC = read_word_signed(i2c_addr, 0xBC)
MD = read_word_signed(i2c_addr, 0xBE)

while True:
 # read temperature cmd
 bus.write_byte_data(i2c_addr, 0xF4, 0x2E) 
 time.sleep(0.005)  # Wait 5ms
 msb = bus.read_byte_data(i2c_addr, 0xF6)
 lsb = bus.read_byte_data(i2c_addr, 0xF6+1)
 raw_temp = (msb << 8) + lsb

 X1 = ((raw_temp - AC6) * AC5) >> 15
 X2 = (MC << 11) / (X1 + MD)
 B5 = X1 + X2

 # Read pressure cmd
 bus.write_byte_data(i2c_addr, 0xF4, 0x34 + (1 << 6)) 
 time.sleep(0.026)
 msb = bus.read_byte_data(i2c_addr, 0xF6)
 lsb = bus.read_byte_data(i2c_addr, 0xF6+1)
 xlsb = bus.read_byte_data(i2c_addr, 0xF6+2)
 raw_ps = ((msb << 16) + (lsb << 8) + xlsb) >> (8-mode)

 B6 = B5 - 4000
 X1 = (B2 * (B6 * B6) >> 12) >> 11
 X2 = (AC2 * B6) >> 11
 X3 = X1 + X2
 B3 = (((AC1 * 4 + X3) << 3) + 2) / 4
 X1 = (AC3 * B6) >> 13
 X2 = (B1 * ((B6 * B6) >> 12)) >> 16
 X3 = ((X1 + X2) + 2) >> 2
 B4 = (AC4 * (X3 + 32768)) >> 15
 B7 = (raw_ps - B3) * (50000 >> mode)

 if (B7 < 0x80000000):
  p = (B7 * 2) / B4
 else:
  p = (B7 / B4) * 2

 X1 = (p >> 8) * (p >> 8)
 X1 = (X1 * 3038) >> 16
 X2 = (-7357 * p) >> 16
 # Pressure in Pascals
 p = p + ((X1 + X2 + 3791) >> 4)
 p = p * 0.000295333727 # Convert to inches Hg
 # Adjust for elevation: Add 1200 Pa / 100 meters in elev.
 # MPLS is 264m above sea level
 # Converted to inches Hg this is 0.935
 print p + 0.935 
 time.sleep(10)



Wednesday, May 27, 2015

Installing the gdb Debugger on an Arduino Galileo (Clanton)

My previous post pointed out that the gcc compiler can be installed used opkg on an Arduino Galileo running Clanton (and booting from an SD card so there is room). I wasn't able to install the C/C++ gdb debugger using opkg, though.

Download the gdb Source Code


With the gcc compiler installed, though, it is possible to download the source and build gdb for the Galileo.  You can find the source code in the GNU Project Archives.  Just be sure to unzip and untar the source in a directory on the SD card so that you'll have enough room.

Prerequisites 


gdb needs ncurses.  I installed the ncurses, ncurses-dev, and ncurses-terminfo packages via opkg.

The first time that I tried to compile gdb, the build process terminated prematurely with an error because the ar utility didn't work correctly.  Rather than using the version of ar that comes with BusyBox, use a soft link (or the update-alternatives utility) so that ar points to the version of ar in the /usr/i586-poky-linux-uclibc/bin/ directory.

With these pieces in place, I was able to run configure, make, and make install.  The build process took more than a couple hours, so you won't want to be in a hurry.

I should note that the build process did end with an error with a message telling me that makeinfo is missing, but that didn't impact the building and running of the program.

Installing the gcc Compiler on an Arduino Galileo Running Clanton

I posted yesterday about a problem I ran into in trying to get the tools to cross-compile C code to run on my first-generation Arduino Galileo.  As the previous post indicates, I fixed the problem and am now able to compile C programs in a Linux environment so that they run on Clanton.  I should have dug a bit deeper, though...

It turns out that it is quite possible to get the gcc compiler and tools that run on the Galileo.  AlexT's Galileo and Edison Pages has a post about configuring opkg so that you can install the required packages.

When trying to install gcc, I found that I did need to pay attention to AlexT's note and use the command opkg install --force-overwrite uclibc before continuing.  After installing the binutils, gcc, gcc-symlinks, and libgcc-s-dev, I can compile C programs right on the Galileo without needing to cross-compile on another platform. (As noted in my post yesterday, I boot my Galileo from an SD card using the Clanton image provided by Intel).

There are also packages available for g++.

Note (05/30/2015): The OS image for download at https://software.intel.com/en-us/iot/downloads includes development tools, but I find that image is too small (in terms of disk space) to be useful. Until I successfully build a custom image using the Yocto tools, I find it easier to use the older image (uClibc microSD card image) and add gcc.




Tuesday, May 26, 2015

Arduino Galileo Setup Error on Linux (invalid mode '+111')

I've had an Arduino Galileo (1st generation) board for more than a year, but I haven't done much with it.  I decided that I would perhaps get more use out of it if I could cross-compile C/C++ code to run on it.

To get the necessary version of gcc that servers as the crosscompiler, I downloaded the Linux version of the Arduino IDE for Intel from the Intel site and tried to install it on a virtual machine running Fedora 21, but the script that installs the Intel compiler and tools (arduino-1.5.3/hardware/tools/install_script.sh) kept terminating with an error. This script is run by the IDE the first time you try to compile a sketch. You have to run this script before you can work with the compiler and tools at the command line to cross-compile code for the Galileo.  The error message said

find: invalid mode '+111'

I found the solution to the problem in a post by Sulamita on the Intel Community discussion board. Her solution (changing +111 to /111 on line 111 of the script) fixed the problem for me.  I changed line 111 from

executable_files=$($SUDO_EXEC find "$native_sysroot" -type f -perm +111 -exec printf "\"%s\" " {} \; )

to

executable_files=$($SUDO_EXEC find "$native_sysroot" -type f -perm /111 -exec printf "\"%s\" " {} \; )

I can now compile C code for the Galileo following the steps show in the PDF "Quick Start Guide for Using Intel Compiler to Build Applications Targeting Quark SoC on Galileo Board."  (By the way, I boot my Galileo from an image on an SD card. The image is available at the Intel Support and Downloads site for Galileo.)