diff --git a/cosmicpi-arduino_V1.6.ino b/cosmicpi-arduino_V1.6.ino new file mode 100644 index 0000000..4b49d7c --- /dev/null +++ b/cosmicpi-arduino_V1.6.ino @@ -0,0 +1,529 @@ +//Cosmic Pi software for Arduino - modified for STM32, +//J. Devine +//July 2019. +//Licensed under GPL V3 or later. +//cosmicpi.org + +#include +// LPS lib from here: https://github.com/pololu/lps-arduino +#include "src/LPS.h" +// LSM9DS1 lib from here: https://github.com/adafruit/Adafruit_LSM9DS1 +//with some address tweaks + +//#include "src/Adafruit_Sensor.h" +#include "src/Adafruit_LSM9DS1.h" +// HTU21D lib from here: https://github.com/adafruit/Adafruit_HTU21DF_Library +#include "src/SparkFunHTU21D.h" + + +//counter for how long the leds stay on. +int last_event_LED = 0; + + +// i2c +Adafruit_LSM9DS1 lsm = Adafruit_LSM9DS1(); + +//declare pressure sensor +LPS ps; + +//Create an instance of the object +HTU21D myHumidity; + + + +// string used for passing data from the dump routines +static const int TXTLEN = 512; +static char txt[TXTLEN]; //internal buffer +static char txts[TXTLEN]; //sensor buffer + + +//pinouts +/* + PA0 - Pin 14 - Shaped Signal 1 + PA1 - Pin 15 - Shaped Signal 2 + PA2 - Pin 16 - TX0 + PA3 - Pin 16 - RX0 + PA4 - Pin 20 - LED1 - Power/GPS + PA5 - Pin 21 - LED2 - Event + PA6 - Pin 22 - Injection leds + PA7 - Pin 23 - Bias FB1 + PA8 - Pin 41 - SCL_Slave + PA9 - Pin 42 - USB_OTG_VBUS + PA10 - Pin 43 - GPSTX + PA11 - Pin 44 - USBOTG DM + PA12 - Pin 45 - USBOTG DP + PA13 - Pin 46 - SWDIO + PA14 - Pin 49 - SWCLK + PA15 - Pin 50 - GPSPPS Input + PB0 - Pin 26 - Bias FB2 + PB1 - Pin 27 - Flag to RPi + PB2 Pin 28 - NC + PB 3 - Pin 55 - NC + PB4 - Pin 56 - SDA_Slave + PB5 - Pin 57 - NC + PB6 - Pin 58 - GPSRX + PB7 - Pin 59 - SDA0 + PB8 - Pin 61 - SCL0 + PB9 - Pin 62 - NC + PB10 - Pin 29 - Trigout (input to STM) + PB12 - Pin 33 - NC + PB13 - Pin 34 0 HVPSU SCLK (Clock to MAX1932) + PB14, PB15 - NC + PC0 - Pin 8 - NC + PC1 - Pin 9 - HVPSU CL1 + PC2 - Pin 10 - HVPSU CL2 + PC3 - Pin 11 - HV PSU DIN + PC4, PC5, PC6- NC + PC7 - Pin 38 - HVPSU CS2 + PC8 - Pin 39 - HVPSU CS1 + PC9 - Pin 40 - Mag Interrupt + PC10 - Pin 51 - NC + PC11 - Pin 52 - STRIGOUT B + PC12 - Pin 53 - STRIGOUT A + PC13 - Pin 2 - Baro Int + PC14 - Pin 3 - Accelint 1 + PC15 - Pin 4 - Accelint 2. + +*/ + +//eeprom writing table +/* + eeprom status byte - 00 + channel 0 high byte - 01 + channel 0 low byte - 02 + channel 1 high byte - 03 + channel 1 low byte - 04 + HV bias ch 0 value - 05 + HV bias ch 1 value - 06 + + if eeprom status byte = 1 then only one set of values are stored + if =2 then a backup set are located higher in memory, not used at the moment +*/ + + +#include +#include +static const int SERIAL_BAUD_RATE = 19200; // Serial baud rate for version 1.5 production +static const int GPS_BAUD_RATE = 9600; // GPS and Serial1 line + +// LED pins +#define PPS_PIN PA4 // PPS (Pulse Per Second) and LED +#define EVT_PIN PA5 // Cosmic ray event detected + +char numconvbuff[16]; + +// Leds flag +bool leds_on = true; + +// How long the event LED should light up (in ms) +static int event_LED_time = 15; + +long eventCount = 0; +int eventStack = 0; +unsigned long pps_micros = 0; +unsigned long pps_micros_old = 0; +unsigned long micros_since_pps = 0; + +#define maxevent 100 //we don't expect more events than this +unsigned long evttime [maxevent]; + +//set up serial output buffer +#define OutBuffSize 1024 //we don't expect more chars than this in the buffer +char outputbuffer[OutBuffSize]; //1024 character output buffer +int writeincounter = 0; //the place for writing to the buffer +int readoutcounter = 0; //the place for reading out from the buffer + +void WriteToOutputBuff(char *outstring) +{ + int stringlengthmax = strlen(outstring); + + for (int addchar = 0; addchar < stringlengthmax; addchar++) { + outputbuffer[writeincounter] = outstring[addchar]; + writeincounter++; + } +} + +void ReadFromOutputBuff() +{ + char digitout[2]; + if (writeincounter > readoutcounter) + { + digitout[0] = outputbuffer[readoutcounter]; + digitout[1] = '\0'; + Serial.print(digitout); + readoutcounter++; + } + if (readoutcounter == writeincounter) { + readoutcounter = 0; + writeincounter = 0; + //Serial.println("BUFFERCLEAR"); + } +} + +void setupSensor() +{ + // 1.) Set the accelerometer range + lsm.setupAccel(lsm.LSM9DS1_ACCELRANGE_2G); + //lsm.setupAccel(lsm.LSM9DS1_ACCELRANGE_4G); + //lsm.setupAccel(lsm.LSM9DS1_ACCELRANGE_8G); + //lsm.setupAccel(lsm.LSM9DS1_ACCELRANGE_16G); + + // 2.) Set the magnetometer sensitivity + lsm.setupMag(lsm.LSM9DS1_MAGGAIN_4GAUSS); + //lsm.setupMag(lsm.LSM9DS1_MAGGAIN_8GAUSS); + //lsm.setupMag(lsm.LSM9DS1_MAGGAIN_12GAUSS); + //lsm.setupMag(lsm.LSM9DS1_MAGGAIN_16GAUSS); + + // 3.) Setup the gyroscope + lsm.setupGyro(lsm.LSM9DS1_GYROSCALE_245DPS); + //lsm.setupGyro(lsm.LSM9DS1_GYROSCALE_500DPS); + //lsm.setupGyro(lsm.LSM9DS1_GYROSCALE_2000DPS); +} + + + +// GPS and time flags +boolean gps_ok = false; // Chip OK flag +boolean pps_recieved = false; + + + +// ------------------------- Arudino Functions + +// Arduino setup function, initialize hardware and software +// This is the first function to be called when the sketch is started + +//setup serials +HardwareSerial GPS(PA10, PB6); + +void setup() { + Serial.begin(SERIAL_BAUD_RATE); + Serial.print("ON"); + + if (leds_on) { + pinMode(EVT_PIN, OUTPUT); // Pin for the cosmic ray event + pinMode(PPS_PIN, OUTPUT); // Pin for the PPS (LED pin) + } + if (leds_on) { + digitalWrite(PPS_PIN, HIGH); // Turn on led + } + + // start the GPS + GPS.begin(GPS_BAUD_RATE); + GpsSetup(); + + // start the i2c bus + Wire.begin(); + Wire.setSDA(PB7); + Wire.setSCL(PB8); + + //setup the thresholds + //initialise to values from EEPROM, else use defaults. + + int eeprom_read = 0; + + eeprom_read = EEPROM.read(0x00); + if (eeprom_read > 0) { + + Serial.println("Found existing settings for Thresholds and HV, these will be applied"); + Serial.print("First channel threshold settings: 0x"); + Serial.print(EEPROM.read(0x01), HEX); + Serial.println(EEPROM.read(0x02), HEX); + Serial.print("Second channel threshold settings: 0x"); + Serial.print(EEPROM.read(0x03), HEX); + Serial.println(EEPROM.read(0x04), HEX); + + + + Wire.begin(); + Wire.beginTransmission(byte(0x60)); // transmit to device #112 + Wire.write(byte(0x00)); //sets value to the first channel + eeprom_read = EEPROM.read(0x01); + Wire.write(byte(eeprom_read)); + eeprom_read = EEPROM.read(0x02); + Wire.write(byte(eeprom_read)); + Wire.endTransmission(); // stop transmitting + + Wire.beginTransmission(byte(0x60)); // transmit to device #112 + Wire.write(byte(0x01)); //sets value to the first channel + eeprom_read = EEPROM.read(0x03); + Wire.write(byte(eeprom_read)); + eeprom_read = EEPROM.read(0x04); + Wire.write(byte(eeprom_read)); + Wire.endTransmission(); // stop transmitting + Serial.println("Threshold settings complete"); + + } + else + { + + Serial.println("No existing settings for Thresholds and HV found, using defaults"); + Serial.print("First channel threshold settings: 0x"); + Serial.print(0x22F, HEX); + Serial.print("Second channel threshold settings: 0x"); + Serial.print(0x22F, HEX); + + + Wire.begin(); + Wire.beginTransmission(byte(0x60)); // transmit to device #112 + Wire.write(byte(0x00)); //sets value to the first channel + Wire.write(byte(0x02)); + Wire.write(byte(0x2F)); + Wire.endTransmission(); // stop transmitting + + Wire.beginTransmission(byte(0x60)); // transmit to device #112 + Wire.write(byte(0x01)); //sets value to the first channel + Wire.write(byte(0x02)); + Wire.write(byte(0x2F)); + Wire.endTransmission(); // stop transmitting + + Serial.println("Threshold settings complete"); + + + } + + //set the high voltage + + //config SPI pins + pinMode(PC7, OUTPUT); + pinMode(PC8, OUTPUT); + pinMode(PC3, OUTPUT); + pinMode(PB13, OUTPUT); + + + eeprom_read = EEPROM.read(0x00); + if (eeprom_read > 0) { + Serial.println("Applying stored values for HV channels"); + + Serial.print("HV channel settings Ch1: "); + Serial.print(EEPROM.read(0x05), HEX); + Serial.print(" Ch2: "); + Serial.println(EEPROM.read(0x06), HEX); + + //set HV channel 1 + eeprom_read = EEPROM.read(0x05); + digitalWrite(PC7, LOW); + setHV(byte(eeprom_read)); + digitalWrite(PC7, HIGH); + //set HV channel 2 + eeprom_read = EEPROM.read(0x06); + digitalWrite(PC8, LOW); + setHV(byte(eeprom_read)); + digitalWrite(PC8, HIGH); + + Serial.println("HV channels set"); + + } + else + { + Serial.println("Applying default values for HV channels"); + + Serial.print("HV channel settings Ch1: "); + Serial.print(0xAC, HEX); + Serial.print(" Ch2: "); + Serial.println(0xAC, HEX); + + digitalWrite(PC7, LOW); + setHV(0xAC); + digitalWrite(PC7, HIGH); + //set HV channel 2 + digitalWrite(PC8, LOW); + setHV(0xAC); + digitalWrite(PC8, HIGH); + Serial.println("HV channels set"); + + } + + + +//init the sensor suite + + Serial.println("Cosmic Pi V1.6 sensor init routine"); + //init LSM9DS1 + //lsm.begin(); + + if (!lsm.begin()) + { + Serial.println("Oops ... unable to initialize the LSM9DS1. Check your wiring!"); + while (1); + } + setupSensor(); + lsm.read(); /* ask it to read in the data */ + + /* Get a new sensor event */ + sensors_event_t a, m, g, temp; + + lsm.getEvent(&a, &m, &g, &temp); + + //init lps + ps.init(); + ps.enableDefault(); + + //init htu21d + myHumidity.begin(); + + Serial.println("INFO: Sensor setup complete\n"); + //attach the interrupts + attachInterrupt(digitalPinToInterrupt(PA15), GPS_PPS, RISING); + attachInterrupt(digitalPinToInterrupt(PB10), Event_Int, RISING); + + Serial.println("INFO: Running\n"); + +} + +void loop() { + pipeGPS(); + ReadFromOutputBuff(); + + //turn off the event led after the set time + if (millis() >= (last_event_LED + event_LED_time)){ + if (leds_on) { + digitalWrite(EVT_PIN, LOW); + } + } +} + +void GPS_PPS() +{ + + //stop listening for events while we process this interrupt + detachInterrupt(digitalPinToInterrupt(PB10)); + + //now PPS is coming through, we can switch off the internal timer. + pps_recieved = true; + + //set the pps micros value as the micro time now, and buffer the last value; + pps_micros_old = pps_micros; + pps_micros = micros(); + //how many microseconds since the last pps? (just in case we're running fast/slow) + micros_since_pps = pps_micros - pps_micros_old; + + if (leds_on) { + digitalWrite(PPS_PIN, !digitalRead(PPS_PIN)); // Toggle led + } + + + //Serial.println("pps"); + //process all events to the event buffer now that the second has ended + //Serial.print("Dumping Event Buffer"); + //print out the event stack + for (int i = 0; i < eventStack; i++) { + sprintf(txt, "Event: sub second micros:%d/%d; Event Count:%d\n", evttime[i], micros_since_pps, (eventCount - eventStack + i)); + WriteToOutputBuff(txt); + } + sprintf(txt, "PPS: GPS lock:1;\n"); + WriteToOutputBuff(txt); + + +//now print the sensors + //Serial.print("Sensor section"); + lsm.read(); /* ask it to read in the data */ + + /* Get a new sensor event */ + + sensors_event_t a, m, g, temp; + lsm.getEvent(&a, &m, &g, &temp); + + float pressure = ps.readPressureMillibars(); + float altitude = ps.pressureToAltitudeMeters(pressure); + float temperature = ps.readTemperatureC(); + + float humd = myHumidity.readHumidity(); + float temphum = myHumidity.readTemperature(); + +Serial.print("Altitude: "); +Serial.print(altitude, 6); +Serial.println(';'); +Serial.print("TemperatureCBaro: "); +Serial.print(temperature, 6); +Serial.println(';'); + +Serial.print("AccelX: "); +Serial.print(a.acceleration.x, 6); +Serial.println(';'); +Serial.print("AccelY: "); +Serial.print(a.acceleration.y, 6); +Serial.println(';'); +Serial.print("AccelZ: "); +Serial.print(a.acceleration.z, 6); +Serial.println(';'); + +Serial.print("MagX: "); +Serial.print(m.magnetic.x, 6); +Serial.println(';'); +Serial.print("MagY: "); +Serial.print(m.magnetic.y, 6); +Serial.println(';'); +Serial.print("MagZ: "); +Serial.print(m.magnetic.z, 6); +Serial.println(';'); + +Serial.print("TemperatureCHumid: "); +Serial.print(temphum, 6); +Serial.println(';'); + +Serial.print("Humidity: "); +Serial.print(humd, 6); +Serial.println(';'); + + //average temp from all 3 sensors + float out = 0; + out = temphum + temperature; + out = out / 2; +Serial.print("TemperatureC: "); +Serial.print(out, 6); +Serial.println(';'); +//Serial.print(txts); + + + + + //now we can reset the event stack and listen for events again + eventStack=0; + attachInterrupt(digitalPinToInterrupt(PB10), Event_Int, RISING); +} + + +void Event_Int() +{ + /* WriteToOutputBuff("evtx:"); + ltoa(eventCount,numconvbuff,10); + WriteToOutputBuff(numconvbuff); + + //Serial.print(name); + Serial.print("count="); + Serial.print(eventCount); + Serial.print(" "); + Serial.println(micros()-pps_micros); + */ + eventCount++; + //Serial.println("e"); + //write this event to the stack of times + evttime[eventStack] = micros() - pps_micros; + //increment the stack counter + eventStack++; + + //turn on the event led + last_event_LED = millis(); + if (leds_on) { + digitalWrite(EVT_PIN, HIGH); + } +} + +byte setHV(byte _send) // This function is what bitbangs the data +{ + if (_send > 0x5F) { + for (int i = 0; i < 8; i++) // There are 8 bits in a byte + { + digitalWrite(PC3, bitRead(_send, 7 - i)); // Set MOSI + //delay(1); + digitalWrite(PB13, HIGH); // SCK high + //bitWrite(_receive, i, digitalRead(MISO_pin)); // Capture MISO + digitalWrite(PB13, LOW); // SCK low + //digitalWrite(MOSI_pin, LOW); // Set MOSI + + } + //digitalWrite(SS_pin[j], HIGH); // SS high again + } +} +; diff --git a/gps_reading.ino b/gps_reading.ino new file mode 100644 index 0000000..dcd8651 --- /dev/null +++ b/gps_reading.ino @@ -0,0 +1,50 @@ + +// WARNING: One up the spout !! +// The GPS chip puts the next nmea string in its output buffer +// only if its been read, IE its empty. +// So if you read infrequently the string in the buffer is old and +// has the WRONG time !!! The string lies around like a bullet in +// the breach waiting for some mug. + +//I don't know who wrote that comment.. I'm guessing it was Julian! + +boolean pipeGPS() { + while (GPS.available()) { + char c[2]; + c[0] = GPS.read(); + c[1]='\0'; //null character for termination required. +//Serial.print(c); +WriteToOutputBuff(c); + } +} + +// GPS setup + +void GpsSetup() { + +// definitions for different outputs +// only one of these strings can be used at a time +// otherwise they will overwrite each other +// for more information take a look at the QUECTEL L70 protocoll specification: http://docs-europe.electrocomponents.com/webdocs/147d/0900766b8147dbdd.pdf +#define RMCGGA "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" // RCM & GGA +#define ZDA "$PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0*29" // ZDA +#define GGAZDA "$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0*28" // GGA & ZDA +#define GGA "$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" // GGA + +// gets the firmware version +#define FMWVERS "$PMTK605*31" // PMTK_Q_RELEASE +// Sets the update intervall +#define NORMAL "$PMTK220,1000*1F" // PMTK_SET_NMEA_UPDATE_1HZ +// disables updates for the antenna status (only Adafruit ultimate GPS?) +#define NOANTENNA "$PGCMD,33,0*6D" // PGCMD_NOAN +delay(500); //added delay to give GPS time to boot. + GPS.println(NOANTENNA); + GPS.println(GGAZDA); + GPS.println(NORMAL); + GPS.println(FMWVERS); + delay(1000); //wait a bit longer and repeat just in case it hasn't booted + GPS.println(NOANTENNA); + GPS.println(GGAZDA); + GPS.println(NORMAL); + GPS.println(FMWVERS); +} diff --git a/src/Adafruit_LSM9DS1.cpp b/src/Adafruit_LSM9DS1.cpp new file mode 100644 index 0000000..25ff55d --- /dev/null +++ b/src/Adafruit_LSM9DS1.cpp @@ -0,0 +1,594 @@ +/*************************************************************************** + This is a library for the LSM9DS1 Accelerometer and magnentometer/compass + + Designed specifically to work with the Adafruit LSM9DS1 Breakouts + + These sensors use I2C to communicate, 2 pins are required to interface. + + Adafruit invests time and resources providing this open source code, + please support Adafruit andopen-source hardware by purchasing products + from Adafruit! + + Written by Kevin Townsend for Adafruit Industries. + BSD license, all text above must be included in any redistribution + ***************************************************************************/ +#include "Adafruit_LSM9DS1.h" + +/*************************************************************************** + CONSTRUCTOR + ***************************************************************************/ + +void Adafruit_LSM9DS1::initI2C( TwoWire* wireBus, int32_t sensorID ) { + _i2c = true; + _wire = wireBus; + _lsm9dso_sensorid_accel = sensorID + 1; + _lsm9dso_sensorid_mag = sensorID + 2; + _lsm9dso_sensorid_gyro = sensorID + 3; + _lsm9dso_sensorid_temp = sensorID + 4; + _accelSensor = Sensor(this, &Adafruit_LSM9DS1::readAccel, &Adafruit_LSM9DS1::getAccelEvent, &Adafruit_LSM9DS1::getAccelSensor); + _magSensor = Sensor(this, &Adafruit_LSM9DS1::readMag, &Adafruit_LSM9DS1::getMagEvent, &Adafruit_LSM9DS1::getMagSensor); + _gyroSensor = Sensor(this, &Adafruit_LSM9DS1::readGyro, &Adafruit_LSM9DS1::getGyroEvent, &Adafruit_LSM9DS1::getGyroSensor); + _tempSensor = Sensor(this, &Adafruit_LSM9DS1::readTemp, &Adafruit_LSM9DS1::getTempEvent, &Adafruit_LSM9DS1::getTempSensor); +} + + +// default +Adafruit_LSM9DS1::Adafruit_LSM9DS1( int32_t sensorID ) { + initI2C(&Wire, sensorID); +} + +Adafruit_LSM9DS1::Adafruit_LSM9DS1( TwoWire* wireBus, int32_t sensorID ) { + initI2C(wireBus, sensorID); +} + +Adafruit_LSM9DS1::Adafruit_LSM9DS1(int8_t xgcs, int8_t mcs, int32_t sensorID ) { + _i2c = false; + // hardware SPI! + _csm = mcs; + _csxg = xgcs; + _mosi = _miso = _clk = -1; + _lsm9dso_sensorid_accel = sensorID + 1; + _lsm9dso_sensorid_mag = sensorID + 2; + _lsm9dso_sensorid_gyro = sensorID + 3; + _lsm9dso_sensorid_temp = sensorID + 4; + _accelSensor = Sensor(this, &Adafruit_LSM9DS1::readAccel, &Adafruit_LSM9DS1::getAccelEvent, &Adafruit_LSM9DS1::getAccelSensor); + _magSensor = Sensor(this, &Adafruit_LSM9DS1::readMag, &Adafruit_LSM9DS1::getMagEvent, &Adafruit_LSM9DS1::getMagSensor); + _gyroSensor = Sensor(this, &Adafruit_LSM9DS1::readGyro, &Adafruit_LSM9DS1::getGyroEvent, &Adafruit_LSM9DS1::getGyroSensor); + _tempSensor = Sensor(this, &Adafruit_LSM9DS1::readTemp, &Adafruit_LSM9DS1::getTempEvent, &Adafruit_LSM9DS1::getTempSensor); +} + +Adafruit_LSM9DS1::Adafruit_LSM9DS1(int8_t sclk, int8_t smiso, int8_t smosi, int8_t xgcs, int8_t mcs, int32_t sensorID ) { + _i2c = false; + // software SPI! + _csm = mcs; + _csxg = xgcs; + _mosi = smosi; + _miso = smiso; + _clk = sclk; + _lsm9dso_sensorid_accel = sensorID + 1; + _lsm9dso_sensorid_mag = sensorID + 2; + _lsm9dso_sensorid_gyro = sensorID + 3; + _lsm9dso_sensorid_temp = sensorID + 4; + _accelSensor = Sensor(this, &Adafruit_LSM9DS1::readAccel, &Adafruit_LSM9DS1::getAccelEvent, &Adafruit_LSM9DS1::getAccelSensor); + _magSensor = Sensor(this, &Adafruit_LSM9DS1::readMag, &Adafruit_LSM9DS1::getMagEvent, &Adafruit_LSM9DS1::getMagSensor); + _gyroSensor = Sensor(this, &Adafruit_LSM9DS1::readGyro, &Adafruit_LSM9DS1::getGyroEvent, &Adafruit_LSM9DS1::getGyroSensor); + _tempSensor = Sensor(this, &Adafruit_LSM9DS1::readTemp, &Adafruit_LSM9DS1::getTempEvent, &Adafruit_LSM9DS1::getTempSensor); +} + +bool Adafruit_LSM9DS1::begin() +{ + if (_i2c) { + _wire->setSDA(PB7); + _wire->setSCL(PB8); + _wire->begin(); + } else if (_clk == -1) { + // Hardware SPI + pinMode(_csxg, OUTPUT); + pinMode(_csm, OUTPUT); + digitalWrite(_csxg, HIGH); + digitalWrite(_csm, HIGH); + SPI.begin(); + } else { + //Serial.println("softSPI"); + // Sofware SPI + pinMode(_clk, OUTPUT); + pinMode(_mosi, OUTPUT); + pinMode(_miso, INPUT); + pinMode(_csxg, OUTPUT); + pinMode(_csm, OUTPUT); + digitalWrite(_csxg, HIGH); + digitalWrite(_csm, HIGH); + digitalWrite(_clk, HIGH); + } + + + // soft reset & reboot accel/gyro + write8(XGTYPE, LSM9DS1_REGISTER_CTRL_REG8, 0x05); + // soft reset & reboot magnetometer + write8(MAGTYPE, LSM9DS1_REGISTER_CTRL_REG2_M, 0x0C); + + delay(10); + + + /* + for (uint8_t i=0; i<0x30; i++) { + Serial.print("XG $"); Serial.print(i, HEX); Serial.print(" = 0x"); + Serial.println(read8(XGTYPE, i), HEX); + } + for (uint8_t i=0; i<0x30; i++) { + Serial.print("M $"); Serial.print(i, HEX); Serial.print(" = 0x"); + Serial.println(read8(MAGTYPE, i), HEX); + } + */ + + uint8_t id = read8(XGTYPE, LSM9DS1_REGISTER_WHO_AM_I_XG); + //Serial.print ("XG whoami: 0x"); Serial.println(id, HEX); + if (id != LSM9DS1_XG_ID) + return false; + + id = read8(MAGTYPE, LSM9DS1_REGISTER_WHO_AM_I_M); + //Serial.print ("MAG whoami: 0x"); Serial.println(id, HEX); + if (id != LSM9DS1_MAG_ID) + return false; + + // enable gyro continuous + write8(XGTYPE, LSM9DS1_REGISTER_CTRL_REG1_G, 0xC0); // on XYZ + + // Enable the accelerometer continous + write8(XGTYPE, LSM9DS1_REGISTER_CTRL_REG5_XL, 0x38); // enable X Y and Z axis + write8(XGTYPE, LSM9DS1_REGISTER_CTRL_REG6_XL, 0xC0); // 1 KHz out data rate, BW set by ODR, 408Hz anti-aliasing + + + // enable mag continuous + //write8(MAGTYPE, LSM9DS1_REGISTER_CTRL_REG1_M, 0xFC); // high perf XY, 80 Hz ODR + write8(MAGTYPE, LSM9DS1_REGISTER_CTRL_REG3_M, 0x00); // continuous mode + //write8(MAGTYPE, LSM9DS1_REGISTER_CTRL_REG4_M, 0x0C); // high perf Z mode + + + + // Set default ranges for the various sensors + setupAccel(LSM9DS1_ACCELRANGE_2G); + setupMag(LSM9DS1_MAGGAIN_4GAUSS); + setupGyro(LSM9DS1_GYROSCALE_245DPS); + + return true; +} + +/*************************************************************************** + PUBLIC FUNCTIONS + ***************************************************************************/ +void Adafruit_LSM9DS1::read() +{ + /* Read all the sensors. */ + readAccel(); + readMag(); + readGyro(); + readTemp(); +} + +void Adafruit_LSM9DS1::readAccel() { + // Read the accelerometer + byte buffer[6]; + readBuffer(XGTYPE, + 0x80 | LSM9DS1_REGISTER_OUT_X_L_XL, + 6, buffer); + + uint8_t xlo = buffer[0]; + int16_t xhi = buffer[1]; + uint8_t ylo = buffer[2]; + int16_t yhi = buffer[3]; + uint8_t zlo = buffer[4]; + int16_t zhi = buffer[5]; + + // Shift values to create properly formed integer (low byte first) + xhi <<= 8; xhi |= xlo; + yhi <<= 8; yhi |= ylo; + zhi <<= 8; zhi |= zlo; + accelData.x = xhi; + accelData.y = yhi; + accelData.z = zhi; +} + +void Adafruit_LSM9DS1::readMag() { + // Read the magnetometer + byte buffer[6]; + readBuffer(MAGTYPE, + 0x80 | LSM9DS1_REGISTER_OUT_X_L_M, + 6, buffer); + + uint8_t xlo = buffer[0]; + int16_t xhi = buffer[1]; + uint8_t ylo = buffer[2]; + int16_t yhi = buffer[3]; + uint8_t zlo = buffer[4]; + int16_t zhi = buffer[5]; + + // Shift values to create properly formed integer (low byte first) + xhi <<= 8; xhi |= xlo; + yhi <<= 8; yhi |= ylo; + zhi <<= 8; zhi |= zlo; + magData.x = xhi; + magData.y = yhi; + magData.z = zhi; +} + +void Adafruit_LSM9DS1::readGyro() { + // Read gyro + byte buffer[6]; + readBuffer(XGTYPE, + 0x80 | LSM9DS1_REGISTER_OUT_X_L_G, + 6, buffer); + + uint8_t xlo = buffer[0]; + int16_t xhi = buffer[1]; + uint8_t ylo = buffer[2]; + int16_t yhi = buffer[3]; + uint8_t zlo = buffer[4]; + int16_t zhi = buffer[5]; + + // Shift values to create properly formed integer (low byte first) + xhi <<= 8; xhi |= xlo; + yhi <<= 8; yhi |= ylo; + zhi <<= 8; zhi |= zlo; + + gyroData.x = xhi; + gyroData.y = yhi; + gyroData.z = zhi; +} + +void Adafruit_LSM9DS1::readTemp() { + // Read temp sensor + byte buffer[2]; + readBuffer(XGTYPE, + 0x80 | LSM9DS1_REGISTER_TEMP_OUT_L, + 2, buffer); + uint8_t xlo = buffer[0]; + int16_t xhi = buffer[1]; + + xhi <<= 8; xhi |= xlo; + + // Shift values to create properly formed integer (low byte first) + temperature = xhi; +} + +void Adafruit_LSM9DS1::setupAccel ( lsm9ds1AccelRange_t range ) +{ + uint8_t reg = read8(XGTYPE, LSM9DS1_REGISTER_CTRL_REG6_XL); + reg &= ~(0b00011000); + reg |= range; + //Serial.println("set range: "); + write8(XGTYPE, LSM9DS1_REGISTER_CTRL_REG6_XL, reg ); + + switch (range) + { + case LSM9DS1_ACCELRANGE_2G: + _accel_mg_lsb = LSM9DS1_ACCEL_MG_LSB_2G; + break; + case LSM9DS1_ACCELRANGE_4G: + _accel_mg_lsb = LSM9DS1_ACCEL_MG_LSB_4G; + break; + case LSM9DS1_ACCELRANGE_8G: + _accel_mg_lsb = LSM9DS1_ACCEL_MG_LSB_8G; + break; + case LSM9DS1_ACCELRANGE_16G: + _accel_mg_lsb =LSM9DS1_ACCEL_MG_LSB_16G; + break; + } +} + +void Adafruit_LSM9DS1::setupMag ( lsm9ds1MagGain_t gain ) +{ + uint8_t reg = read8(MAGTYPE, LSM9DS1_REGISTER_CTRL_REG2_M); + reg &= ~(0b01100000); + reg |= gain; + write8(MAGTYPE, LSM9DS1_REGISTER_CTRL_REG2_M, reg ); + + switch(gain) + { + case LSM9DS1_MAGGAIN_4GAUSS: + _mag_mgauss_lsb = LSM9DS1_MAG_MGAUSS_4GAUSS; + break; + case LSM9DS1_MAGGAIN_8GAUSS: + _mag_mgauss_lsb = LSM9DS1_MAG_MGAUSS_8GAUSS; + break; + case LSM9DS1_MAGGAIN_12GAUSS: + _mag_mgauss_lsb = LSM9DS1_MAG_MGAUSS_12GAUSS; + break; + case LSM9DS1_MAGGAIN_16GAUSS: + _mag_mgauss_lsb = LSM9DS1_MAG_MGAUSS_16GAUSS; + break; + } +} + +void Adafruit_LSM9DS1::setupGyro ( lsm9ds1GyroScale_t scale ) +{ + uint8_t reg = read8(XGTYPE, LSM9DS1_REGISTER_CTRL_REG1_G); + reg &= ~(0b00011000); + reg |= scale; + write8(XGTYPE, LSM9DS1_REGISTER_CTRL_REG1_G, reg ); + + switch(scale) + { + case LSM9DS1_GYROSCALE_245DPS: + _gyro_dps_digit = LSM9DS1_GYRO_DPS_DIGIT_245DPS; + break; + case LSM9DS1_GYROSCALE_500DPS: + _gyro_dps_digit = LSM9DS1_GYRO_DPS_DIGIT_500DPS; + break; + case LSM9DS1_GYROSCALE_2000DPS: + _gyro_dps_digit = LSM9DS1_GYRO_DPS_DIGIT_2000DPS; + break; + } +} + + +/*************************************************************************** + UNIFIED SENSOR FUNCTIONS + ***************************************************************************/ + +/**************************************************************************/ +/*! + @brief Gets the most recent accel sensor event +*/ +/**************************************************************************/ +bool Adafruit_LSM9DS1::getEvent(sensors_event_t *accelEvent, + sensors_event_t *magEvent, + sensors_event_t *gyroEvent, + sensors_event_t *tempEvent ) +{ + /* Grab new sensor reading and timestamp. */ + read(); + uint32_t timestamp = millis(); + + /* Update appropriate sensor events. */ + if (accelEvent) getAccelEvent(accelEvent, timestamp); + if (magEvent) getMagEvent(magEvent, timestamp); + if (gyroEvent) getGyroEvent(gyroEvent, timestamp); + if (tempEvent) getTempEvent(tempEvent, timestamp); + + return true; +} + +/**************************************************************************/ +/*! + @brief Gets the sensor_t data +*/ +/**************************************************************************/ +void Adafruit_LSM9DS1::getSensor(sensor_t *accel, sensor_t *mag, + sensor_t *gyro, sensor_t *temp ) +{ + /* Update appropriate sensor metadata. */ + if (accel) getAccelSensor(accel); + if (mag) getMagSensor(mag); + if (gyro) getGyroSensor(gyro); + if (temp) getTempSensor(temp); +} + +/*************************************************************************** + PRIVATE FUNCTIONS + ***************************************************************************/ +void Adafruit_LSM9DS1::write8(boolean type, byte reg, byte value) +{ + byte address, _cs; + + if (type == MAGTYPE) { + address = LSM9DS1_ADDRESS_MAG; + _cs = _csm; + } else { + address = LSM9DS1_ADDRESS_ACCELGYRO; + _cs = _csxg; + } + if (_i2c) { + _wire->beginTransmission(address); + _wire->write(reg); + _wire->write(value); + _wire->endTransmission(); + /* + Serial.print("0x"); Serial.print(address, HEX); + Serial.print(" $"); Serial.print(reg, HEX); Serial.print(" = "); + Serial.println(value, HEX); + */ + } else { + digitalWrite(_cs, LOW); + if (_clk == -1) // hardware SPI + SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE3)); + // set address + spixfer(reg & 0x7F); // write data + spixfer(value); + if (_clk == -1) // hardware SPI + SPI.endTransaction(); + digitalWrite(_cs, HIGH); + + } +} + +byte Adafruit_LSM9DS1::read8(boolean type, byte reg) +{ + uint8_t value; + + readBuffer(type, reg, 1, &value); + + return value; +} + +byte Adafruit_LSM9DS1::readBuffer(boolean type, byte reg, byte len, uint8_t *buffer) +{ + byte address, _cs; + + if (type == MAGTYPE) { + address = LSM9DS1_ADDRESS_MAG; + _cs = _csm; + } else { + address = LSM9DS1_ADDRESS_ACCELGYRO; + _cs = _csxg; + } + + if (_i2c) { + _wire->beginTransmission(address); + _wire->write(reg); + _wire->endTransmission(); + if (_wire->requestFrom(address, (byte)len) != len) { + return 0; + } + + /* + Serial.print("0x"); Serial.print(address, HEX); + Serial.print(" $"); Serial.print(reg, HEX); Serial.print(": "); + */ + + for (uint8_t i=0; iread(); + //Serial.print(buffer[i], HEX); Serial.print(", "); + } + //Serial.println(); + + } else { + if (_clk == -1) // hardware SPI + SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE3)); + else + digitalWrite(_clk, HIGH); + // set address + + digitalWrite(_cs, LOW); + + if(type == MAGTYPE) + reg |= 0x40; + + spixfer(reg | 0x80 ); // readdata + for (uint8_t i=0; i=0; i--) { + reply <<= 1; + digitalWrite(_clk, LOW); + digitalWrite(_mosi, data & (1<version = sizeof(sensors_event_t); + event->sensor_id = _lsm9dso_sensorid_accel; + event->type = SENSOR_TYPE_ACCELEROMETER; + event->timestamp = timestamp; + event->acceleration.x = accelData.x * _accel_mg_lsb; + event->acceleration.x /= 1000; + event->acceleration.x *= SENSORS_GRAVITY_STANDARD; + event->acceleration.y = accelData.y * _accel_mg_lsb; + event->acceleration.y /= 1000; + event->acceleration.y *= SENSORS_GRAVITY_STANDARD; + event->acceleration.z = accelData.z * _accel_mg_lsb; + event->acceleration.z /= 1000; + event->acceleration.z *= SENSORS_GRAVITY_STANDARD; +} + +void Adafruit_LSM9DS1::getMagEvent(sensors_event_t* event, uint32_t timestamp) { + memset(event, 0, sizeof(sensors_event_t)); + event->version = sizeof(sensors_event_t); + event->sensor_id = _lsm9dso_sensorid_mag; + event->type = SENSOR_TYPE_MAGNETIC_FIELD; + event->timestamp = timestamp; + event->magnetic.x = magData.x * _mag_mgauss_lsb; + event->magnetic.x /= 1000; + event->magnetic.y = magData.y * _mag_mgauss_lsb; + event->magnetic.y /= 1000; + event->magnetic.z = magData.z * _mag_mgauss_lsb; + event->magnetic.z /= 1000; +} + +void Adafruit_LSM9DS1::getGyroEvent(sensors_event_t* event, uint32_t timestamp) { + memset(event, 0, sizeof(sensors_event_t)); + event->version = sizeof(sensors_event_t); + event->sensor_id = _lsm9dso_sensorid_accel; + event->type = SENSOR_TYPE_GYROSCOPE; + event->timestamp = timestamp; + event->gyro.x = gyroData.x * _gyro_dps_digit; + event->gyro.y = gyroData.y * _gyro_dps_digit; + event->gyro.z = gyroData.z * _gyro_dps_digit; +} + +void Adafruit_LSM9DS1::getTempEvent(sensors_event_t* event, uint32_t timestamp) { + memset(event, 0, sizeof(sensors_event_t)); + event->version = sizeof(sensors_event_t); + event->sensor_id = _lsm9dso_sensorid_temp; + event->type = SENSOR_TYPE_AMBIENT_TEMPERATURE; + event->timestamp = timestamp; + // This is just a guess since the staring point (21C here) isn't documented :( + event->temperature = 21.0 + (float)temperature/8; + //event->temperature /= LSM9DS1_TEMP_LSB_DEGREE_CELSIUS; +} + +void Adafruit_LSM9DS1::getAccelSensor(sensor_t* sensor) { + memset(sensor, 0, sizeof(sensor_t)); + strncpy (sensor->name, "LSM9DS1_A", sizeof(sensor->name) - 1); + sensor->name[sizeof(sensor->name)- 1] = 0; + sensor->version = 1; + sensor->sensor_id = _lsm9dso_sensorid_accel; + sensor->type = SENSOR_TYPE_ACCELEROMETER; + sensor->min_delay = 0; + sensor->max_value = 0.0; // ToDo + sensor->min_value = 0.0; // ToDo + sensor->resolution = 0.0; // ToDo +} + +void Adafruit_LSM9DS1::getMagSensor(sensor_t* sensor) { + memset(sensor, 0, sizeof(sensor_t)); + strncpy (sensor->name, "LSM9DS1_M", sizeof(sensor->name) - 1); + sensor->name[sizeof(sensor->name)- 1] = 0; + sensor->version = 1; + sensor->sensor_id = _lsm9dso_sensorid_mag; + sensor->type = SENSOR_TYPE_MAGNETIC_FIELD; + sensor->min_delay = 0; + sensor->max_value = 0.0; // ToDo + sensor->min_value = 0.0; // ToDo + sensor->resolution = 0.0; // ToDo +} + +void Adafruit_LSM9DS1::getGyroSensor(sensor_t* sensor) { + memset(sensor, 0, sizeof(sensor_t)); + strncpy (sensor->name, "LSM9DS1_G", sizeof(sensor->name) - 1); + sensor->name[sizeof(sensor->name)- 1] = 0; + sensor->version = 1; + sensor->sensor_id = _lsm9dso_sensorid_gyro; + sensor->type = SENSOR_TYPE_GYROSCOPE; + sensor->min_delay = 0; + sensor->max_value = 0.0; // ToDo + sensor->min_value = 0.0; // ToDo + sensor->resolution = 0.0; // ToDo +} + +void Adafruit_LSM9DS1::getTempSensor(sensor_t* sensor) { + memset(sensor, 0, sizeof(sensor_t)); + strncpy (sensor->name, "LSM9DS1_T", sizeof(sensor->name) - 1); + sensor->name[sizeof(sensor->name)- 1] = 0; + sensor->version = 1; + sensor->sensor_id = _lsm9dso_sensorid_temp; + sensor->type = SENSOR_TYPE_AMBIENT_TEMPERATURE; + sensor->min_delay = 0; + sensor->max_value = 0.0; // ToDo + sensor->min_value = 0.0; // ToDo + sensor->resolution = 0.0; // ToDo +} diff --git a/src/Adafruit_LSM9DS1.h b/src/Adafruit_LSM9DS1.h new file mode 100644 index 0000000..feee072 --- /dev/null +++ b/src/Adafruit_LSM9DS1.h @@ -0,0 +1,280 @@ +/*************************************************************************** + This is a library for the LSM9DS1 Accelerometer and magnentometer/compass + + Designed specifically to work with the Adafruit LSM9DS1 Breakouts + + These sensors use I2C to communicate, 2 pins are required to interface. + + Adafruit invests time and resources providing this open source code, + please support Adafruit andopen-source hardware by purchasing products + from Adafruit! + + Written by Kevin Townsend for Adafruit Industries. + BSD license, all text above must be included in any redistribution + ***************************************************************************/ +#ifndef __LSM9DS1_H__ +#define __LSM9DS1_H__ + +#if (ARDUINO >= 100) + #include "Arduino.h" +#else + #include "WProgram.h" +#endif +#include "Wire.h" +#include +#include "Adafruit_Sensor.h" + +#define LSM9DS1_ADDRESS_ACCELGYRO (0x6A) +#define LSM9DS1_ADDRESS_MAG (0x1C) +#define LSM9DS1_XG_ID (0b01101000) +#define LSM9DS1_MAG_ID (0b00111101) + + +// Linear Acceleration: mg per LSB +#define LSM9DS1_ACCEL_MG_LSB_2G (0.061F) +#define LSM9DS1_ACCEL_MG_LSB_4G (0.122F) +#define LSM9DS1_ACCEL_MG_LSB_8G (0.244F) +#define LSM9DS1_ACCEL_MG_LSB_16G (0.732F) + +// Magnetic Field Strength: gauss range +#define LSM9DS1_MAG_MGAUSS_4GAUSS (0.14F) +#define LSM9DS1_MAG_MGAUSS_8GAUSS (0.29F) +#define LSM9DS1_MAG_MGAUSS_12GAUSS (0.43F) +#define LSM9DS1_MAG_MGAUSS_16GAUSS (0.58F) + +// Angular Rate: dps per LSB +#define LSM9DS1_GYRO_DPS_DIGIT_245DPS (0.00875F) +#define LSM9DS1_GYRO_DPS_DIGIT_500DPS (0.01750F) +#define LSM9DS1_GYRO_DPS_DIGIT_2000DPS (0.07000F) + +// Temperature: LSB per degree celsius +#define LSM9DS1_TEMP_LSB_DEGREE_CELSIUS (8) // 1°C = 8, 25° = 200, etc. + +#define MAGTYPE (true) +#define XGTYPE (false) + +/* Forward reference required for function pointers below. */ +class Adafruit_LSM9DS1; + +/* Pointer to member functions for read, get event, and get sensor. These are used */ +/* by the Adafruit_LSM9DS1::Sensor class to read and retrieve individual sensors. */ +typedef void (Adafruit_LSM9DS1::*lsm9ds1_read_func)(void); +typedef void (Adafruit_LSM9DS1::*lsm9ds1_get_event_func)(sensors_event_t*, uint32_t); +typedef void (Adafruit_LSM9DS1::*lsm9ds1_get_sensor_func)(sensor_t*); + +class Adafruit_LSM9DS1 +{ + public: + Adafruit_LSM9DS1 ( int32_t sensorID = 0 ); + Adafruit_LSM9DS1 ( TwoWire* wireBus, int32_t sensorID = 0 ); + Adafruit_LSM9DS1 ( int8_t xmcs, int8_t gcs, int32_t sensorID = 0 ); + Adafruit_LSM9DS1 ( int8_t clk, int8_t miso, int8_t mosi, int8_t xmcs, int8_t gcs, int32_t sensorID = 0 ); + + void initI2C( TwoWire* wireBus, int32_t sensorID ); + + + typedef enum + { + LSM9DS1_REGISTER_WHO_AM_I_XG = 0x0F, + LSM9DS1_REGISTER_CTRL_REG1_G = 0x10, + LSM9DS1_REGISTER_CTRL_REG2_G = 0x11, + LSM9DS1_REGISTER_CTRL_REG3_G = 0x12, + LSM9DS1_REGISTER_TEMP_OUT_L = 0x15, + LSM9DS1_REGISTER_TEMP_OUT_H = 0x16, + LSM9DS1_REGISTER_STATUS_REG = 0x17, + LSM9DS1_REGISTER_OUT_X_L_G = 0x18, + LSM9DS1_REGISTER_OUT_X_H_G = 0x19, + LSM9DS1_REGISTER_OUT_Y_L_G = 0x1A, + LSM9DS1_REGISTER_OUT_Y_H_G = 0x1B, + LSM9DS1_REGISTER_OUT_Z_L_G = 0x1C, + LSM9DS1_REGISTER_OUT_Z_H_G = 0x1D, + LSM9DS1_REGISTER_CTRL_REG4 = 0x1E, + LSM9DS1_REGISTER_CTRL_REG5_XL = 0x1F, + LSM9DS1_REGISTER_CTRL_REG6_XL = 0x20, + LSM9DS1_REGISTER_CTRL_REG7_XL = 0x21, + LSM9DS1_REGISTER_CTRL_REG8 = 0x22, + LSM9DS1_REGISTER_CTRL_REG9 = 0x23, + LSM9DS1_REGISTER_CTRL_REG10 = 0x24, + + LSM9DS1_REGISTER_OUT_X_L_XL = 0x28, + LSM9DS1_REGISTER_OUT_X_H_XL = 0x29, + LSM9DS1_REGISTER_OUT_Y_L_XL = 0x2A, + LSM9DS1_REGISTER_OUT_Y_H_XL = 0x2B, + LSM9DS1_REGISTER_OUT_Z_L_XL = 0x2C, + LSM9DS1_REGISTER_OUT_Z_H_XL = 0x2D, + + } lsm9ds1AccGyroRegisters_t; + + typedef enum + { + + LSM9DS1_REGISTER_WHO_AM_I_M = 0x0F, + LSM9DS1_REGISTER_CTRL_REG1_M = 0x20, + LSM9DS1_REGISTER_CTRL_REG2_M = 0x21, + LSM9DS1_REGISTER_CTRL_REG3_M = 0x22, + LSM9DS1_REGISTER_CTRL_REG4_M = 0x23, + LSM9DS1_REGISTER_CTRL_REG5_M = 0x24, + LSM9DS1_REGISTER_STATUS_REG_M = 0x27, + LSM9DS1_REGISTER_OUT_X_L_M = 0x28, + LSM9DS1_REGISTER_OUT_X_H_M = 0x29, + LSM9DS1_REGISTER_OUT_Y_L_M = 0x2A, + LSM9DS1_REGISTER_OUT_Y_H_M = 0x2B, + LSM9DS1_REGISTER_OUT_Z_L_M = 0x2C, + LSM9DS1_REGISTER_OUT_Z_H_M = 0x2D, + LSM9DS1_REGISTER_CFG_M = 0x30, + LSM9DS1_REGISTER_INT_SRC_M = 0x31, + } lsm9ds1MagRegisters_t; + + typedef enum + { + LSM9DS1_ACCELRANGE_2G = (0b00 << 3), + LSM9DS1_ACCELRANGE_16G = (0b01 << 3), + LSM9DS1_ACCELRANGE_4G = (0b10 << 3), + LSM9DS1_ACCELRANGE_8G = (0b11 << 3), + } lsm9ds1AccelRange_t; + + typedef enum + { + LSM9DS1_ACCELDATARATE_POWERDOWN = (0b0000 << 4), + LSM9DS1_ACCELDATARATE_3_125HZ = (0b0001 << 4), + LSM9DS1_ACCELDATARATE_6_25HZ = (0b0010 << 4), + LSM9DS1_ACCELDATARATE_12_5HZ = (0b0011 << 4), + LSM9DS1_ACCELDATARATE_25HZ = (0b0100 << 4), + LSM9DS1_ACCELDATARATE_50HZ = (0b0101 << 4), + LSM9DS1_ACCELDATARATE_100HZ = (0b0110 << 4), + LSM9DS1_ACCELDATARATE_200HZ = (0b0111 << 4), + LSM9DS1_ACCELDATARATE_400HZ = (0b1000 << 4), + LSM9DS1_ACCELDATARATE_800HZ = (0b1001 << 4), + LSM9DS1_ACCELDATARATE_1600HZ = (0b1010 << 4) + } lm9ds1AccelDataRate_t; + + typedef enum + { + LSM9DS1_MAGGAIN_4GAUSS = (0b00 << 5), // +/- 4 gauss + LSM9DS1_MAGGAIN_8GAUSS = (0b01 << 5), // +/- 8 gauss + LSM9DS1_MAGGAIN_12GAUSS = (0b10 << 5), // +/- 12 gauss + LSM9DS1_MAGGAIN_16GAUSS = (0b11 << 5) // +/- 16 gauss + } lsm9ds1MagGain_t; + + typedef enum + { + LSM9DS1_MAGDATARATE_3_125HZ = (0b000 << 2), + LSM9DS1_MAGDATARATE_6_25HZ = (0b001 << 2), + LSM9DS1_MAGDATARATE_12_5HZ = (0b010 << 2), + LSM9DS1_MAGDATARATE_25HZ = (0b011 << 2), + LSM9DS1_MAGDATARATE_50HZ = (0b100 << 2), + LSM9DS1_MAGDATARATE_100HZ = (0b101 << 2) + } lsm9ds1MagDataRate_t; + + typedef enum + { + LSM9DS1_GYROSCALE_245DPS = (0b00 << 3), // +/- 245 degrees per second rotation + LSM9DS1_GYROSCALE_500DPS = (0b01 << 3), // +/- 500 degrees per second rotation + LSM9DS1_GYROSCALE_2000DPS = (0b11 << 3) // +/- 2000 degrees per second rotation + } lsm9ds1GyroScale_t; + + typedef struct vector_s + { + float x; + float y; + float z; + } lsm9ds1Vector_t; + + lsm9ds1Vector_t accelData; // Last read accelerometer data will be available here + lsm9ds1Vector_t magData; // Last read magnetometer data will be available here + lsm9ds1Vector_t gyroData; // Last read gyroscope data will be available here + int16_t temperature; // Last read temperzture data will be available here + + bool begin ( void ); + void read ( void ); + void readAccel ( void ); + void readMag ( void ); + void readGyro ( void ); + void readTemp ( void ); + void setupAccel ( lsm9ds1AccelRange_t range ); + void setupMag ( lsm9ds1MagGain_t gain ); + void setupGyro ( lsm9ds1GyroScale_t scale ); + void write8 ( boolean type, byte reg, byte value ); + byte read8 ( boolean type, byte reg); + byte readBuffer ( boolean type, byte reg, byte len, uint8_t *buffer); + uint8_t spixfer ( uint8_t data ); + + /* Adafruit Unified Sensor Functions (not standard yet ... the current base class only */ + /* supports one sensor type, and we need to update the unified base class to support */ + /* multiple sensors in a single driver, returning an array */ + bool getEvent ( sensors_event_t* accel, sensors_event_t* mag, sensors_event_t* gyro, sensors_event_t* temp ); + void getSensor ( sensor_t* accel, sensor_t* mag, sensor_t* gyro, sensor_t* temp ); + + /* Subclass to expose each sensor on the LSM9DS1 as an Adafruit_Sensor instance. */ + class Sensor: public Adafruit_Sensor { + public: + Sensor() {} + Sensor(const Sensor& copy): + _parent(copy._parent), + _readFunc(copy._readFunc), + _eventFunc(copy._eventFunc), + _sensorFunc(copy._sensorFunc) + {} + Sensor(Adafruit_LSM9DS1* parent, lsm9ds1_read_func readFunc, + lsm9ds1_get_event_func eventFunc, lsm9ds1_get_sensor_func sensorFunc): + _parent(parent), + _readFunc(readFunc), + _eventFunc(eventFunc), + _sensorFunc(sensorFunc) + {} + virtual bool getEvent(sensors_event_t* event) { + /* Take new reading. */ + (_parent->*_readFunc)(); + /* Fill in event data. */ + (_parent->*_eventFunc)(event, millis()); + } + virtual void getSensor(sensor_t* sensor) { + /* Fill in sensor metadata. */ + (_parent->*_sensorFunc)(sensor); + } + + private: + Adafruit_LSM9DS1* _parent; + lsm9ds1_read_func _readFunc; + lsm9ds1_get_event_func _eventFunc; + lsm9ds1_get_sensor_func _sensorFunc; + }; + + /* Individual Adafruit_Sensor instances for each sensor on the board. */ + Sensor& getAccel ( void ) { return _accelSensor; } + Sensor& getMag ( void ) { return _magSensor; } + Sensor& getGyro ( void ) { return _gyroSensor; } + Sensor& getTemp ( void ) { return _tempSensor; } + + private: + boolean _i2c; + TwoWire* _wire; + int8_t _csm, _csxg, _mosi, _miso, _clk; + float _accel_mg_lsb; + float _mag_mgauss_lsb; + float _gyro_dps_digit; + int32_t _lsm9dso_sensorid_accel; + int32_t _lsm9dso_sensorid_mag; + int32_t _lsm9dso_sensorid_gyro; + int32_t _lsm9dso_sensorid_temp; + Sensor _accelSensor; + Sensor _magSensor; + Sensor _gyroSensor; + Sensor _tempSensor; + + /* Functions to get individual sensor measurements and metadata. */ + /* Note that these functions will NOT update the sensor state before getting */ + /* a new reading. You MUST call read() manually to update the sensor state */ + /* before calling these functions! */ + void getAccelEvent ( sensors_event_t* event, uint32_t timestamp ); + void getMagEvent ( sensors_event_t* event, uint32_t timestamp ); + void getGyroEvent ( sensors_event_t* event, uint32_t timestamp ); + void getTempEvent ( sensors_event_t* event, uint32_t timestamp ); + void getAccelSensor ( sensor_t* sensor ); + void getMagSensor ( sensor_t* sensor ); + void getGyroSensor ( sensor_t* sensor ); + void getTempSensor ( sensor_t* sensor ); + +}; + +#endif diff --git a/src/Adafruit_Sensor.h b/src/Adafruit_Sensor.h new file mode 100644 index 0000000..8ac638f --- /dev/null +++ b/src/Adafruit_Sensor.h @@ -0,0 +1,156 @@ +/* +* Copyright (C) 2008 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software< /span> +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/* Update by K. Townsend (Adafruit Industries) for lighter typedefs, and + * extended sensor support to include color, voltage and current */ + +#ifndef _ADAFRUIT_SENSOR_H +#define _ADAFRUIT_SENSOR_H + +#ifndef ARDUINO + #include +#elif ARDUINO >= 100 + #include "Arduino.h" + #include "Print.h" +#else + #include "WProgram.h" +#endif + +/* Intentionally modeled after sensors.h in the Android API: + * https://github.com/android/platform_hardware_libhardware/blob/master/include/hardware/sensors.h */ + +/* Constants */ +#define SENSORS_GRAVITY_EARTH (9.80665F) /**< Earth's gravity in m/s^2 */ +#define SENSORS_GRAVITY_MOON (1.6F) /**< The moon's gravity in m/s^2 */ +#define SENSORS_GRAVITY_SUN (275.0F) /**< The sun's gravity in m/s^2 */ +#define SENSORS_GRAVITY_STANDARD (SENSORS_GRAVITY_EARTH) +#define SENSORS_MAGFIELD_EARTH_MAX (60.0F) /**< Maximum magnetic field on Earth's surface */ +#define SENSORS_MAGFIELD_EARTH_MIN (30.0F) /**< Minimum magnetic field on Earth's surface */ +#define SENSORS_PRESSURE_SEALEVELHPA (1013.25F) /**< Average sea level pressure is 1013.25 hPa */ +#define SENSORS_DPS_TO_RADS (0.017453293F) /**< Degrees/s to rad/s multiplier */ +#define SENSORS_GAUSS_TO_MICROTESLA (100) /**< Gauss to micro-Tesla multiplier */ + +/** Sensor types */ +typedef enum +{ + SENSOR_TYPE_ACCELEROMETER = (1), /**< Gravity + linear acceleration */ + SENSOR_TYPE_MAGNETIC_FIELD = (2), + SENSOR_TYPE_ORIENTATION = (3), + SENSOR_TYPE_GYROSCOPE = (4), + SENSOR_TYPE_LIGHT = (5), + SENSOR_TYPE_PRESSURE = (6), + SENSOR_TYPE_PROXIMITY = (8), + SENSOR_TYPE_GRAVITY = (9), + SENSOR_TYPE_LINEAR_ACCELERATION = (10), /**< Acceleration not including gravity */ + SENSOR_TYPE_ROTATION_VECTOR = (11), + SENSOR_TYPE_RELATIVE_HUMIDITY = (12), + SENSOR_TYPE_AMBIENT_TEMPERATURE = (13), + SENSOR_TYPE_VOLTAGE = (15), + SENSOR_TYPE_CURRENT = (16), + SENSOR_TYPE_COLOR = (17) +} sensors_type_t; + +/** struct sensors_vec_s is used to return a vector in a common format. */ +typedef struct { + union { + float v[3]; + struct { + float x; + float y; + float z; + }; + /* Orientation sensors */ + struct { + float roll; /**< Rotation around the longitudinal axis (the plane body, 'X axis'). Roll is positive and increasing when moving downward. -90°<=roll<=90° */ + float pitch; /**< Rotation around the lateral axis (the wing span, 'Y axis'). Pitch is positive and increasing when moving upwards. -180°<=pitch<=180°) */ + float heading; /**< Angle between the longitudinal axis (the plane body) and magnetic north, measured clockwise when viewing from the top of the device. 0-359° */ + }; + }; + int8_t status; + uint8_t reserved[3]; +} sensors_vec_t; + +/** struct sensors_color_s is used to return color data in a common format. */ +typedef struct { + union { + float c[3]; + /* RGB color space */ + struct { + float r; /**< Red component */ + float g; /**< Green component */ + float b; /**< Blue component */ + }; + }; + uint32_t rgba; /**< 24-bit RGBA value */ +} sensors_color_t; + +/* Sensor event (36 bytes) */ +/** struct sensor_event_s is used to provide a single sensor event in a common format. */ +typedef struct +{ + int32_t version; /**< must be sizeof(struct sensors_event_t) */ + int32_t sensor_id; /**< unique sensor identifier */ + int32_t type; /**< sensor type */ + int32_t reserved0; /**< reserved */ + int32_t timestamp; /**< time is in milliseconds */ + union + { + float data[4]; + sensors_vec_t acceleration; /**< acceleration values are in meter per second per second (m/s^2) */ + sensors_vec_t magnetic; /**< magnetic vector values are in micro-Tesla (uT) */ + sensors_vec_t orientation; /**< orientation values are in degrees */ + sensors_vec_t gyro; /**< gyroscope values are in rad/s */ + float temperature; /**< temperature is in degrees centigrade (Celsius) */ + float distance; /**< distance in centimeters */ + float light; /**< light in SI lux units */ + float pressure; /**< pressure in hectopascal (hPa) */ + float relative_humidity; /**< relative humidity in percent */ + float current; /**< current in milliamps (mA) */ + float voltage; /**< voltage in volts (V) */ + sensors_color_t color; /**< color in RGB component values */ + }; +} sensors_event_t; + +/* Sensor details (40 bytes) */ +/** struct sensor_s is used to describe basic information about a specific sensor. */ +typedef struct +{ + char name[12]; /**< sensor name */ + int32_t version; /**< version of the hardware + driver */ + int32_t sensor_id; /**< unique sensor identifier */ + int32_t type; /**< this sensor's type (ex. SENSOR_TYPE_LIGHT) */ + float max_value; /**< maximum value of this sensor's value in SI units */ + float min_value; /**< minimum value of this sensor's value in SI units */ + float resolution; /**< smallest difference between two values reported by this sensor */ + int32_t min_delay; /**< min delay in microseconds between events. zero = not a constant rate */ +} sensor_t; + +class Adafruit_Sensor { + public: + // Constructor(s) + Adafruit_Sensor() {} + virtual ~Adafruit_Sensor() {} + + // These must be defined by the subclass + virtual void enableAutoRange(bool enabled) { (void)enabled; /* suppress unused warning */ }; + virtual bool getEvent(sensors_event_t*) = 0; + virtual void getSensor(sensor_t*) = 0; + + private: + bool _autoRange; +}; + +#endif diff --git a/src/LICENSE_LPS_LSM303.txt b/src/LICENSE_LPS_LSM303.txt new file mode 100644 index 0000000..795e837 --- /dev/null +++ b/src/LICENSE_LPS_LSM303.txt @@ -0,0 +1,25 @@ +Copyright (c) 2013 Pololu Corporation. For more information, see + +http://www.pololu.com/ +http://forum.pololu.com/ + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/LICENSE_sparkfun.md b/src/LICENSE_sparkfun.md new file mode 100644 index 0000000..f8dc62d --- /dev/null +++ b/src/LICENSE_sparkfun.md @@ -0,0 +1,9 @@ + +License Information +------------------- + +The hardware is released under [Creative Commons Share-alike 3.0](http://creativecommons.org/licenses/by-sa/3.0/). + +All other code is open source so please feel free to do anything you want with it; you buy me a beer if you use this and we meet someday ([Beerware license](http://en.wikipedia.org/wiki/Beerware)). + + diff --git a/src/LPS.cpp b/src/LPS.cpp new file mode 100644 index 0000000..58fe0d8 --- /dev/null +++ b/src/LPS.cpp @@ -0,0 +1,235 @@ +#include "LPS.h" +#include + +// Defines /////////////////////////////////////////////////////////// + +// The Arduino two-wire interface uses a 7-bit number for the address, +// and sets the last bit correctly based on reads and writes +#define SA0_LOW_ADDRESS 0b1011100 +#define SA0_HIGH_ADDRESS 0b1011101 + +#define TEST_REG_NACK -1 + +#define LPS331AP_WHO_ID 0xBB +#define LPS25H_WHO_ID 0xBD + +// Constructors ////////////////////////////////////////////////////// + +LPS::LPS(void) +{ + _device = device_auto; + + // Pololu board pulls SA0 high, so default assumption is that it is + // high + address = SA0_LOW_ADDRESS; +} + +// Public Methods //////////////////////////////////////////////////// + +// sets or detects device type and slave address; returns bool indicating success +bool LPS::init(deviceType device, byte sa0) +{ + if (!detectDeviceAndAddress(device, (sa0State)sa0)) + return false; + + switch (_device) + { + case device_25H: + translated_regs[-INTERRUPT_CFG] = LPS25H_INTERRUPT_CFG; + translated_regs[-INT_SOURCE] = LPS25H_INT_SOURCE; + translated_regs[-THS_P_L] = LPS25H_THS_P_L; + translated_regs[-THS_P_H] = LPS25H_THS_P_H; + return true; + break; + + case device_331AP: + translated_regs[-INTERRUPT_CFG] = LPS331AP_INTERRUPT_CFG; + translated_regs[-INT_SOURCE] = LPS331AP_INT_SOURCE; + translated_regs[-THS_P_L] = LPS331AP_THS_P_L; + translated_regs[-THS_P_H] = LPS331AP_THS_P_H; + return true; + break; + } +} + +// turns on sensor and enables continuous output +void LPS::enableDefault(void) +{ + if (_device == device_25H) + { + // 0xB0 = 0b10110000 + // PD = 1 (active mode); ODR = 011 (12.5 Hz pressure & temperature output data rate) + writeReg(CTRL_REG1, 0xB0); + } + else if (_device == device_331AP) + { + // 0xE0 = 0b11100000 + // PD = 1 (active mode); ODR = 110 (12.5 Hz pressure & temperature output data rate) + writeReg(CTRL_REG1, 0xE0); + } +} + +// writes register +void LPS::writeReg(int reg, byte value) +{ + // if dummy register address, look up actual translated address (based on device type) + if (reg < 0) + { + reg = translated_regs[-reg]; + } + + Wire.beginTransmission(address); + Wire.write(reg); + Wire.write(value); + Wire.endTransmission(); +} + +// reads register +byte LPS::readReg(int reg) +{ + byte value; + + // if dummy register address, look up actual translated address (based on device type) + if (reg < 0) + { + reg = translated_regs[-reg]; + } + + Wire.beginTransmission(address); + Wire.write(reg); + Wire.endTransmission(false); // restart + Wire.requestFrom(address, (byte)1); + value = Wire.read(); + Wire.endTransmission(); + + return value; +} + +// reads pressure in millibars (mbar)/hectopascals (hPa) +float LPS::readPressureMillibars(void) +{ + return (float)readPressureRaw() / 4096; +} + +// reads pressure in inches of mercury (inHg) +float LPS::readPressureInchesHg(void) +{ + return (float)readPressureRaw() / 138706.5; +} + +// reads pressure and returns raw 24-bit sensor output +int32_t LPS::readPressureRaw(void) +{ + Wire.beginTransmission(address); + // assert MSB to enable register address auto-increment + Wire.write(PRESS_OUT_XL | (1 << 7)); + Wire.endTransmission(); + Wire.requestFrom(address, (byte)3); + + while (Wire.available() < 3); + + uint8_t pxl = Wire.read(); + uint8_t pl = Wire.read(); + uint8_t ph = Wire.read(); + + // combine bytes + return (int32_t)(int8_t)ph << 16 | (uint16_t)pl << 8 | pxl; +} + +// reads temperature in degrees C +float LPS::readTemperatureC(void) +{ + return 42.5 + (float)readTemperatureRaw() / 480; +} + +// reads temperature in degrees F +float LPS::readTemperatureF(void) +{ + return 108.5 + (float)readTemperatureRaw() / 480 * 1.8; +} + +// reads temperature and returns raw 16-bit sensor output +int16_t LPS::readTemperatureRaw(void) +{ + Wire.beginTransmission(address); + // assert MSB to enable register address auto-increment + Wire.write(TEMP_OUT_L | (1 << 7)); + Wire.endTransmission(); + Wire.requestFrom(address, (byte)2); + + while (Wire.available() < 2); + + uint8_t tl = Wire.read(); + uint8_t th = Wire.read(); + + // combine bytes + return (int16_t)(th << 8 | tl); +} + +// converts pressure in mbar to altitude in meters, using 1976 US +// Standard Atmosphere model (note that this formula only applies to a +// height of 11 km, or about 36000 ft) +// If altimeter setting (QNH, barometric pressure adjusted to sea +// level) is given, this function returns an indicated altitude +// compensated for actual regional pressure; otherwise, it returns +// the pressure altitude above the standard pressure level of 1013.25 +// mbar or 29.9213 inHg +float LPS::pressureToAltitudeMeters(float pressure_mbar, float altimeter_setting_mbar) +{ + return (1 - pow(pressure_mbar / altimeter_setting_mbar, 0.190263)) * 44330.8; +} + +// converts pressure in inHg to altitude in feet; see notes above +float LPS::pressureToAltitudeFeet(float pressure_inHg, float altimeter_setting_inHg) +{ + return (1 - pow(pressure_inHg / altimeter_setting_inHg, 0.190263)) * 145442; +} + +// Private Methods /////////////////////////////////////////////////// + +bool LPS::detectDeviceAndAddress(deviceType device, sa0State sa0) +{ + if (sa0 == sa0_auto || sa0 == sa0_high) + { + address = SA0_HIGH_ADDRESS; + if (detectDevice(device)) return true; + } + if (sa0 == sa0_auto || sa0 == sa0_low) + { + address = SA0_LOW_ADDRESS; + if (detectDevice(device)) return true; + } + + return false; +} + +bool LPS::detectDevice(deviceType device) +{ + int id = testWhoAmI(address); + + if ((device == device_auto || device == device_25H) && id == LPS25H_WHO_ID) + { + _device = device_25H; + return true; + } + if ((device == device_auto || device == device_331AP) && id == LPS331AP_WHO_ID) + { + _device = device_331AP; + return true; + } + + return false; +} + +int LPS::testWhoAmI(byte address) +{ + Wire.beginTransmission(address); + Wire.write(WHO_AM_I); + Wire.endTransmission(); + + Wire.requestFrom(address, (byte)1); + if (Wire.available()) + return Wire.read(); + else + return TEST_REG_NACK; +} \ No newline at end of file diff --git a/src/LPS.h b/src/LPS.h new file mode 100644 index 0000000..63143cc --- /dev/null +++ b/src/LPS.h @@ -0,0 +1,110 @@ +#ifndef LPS_h +#define LPS_h + +#include // for byte data type + +class LPS +{ + public: + enum deviceType { device_331AP, device_25H, device_auto }; + enum sa0State { sa0_low, sa0_high, sa0_auto }; + + // register addresses + // Note: where register names differ between the register mapping table and + // the register descriptions in the datasheets, the names from the register + // descriptions are used here. + enum regAddr + { + REF_P_XL = 0x08, + REF_P_L = 0x09, + REF_P_H = 0x0A, + + WHO_AM_I = 0x0F, + + RES_CONF = 0x10, + + CTRL_REG1 = 0x20, + CTRL_REG2 = 0x21, + CTRL_REG3 = 0x22, + CTRL_REG4 = 0x23, // 25H + + STATUS_REG = 0x27, + + PRESS_OUT_XL = 0x28, + PRESS_OUT_L = 0x29, + PRESS_OUT_H = 0x2A, + + TEMP_OUT_L = 0x2B, + TEMP_OUT_H = 0x2C, + + FIFO_CTRL = 0x2E, // 25H + FIFO_STATUS = 0x2F, // 25H + + AMP_CTRL = 0x30, // 331AP + + RPDS_L = 0x39, // 25H + RPDS_H = 0x3A, // 25H + + DELTA_PRESS_XL = 0x3C, // 331AP + DELTA_PRESS_L = 0x3D, // 331AP + DELTA_PRESS_H = 0x3E, // 331AP + + + // dummy addresses for registers in different locations on different devices; + // the library translates these based on device type + // value with sign flipped is used as index into translated_regs array + + INTERRUPT_CFG = -1, + INT_SOURCE = -2, + THS_P_L = -3, + THS_P_H = -4, + // update dummy_reg_count if registers are added here! + + + // device-specific register addresses + + LPS331AP_INTERRUPT_CFG = 0x23, + LPS331AP_INT_SOURCE = 0x24, + LPS331AP_THS_P_L = 0x25, + LPS331AP_THS_P_H = 0x26, + + LPS25H_INTERRUPT_CFG = 0x24, + LPS25H_INT_SOURCE = 0x25, + LPS25H_THS_P_L = 0x30, + LPS25H_THS_P_H = 0x31, + }; + + LPS(void); + + bool init(deviceType device = device_auto, byte sa0 = sa0_auto); + deviceType getDeviceType(void) { return _device; } + byte getAddress(void) { return address; } + + void enableDefault(void); + + void writeReg(int reg, byte value); + byte readReg(int reg); + + float readPressureMillibars(void); + float readPressureInchesHg(void); + int32_t readPressureRaw(void); + float readTemperatureC(void); + float readTemperatureF(void); + int16_t readTemperatureRaw(void); + + static float pressureToAltitudeMeters(float pressure_mbar, float altimeter_setting_mbar = 1013.25); + static float pressureToAltitudeFeet(float pressure_inHg, float altimeter_setting_inHg = 29.9213); + + private: + deviceType _device; // chip type (331AP or 25H) + byte address; + + static const int dummy_reg_count = 4; + regAddr translated_regs[dummy_reg_count + 1]; // index 0 not used + + bool detectDeviceAndAddress(deviceType device, sa0State sa0); + bool detectDevice(deviceType device); + int testWhoAmI(byte address); +}; + +#endif \ No newline at end of file diff --git a/src/SparkFunHTU21D.cpp b/src/SparkFunHTU21D.cpp new file mode 100644 index 0000000..9682e39 --- /dev/null +++ b/src/SparkFunHTU21D.cpp @@ -0,0 +1,211 @@ +/* + HTU21D Humidity Sensor Library + By: Nathan Seidle + SparkFun Electronics + Date: September 22nd, 2013 + License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license). + + This library allows an Arduino to read from the HTU21D low-cost high-precision humidity sensor. + + If you have feature suggestions or need support please use the github support page: https://github.com/sparkfun/HTU21D + + Hardware Setup: The HTU21D lives on the I2C bus. Attach the SDA pin to A4, SCL to A5. If you are using the SparkFun + breakout board you *do not* need 4.7k pull-up resistors on the bus (they are built-in). + + Link to the breakout board product: + + Software: + Call HTU21D.Begin() in setup. + HTU21D.ReadHumidity() will return a float containing the humidity. Ex: 54.7 + HTU21D.ReadTemperature() will return a float containing the temperature in Celsius. Ex: 24.1 + HTU21D.SetResolution(byte: 0b.76543210) sets the resolution of the readings. + HTU21D.check_crc(message, check_value) verifies the 8-bit CRC generated by the sensor + HTU21D.read_user_register() returns the user register. Used to set resolution. +*/ + +#include + +#include "SparkFunHTU21D.h" + +HTU21D::HTU21D() +{ + //Set initial values for private vars +} + +//Begin +/*******************************************************************************************/ +//Start I2C communication +bool HTU21D::begin(TwoWire &wirePort) +{ + _i2cPort = &wirePort; //Grab which port the user wants us to use + _i2cPort->setSDA(PB7); + _i2cPort->setSCL(PB8); + _i2cPort->begin(); + + // check availability + /*_i2cPort->beginTransmission(HTU21D_ADDRESS); + int err = _i2cPort->beginTransmission(HTU21D_ADDRESS); + if (err == 0){ + return true; + } else { + return false; + }*/ + + return true; +} + +#define MAX_WAIT 100 +#define DELAY_INTERVAL 10 +#define MAX_COUNTER (MAX_WAIT/DELAY_INTERVAL) + +//Given a command, reads a given 2-byte value with CRC from the HTU21D +uint16_t HTU21D::readValue(byte cmd) +{ + //Request a humidity reading + _i2cPort->beginTransmission(HTU21D_ADDRESS); + _i2cPort->write(cmd); //Measure value (prefer no hold!) + _i2cPort->endTransmission(); + + //Hang out while measurement is taken. datasheet says 50ms, practice may call for more + bool validResult; + byte counter; + for (counter = 0, validResult = 0 ; counter < MAX_COUNTER && !validResult ; counter++) + { + delay(DELAY_INTERVAL); + + //Comes back in three bytes, data(MSB) / data(LSB) / Checksum + validResult = (3 == _i2cPort->requestFrom(HTU21D_ADDRESS, 3)); + } + + if (!validResult) return (ERROR_I2C_TIMEOUT); //Error out + + byte msb, lsb, checksum; + msb = _i2cPort->read(); + lsb = _i2cPort->read(); + checksum = _i2cPort->read(); + + uint16_t rawValue = ((uint16_t) msb << 8) | (uint16_t) lsb; + + if (checkCRC(rawValue, checksum) != 0) return (ERROR_BAD_CRC); //Error out + + return rawValue & 0xFFFC; // Zero out the status bits +} + +//Read the humidity +/*******************************************************************************************/ +//Calc humidity and return it to the user +//Returns 998 if I2C timed out +//Returns 999 if CRC is wrong +float HTU21D::readHumidity(void) +{ + uint16_t rawHumidity = readValue(TRIGGER_HUMD_MEASURE_NOHOLD); + + if(rawHumidity == ERROR_I2C_TIMEOUT || rawHumidity == ERROR_BAD_CRC) return(rawHumidity); + + //Given the raw humidity data, calculate the actual relative humidity + float tempRH = rawHumidity * (125.0 / 65536.0); //2^16 = 65536 + float rh = tempRH - 6.0; //From page 14 + + return (rh); +} + +//Read the temperature +/*******************************************************************************************/ +//Calc temperature and return it to the user +//Returns 998 if I2C timed out +//Returns 999 if CRC is wrong +float HTU21D::readTemperature(void) +{ + uint16_t rawTemperature = readValue(TRIGGER_TEMP_MEASURE_NOHOLD); + + if(rawTemperature == ERROR_I2C_TIMEOUT || rawTemperature == ERROR_BAD_CRC) return(rawTemperature); + + //Given the raw temperature data, calculate the actual temperature + float tempTemperature = rawTemperature * (175.72 / 65536.0); //2^16 = 65536 + float realTemperature = tempTemperature - 46.85; //From page 14 + + return (realTemperature); +} + +//Set sensor resolution +/*******************************************************************************************/ +//Sets the sensor resolution to one of four levels +//Page 12: +// 0/0 = 12bit RH, 14bit Temp +// 0/1 = 8bit RH, 12bit Temp +// 1/0 = 10bit RH, 13bit Temp +// 1/1 = 11bit RH, 11bit Temp +//Power on default is 0/0 + +void HTU21D::setResolution(byte resolution) +{ + byte userRegister = readUserRegister(); //Go get the current register state + userRegister &= B01111110; //Turn off the resolution bits + resolution &= B10000001; //Turn off all other bits but resolution bits + userRegister |= resolution; //Mask in the requested resolution bits + + //Request a write to user register + writeUserRegister(userRegister); +} + +//Read the user register +byte HTU21D::readUserRegister(void) +{ + byte userRegister; + + //Request the user register + _i2cPort->beginTransmission(HTU21D_ADDRESS); + _i2cPort->write(READ_USER_REG); //Read the user register + _i2cPort->endTransmission(); + + //Read result + _i2cPort->requestFrom(HTU21D_ADDRESS, 1); + + userRegister = _i2cPort->read(); + + return (userRegister); +} + +void HTU21D::writeUserRegister(byte val) +{ + _i2cPort->beginTransmission(HTU21D_ADDRESS); + _i2cPort->write(WRITE_USER_REG); //Write to the user register + _i2cPort->write(val); //Write the new resolution bits + _i2cPort->endTransmission(); +} + +//Give this function the 2 byte message (measurement) and the check_value byte from the HTU21D +//If it returns 0, then the transmission was good +//If it returns something other than 0, then the communication was corrupted +//From: http://www.nongnu.org/avr-libc/user-manual/group__util__crc.html +//POLYNOMIAL = 0x0131 = x^8 + x^5 + x^4 + 1 : http://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks +#define SHIFTED_DIVISOR 0x988000 //This is the 0x0131 polynomial shifted to farthest left of three bytes + +byte HTU21D::checkCRC(uint16_t message_from_sensor, uint8_t check_value_from_sensor) +{ + //Test cases from datasheet: + //message = 0xDC, checkvalue is 0x79 + //message = 0x683A, checkvalue is 0x7C + //message = 0x4E85, checkvalue is 0x6B + + uint32_t remainder = (uint32_t)message_from_sensor << 8; //Pad with 8 bits because we have to add in the check value + remainder |= check_value_from_sensor; //Add on the check value + + uint32_t divsor = (uint32_t)SHIFTED_DIVISOR; + + for (int i = 0 ; i < 16 ; i++) //Operate on only 16 positions of max 24. The remaining 8 are our remainder and should be zero when we're done. + { + //Serial.print("remainder: "); + //Serial.println(remainder, BIN); + //Serial.print("divsor: "); + //Serial.println(divsor, BIN); + //Serial.println(); + + if ( remainder & (uint32_t)1 << (23 - i) ) //Check if there is a one in the left position + remainder ^= divsor; + + divsor >>= 1; //Rotate the divsor max 16 times so that we have 8 bits left of a remainder + } + + return (byte)remainder; +} diff --git a/src/SparkFunHTU21D.h b/src/SparkFunHTU21D.h new file mode 100644 index 0000000..57a9ee4 --- /dev/null +++ b/src/SparkFunHTU21D.h @@ -0,0 +1,72 @@ +/* + HTU21D Humidity Sensor Library + By: Nathan Seidle + SparkFun Electronics + Date: September 22nd, 2013 + License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license). + + Get humidity and temperature from the HTU21D sensor. + + This same library should work for the other similar sensors including the Si + + */ + +#pragma once + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#include + +#define HTU21D_ADDRESS 0x40 //Unshifted 7-bit I2C address for the sensor + +#define ERROR_I2C_TIMEOUT 998 +#define ERROR_BAD_CRC 999 + +#define TRIGGER_TEMP_MEASURE_HOLD 0xE3 +#define TRIGGER_HUMD_MEASURE_HOLD 0xE5 +#define TRIGGER_TEMP_MEASURE_NOHOLD 0xF3 +#define TRIGGER_HUMD_MEASURE_NOHOLD 0xF5 +#define WRITE_USER_REG 0xE6 +#define READ_USER_REG 0xE7 +#define SOFT_RESET 0xFE + +#define USER_REGISTER_RESOLUTION_MASK 0x81 +#define USER_REGISTER_RESOLUTION_RH12_TEMP14 0x00 +#define USER_REGISTER_RESOLUTION_RH8_TEMP12 0x01 +#define USER_REGISTER_RESOLUTION_RH10_TEMP13 0x80 +#define USER_REGISTER_RESOLUTION_RH11_TEMP11 0x81 + +#define USER_REGISTER_END_OF_BATTERY 0x40 +#define USER_REGISTER_HEATER_ENABLED 0x04 +#define USER_REGISTER_DISABLE_OTP_RELOAD 0x02 + +class HTU21D { + +public: + HTU21D(); + + //Public Functions + bool begin(TwoWire &wirePort = Wire); //If user doesn't specificy then Wire will be used + float readHumidity(void); + float readTemperature(void); + void setResolution(byte resBits); + + byte readUserRegister(void); + void writeUserRegister(byte val); + + //Public Variables + +private: + //Private Functions + TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware + + byte checkCRC(uint16_t message_from_sensor, uint8_t check_value_from_sensor); + uint16_t readValue(byte cmd); + + //Private Variables + +};