Notes About Working with Various Arduino & Netduino Microcontroller Boards

Friday, May 30, 2014

Reading Temperature from Multiple 1-Wire DS18B20 Sensors via a um-FPU64 Math Coprocessor

The um-FPU64 math coprocessor includes interfaces for connecting to many different kinds of sensors via its DEVIO functions. The free IDE that comes with the um-FPU64 includes examples of us er-defined routines stored in the coprocessor that  can be called from from an Arduino sketch, but I wanted to try to get it to work just from the "Arduino side" (without using the um-FPU64 IDE to program routines).  This example is really just exploration.  An Arduino can very handily read DS18B20s without help.  This example is really more about trying out the DEVIO instructions sent to the coprocessor using the Arduino FPU64 library. The code below reads the temperature from multiple DS18B20s, converts the reading to Celsius and Fahrenheit. It then calculates the average of the readings using the FPU64's matrix functions.  The average temperature in Celsius and Fahrenheit is then displayed on a Sainsmart I2C LCD display.  The Arduino drives the display because I haven't been able to get the um-FPU64's DEVIO LCD and DEVIO I2C functions to work with this display.

The um-FPU64 is available from Solarbotics. You can find documentation for the um-FPU64 on the MicroMega Corp. Web site.

The Arduino FPU64 library is available here on the Arduino Playground.

See my previous post on the SainSmart IIC/I2C/TWI Serial 2004 20x4 LCD Module Shield for a quick introduction and link to the relevant library.

Connections


The um-FPU64 is connected to the Arduino Uno via SPI.  See this diagram on the Arduino Playground for connection information.  It is also possible to connect the coprocessor to the Arduion via I2C, though that is a slower connection.  See my earlier post, Connecting an uM-FPU64 Math Co-Processor to an Arduino Due via I2C, for more information.

The DS18B20s are hooked up for non-parasitic power.  Looking at the flat side of the sensor's head, the left pin is connected to 3V3, the center pin is connected to the center pin of the next sensor, and the right pin is connected to GND.  There is a 4.7k Ohm pull-up resistor on the connection to the FPU's D0 pin.

Code


The Fpu.write() instruction can send up to 6 FPU instructions and parameters in a single call.  This code tries to be efficient by sending multiple commands and parameters in each call, where this is possible.  Note that grouping multiple DEVIO calls in one Fpu.write() does not work or produces odd results. 

#include <Fpu64.h>
#include <SPI.h>
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>


// ---> Change to number of DS1820B devices <--- 
#define DEV_CNT   5

//--- uM-FPU Register Definitions ---
#define RAW_TEMP  15   
#define AVG_TEMP  16
#define TEMP_C    20 //Start of group of registers to hold temp C data for each device 
#define TEMP_F    30 //Start of group of registers to hold temp F data for each device
#define AVG_C     40
#define AVG_F     50
#define DEV_CNT   60
#define DEV_ADDR  130 //Start of group of 128bit registers to hold 1-wire addr data for each device 

//--- OneWire Command Definitions --
#define OWIRE               0x10
#define ENABLE              0x01
#define RESET               0x02
#define SEARCH              0x05
#define SELECT              0x03
#define WRITE_BYTE          0x20
#define CONVERT_T           0x44
#define READ_SCRATCH_PAD    0xBE
#define READ_REG16_LSB_SE   0x3D

// Definition of matrix average operation not included in FPU64.h
#define AVE                 0x16

#define FPU64_PIN_D0        0x00

//Addr: 0x3F, 20 chars & 4 lines
LiquidCrystal_I2C lcd(0x3F, 20, 4); 

// Buffer for string representation of temperature to send to LCD
char text[10];

void setup() {
  Fpu.begin(); 
  delay(1000);
  Fpu.reset(); 
  lcd.init(); 
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Please wait...");
}  

