Sunday 10 July 2011

Weather station -improvements inc SD card data storage

Having an instant pressure and temperature readout on an LCD is kinda cool, but not hugely useful...

So I popped in an SD card using the SPI comms interface on the ATMega (plus a few resistors to drop the 5v signal down to 3.3V that the SD card uses) and can log CSV data that can be read by a PC later.

Next step - adding a real time clock so the board logs the current time/date rather than milliseconds from power on!

Code after the jump


/*
Basic weather station
Measures pressure and temperature from a BMP085 sparkfun sensor board
Power to the 3V3 regulator is sourced from digital pin 8
Readings are output on serial and to a 2x16 LCD (using a 595 latch to reduce the pincount needed to control the LCD)


Matt Wright 2011

Assorted code from various sources including:
SparkFun Electronics

 * SD card attached to SPI bus as follows:
 ** MOSI - pin 11
 ** MISO - pin 12
 ** CLK - pin 13
 ** CS - pin 4

*/


#include <Wire.h>
#include <SD.h>

//LCD lib, via shift register (595 chip)
#include <LiquidCrystal595.h>

//Watchdog statements
#include <avr/sleep.h>
#include <avr/wdt.h>
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
volatile boolean f_wdt=1;


// BMP085 barometric pressure sensor
#define BMP085_ADDRESS 0x77  // I2C address of BMP085
const unsigned char OSS = 0;  // Oversampling Setting

// BQ32000DR real time clock
#define rtc_addr_write 0xD0
#define rtc_addr_read 0xD1

// Calibration values
int ac1;
int ac2;
int ac3;
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1;
int b2;
int mb;
int mc;
int md;




// b5 is calculated in bmp085GetTemperature(...), this variable is also used in bmp085GetPressure(...)
// so ...Temperature(...) must be called before ...Pressure(...).
long b5;

short temperature;
long pressure;
// LCD panel
// initialize the library with the numbers of the interface pins + the row count
// datapin, latchpin, clockpin, num_lines
LiquidCrystal595 lcd(4,5,6);

String strStatus = "Wx station v01";

File myFile;

void setup()
{
  int i;
  Serial.begin(9600);
  Serial.println("Starting up pressure sensor...");
 
  //power up the 3V3 rail, wait 100ms for everything to get going???
  pinMode(8, OUTPUT);
  digitalWrite(8, HIGH);
  delay(50);
 
  //Start comms with the BMP085 board
  Wire.begin();
  bmp085Calibration();
  lcd.begin(16, 2);
  print_to_LCD();
 
  //SD card initialisation
  pinMode(10, OUTPUT);
  if (!SD.begin(10)) {
    Serial.println("SD card initialization failed!");
    return;
  }
  Serial.println("SD card initialization done.");
 
  myFile = SD.open("data.txt", FILE_WRITE);
  if (myFile)
  {
      myFile.println("Wx Station v01 - data capture file");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
 
  /*
  //test code for getting time>>>
  Wire.beginTransmission(rtc_addr_write);
  Wire.send(0x00);
  Wire.endTransmission();
  Wire.beginTransmission(rtc_addr_write);
  Wire.send(B10000000);
  Wire.endTransmission(); 
 
  for(i=0;i<10;i++)
  {
    Wire.beginTransmission(rtc_addr_read);
//    Wire.send(0x00);
//    Wire.endTransmission();
//    Wire.beginTransmission(rtc_addr_read);
    Wire.send(i);
    Wire.endTransmission();
    Serial.println("I2C: Tx " + String(i));
   
    Wire.requestFrom(rtc_addr_read, 7);    // request bytes from slave device
 
    while(Wire.available())    // slave may send less than requested
    {
      unsigned int ic = Bcd2Dec(Wire.receive()); // receive a byte as character
      Serial.print(ic);         // print the character
    }
    Serial.println(";");
    delay(10);
  }
  */
 
 
  //config watchdog
  cbi( SMCR,SE );      // sleep enable, power down mode
  cbi( SMCR,SM0 );     // power down mode
  sbi( SMCR,SM1 );     // power down mode
  cbi( SMCR,SM2 );     // power down mode
  //select watchdog timer
  setup_watchdog(9);
 
}



void loop()
{
  if (f_wdt==1)
  {  // wait for timed out watchdog / flag is set when a watchdog timeout occurs
    f_wdt=0;       // reset flag 
    //turn on 3V3 rail to sensor
    digitalWrite(8, HIGH);
    delay(50);
    //get readings
    temperature = bmp085GetTemperature(bmp085ReadUT());
    pressure = bmp085GetPressure(bmp085ReadUP());
 
    //turn off supply to sensor
    digitalWrite(8, LOW);
    print_to_LCD();
    print_to_SD();
   
    //wait for LCD print to finish then go back to sleep!
    delay(200);
    system_sleep();
  }
 
    //delay(5000);
}

// 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);
  Serial.println("Calibrated sensor");
 
}

