Notes About Working with Various Arduino & Netduino Microcontroller Boards

Thursday, June 26, 2014

Two I2C Devices with the Same Address with an Arduino Due

The Arduino Due has two separate I2C connections, not just redundant pins connected to the same I2C pins on the processor (as on the Arduino Uno).  This makes it possible to use two I2C devices with the same device address.

Here is a quick example that uses two TMP102 breakout boards from Sparkfun. The new version of this board requires removing a solder jumper to change the device's I2C address.  Otherwise, the boards all use 0x48, regardless of how the ADD0 pin is connected.

Connections


TMP102 #1  Arduino Due
VCC        3.3V
GND        GND
SDA        SDA (D20) (Wire)
SCL        SCL (D19) (Wire)
ADD0       (Not connected)

TMP102 #2  Arduino Due
VCC        3.3V
GND        GND
SDA        SDA1 (Wire1)
SCL        SCL1 (Wire1)
ADD0       (Not connected)


Code


#include <Wire.h>

// Formula to convert Celsius to Fahrenheit
// This is not directly tied to the functioning of the TMP102
// and is a simple formula, so it useful to use #define for the 
// conversion function.
#define C_TO_F(x) (x * 1.8 + 32.0)
#define I2C_ADDR 0x48

void setup() {
  Wire.begin();
  Wire1.begin();
  Serial.begin(9600);
}

void loop() {
  Wire.requestFrom(I2C_ADDR, 2);
  byte msb1 = Wire.read();
  byte lsb1 = Wire.read();
  Wire1.requestFrom(I2C_ADDR, 2);
  byte msb2 = Wire1.read();
  byte lsb2 = Wire1.read();
  double t1 = (((msb1 << 8) | lsb1) >> 4) * 0.0625;
  double t2 = (((msb2 << 8) | lsb2) >> 4) * 0.0625;
  Serial.print(C_TO_F(t1), 3);
  Serial.print(" ");
  Serial.println(C_TO_F(t2), 3);
  delay(1000);
}

Wednesday, June 25, 2014

Measuring Barometric Pressure using a MPL115A1 with an Arduino Due

The example below shows how to use a MPL115A1 (on a breakout board from Sparkfun) to measure barometric pressure with an Arduino Due.

This code is a simplified version of code by Jeremiah McConnell  and Jim Lindblom.  Their code does much more than measure the barometric pressure (which is all I need to do).  Because barometric pressure given in weather forecasts is usually adjusted to sea level, I have modified the code a bit to take this into account.  Adjust the value of seaLevelAdjInHg to match the facts at your location.  The comments in the code explain.  To keep things simple for this post, the program prints the current pressure in inches of mercury to the serial monitor in the Arduino IDE.

I have tested this using the Arduino Due, which has separate, dedicated pins for SPI.

Connections


MPL115A1  Arduino Due
SDN       D10      
CSN       D9
SDO       SPI1 (marked by dot on board)
SDI       SPI4 (middle pin 2nd column)
SCK       SPI3 (middle pin 1st column, below SPI1)
GND       GND  (Arduino Due power pins)
VCC       3.3V (Arduino Due power pins)


Code


#include <SPI.h>

// Pin definitions
#define MPL115A1_ENABLE_PIN 9
#define MPL115A1_SELECT_PIN 10
// Masks for MPL115A1 SPI i/o
#define MPL115A1_READ_MASK  0x80
#define MPL115A1_WRITE_MASK 0x7F 
// MPL115A1 register address map
#define PRESH   0x00    // 80
#define PRESL   0x02    // 82
#define TEMPH   0x04    // 84
#define TEMPL   0x06    // 86
#define A0MSB   0x08    // 88
#define A0LSB   0x0A    // 8A
#define B1MSB   0x0C    // 8C
#define B1LSB   0x0E    // 8E
#define B2MSB   0x10    // 90
#define B2LSB   0x12    // 92
#define C12MSB  0x14    // 94
#define C12LSB  0x16    // 96
#define C11MSB  0x18    // 98
#define C11LSB  0x1A    // 9A
#define C22MSB  0x1C    // 9C
#define C22LSB  0x1E    // 9E
// Unit conversion macros
#define FT_TO_M(x) ((long)((x)*(0.3048)))
#define KPA_TO_INHG(x) ((x)*(0.295333727))
#define KPA_TO_MMHG(x) ((x)*(7.50061683))
#define KPA_TO_PSIA(x) ((x)*(0.145037738))
#define KPA_TO_KGCM2(x) ((x)*(0.0102))
#define INHG_TO_PSIA(x) ((x)*(0.49109778))
#define DEGC_TO_DEGF(x) ((x)*(9.0/5.0)+32)