void loop() { 
  // Start 1-wire bus connected to um-FPU64 pin D0  
  Fpu.write(DEVIO, OWIRE, ENABLE, FPU64_PIN_D0, 0x00);          
  Fpu.write(DEVIO, OWIRE, RESET);
  // Find 1-wire devices on bus
  // Device addresses stored starting at reg DEV_ADDR (128 bit)
  // Device addresses stored starting with least significant byte
  Fpu.write(DEVIO, OWIRE, SEARCH, DEV_CNT, DEV_ADDR);    
  // Cycle though devices, get ID & temp reported by each
  for(int i = 0; i < DEV_CNT; i++) {
    // Need to reset bus each time a device is accessed
    Fpu.write(DEVIO, OWIRE, RESET);  
    // Select device to communicate with
    Fpu.write(DEVIO, OWIRE, SELECT, DEV_ADDR+i);
    // Command device to take temp reading & store
    // in device's scratchpad
    Fpu.write(DEVIO, OWIRE, WRITE_BYTE, CONVERT_T);
    // Wait for reading to complete
    delay(750);
    Fpu.write(DEVIO, OWIRE, RESET);
    Fpu.write(DEVIO, OWIRE, SELECT, DEV_ADDR+i);
    // Send command to read raw temp data from device's scratch pad
    Fpu.write(DEVIO, OWIRE, WRITE_BYTE, READ_SCRATCH_PAD);
    // Now load raw data into FPU's RAW_TEMP register for calculation    
    // Read 2 bytes starting with least significant byte (LSB)
    // This value is a long integer
    Fpu.write(DEVIO, OWIRE, READ_REG16_LSB_SE, RAW_TEMP);
    // Use the FPU TEMP_C register corresponding to device as accumulator.
    // Load value from RAW_TEMP register
    // Convert it to a float & divide by 16 to get Celsius
    Fpu.write(SELECTA, TEMP_C+i, LSET, RAW_TEMP, F_FLOAT, FDIVI, 16); 
    //FpuSerial.printFloat(84);
    //Serial.print(" C, ");
    // Convert to Fahrenheit in corresponding register
    Fpu.write(SELECTA, TEMP_F+i, FSET, TEMP_C+i, FMULI, 9);
    Fpu.write(FDIVI, 5, FADDI, 32);
    // If last device, calculate and print averages using FPU64's matrix functions 
    if(i == DEV_CNT-1) {
      //Serial.print("\n>>> AVG: ");
      // Define a 1-column matrix using registers with TEMP_C values
      Fpu.write(SELECTMA, TEMP_C, DEV_CNT, 1);
      // Matrix average function puts average in reg 0 -
      // move average to reg AVG_TEMP so that it is not overwritten in reg 0
      // Convert float to ASCII using 9 digit format with 5 decimal places
      Fpu.write(MOP, AVE, SELECTA, AVG_TEMP, FSET0, FTOA, 95);
      // Clear buffer for string representation of temperature
      memset(text, 0x0, sizeof text);
      Fpu.readString(text);
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print(text);
      lcd.print(" C");
      // Repeat with matrix of Fahrenheit temps
      Fpu.write(SELECTMA, TEMP_F, DEV_CNT, 1);
      Fpu.write(MOP, AVE, SELECTA, AVG_TEMP, FSET0, FTOA, 95);
      memset(text, 0x20, sizeof text);
      Fpu.readString(text);
      lcd.setCursor(0, 1);
      lcd.print(text);
      lcd.print(" F");
    }
  }
}

Monday, May 19, 2014

Resolving a Connectivity Problem with the Arduino WiFi Shield: Wireless Network Joined but Cannot Send or Receive Data

I had used the Arduino WiFi shield with an Arduino Uno successfully in the past, but a few months have gone by since I last did so.  It came as a surprise yesterday when I couldn't get the shield to work.  It connected to my wireless network without any problems, but I couldn't get it to connect to any other device or computer over the network or accept any connections.  Even the WiFi examples included in the IDE wouldn't work.  I had previously updated the shield's firmware (to version 1.1.0), so I didn't think that could be the issue.  I tried several versions of the Arduino IDE, but that didn't help either.

After downloading the latest version of the Arduino IDE (ver. 1.5.6 beta r2) and installing the firmware that comes with it, I have gotten the Arduino WiFi shield to work again.   Here are some notes on the process that worked for me (under Windows 8).

  1. Download the  AVR32 USB driver from http://www.dfrobot.com/image/data/TEL0064/AVR32%20USB%20Driver.zip.
  2. With the WiFi shield connected to the computer via USB and the J3 jumper in place (see the Arduino instructions for flashing the firmware to the WiFi shield), update the driver for the Atmel USB Device/AT32UC3A in the Device Manager (using the one downloaded in the previous step).
  3. When running the commands in the Arduino document to update the firmware, edit the paths for wifi_dnld.elf and  wifiHD.elf to point to the files in the Arduino IDE 1.5.6 installation.  The commands should look something like this -
    • batchisp.exe -device AT32UC3A1256 -hardware usb -operation erase f memory flash blankcheck loadbuffer D:/Arduino/hardware/arduino/avr/firmwares/wifishield/binary/wifi_dnld.elf program verify start reset 0
    • batchisp.exe -device AT32UC3A1256 -hardware usb -operation erase f memory flash blankcheck loadbuffer D:/Arduino/hardware/arduino/avr/firmwares/wifishield/binary/wifiHD.elf program verify start reset 0
    • Of course, your exact paths will most likely be different.  Just be sure to use slashes (/) rather than backslashes in the paths to the .elf files.


Thursday, May 8, 2014

RFID Using the Innovations ID20-LA with the Arduino Yun's USB Host Port

I've posted previously about using the Innovations ID20-LA and the Sparkfun RFID Reader breakout board with the Arduino Yun.  My earlier post used the serial break-out pins on the reader board to connect to the Yun using SoftwareSerial (on the Arduino side of the Yun).  It is also possible to use the USB connector on the reader board to connect to the Yun's USB host port.  The correct FTDI driver and Python pySerial library make this method very easy to use. In this case, the USB host is accessed via the Linux side of the Yun.

The following instructions assume that you are connected to the Yun via SSH. Log in using an account with root privileges.  You could also use the YunSerialTerminal example to access the command prompt.

First, make sure that your opkg package data is up to date:

opkg update

Then install the FTDI driver:

opkg install kmod-usb-serial-ftdi

After the FTDI support is installed, connect the reader and reader break-out board.  You should see the serial USB device in the device tree as /dev/ttyUSB0.

The following opkg command installs 

opkg install pyserial

You can run a quick test using the following Python code:

import serial
serial = serial.Serial("/dev/ttyUSB0", baudrate=9600)

code = ''

while True:
        data = serial.read()
        if data == '\r':
                print(code)
                code = ''
        else:
                code = code + data


Monday, May 5, 2014

Reading Barometric Pressure with an Arduino Galileo & BMP085 Pressure Sensor

I have an Adafruit BMP085 (now replaced by BMP180) pressure sensor that I wanted to use with an Arduino Galileo, but I couldn't get the code that I had used with an Uno to work with the Arduino Galileo - the readings were wildly wrong. The libraries from Adafruit would not compile for the Galileo.

Starting with the sample BMP085 Arduino code from Sparkfun, I have come up with the following code that reads the adjusted barometric pressure.  To keep this example simple,   the code prints the pressure (in inches of Mercury) to the serial console.

The key to getting correct readings with to change the numeric data types to portable C numeric types (int16_t, uint16_t, etc...).

This example also uses C++ style output streaming (to the serial console), as suggested by Mikal Hart in this post at the Arduino Playground.

This code also works with the Arduino Yun.

Connections


Adafruit BMP085  Galileo
3Vo              3.3V
SCL              SCL
SDA              SDA
GND              GND

Code


#include <Wire.h>

#define BMP085_ADDRESS 0x77  // I2C address of BMP085

// Provides C++ style output streaming
template<class T> inline Print &operator <<(Print &obj, T arg) { 
  obj.print(arg); return obj; 
}

const uint8_t OSS = 0;  // Oversampling Setting

// 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;
double tempF;

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

void loop()
{
  t = bmp085GetTemperature(bmp085ReadUT());
  p = bmp085GetPressure(bmp085ReadUP());
  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. Converted to inches Hg this is 0.935
  pInHg += 0.935;
  pInHg = trunc(pInHg * 100)/100;
  
  // Print output to serial console
  Serial << "Pressure: " << pInHg << " in. Hg\n";
  
  delay(1000);
}

// Stores all of the bmp085's calibration values into global variables
// Calibration values are required to calculate temp and pressure
// This function should be called at the beginning of the program
void bmp085Calibration()
{
  ac1 = bmp085ReadInt(0xAA);
  ac2 = bmp085ReadInt(0xAC);
  ac3 = bmp085ReadInt(0xAE);
  ac4 = bmp085ReadInt(0xB0);
  ac5 = bmp085ReadInt(0xB2);
  ac6 = bmp085ReadInt(0xB4);
  b1 = bmp085ReadInt(0xB6);
  b2 = bmp085ReadInt(0xB8);
  mb = bmp085ReadInt(0xBA);
  mc = bmp085ReadInt(0xBC);
  md = bmp085ReadInt(0xBE);
}

// Calculate temperature given unadjusted temp ut.
// Value returned will be in units of 0.1 deg C
short bmp085GetTemperature(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 bmp085GetPressure(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;
}

// Read 1 byte from the BMP085 at 'address'
int8_t bmp085Read(uint8_t address)
{
  uint8_t data;
  
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
  
  Wire.requestFrom(BMP085_ADDRESS, 1);
  while(!Wire.available())
    ;
    
  return Wire.read();
}

// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int16_t bmp085ReadInt(uint8_t address)
{
  uint8_t msb, lsb;
  
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
  
  Wire.requestFrom(BMP085_ADDRESS, 2);
  while(Wire.available()<2)
    ;
  msb = Wire.read();
  lsb = Wire.read();
  
  return (int16_t) msb<<8 | lsb;
}

// Read the uncompensated temperature value
uint16_t bmp085ReadUT()
{
  uint16_t ut;
  
  // Write 0x2E into Register 0xF4
  // This requests a temperature reading
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x2E);
  Wire.endTransmission();
  
  // Wait at least 4.5ms
  delay(5);
  
  // Read two bytes from registers 0xF6 and 0xF7
  ut = bmp085ReadInt(0xF6);
  return ut;
}

// Read the uncompensated pressure value
uint32_t bmp085ReadUP()
{
  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;
  
  // Write 0x34+(OSS<<6) into register 0xF4
  // Request a pressure reading w/ oversampling setting
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x34 + (OSS << 6));
  Wire.endTransmission();
  
  // Wait for conversion, delay time dependent on OSS
  delay(2 + (3 << OSS));
  
  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF6);
  Wire.endTransmission();
  Wire.requestFrom(BMP085_ADDRESS, 3);
  
  // Wait for data to become available
  while(Wire.available() < 3)
    ;
  msb = Wire.read();
  lsb = Wire.read();
  xlsb = Wire.read();
  
  up = (((uint32_t) msb << 16) | ((uint32_t) lsb << 8) | (uint32_t) xlsb) >> (8-OSS);
  
  return up;
}