// Calculate temperature given ut.
// Value returned will be in units of 0.1 deg C
short bmp085GetTemperature(unsigned int ut)
{
  long x1, x2;
 
  x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
  x2 = ((long)mc << 11)/(x1 + md);
  b5 = x1 + x2;

  return ((b5 + 8)>>4); 
}

// Calculate pressure given 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.
long bmp085GetPressure(unsigned long up)
{
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;
 
  b6 = b5 - 4000;
  // Calculate B3
  x1 = (b2 * (b6 * b6)>>12)>>11;
  x2 = (ac2 * b6)>>11;
  x3 = x1 + x2;
  b3 = (((((long)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 * (unsigned long)(x3 + 32768))>>15;
 
  b7 = ((unsigned long)(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'
char bmp085Read(unsigned char address)
{
  unsigned char data;
 
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.send(address);
  Wire.endTransmission();
 
  Wire.requestFrom(BMP085_ADDRESS, 1);
  while(!Wire.available())
    ;
   
  return Wire.receive();
}

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

// Read the uncompensated temperature value
unsigned int bmp085ReadUT()
{
  unsigned int ut;
 
  // Write 0x2E into Register 0xF4
  // This requests a temperature reading
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.send(0xF4);
  Wire.send(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
unsigned long 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.send(0xF4);
  Wire.send(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.send(0xF6);
  Wire.endTransmission();
  Wire.requestFrom(BMP085_ADDRESS, 3);
 
  // Wait for data to become available
  while(Wire.available() < 3)
    ;
  msb = Wire.receive();
  lsb = Wire.receive();
  xlsb = Wire.receive();
 
  up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
 
  return up;
}

void print_to_LCD()
{
/*
LCD formatting
0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15
P  =  x  x  x  m  B  .  T  =  x   x   .   x
s  t  a  t  u  s  s  t  r  i  n   g
*/

  int pr = pressure*0.01;
  int te = temperature*0.1;

  String readout = String(pressure);
  lcd.setCursor(2,0);
  lcd.print(readout);
 
  readout = String(temperature);
  lcd.setCursor(11,0);
  lcd.print(readout);
  lcd.setCursor(10,0);
  lcd.print(readout); 
  lcd.setCursor(12,0);
  lcd.print("."); 
 
  lcd.setCursor(0,1);
  lcd.print(strStatus);
  lcd.setCursor(0,0);
  lcd.print("P=");
  lcd.setCursor(5,0);
  lcd.print("mB");
  lcd.setCursor(8,0);
  lcd.print("T=");
  lcd.setCursor(14,0);
  lcd.print("C");
 
}

void print_to_SD()
{

  String readout = String(millis()) + " , " + pressure + " , " + temperature;
  myFile = SD.open("data.txt", FILE_WRITE);
  if (myFile) {
    myFile.println(readout);
    myFile.close();
  }
  Serial.println(readout);
}


//////////////////////////
//watchdog funcs

void system_sleep() {

  cbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter OFF

  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
  sleep_enable();

  sleep_mode();                        // System sleeps here

    sleep_disable();                     // System continues execution here when watchdog timed out
    sbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter ON

}
//****************************************************************
// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
void setup_watchdog(int ii)
{
  byte bb;
  int ww;
  if (ii > 9 ) ii=9;
  bb=ii & 7;
  if (ii > 7) bb|= (1<<5);
  bb|= (1<<WDCE);
  ww=bb;
  Serial.println(ww);

  MCUSR &= ~(1<<WDRF);
  // start timed sequence
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  // set new watchdog timeout value
  WDTCSR = bb;
  WDTCSR |= _BV(WDIE);

}
//**************************************************************** 
// Watchdog Interrupt Service / is executed when  watchdog timed out
ISR(WDT_vect)
{
  f_wdt=1;  // set global flag
}

unsigned short Bcd2Dec(unsigned short bcd)
{
  return ((bcd >> 4)*10+(bcd & 0x0F));
}

No comments:

Post a Comment