/* Barometric pressue in weather forcasts is adjusted to sea level.
   Add 1200 Pa for each 100m above sea level in elevation.  
   Multiply by 0.000295333727 to convert to inches of Hg 
   For my location, that comes to 0.930 */ 
double seaLevelAdjInHg = 0.930;

void setup() {
    // initialize serial i/o
    Serial.begin(9600);   
    // initialize SPI interface
    SPI.begin();
   // initialize the chip select and enable pins
    pinMode(MPL115A1_SELECT_PIN, OUTPUT);
    pinMode(MPL115A1_ENABLE_PIN, OUTPUT);
    // sleep the MPL115A1
    digitalWrite(MPL115A1_ENABLE_PIN, LOW);
    // set the chip select inactive, select signal is CS LOW
    digitalWrite(MPL115A1_SELECT_PIN, HIGH);
}    

void loop() {
    
    float pressure_pKa = 0;
    float temperature_c= 0;
    long altitude_ft = 0;
    // wake the MPL115A1
    digitalWrite(MPL115A1_ENABLE_PIN, HIGH);
    delay(20);  // give the chip a few ms to wake up
    pressure_pKa = calculatePressurekPa();
    temperature_c = calculateTemperatureC();
    // put the MPL115A1 to sleep, it has this feature why not use it
    // while in shutdown the part draws ~1uA
    digitalWrite(MPL115A1_ENABLE_PIN, LOW);
    double pressure = KPA_TO_INHG(pressure_pKa); 
    pressure += seaLevelAdjInHg;   
    Serial.print(pressure, 2);
    Serial.println(" in Hg ");  
    // wait a few seconds before looping
    delay(5000);
}

float calculateTemperatureC() {
    unsigned int uiTadc;
    unsigned char uiTH, uiTL;
    unsigned int temperature_counts = 0;
    writeRegister(0x22, 0x00);  // Start temperature conversion
    delay(2);                   // Max wait time is 0.7ms, typ 0.6ms 
    // Read pressure
    uiTH = readRegister(TEMPH);
    uiTL = readRegister(TEMPL);
    uiTadc = (unsigned int) uiTH << 8;
    uiTadc += (unsigned int) uiTL & 0x00FF;
    // Temperature is a 10bit value
    uiTadc = uiTadc >> 6;
    // -5.35 counts per °C, 472 counts is 25°C
    return 25 + (uiTadc - 472) / -5.35;
}

float calculatePressurekPa() {
    // See Freescale document AN3785 for detailed explanation
    // of this implementation.
    signed char sia0MSB, sia0LSB;
    signed char sib1MSB, sib1LSB;
    signed char sib2MSB, sib2LSB;
    signed char sic12MSB, sic12LSB;
    signed char sic11MSB, sic11LSB;
    signed char sic22MSB, sic22LSB;
    signed int sia0, sib1, sib2, sic12, sic11, sic22, siPcomp;
    float decPcomp;
    signed long lt1, lt2, lt3, si_c11x1, si_a11, si_c12x2;
    signed long si_a1, si_c22x2, si_a2, si_a1x1, si_y1, si_a2x2;
    unsigned int uiPadc, uiTadc;
    unsigned char uiPH, uiPL, uiTH, uiTL;
    writeRegister(0x24, 0x00);      // Start Both Conversions
    //writeRegister(0x20, 0x00);    // Start Pressure Conversion
    //writeRegister(0x22, 0x00);    // Start temperature conversion
    delay(2);                       // Max wait time is 1ms, typ 0.8m
    // Read pressure
    uiPH = readRegister(PRESH);
    uiPL = readRegister(PRESL);
    uiTH = readRegister(TEMPH);
    uiTL = readRegister(TEMPL);    
    uiPadc = (unsigned int) uiPH << 8;
    uiPadc += (unsigned int) uiPL & 0x00FF;
    uiTadc = (unsigned int) uiTH << 8;
    uiTadc += (unsigned int) uiTL & 0x00FF;
    // Placing Coefficients into 16-bit Variables
    // a0
    sia0MSB = readRegister(A0MSB);
    sia0LSB = readRegister(A0LSB);
    sia0 = (signed int) sia0MSB << 8;
    sia0 += (signed int) sia0LSB & 0x00FF;
    // b1
    sib1MSB = readRegister(B1MSB);
    sib1LSB = readRegister(B1LSB);
    sib1 = (signed int) sib1MSB << 8;
    sib1 += (signed int) sib1LSB & 0x00FF;
    // b2
    sib2MSB = readRegister(B2MSB);
    sib2LSB = readRegister(B2LSB);
    sib2 = (signed int) sib2MSB << 8;
    sib2 += (signed int) sib2LSB & 0x00FF;
    // c12
    sic12MSB = readRegister(C12MSB);
    sic12LSB = readRegister(C12LSB);
    sic12 = (signed int) sic12MSB << 8;
    sic12 += (signed int) sic12LSB & 0x00FF;
    // c11
    sic11MSB = readRegister(C11MSB);
    sic11LSB = readRegister(C11LSB);
    sic11 = (signed int) sic11MSB << 8;
    sic11 += (signed int) sic11LSB & 0x00FF;
    // c22
    sic22MSB = readRegister(C22MSB);
    sic22LSB = readRegister(C22LSB);
    sic22 = (signed int) sic22MSB << 8;
    sic22 += (signed int) sic22LSB & 0x00FF;
    // Coefficient 9 equation compensation
    uiPadc = uiPadc >> 6;
    uiTadc = uiTadc >> 6;
    // Step 1 c11x1 = c11 * Padc
    lt1 = (signed long) sic11;
    lt2 = (signed long) uiPadc;
    lt3 = lt1*lt2;
    si_c11x1 = (signed long) lt3;
    // Step 2 a11 = b1 + c11x1
    lt1 = ((signed long)sib1)<<14;
    lt2 = (signed long) si_c11x1;
    lt3 = lt1 + lt2;
    si_a11 = (signed long)(lt3>>14);
    // Step 3 c12x2 = c12 * Tadc
    lt1 = (signed long) sic12;
    lt2 = (signed long) uiTadc;
    lt3 = lt1*lt2;
    si_c12x2 = (signed long)lt3;
    // Step 4 a1 = a11 + c12x
    lt1 = ((signed long)si_a11<<11);
    lt2 = (signed long)si_c12x2;
    lt3 = lt1 + lt2;
    si_a1 = (signed long) lt3>>11;
    // Step 5 c22x2 = c22*Tadc
    lt1 = (signed long)sic22;
    lt2 = (signed long)uiTadc;
    lt3 = lt1 * lt2;
    si_c22x2 = (signed long)(lt3);
    // Step 6 a2 = b2 + c22x2
    lt1 = ((signed long)sib2<<15);
    lt2 = ((signed long)si_c22x2>1);
    lt3 = lt1+lt2;
    si_a2 = ((signed long)lt3>>16);
    // Step 7 a1x1 = a1 * Padc
    lt1 = (signed long)si_a1;
    lt2 = (signed long)uiPadc;
    lt3 = lt1*lt2;
    si_a1x1 = (signed long)(lt3);
    // Step 8 y1 = a0 + a1x1
    lt1 = ((signed long)sia0<<10);
    lt2 = (signed long)si_a1x1;
    lt3 = lt1+lt2;
    si_y1 = ((signed long)lt3>>10);
    // Step 9 a2x2 = a2 * Tadc
    lt1 = (signed long)si_a2;
    lt2 = (signed long)uiTadc;
    lt3 = lt1*lt2;
    si_a2x2 = (signed long)(lt3);
    // Step 10 pComp = y1 + a2x2
    lt1 = ((signed long)si_y1<<10);
    lt2 = (signed long)si_a2x2;
    lt3 = lt1+lt2;
    // Fixed point result with rounding
    //siPcomp = ((signed int)lt3>>13);
    siPcomp = lt3/8192;
    // decPcomp is defined as a floating point number
    // Conversion to decimal value from 1023 ADC count value
    // ADC counts are 0 to 1023, pressure is 50 to 115kPa respectively
    decPcomp = ((65.0/1023.0)*siPcomp)+50;
    return decPcomp;
}

unsigned int readRegister(byte thisRegister) {   
    byte result = 0;
    // select the MPL115A1
    digitalWrite(MPL115A1_SELECT_PIN, LOW);
    // send the request
    SPI.transfer(thisRegister | MPL115A1_READ_MASK);
    result = SPI.transfer(0x00);
    // deselect the MPL115A1
    digitalWrite(MPL115A1_SELECT_PIN, HIGH);
    return result;  
}

void writeRegister(byte thisRegister, byte thisValue) {
    // select the MPL115A1
    digitalWrite(MPL115A1_SELECT_PIN, LOW);
    // send the request
    SPI.transfer(thisRegister & MPL115A1_WRITE_MASK);
    SPI.transfer(thisValue);    
    // deselect the MPL115A1
    digitalWrite(MPL115A1_SELECT_PIN, HIGH);
}

Tuesday, June 10, 2014

Python Code to Read & Parse TSIP Data from a Copernicus II GPS Module on an Arduino Yun

The following example shows to read and parse some basic GPS data (location and time) in TSIP format from a Copernicus II GPS module. TSIP data is in binary format, unlike NMEA sentences that are in ASCII format. This code is in Python and is designed to run on an Arduino Yun.  In this case, the Copernicus II is connected to the Arduino Yun using the Yun's USB port.  I use a Sparkfun 3.3V FTDI adapter.

Connections


Copernicus II FTDI Adapter
TX-B          RXI
RX-B          TXO
GND           GND

The Copernicus II also has its VCC and GND connected to 3.3V and GND on the Yun.

This example assumes that you are using SSH to access your Yun's command line.  The program outputs the current GPS time and location to the terminal.

Prerequisits


This example requires a couple modules that are installed using opkg at the command line on the Yun.  The following commands will install them:


opkg update
opkg install kmod-usb-serial-ftdi
opkg install pyserial

Code


import serial
import struct
import math
import datetime

ser = serial.Serial("/dev/ttyUSB0", baudrate=38400)
tsip = []
last = ''
start = 0
dle_cnt = 0
id = 0

DLE = '\x10'
ETX = '\x03'

time = "No time data yet"
today = "No date data yet"

# GPS date counts weeks starting Jan. 6, 1980
startDate = datetime.date(1980, 1, 6)

while True:
  data = ser.read()
  # Test for data frame start marker DLE (0x10)
  if start == 0 and data == DLE:
    start = 1
  # To avoid confusion, when the frame marker DLE (0x10) occurs in the sequence of data
        # bytes in the data frame, it is doubled.  We need to drop the extra 0x10 by skipping the 
  # rest of the loop for the current iteration. Count the number of DLEs to track whether 
  # it is even or odd.  The count DLE that marks the end of the frame - before ETX (0x03) - 
  # is always odd. See p. 122 of the Copernicus II manual.
  elif start == 1 and data == DLE:
    dle_cnt = dle_cnt + 1
    if last == DLE:
      continue
  # If the last byte was DLE (0x10) and the current byte is not ETX (0x03),
  # then the current byte is the packet ID
  elif start == 1 and data != ETX and last == DLE:
    id = data
  # If the current byte is 0x03 (ETX), has come right after DLE (0x10), & the 
  # DLE count is odd, we have reached the end of the data frame.
  elif start == 1 and data == ETX and last == DLE and dle_cnt % 2 == 1:
    dle_cnt = 0
    last = ''
    tsip.append( data )

    # Packet 0x41 has GPS time data
    if id == '\x41':
      # t is number of seconds since start of week
      t = int(struct.unpack('f', "".join(tsip[2:6]))[0])
      # w is number of weeks since Jan. 6, 1980
      w = int(struct.unpack('h', "".join(tsip[6:8]))[0])
      # off is offset in seconds between GPS time and GMT
      off = struct.unpack('f', "".join(tsip[8:12]))[0]
      # Subtract offset seconds from time in seconds from start 
      # of week
      t = t - off
      # Day of current week; Sunday = 0
      day = math.floor(t / 86400)
      # Hour of the day
      hour = math.floor((t - (day * 86400))/3600)
      # Minutes after the hour
      min = math.floor((t - (day * 86400) - (hour * 3600))/60)
      # Seconds into current minute
      sec = t - (day * 86400) - (hour * 3600) - (min * 60)      
      time = "%02d:%02d:%02d GMT" % (hour, min, sec)
      today = startDate + datetime.timedelta(weeks=w) + \
        datetime.timedelta(days=day)
      
    # Packet 0x84 has the position data we need.
    # See p. 163 of the Copernicus II manual for structure of this packet
    if id == '\x84':
      # Join bytes into string without added spaces and unpack as big-endian 
      # double.  struct.unpack returns a tuple. Our value is in the first element.
      lat_rad = struct.unpack('>d', "".join(tsip[2:10]))[0]
      lat_deg = lat_rad * 180.0 / math.pi
      lat_dir = 'N' if lat_deg > 0 else 'S'
      long_rad = struct.unpack('>d', "".join(tsip[10:18]))[0]
      long_deg = long_rad * 180.0 / math.pi
      long_dir = 'E' if long_deg > 0 else 'W'
      print "%s %s:  %02.6f %s  %03.6f %s\n" % (today, time, abs(lat_deg), 
        lat_dir, abs(long_deg), long_dir)
      id = 0
    tsip = []
    start = 0
    continue
  last = data    
  tsip.append(data)